1 /* 2 * Copyright (c) 2011, 2017, 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.util.List; 28 29 import org.graalvm.compiler.graph.Node; 30 import org.graalvm.compiler.loop.DefaultLoopPolicies; 31 import org.graalvm.compiler.loop.phases.LoopFullUnrollPhase; 32 import org.graalvm.compiler.loop.phases.LoopPeelingPhase; 33 import org.graalvm.compiler.nodes.ConstantNode; 34 import org.graalvm.compiler.nodes.ReturnNode; 35 import org.graalvm.compiler.nodes.extended.BoxNode; 36 import org.graalvm.compiler.nodes.extended.ValueAnchorNode; 37 import org.graalvm.compiler.nodes.java.LoadFieldNode; 38 import org.graalvm.compiler.nodes.virtual.AllocatedObjectNode; 39 import org.graalvm.compiler.nodes.virtual.CommitAllocationNode; 40 import org.graalvm.compiler.phases.common.CanonicalizerPhase; 41 import org.graalvm.compiler.phases.schedule.SchedulePhase; 42 import org.graalvm.compiler.virtual.phases.ea.PartialEscapePhase; 43 import org.junit.Assert; 44 import org.junit.Assume; 45 import org.junit.Test; 46 47 import jdk.vm.ci.meta.JavaConstant; 48 49 /** 50 * The PartialEscapeAnalysisPhase is expected to remove all allocations and return the correct 51 * values. 52 */ 53 public class EscapeAnalysisTest extends EATestBase { 54 55 @Test test1()56 public void test1() { 57 testEscapeAnalysis("test1Snippet", JavaConstant.forInt(101), false); 58 } 59 test1Snippet()60 public static int test1Snippet() { 61 Integer x = new Integer(101); 62 return x.intValue(); 63 } 64 65 @Test test2()66 public void test2() { 67 testEscapeAnalysis("test2Snippet", JavaConstant.forInt(0), false); 68 } 69 test2Snippet()70 public static int test2Snippet() { 71 Integer[] x = new Integer[0]; 72 return x.length; 73 } 74 75 @Test test3()76 public void test3() { 77 testEscapeAnalysis("test3Snippet", JavaConstant.NULL_POINTER, false); 78 } 79 test3Snippet()80 public static Object test3Snippet() { 81 Integer[] x = new Integer[1]; 82 return x[0]; 83 } 84 85 @Test testMonitor()86 public void testMonitor() { 87 testEscapeAnalysis("testMonitorSnippet", JavaConstant.forInt(0), false); 88 } 89 testMonitorSnippet()90 public static int testMonitorSnippet() { 91 Integer x = new Integer(0); 92 Double y = new Double(0); 93 Object z = new Object(); 94 synchronized (x) { 95 synchronized (y) { 96 synchronized (z) { 97 notInlineable(); 98 } 99 } 100 } 101 return x.intValue(); 102 } 103 104 @Test testMonitor2()105 public void testMonitor2() { 106 testEscapeAnalysis("testMonitor2Snippet", JavaConstant.forInt(0), false); 107 } 108 109 /** 110 * This test case differs from the last one in that it requires inlining within a synchronized 111 * region. 112 */ testMonitor2Snippet()113 public static int testMonitor2Snippet() { 114 Integer x = new Integer(0); 115 Double y = new Double(0); 116 Object z = new Object(); 117 synchronized (x) { 118 synchronized (y) { 119 synchronized (z) { 120 notInlineable(); 121 return x.intValue(); 122 } 123 } 124 } 125 } 126 127 @Test testMerge()128 public void testMerge() { 129 testEscapeAnalysis("testMerge1Snippet", JavaConstant.forInt(0), true); 130 } 131 testMerge1Snippet(int a)132 public static int testMerge1Snippet(int a) { 133 TestClassInt obj = new TestClassInt(1, 0); 134 if (a < 0) { 135 obj.x = obj.x + 1; 136 } else { 137 obj.x = obj.x + 2; 138 obj.y = 0; 139 } 140 if (obj.x > 1000) { 141 return 1; 142 } 143 return obj.y; 144 } 145 146 @Test testSimpleLoop()147 public void testSimpleLoop() { 148 testEscapeAnalysis("testSimpleLoopSnippet", JavaConstant.forInt(1), false); 149 } 150 testSimpleLoopSnippet(int a)151 public int testSimpleLoopSnippet(int a) { 152 TestClassInt obj = new TestClassInt(1, 2); 153 for (int i = 0; i < a; i++) { 154 notInlineable(); 155 } 156 return obj.x; 157 } 158 159 @Test testModifyingLoop()160 public void testModifyingLoop() { 161 testEscapeAnalysis("testModifyingLoopSnippet", JavaConstant.forInt(1), false); 162 } 163 testModifyingLoopSnippet(int a)164 public int testModifyingLoopSnippet(int a) { 165 TestClassInt obj = new TestClassInt(1, 2); 166 for (int i = 0; i < a; i++) { 167 obj.x = 3; 168 notInlineable(); 169 } 170 return obj.x <= 3 ? 1 : 0; 171 } 172 173 @Test testMergeAllocationsInt()174 public void testMergeAllocationsInt() { 175 testEscapeAnalysis("testMergeAllocationsIntSnippet", JavaConstant.forInt(1), false); 176 } 177 testMergeAllocationsIntSnippet(int a)178 public int testMergeAllocationsIntSnippet(int a) { 179 TestClassInt obj; 180 if (a < 0) { 181 obj = new TestClassInt(1, 2); 182 notInlineable(); 183 } else { 184 obj = new TestClassInt(1, 2); 185 notInlineable(); 186 } 187 return obj.x <= 3 ? 1 : 0; 188 } 189 190 @Test testMergeAllocationsInt2()191 public void testMergeAllocationsInt2() { 192 testEscapeAnalysis("testMergeAllocationsInt2Snippet", JavaConstant.forInt(1), true); 193 } 194 testMergeAllocationsInt2Snippet(int a)195 public int testMergeAllocationsInt2Snippet(int a) { 196 /* 197 * The initial object in obj exists until the end of the function, but it can still be 198 * merged with the one allocated in the else block because noone can observe the identity. 199 */ 200 TestClassInt obj = new TestClassInt(1, 2); 201 if (a < 0) { 202 notInlineable(); 203 } else { 204 obj = new TestClassInt(1, 2); 205 notInlineable(); 206 } 207 return obj.x <= 3 ? 1 : 0; 208 } 209 210 @Test testMergeAllocationsInt3()211 public void testMergeAllocationsInt3() { 212 // ensure that the result is not constant: 213 assertTrue(testMergeAllocationsInt3Snippet(true)); 214 assertFalse(testMergeAllocationsInt3Snippet(false)); 215 216 prepareGraph("testMergeAllocationsInt3Snippet", true); 217 assertFalse(graph.getNodes().filter(ReturnNode.class).first().result().isConstant()); 218 } 219 testMergeAllocationsInt3Snippet(boolean a)220 public boolean testMergeAllocationsInt3Snippet(boolean a) { 221 TestClassInt phi1; 222 TestClassInt phi2; 223 if (a) { 224 field = new TestClassObject(); 225 field = new TestClassObject(); 226 phi1 = phi2 = new TestClassInt(1, 2); 227 } else { 228 phi1 = new TestClassInt(2, 3); 229 phi2 = new TestClassInt(3, 4); 230 } 231 return phi1 == phi2; 232 } 233 234 @Test testMergeAllocationsObj()235 public void testMergeAllocationsObj() { 236 testEscapeAnalysis("testMergeAllocationsObjSnippet", JavaConstant.forInt(1), false); 237 } 238 testMergeAllocationsObjSnippet(int a)239 public int testMergeAllocationsObjSnippet(int a) { 240 TestClassObject obj; 241 Integer one = 1; 242 Integer two = 2; 243 Integer three = 3; 244 if (a < 0) { 245 obj = new TestClassObject(one, two); 246 notInlineable(); 247 } else { 248 obj = new TestClassObject(one, three); 249 notInlineable(); 250 } 251 return ((Integer) obj.x).intValue() <= 3 ? 1 : 0; 252 } 253 254 @Test testMergeAllocationsObjCirc()255 public void testMergeAllocationsObjCirc() { 256 testEscapeAnalysis("testMergeAllocationsObjCircSnippet", JavaConstant.forInt(1), false); 257 } 258 testMergeAllocationsObjCircSnippet(int a)259 public int testMergeAllocationsObjCircSnippet(int a) { 260 TestClassObject obj; 261 Integer one = 1; 262 Integer two = 2; 263 Integer three = 3; 264 if (a < 0) { 265 obj = new TestClassObject(one); 266 obj.y = obj; 267 obj.y = two; 268 notInlineable(); 269 } else { 270 obj = new TestClassObject(one); 271 obj.y = obj; 272 obj.y = three; 273 notInlineable(); 274 } 275 return ((Integer) obj.x).intValue() <= 3 ? 1 : 0; 276 } 277 278 static class MyException extends RuntimeException { 279 280 private static final long serialVersionUID = 0L; 281 282 protected Integer value; 283 MyException(Integer value)284 MyException(Integer value) { 285 super((Throwable) null); 286 this.value = value; 287 } 288 289 @SuppressWarnings("sync-override") 290 @Override fillInStackTrace()291 public final Throwable fillInStackTrace() { 292 return null; 293 } 294 } 295 296 @Test testMergeAllocationsException()297 public void testMergeAllocationsException() { 298 testEscapeAnalysis("testMergeAllocationsExceptionSnippet", JavaConstant.forInt(1), false); 299 } 300 testMergeAllocationsExceptionSnippet(int a)301 public int testMergeAllocationsExceptionSnippet(int a) { 302 MyException obj; 303 Integer one = 1; 304 if (a < 0) { 305 obj = new MyException(one); 306 notInlineable(); 307 } else { 308 obj = new MyException(one); 309 notInlineable(); 310 } 311 return obj.value <= 3 ? 1 : 0; 312 } 313 314 /** 315 * Tests that a graph with allocations that does not make progress during PEA will not be 316 * changed. 317 */ 318 @Test testChangeHandling()319 public void testChangeHandling() { 320 prepareGraph("testChangeHandlingSnippet", false); 321 Assert.assertEquals(2, graph.getNodes().filter(CommitAllocationNode.class).count()); 322 Assert.assertEquals(1, graph.getNodes().filter(BoxNode.class).count()); 323 List<Node> nodes = graph.getNodes().snapshot(); 324 // verify that an additional run doesn't add or remove nodes 325 new PartialEscapePhase(false, false, new CanonicalizerPhase(), null, graph.getOptions()).apply(graph, context); 326 Assert.assertEquals(nodes.size(), graph.getNodeCount()); 327 for (Node node : nodes) { 328 Assert.assertTrue(node.isAlive()); 329 } 330 } 331 332 public volatile Object field; 333 testChangeHandlingSnippet(int a)334 public int testChangeHandlingSnippet(int a) { 335 Object obj; 336 Integer one = 1; 337 obj = new MyException(one); 338 if (a < 0) { 339 notInlineable(); 340 } else { 341 obj = new Integer(1); 342 notInlineable(); 343 } 344 field = obj; 345 return 1; 346 } 347 348 /** 349 * Test the case where allocations before and during a loop that have no usages other than their 350 * phi need to be recognized as an important change. This needs a loop so that the allocation is 351 * not trivially removed by dead code elimination. 352 */ 353 @Test testRemovalSpecialCase()354 public void testRemovalSpecialCase() { 355 prepareGraph("testRemovalSpecialCaseSnippet", false); 356 Assert.assertEquals(2, graph.getNodes().filter(CommitAllocationNode.class).count()); 357 // create the situation by removing the if 358 graph.replaceFixedWithFloating(graph.getNodes().filter(LoadFieldNode.class).first(), graph.unique(ConstantNode.forInt(0))); 359 new CanonicalizerPhase().apply(graph, context); 360 // verify that an additional run removes all allocations 361 new PartialEscapePhase(false, false, new CanonicalizerPhase(), null, graph.getOptions()).apply(graph, context); 362 Assert.assertEquals(0, graph.getNodes().filter(CommitAllocationNode.class).count()); 363 } 364 365 public volatile int field2; 366 testRemovalSpecialCaseSnippet(int a)367 public int testRemovalSpecialCaseSnippet(int a) { 368 Object phi = new Object(); 369 for (int i = 0; i < a; i++) { 370 field = null; 371 if (field2 == 1) { 372 phi = new Object(); 373 } 374 } 375 return phi == null ? 1 : 0; 376 } 377 378 @Test testCheckCast()379 public void testCheckCast() { 380 testEscapeAnalysis("testCheckCastSnippet", getSnippetReflection().forObject(TestClassObject.class), true); 381 } 382 testCheckCastSnippet()383 public Object testCheckCastSnippet() { 384 TestClassObject obj = new TestClassObject(TestClassObject.class); 385 TestClassObject obj2 = new TestClassObject(obj); 386 return ((TestClassObject) obj2.x).x; 387 } 388 389 @Test testInstanceOf()390 public void testInstanceOf() { 391 testEscapeAnalysis("testInstanceOfSnippet", JavaConstant.forInt(1), false); 392 } 393 testInstanceOfSnippet()394 public boolean testInstanceOfSnippet() { 395 TestClassObject obj = new TestClassObject(TestClassObject.class); 396 TestClassObject obj2 = new TestClassObject(obj); 397 return obj2.x instanceof TestClassObject; 398 } 399 400 @SuppressWarnings("unused") testNewNodeSnippet()401 public static void testNewNodeSnippet() { 402 new ValueAnchorNode(null); 403 } 404 405 /** 406 * This test makes sure that the allocation of a {@link Node} can be removed. It therefore also 407 * tests the intrinsification of {@link Object#getClass()}. 408 */ 409 @Test testNewNode()410 public void testNewNode() { 411 // Trackking of creation interferes with escape analysis 412 Assume.assumeFalse(Node.TRACK_CREATION_POSITION); 413 testEscapeAnalysis("testNewNodeSnippet", null, false); 414 } 415 416 private static final TestClassObject staticObj = new TestClassObject(); 417 testFullyUnrolledLoopSnippet()418 public static Object testFullyUnrolledLoopSnippet() { 419 /* 420 * This tests a case that can appear if PEA is performed both before and after loop 421 * unrolling/peeling: If the VirtualInstanceNode is not duplicated correctly with the loop, 422 * the resulting object will reference itself, and not a second (different) object. 423 */ 424 TestClassObject obj = staticObj; 425 for (int i = 0; i < 2; i++) { 426 obj = new TestClassObject(obj); 427 } 428 return obj.x; 429 } 430 431 @Test testFullyUnrolledLoop()432 public void testFullyUnrolledLoop() { 433 prepareGraph("testFullyUnrolledLoopSnippet", false); 434 new LoopFullUnrollPhase(new CanonicalizerPhase(), new DefaultLoopPolicies()).apply(graph, context); 435 new PartialEscapePhase(false, new CanonicalizerPhase(), graph.getOptions()).apply(graph, context); 436 Assert.assertEquals(1, returnNodes.size()); 437 Assert.assertTrue(returnNodes.get(0).result() instanceof AllocatedObjectNode); 438 CommitAllocationNode commit = ((AllocatedObjectNode) returnNodes.get(0).result()).getCommit(); 439 Assert.assertEquals(2, commit.getValues().size()); 440 Assert.assertEquals(1, commit.getVirtualObjects().size()); 441 Assert.assertTrue("non-cyclic data structure expected", commit.getVirtualObjects().get(0) != commit.getValues().get(0)); 442 } 443 444 @SuppressWarnings("unused") private static Object staticField; 445 inlinedPart(TestClassObject obj)446 private static TestClassObject inlinedPart(TestClassObject obj) { 447 TestClassObject ret = new TestClassObject(obj); 448 staticField = null; 449 return ret; 450 } 451 testPeeledLoopSnippet()452 public static Object testPeeledLoopSnippet() { 453 TestClassObject obj = staticObj; 454 int i = 0; 455 do { 456 obj = inlinedPart(obj); 457 } while (i++ < 10); 458 staticField = obj; 459 return obj.x; 460 } 461 462 @Test testPeeledLoop()463 public void testPeeledLoop() { 464 prepareGraph("testPeeledLoopSnippet", false); 465 new LoopPeelingPhase(new DefaultLoopPolicies()).apply(graph, getDefaultHighTierContext()); 466 new SchedulePhase(graph.getOptions()).apply(graph); 467 } 468 testDeoptMonitorSnippetInner(Object o2, Object t, int i)469 public static void testDeoptMonitorSnippetInner(Object o2, Object t, int i) { 470 staticField = null; 471 if (i == 0) { 472 staticField = o2; 473 Number n = (Number) t; 474 n.toString(); 475 } 476 } 477 testDeoptMonitorSnippet(Object t, int i)478 public static void testDeoptMonitorSnippet(Object t, int i) { 479 TestClassObject o = new TestClassObject(); 480 TestClassObject o2 = new TestClassObject(o); 481 482 synchronized (o) { 483 testDeoptMonitorSnippetInner(o2, t, i); 484 } 485 } 486 487 @Test testDeoptMonitor()488 public void testDeoptMonitor() { 489 test("testDeoptMonitorSnippet", new Object(), 0); 490 } 491 } 492