1 /* 2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 package org.graalvm.compiler.core.test.ea; 26 27 import java.lang.ref.SoftReference; 28 29 import org.junit.Assert; 30 import org.junit.Ignore; 31 import org.junit.Test; 32 33 import org.graalvm.compiler.api.directives.GraalDirectives; 34 import org.graalvm.compiler.core.test.TypeSystemTest; 35 import org.graalvm.compiler.graph.Node; 36 import org.graalvm.compiler.nodes.AbstractMergeNode; 37 import org.graalvm.compiler.nodes.ReturnNode; 38 import org.graalvm.compiler.nodes.cfg.ControlFlowGraph; 39 import org.graalvm.compiler.nodes.extended.BoxNode; 40 import org.graalvm.compiler.nodes.extended.UnboxNode; 41 import org.graalvm.compiler.nodes.java.LoadFieldNode; 42 import org.graalvm.compiler.nodes.java.LoadIndexedNode; 43 import org.graalvm.compiler.nodes.java.NewArrayNode; 44 import org.graalvm.compiler.nodes.java.NewInstanceNode; 45 import org.graalvm.compiler.nodes.java.StoreFieldNode; 46 import org.graalvm.compiler.nodes.virtual.CommitAllocationNode; 47 import org.graalvm.compiler.phases.common.CanonicalizerPhase; 48 import org.graalvm.compiler.phases.common.DeadCodeEliminationPhase; 49 50 /** 51 * The PartialEscapeAnalysisPhase is expected to remove all allocations and return the correct 52 * values. 53 */ 54 public class PartialEscapeAnalysisTest extends EATestBase { 55 56 public static class TestObject { 57 58 public int x; 59 public int y; 60 61 public TestObject(int x, int y) { 62 this.x = x; 63 this.y = y; 64 } 65 } 66 67 public static class TestObject2 { 68 69 public Object x; 70 public Object y; 71 72 public TestObject2(Object x, Object y) { 73 this.x = x; 74 this.y = y; 75 } 76 } 77 78 @Test 79 public void test1() { 80 testPartialEscapeAnalysis("test1Snippet", 0.25, 1); 81 } 82 83 @SuppressWarnings("all") 84 public static Object test1Snippet(int a, int b, Object x, Object y) { 85 TestObject2 obj = new TestObject2(x, y); 86 if (a < 0) { 87 if (b < 0) { 88 return obj; 89 } else { 90 return obj.y; 91 } 92 } else { 93 return obj.x; 94 } 95 } 96 97 @Test 98 public void test2() { 99 testPartialEscapeAnalysis("test2Snippet", 1.5, 3, LoadIndexedNode.class); 100 } 101 102 public static Object test2Snippet(int a, Object x, Object y, Object z) { 103 TestObject2 obj = new TestObject2(x, y); 104 obj.x = new TestObject2(obj, z); 105 if (a < 0) { 106 ((TestObject2) obj.x).y = null; 107 obj.y = null; 108 return obj; 109 } else { 110 ((TestObject2) obj.x).y = Integer.class; 111 ((TestObject2) obj.x).x = null; 112 return obj.x; 113 } 114 } 115 116 @Test 117 public void test3() { 118 testPartialEscapeAnalysis("test3Snippet", 0.5, 1, StoreFieldNode.class, LoadFieldNode.class); 119 } 120 121 @SuppressWarnings("deprecation") 122 public static Object test3Snippet(int a) { 123 if (a < 0) { 124 TestObject obj = new TestObject(1, 2); 125 obj.x = 123; 126 obj.y = 234; 127 obj.x = 123111; 128 obj.y = new Integer(123).intValue(); 129 return obj; 130 } else { 131 return null; 132 } 133 } 134 135 @Test 136 public void testArrayCopy() { 137 testPartialEscapeAnalysis("testArrayCopySnippet", 0, 0); 138 } 139 140 public static Object[] array = new Object[]{1, 2, 3, 4, 5, "asdf", "asdf"}; 141 public static char[] charArray = new char[]{1, 2, 3, 4, 5, 'a', 'f'}; 142 143 public static Object testArrayCopySnippet(int a) { 144 Object[] tmp = new Object[]{a != 1 ? array[a] : null}; 145 Object[] tmp2 = new Object[5]; 146 System.arraycopy(tmp, 0, tmp2, 4, 1); 147 return tmp2[4]; 148 } 149 150 @Test 151 public void testPrimitiveArraycopy() { 152 testPartialEscapeAnalysis("testPrimitiveArraycopySnippet", 0, 0); 153 } 154 155 public static Object testPrimitiveArraycopySnippet(int a) { 156 char[] tmp = new char[]{a != 1 ? charArray[a] : 0}; 157 char[] tmp2 = new char[5]; 158 System.arraycopy(tmp, 0, tmp2, 4, 1); 159 return tmp2[4]; 160 } 161 162 @Test 163 @Ignore 164 public void testCache() { 165 testPartialEscapeAnalysis("testCacheSnippet", 0.75, 1); 166 } 167 168 public static class CacheKey { 169 170 private final int idx; 171 private final Object ref; 172 173 public CacheKey(int idx, Object ref) { 174 this.idx = idx; 175 this.ref = ref; 176 } 177 178 @Override 179 public int hashCode() { 180 return 31 * idx + ref.hashCode(); 181 } 182 183 public synchronized boolean equals(CacheKey other) { 184 return idx == other.idx && ref == other.ref; 185 } 186 } 187 188 public static CacheKey cacheKey = null; 189 public static Object value = null; 190 191 private static native Object createValue(CacheKey key); 192 193 public static Object testCacheSnippet(int idx, Object ref) { 194 CacheKey key = new CacheKey(idx, ref); 195 if (!key.equals(cacheKey)) { 196 cacheKey = key; 197 value = createValue(key); 198 } 199 return value; 200 } 201 202 public static int testReference1Snippet(Object a) { 203 SoftReference<Object> softReference = new SoftReference<>(a); 204 if (softReference.get().hashCode() == 0) { 205 return 1; 206 } else { 207 return 2; 208 } 209 } 210 211 @Test 212 public void testReference1() { 213 prepareGraph("testReference1Snippet", false); 214 assertDeepEquals(1, graph.getNodes().filter(NewInstanceNode.class).count()); 215 } 216 217 public static int testCanonicalizeSnippet(int v) { 218 CacheKey key = new CacheKey(v, null); 219 220 CacheKey key2; 221 if (key.idx == v) { 222 key2 = new CacheKey(v, null); 223 } else { 224 key2 = null; 225 } 226 return key2.idx; 227 } 228 229 @Test 230 public void testCanonicalize() { 231 prepareGraph("testCanonicalizeSnippet", false); 232 assertTrue(graph.getNodes().filter(ReturnNode.class).count() == 1); 233 assertTrue(graph.getNodes().filter(ReturnNode.class).first().result() == graph.getParameter(0)); 234 } 235 236 public static int testBoxLoopSnippet(int n) { 237 Integer sum = 0; 238 for (Integer i = 0; i < n; i++) { 239 if (sum == null) { 240 sum = null; 241 } else { 242 sum += i; 243 } 244 } 245 return sum; 246 } 247 248 @Test 249 public void testBoxLoop() { 250 testPartialEscapeAnalysis("testBoxLoopSnippet", 0, 0, BoxNode.class, UnboxNode.class); 251 } 252 253 static volatile int staticField; 254 static boolean executedDeoptimizeDirective; 255 256 static class A { 257 String field; 258 } 259 260 public static Object deoptWithVirtualObjectsSnippet() { 261 A a = new A(); 262 a.field = "field"; 263 264 staticField = 5; 265 if (staticField == 5) { 266 GraalDirectives.deoptimize(); 267 executedDeoptimizeDirective = true; 268 } 269 270 return a.field; 271 } 272 273 /** 274 * Tests deoptimizing with virtual objects in debug info. 275 */ 276 @Test 277 public void testDeoptWithVirtualObjects() { 278 assertFalse(executedDeoptimizeDirective); 279 test("deoptWithVirtualObjectsSnippet"); 280 assertTrue(executedDeoptimizeDirective); 281 } 282 283 @SafeVarargs 284 protected final void testPartialEscapeAnalysis(String snippet, double expectedProbability, int expectedCount, Class<? extends Node>... invalidNodeClasses) { 285 prepareGraph(snippet, false); 286 for (AbstractMergeNode merge : graph.getNodes(AbstractMergeNode.TYPE)) { 287 merge.setStateAfter(null); 288 } 289 new DeadCodeEliminationPhase().apply(graph); 290 new CanonicalizerPhase().apply(graph, context); 291 try { 292 Assert.assertTrue("partial escape analysis should have removed all NewInstanceNode allocations", graph.getNodes().filter(NewInstanceNode.class).isEmpty()); 293 Assert.assertTrue("partial escape analysis should have removed all NewArrayNode allocations", graph.getNodes().filter(NewArrayNode.class).isEmpty()); 294 295 ControlFlowGraph cfg = ControlFlowGraph.compute(graph, true, true, false, false); 296 double frequencySum = 0; 297 int materializeCount = 0; 298 for (CommitAllocationNode materialize : graph.getNodes().filter(CommitAllocationNode.class)) { 299 frequencySum += cfg.blockFor(materialize).getRelativeFrequency() * materialize.getVirtualObjects().size(); 300 materializeCount += materialize.getVirtualObjects().size(); 301 } 302 Assert.assertEquals("unexpected number of MaterializeObjectNodes", expectedCount, materializeCount); 303 Assert.assertEquals("unexpected frequency of MaterializeObjectNodes", expectedProbability, frequencySum, 0.01); 304 for (Node node : graph.getNodes()) { 305 for (Class<? extends Node> clazz : invalidNodeClasses) { 306 Assert.assertFalse("instance of invalid class: " + clazz.getSimpleName(), clazz.isInstance(node) && node.usages().isNotEmpty()); 307 } 308 } 309 } catch (AssertionError e) { 310 TypeSystemTest.outputGraph(graph, snippet + ": " + e.getMessage()); 311 throw e; 312 } 313 } 314 }