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 }