1 /*
   2  * Copyright (c) 2014, 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 import sun.hotspot.WhiteBox;
  25 import sun.hotspot.code.NMethod;
  26 import com.oracle.java.testlibrary.Platform;
  27 
  28 import java.lang.reflect.Method;
  29 import java.lang.invoke.MethodHandle;
  30 import java.lang.invoke.MethodHandles;
  31 import java.lang.invoke.MethodType;
  32 import java.util.function.BiFunction;
  33 
  34 /*
  35  * @test NullCheckDroppingsTest
  36  * @bug 8054492
  37  * @summary "Casting can result in redundant null checks in generated code"
  38  * @library /testlibrary /testlibrary/whitebox /testlibrary/com/oracle/java/testlibrary
  39  * @build NullCheckDroppingsTest
  40  * @run main ClassFileInstaller sun.hotspot.WhiteBox
  41  *                              sun.hotspot.WhiteBox$WhiteBoxPermission
  42  * @run main ClassFileInstaller com.oracle.java.testlibrary.Platform
  43  * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
  44  *                   -Xmixed -XX:-BackgroundCompilation -XX:-TieredCompilation -XX:CompileThreshold=1000
  45  *                   -XX:CompileCommand=exclude,NullCheckDroppingsTest::main NullCheckDroppingsTest
  46  */
  47 
  48 public class NullCheckDroppingsTest {
  49 
  50   private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
  51 
  52   static final BiFunction<Class, Object, Object> fCast = (c, o) -> c.cast(o);
  53 
  54   static final MethodHandle SET_SSINK;
  55   static final MethodHandle MH_CAST;
  56 
  57   static {
  58       try {
  59           SET_SSINK = MethodHandles.lookup().findSetter(NullCheckDroppingsTest.class, "ssink", String.class);
  60           MH_CAST = MethodHandles.lookup().findVirtual(Class.class,
  61                                                        "cast",
  62                                                        MethodType.methodType(Object.class, Object.class));
  63       }
  64       catch (Exception e) {
  65           throw new Error(e);
  66       }
  67   }  
  68 
  69   static volatile String svalue = "A";
  70   static volatile String snull = null;
  71 
  72   String ssink;
  73 
  74   public static void main(String[] args) throws Exception {
  75   
  76       // Only test C2 in Server VM
  77       if (!Platform.isServer()) {
  78           return;
  79       }
  80       // Make sure background compilation is disabled
  81       if (WHITE_BOX.getBooleanVMFlag("BackgroundCompilation")) {
  82           throw new RuntimeException("Background compilation enabled");
  83       }
  84       // Make sure Tiered compilation is disabled
  85       if (WHITE_BOX.getBooleanVMFlag("TieredCompilation")) {
  86           throw new RuntimeException("Tiered compilation enabled");
  87       }
  88       
  89       Method testOne   = NullCheckDroppingsTest.class.getDeclaredMethod("testLoopOne", String.class);
  90       Method testTwo   = NullCheckDroppingsTest.class.getDeclaredMethod("testLoopTwo", String.class);
  91       Method testThree = NullCheckDroppingsTest.class.getDeclaredMethod("testLoopThree", String.class);
  92       Method testFour  = NullCheckDroppingsTest.class.getDeclaredMethod("testLoopFour", String.class);
  93   
  94       NullCheckDroppingsTest t = new NullCheckDroppingsTest();
  95 
  96       // Ensure testLoopOne is compiled
  97       for (int i = 0; i < 3000; i++) {
  98           t.testLoopOne(svalue);
  99           t.testLoopTwo(svalue);
 100           t.testLoopThree(svalue);
 101           t.testLoopFour(svalue);
 102       }
 103       NMethod nm = getNMethod(testOne);
 104       // This should cause a de-optimisation
 105       // if testLoopOne is compiled with a null-check.
 106       t.testLoopOne(snull);
 107       checkDeoptimization(testOne, nm);
 108 
 109       nm = getNMethod(testTwo);
 110       t.testLoopTwo(snull);
 111       checkDeoptimization(testTwo, nm);
 112       
 113       nm = getNMethod(testThree);
 114       t.testLoopThree(snull);
 115       checkDeoptimization(testThree, nm);
 116       
 117       nm = getNMethod(testFour);
 118       t.testLoopFour(snull);
 119       checkDeoptimization(testFour, nm);
 120   }
 121 
 122   void testLoopOne(String s) {
 123       try {
 124           ssink = String.class.cast(s);
 125       } catch (Throwable t) {
 126           throw new Error(t);
 127       }
 128   }
 129 
 130   void testLoopTwo(String s) {
 131       try {
 132           ssink = (String) (Object) MH_CAST.invokeExact(String.class, (Object) s);;
 133       } catch (Throwable t) {
 134           throw new Error(t);
 135       }
 136   }
 137 
 138   void testLoopThree(String s) {
 139       try {
 140           SET_SSINK.invokeExact(this, s);
 141       } catch (Throwable t) {
 142           throw new Error(t);
 143       }
 144   }
 145 
 146   void testLoopFour(String s) {
 147       try {
 148           ssink = (String) fCast.apply(String.class, s);
 149       } catch (Throwable t) {
 150           throw new Error(t);
 151       }
 152   }
 153   
 154   static NMethod getNMethod(Method test) throws Exception {
 155       // Because background compilation is disabled, method should now be compiled
 156       if (!WHITE_BOX.isMethodCompiled(test)) {
 157           throw new RuntimeException(test + " not compiled");
 158       }
 159 
 160       NMethod nm = NMethod.get(test, false); // not OSR nmethod
 161       if (nm == null) {
 162           throw new RuntimeException(test + " missing nmethod?");
 163       }
 164       if (nm.comp_level != 4) {
 165           throw new RuntimeException(test + " compiled by not C2: " + nm);
 166       }
 167       return nm;
 168   }
 169   
 170   static void checkDeoptimization(Method test, NMethod nmOrig) throws Exception {
 171       // Ensure no deoptimization (intrinsic Class.cast() works).
 172       if (!WHITE_BOX.isMethodCompiled(test)) {
 173           throw new RuntimeException(test + " deoptimized");
 174       }
 175 
 176       // Ensure no recompilation.
 177       NMethod nm = NMethod.get(test, false); // not OSR nmethod
 178       if (nm == null) {
 179           throw new RuntimeException(test + " missing nmethod?");
 180       }
 181       if (nm.comp_level != 4) {
 182           throw new RuntimeException(test + " compiled by not C2: " + nm);
 183       }
 184       if (nm.compile_id != nmOrig.compile_id) {
 185           throw new RuntimeException(test + " was recompiled: old nmethod=" + nmOrig + ", new nmethod=" + nm);
 186       } 
 187   }
 188 }