1 /*
   2  * Copyright (c) 2011, 2012, 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.lang.ref.SoftReference;
  26 
  27 import org.junit.Assert;
  28 import org.junit.Ignore;
  29 import org.junit.Test;
  30 
  31 import org.graalvm.compiler.api.directives.GraalDirectives;
  32 import org.graalvm.compiler.core.test.TypeSystemTest;
  33 import org.graalvm.compiler.graph.Node;
  34 import org.graalvm.compiler.nodes.AbstractMergeNode;
  35 import org.graalvm.compiler.nodes.ReturnNode;
  36 import org.graalvm.compiler.nodes.cfg.ControlFlowGraph;
  37 import org.graalvm.compiler.nodes.extended.BoxNode;
  38 import org.graalvm.compiler.nodes.extended.UnboxNode;
  39 import org.graalvm.compiler.nodes.java.LoadFieldNode;
  40 import org.graalvm.compiler.nodes.java.LoadIndexedNode;
  41 import org.graalvm.compiler.nodes.java.NewArrayNode;
  42 import org.graalvm.compiler.nodes.java.NewInstanceNode;
  43 import org.graalvm.compiler.nodes.java.StoreFieldNode;
  44 import org.graalvm.compiler.nodes.virtual.CommitAllocationNode;
  45 import org.graalvm.compiler.phases.common.CanonicalizerPhase;
  46 import org.graalvm.compiler.phases.common.DeadCodeEliminationPhase;
  47 
  48 /**
  49  * The PartialEscapeAnalysisPhase is expected to remove all allocations and return the correct
  50  * values.
  51  */
  52 public class PartialEscapeAnalysisTest extends EATestBase {
  53 
  54     public static class TestObject {
  55 
  56         public int x;
  57         public int y;
  58 
  59         public TestObject(int x, int y) {
  60             this.x = x;
  61             this.y = y;
  62         }
  63     }
  64 
  65     public static class TestObject2 {
  66 
  67         public Object x;
  68         public Object y;
  69 
  70         public TestObject2(Object x, Object y) {
  71             this.x = x;
  72             this.y = y;
  73         }
  74     }
  75 
  76     @Test
  77     public void test1() {
  78         testPartialEscapeAnalysis("test1Snippet", 0.25, 1);
  79     }
  80 
  81     @SuppressWarnings("all")
  82     public static Object test1Snippet(int a, int b, Object x, Object y) {
  83         TestObject2 obj = new TestObject2(x, y);
  84         if (a < 0) {
  85             if (b < 0) {
  86                 return obj;
  87             } else {
  88                 return obj.y;
  89             }
  90         } else {
  91             return obj.x;
  92         }
  93     }
  94 
  95     @Test
  96     public void test2() {
  97         testPartialEscapeAnalysis("test2Snippet", 1.5, 3, LoadIndexedNode.class);
  98     }
  99 
 100     public static Object test2Snippet(int a, Object x, Object y, Object z) {
 101         TestObject2 obj = new TestObject2(x, y);
 102         obj.x = new TestObject2(obj, z);
 103         if (a < 0) {
 104             ((TestObject2) obj.x).y = null;
 105             obj.y = null;
 106             return obj;
 107         } else {
 108             ((TestObject2) obj.x).y = Integer.class;
 109             ((TestObject2) obj.x).x = null;
 110             return obj.x;
 111         }
 112     }
 113 
 114     @Test
 115     public void test3() {
 116         testPartialEscapeAnalysis("test3Snippet", 0.5, 1, StoreFieldNode.class, LoadFieldNode.class);
 117     }
 118 
 119     public static Object test3Snippet(int a) {
 120         if (a < 0) {
 121             TestObject obj = new TestObject(1, 2);
 122             obj.x = 123;
 123             obj.y = 234;
 124             obj.x = 123111;
 125             obj.y = new Integer(123).intValue();
 126             return obj;
 127         } else {
 128             return null;
 129         }
 130     }
 131 
 132     @Test
 133     public void testArrayCopy() {
 134         testPartialEscapeAnalysis("testArrayCopySnippet", 0, 0);
 135     }
 136 
 137     public static Object[] array = new Object[]{1, 2, 3, 4, 5, "asdf", "asdf"};
 138 
 139     public static Object testArrayCopySnippet(int a) {
 140         Object[] tmp = new Object[]{a != 1 ? array[a] : null};
 141         Object[] tmp2 = new Object[5];
 142         System.arraycopy(tmp, 0, tmp2, 4, 1);
 143         return tmp2[4];
 144     }
 145 
 146     @Test
 147     @Ignore
 148     public void testCache() {
 149         testPartialEscapeAnalysis("testCacheSnippet", 0.75, 1);
 150     }
 151 
 152     public static class CacheKey {
 153 
 154         private final int idx;
 155         private final Object ref;
 156 
 157         public CacheKey(int idx, Object ref) {
 158             this.idx = idx;
 159             this.ref = ref;
 160         }
 161 
 162         @Override
 163         public int hashCode() {
 164             return 31 * idx + ref.hashCode();
 165         }
 166 
 167         public synchronized boolean equals(CacheKey other) {
 168             return idx == other.idx && ref == other.ref;
 169         }
 170     }
 171 
 172     public static CacheKey cacheKey = null;
 173     public static Object value = null;
 174 
 175     private static native Object createValue(CacheKey key);
 176 
 177     public static Object testCacheSnippet(int idx, Object ref) {
 178         CacheKey key = new CacheKey(idx, ref);
 179         if (!key.equals(cacheKey)) {
 180             cacheKey = key;
 181             value = createValue(key);
 182         }
 183         return value;
 184     }
 185 
 186     public static int testReference1Snippet(Object a) {
 187         SoftReference<Object> softReference = new SoftReference<>(a);
 188         if (softReference.get().hashCode() == 0) {
 189             return 1;
 190         } else {
 191             return 2;
 192         }
 193     }
 194 
 195     @Test
 196     public void testReference1() {
 197         prepareGraph("testReference1Snippet", false);
 198         assertDeepEquals(1, graph.getNodes().filter(NewInstanceNode.class).count());
 199     }
 200 
 201     public static int testCanonicalizeSnippet(int v) {
 202         CacheKey key = new CacheKey(v, null);
 203 
 204         CacheKey key2;
 205         if (key.idx == v) {
 206             key2 = new CacheKey(v, null);
 207         } else {
 208             key2 = null;
 209         }
 210         return key2.idx;
 211     }
 212 
 213     @Test
 214     public void testCanonicalize() {
 215         prepareGraph("testCanonicalizeSnippet", false);
 216         assertTrue(graph.getNodes().filter(ReturnNode.class).count() == 1);
 217         assertTrue(graph.getNodes().filter(ReturnNode.class).first().result() == graph.getParameter(0));
 218     }
 219 
 220     public static int testBoxLoopSnippet(int n) {
 221         Integer sum = 0;
 222         for (Integer i = 0; i < n; i++) {
 223             if (sum == null) {
 224                 sum = null;
 225             } else {
 226                 sum += i;
 227             }
 228         }
 229         return sum;
 230     }
 231 
 232     @Test
 233     public void testBoxLoop() {
 234         testPartialEscapeAnalysis("testBoxLoopSnippet", 0, 0, BoxNode.class, UnboxNode.class);
 235     }
 236 
 237     static volatile int staticField;
 238     static boolean executedDeoptimizeDirective;
 239 
 240     static class A {
 241         String field;
 242     }
 243 
 244     public static Object deoptWithVirtualObjectsSnippet() {
 245         A a = new A();
 246         a.field = "field";
 247 
 248         staticField = 5;
 249         if (staticField == 5) {
 250             GraalDirectives.deoptimize();
 251             executedDeoptimizeDirective = true;
 252         }
 253 
 254         return a.field;
 255     }
 256 
 257     /**
 258      * Tests deoptimizing with virtual objects in debug info.
 259      */
 260     @Test
 261     public void testDeoptWithVirtualObjects() {
 262         assertFalse(executedDeoptimizeDirective);
 263         test("deoptWithVirtualObjectsSnippet");
 264         assertTrue(executedDeoptimizeDirective);
 265     }
 266 
 267     @SafeVarargs
 268     protected final void testPartialEscapeAnalysis(String snippet, double expectedProbability, int expectedCount, Class<? extends Node>... invalidNodeClasses) {
 269         prepareGraph(snippet, false);
 270         for (AbstractMergeNode merge : graph.getNodes(AbstractMergeNode.TYPE)) {
 271             merge.setStateAfter(null);
 272         }
 273         new DeadCodeEliminationPhase().apply(graph);
 274         new CanonicalizerPhase().apply(graph, context);
 275         try {
 276             Assert.assertTrue("partial escape analysis should have removed all NewInstanceNode allocations", graph.getNodes().filter(NewInstanceNode.class).isEmpty());
 277             Assert.assertTrue("partial escape analysis should have removed all NewArrayNode allocations", graph.getNodes().filter(NewArrayNode.class).isEmpty());
 278 
 279             ControlFlowGraph cfg = ControlFlowGraph.compute(graph, true, true, false, false);
 280             double probabilitySum = 0;
 281             int materializeCount = 0;
 282             for (CommitAllocationNode materialize : graph.getNodes().filter(CommitAllocationNode.class)) {
 283                 probabilitySum += cfg.blockFor(materialize).probability() * materialize.getVirtualObjects().size();
 284                 materializeCount += materialize.getVirtualObjects().size();
 285             }
 286             Assert.assertEquals("unexpected number of MaterializeObjectNodes", expectedCount, materializeCount);
 287             Assert.assertEquals("unexpected probability of MaterializeObjectNodes", expectedProbability, probabilitySum, 0.01);
 288             for (Node node : graph.getNodes()) {
 289                 for (Class<? extends Node> clazz : invalidNodeClasses) {
 290                     Assert.assertFalse("instance of invalid class: " + clazz.getSimpleName(), clazz.isInstance(node) && node.usages().isNotEmpty());
 291                 }
 292             }
 293         } catch (AssertionError e) {
 294             TypeSystemTest.outputGraph(graph, snippet + ": " + e.getMessage());
 295             throw e;
 296         }
 297     }
 298 }