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