1/*
2 * Copyright (c) 2016, 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 */
23package org.graalvm.compiler.core.test;
24
25import java.lang.reflect.Field;
26
27import org.junit.Assert;
28import org.junit.Test;
29
30import org.graalvm.compiler.nodes.IfNode;
31import org.graalvm.compiler.nodes.StructuredGraph;
32import org.graalvm.compiler.phases.common.CanonicalizerPhase;
33import org.graalvm.compiler.phases.common.IterativeConditionalEliminationPhase;
34import org.graalvm.compiler.virtual.phases.ea.EarlyReadEliminationPhase;
35
36import sun.misc.Unsafe;
37
38public class ConditionalEliminationLoadFieldConstantFoldTest extends GraalCompilerTest {
39    public static int intSideEffect;
40
41    public static final B FinalField = new B(10);
42
43    private abstract static class A {
44
45    }
46
47    private static class B extends A {
48        final int a;
49
50        B(int a) {
51            this.a = a;
52        }
53    }
54
55    private static class C extends A {
56        final B b;
57
58        C(B b) {
59            this.b = b;
60        }
61    }
62
63    private static class D extends A {
64        final C c;
65
66        D(C c) {
67            this.c = c;
68        }
69    }
70
71    private static class E extends D {
72        final Object o;
73
74        E(C c, Object o) {
75            super(c);
76            this.o = o;
77        }
78    }
79
80    public static final B CONST_B = new B(10);
81    public static final C CONST_C = new C(CONST_B);
82    public static final D CONST_D = new D(CONST_C);
83
84    public int testReadConstInBranch(B b) {
85        if (b == CONST_B) {
86            if (b.a == 5) {
87                intSideEffect = b.a;
88            } else {
89                intSideEffect = 10;
90            }
91        }
92        return 0;
93    }
94
95    public int testMultipleReadsConstInBranch(D d) {
96        if (d == CONST_D) {
97            C c = d.c;
98            B b = c.b;
99            int res = b.a + 12;
100            if (res == 125) {
101                intSideEffect = 12;
102            }
103        }
104        return 0;
105    }
106
107    public int testLoadFinalInstanceOf(E e) {
108        Object o = e.o;
109        if (o == CONST_C) {
110            if (o instanceof A) {
111                // eliminate, implied by a.x == Const(Subclass)
112                intSideEffect = 1;
113            } else {
114                intSideEffect = 10;
115            }
116        }
117        return 0;
118    }
119
120    public int testLoadFinalTwiceInstanceOf(E e) {
121        if (e.o == CONST_C) {
122            if (e.o instanceof A) {
123                intSideEffect = 1;
124            } else {
125                intSideEffect = 10;
126            }
127        }
128        return 0;
129    }
130
131    static class C1 {
132        final int a;
133
134        C1(int a) {
135            this.a = a;
136        }
137    }
138
139    static class C2 {
140        final C1 c1;
141
142        C2(C1 c1) {
143            this.c1 = c1;
144        }
145    }
146
147    public static int foldThatIsNotAllowed(C2 c2) {
148        // read before, this will be used to load through when folding
149        C1 c1Unknown = c2.c1;
150
151        // be naughty (will be a store field after canonicalization as it has a constant offset, so
152        // we would be able to eliminate the inner if after an early read elimination but we would
153        // fold before and ce the inner if already)
154        //
155        // note: if the offset would not be constant but a parameter we would not even be able to
156        // remove in inner most if as we cannot rewrite the unsafe store to a store field node as
157        // the store might kill ANY_LOCATION
158        UNSAFE.putObject(c2, C2_C1_OFFSET, C1_AFTER_READ_CONST);
159
160        if (c2 == C2_CONST) {
161            if (c1Unknown == C1_CONST) {
162                /*
163                 * This if can be eliminated (as we rewrite the unsafe store with a constant offset
164                 * to a store field node) but the remaining branch must be the false branch. If we
165                 * do not fold through both field loads we will canonicalize the unsafe store to a
166                 * store field, see the new value and can thus eliminate the true branch
167                 *
168                 * if we fold through the load fields we would load from the object read before the
169                 * store so we miss the unsafe update
170                 */
171                if (c2.c1.a == 10) {
172                    intSideEffect = 1;
173                    return 1;
174                } else {
175                    intSideEffect = 2;
176                    return 2;
177                }
178            } else {
179                intSideEffect = -2;
180                return -2;
181            }
182        } else {
183            intSideEffect = -1;
184            return -1;
185        }
186    }
187
188    public int testLoadFinalTwiceNoReadEliminationInstanceOf(E e) {
189        if (e.o == CONST_C) {
190            /*
191             * we cannot eliminate the second read of e.o although it is a final field. the call to
192             * System.gc (or any other memory checkpoint killing ANY_LOCATION) will prohibit the
193             * elimination of the second load, thus we have two different load nodes, we know that
194             * that first load field is a constant but we do not know for the second one, assuming
195             * e.o is final, as it might have been written in between
196             *
197             * this prohibits us to remove the if (fold through all loads to final fields) and the
198             * instance of e.o
199             */
200            System.gc();
201            C c = (C) e.o;
202            if (c.b.a == 10) {
203                intSideEffect = 1;
204            } else {
205                intSideEffect = 10;
206            }
207        }
208        return 0;
209
210    }
211
212    private static final C1 C1_CONST = new C1(0);
213    private static final C2 C2_CONST = new C2(C1_CONST);
214    private static final C1 C1_AFTER_READ_CONST = new C1(10);
215
216    private static Unsafe getUnsafe() {
217        try {
218            return Unsafe.getUnsafe();
219        } catch (SecurityException e) {
220        }
221        try {
222            Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
223            theUnsafeInstance.setAccessible(true);
224            return (Unsafe) theUnsafeInstance.get(Unsafe.class);
225        } catch (Exception e) {
226            throw new RuntimeException("exception while trying to get Unsafe.theUnsafe via reflection:", e);
227        }
228    }
229
230    private static final sun.misc.Unsafe UNSAFE = getUnsafe();
231    private static final long C2_C1_OFFSET;
232
233    static {
234        try {
235            Field f = C2.class.getDeclaredField("c1");
236            C2_C1_OFFSET = UNSAFE.objectFieldOffset(f);
237        } catch (NoSuchFieldException | SecurityException e) {
238            throw new RuntimeException(e);
239        }
240    }
241
242    @Test
243    public void test01() {
244        checkGraph("testReadConstInBranch", 1);
245        test("testReadConstInBranch", new B(1));
246    }
247
248    @Test
249    public void test02() {
250        checkGraph("testMultipleReadsConstInBranch", 1);
251    }
252
253    @Test
254    public void test03() {
255        checkGraph("testLoadFinalInstanceOf", 1);
256    }
257
258    @Test
259    public void test04() {
260        checkGraph("testLoadFinalTwiceInstanceOf", 1);
261    }
262
263    @Test
264    public void test05() {
265        checkGraph("testLoadFinalTwiceNoReadEliminationInstanceOf", 2);
266    }
267
268    @Test(expected = AssertionError.class)
269    @SuppressWarnings("try")
270    public void test06() {
271        Result actual = executeActual(getResolvedJavaMethod("foldThatIsNotAllowed"), null, C2_CONST);
272        UNSAFE.putObject(C2_CONST, C2_C1_OFFSET, C1_CONST);
273        Result expected = executeExpected(getResolvedJavaMethod("foldThatIsNotAllowed"), null, C2_CONST);
274        Assert.assertEquals(expected.returnValue, actual.returnValue);
275    }
276
277    @SuppressWarnings("try")
278    private StructuredGraph checkGraph(String name, int nrOfIfsAfter) {
279        StructuredGraph g = parseForCompile(getResolvedJavaMethod(name));
280        CanonicalizerPhase c = new CanonicalizerPhase();
281        c.apply(g, getDefaultHighTierContext());
282        new EarlyReadEliminationPhase(c).apply(g, getDefaultHighTierContext());
283        new IterativeConditionalEliminationPhase(c, false).apply(g, getDefaultHighTierContext());
284        Assert.assertEquals("Nr of Ifs left does not match", nrOfIfsAfter, g.getNodes().filter(IfNode.class).count());
285        c.apply(g, getDefaultHighTierContext());
286        return g;
287    }
288
289}
290