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