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