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  */
  23 package org.graalvm.compiler.core.test;
  24 
  25 import java.lang.reflect.Field;
  26 
  27 import org.junit.Assert;
  28 import org.junit.Test;
  29 
  30 import org.graalvm.compiler.nodes.IfNode;
  31 import org.graalvm.compiler.nodes.StructuredGraph;
  32 import org.graalvm.compiler.phases.common.CanonicalizerPhase;
  33 import org.graalvm.compiler.phases.common.IterativeConditionalEliminationPhase;
  34 import org.graalvm.compiler.virtual.phases.ea.EarlyReadEliminationPhase;
  35 
  36 import sun.misc.Unsafe;
  37 
  38 public 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 }