1 /*
   2  * Copyright (c) 2014, 2015, 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  * @test
  26  * @bug 8030976 8059226
  27  * @library /testlibrary /test/lib /
  28  * @modules java.base/jdk.internal.org.objectweb.asm
  29  *          java.base/jdk.internal.misc
  30  *          java.compiler
  31  *          java.management
  32  *          jdk.jvmstat/sun.jvmstat.monitor
  33  *
  34  * @build compiler.uncommontrap.TestUnstableIfTrap
  35  *        jdk.test.lib.*
  36  *        compiler.testlibrary.uncommontrap.Verifier
  37  * @run driver ClassFileInstaller sun.hotspot.WhiteBox
  38  *                                sun.hotspot.WhiteBox$WhiteBoxPermission
  39  * @run main/othervm -Xbatch -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions
  40  *                   -XX:+WhiteBoxAPI -XX:+LogCompilation
  41  *                   -XX:CompileCommand=compileonly,UnstableIfExecutable.test
  42  *                   -XX:LogFile=always_taken_not_fired.xml
  43  *                   compiler.uncommontrap.TestUnstableIfTrap ALWAYS_TAKEN false
  44  * @run main/othervm -Xbatch -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions
  45  *                   -XX:+WhiteBoxAPI -XX:+LogCompilation
  46  *                   -XX:CompileCommand=compileonly,UnstableIfExecutable.test
  47  *                   -XX:LogFile=always_taken_fired.xml
  48  *                   compiler.uncommontrap.TestUnstableIfTrap ALWAYS_TAKEN true
  49  * @run main/othervm -Xbatch -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions
  50  *                   -XX:+WhiteBoxAPI -XX:+LogCompilation
  51  *                   -XX:CompileCommand=compileonly,UnstableIfExecutable.test
  52  *                   -XX:LogFile=never_taken_not_fired.xml
  53  *                   compiler.uncommontrap.TestUnstableIfTrap NEVER_TAKEN false
  54  * @run main/othervm -Xbatch -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions
  55  *                   -XX:+WhiteBoxAPI -XX:+LogCompilation
  56  *                   -XX:CompileCommand=compileonly,UnstableIfExecutable.test
  57  *                   -XX:LogFile=never_taken_fired.xml
  58  *                   compiler.uncommontrap.TestUnstableIfTrap NEVER_TAKEN true
  59  * @run driver compiler.testlibrary.uncommontrap.Verifier always_taken_not_fired.xml
  60  *                                                        always_taken_fired.xml
  61  *                                                        never_taken_not_fired.xml
  62  *                                                        never_taken_fired.xml
  63  */
  64 
  65 package compiler.uncommontrap;
  66 
  67 import compiler.testlibrary.uncommontrap.Verifier;
  68 import jdk.internal.org.objectweb.asm.ClassVisitor;
  69 import jdk.internal.org.objectweb.asm.ClassWriter;
  70 import jdk.internal.org.objectweb.asm.Label;
  71 import jdk.internal.org.objectweb.asm.MethodVisitor;
  72 import jdk.test.lib.ByteCodeLoader;
  73 import jdk.test.lib.Platform;
  74 import sun.hotspot.WhiteBox;
  75 
  76 import java.io.File;
  77 import java.io.FileWriter;
  78 import java.io.IOException;
  79 import java.lang.reflect.Method;
  80 import java.util.Properties;
  81 
  82 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT;
  83 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
  84 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
  85 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VOLATILE;
  86 import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC;
  87 import static jdk.internal.org.objectweb.asm.Opcodes.GOTO;
  88 import static jdk.internal.org.objectweb.asm.Opcodes.IADD;
  89 import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_1;
  90 import static jdk.internal.org.objectweb.asm.Opcodes.IFEQ;
  91 import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD;
  92 import static jdk.internal.org.objectweb.asm.Opcodes.ISUB;
  93 import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
  94 
  95 public class TestUnstableIfTrap {
  96     private static final WhiteBox WB = WhiteBox.getWhiteBox();
  97     private static final String CLASS_NAME = "UnstableIfExecutable";
  98     private static final String METHOD_NAME = "test";
  99     private static final String FIELD_NAME = "field";
 100     private static final int ITERATIONS = 1_000_000;
 101     // There is no dependency on particular class file version, so it could be
 102     // set to any version (if you're updating this test for Java 42).
 103     private static final int CLASS_FILE_VERSION = 49;
 104     private static final int MAX_TIER = 4;
 105     // This test aimed to verify that uncommon trap with reason "unstable_if"
 106     // is emitted when method that contain control-flow divergence such that
 107     // one of two branches is never taken (and other one is taken always).
 108     // C2 will made a decision whether or not the branch was ever taken
 109     // depending on method's profile.
 110     // If profile was collected for a few method's invocations, then C2 will not
 111     // trust in branches' probabilities and the tested trap won't be emitted.
 112     // In fact, a method has to be invoked at least 40 time at the day when this
 113     // comment was written (see Parse::dynamic_branch_prediction for an actual
 114     // value). It would be to implementation dependent to use "40" as
 115     // a threshold value in the test, so in order to improve test's robustness
 116     // the threshold value is 1000: if the tested method was compiled by C2
 117     // before it was invoked 1000 times, then we won't verify that trap was
 118     // emitted and fired.
 119     private static final int MIN_INVOCATIONS_BEFORE_C2_COMPILATION = 1000;
 120     /**
 121      * Description of test case parameters and uncommon trap that will
 122      * be emitted during tested method compilation.
 123      */
 124     private static enum TestCaseName {
 125         ALWAYS_TAKEN(false, "taken always"),
 126         NEVER_TAKEN(true, "taken never");
 127         TestCaseName(boolean predicate, String comment) {
 128             this.predicate = predicate;
 129             this.comment = comment;
 130         }
 131 
 132         public final boolean predicate;
 133         public final String name = "unstable_if";
 134         public final String comment;
 135     }
 136 
 137     public static void main(String args[]) {
 138         if (args.length != 2) {
 139             throw new Error("Expected two arguments: test case name and a "
 140                     + "boolean determining if uncommon trap should be fired.");
 141         }
 142         test(TestCaseName.valueOf(args[0]), Boolean.valueOf(args[1]));
 143     }
 144 
 145     private static void test(TestCaseName testCase, boolean shouldBeFired) {
 146         Method testMethod;
 147         Label unstableIfLocation = new Label();
 148         boolean shouldBeEmitted;
 149         boolean compiledToEarly = false;
 150 
 151         try {
 152             Class testClass = ByteCodeLoader.load(CLASS_NAME,
 153                     generateTest(unstableIfLocation));
 154             testMethod = testClass.getDeclaredMethod(METHOD_NAME,
 155                     boolean.class);
 156             for (int i = 0; i < ITERATIONS; i++) {
 157                 testMethod.invoke(null, testCase.predicate);
 158                 if (i < MIN_INVOCATIONS_BEFORE_C2_COMPILATION
 159                         && isMethodCompiledByC2(testMethod)) {
 160                     compiledToEarly = true;
 161                     // There is no sense in further invocations: we already
 162                     // decided to avoid verification.
 163                     break;
 164                 }
 165             }
 166             // We're checking that trap should be emitted (i.e. it was compiled
 167             // by C2) before the trap is fired, because otherwise the nmethod
 168             // will be deoptimized and isMethodCompiledByC2 will return false.
 169             shouldBeEmitted = isMethodCompiledByC2(testMethod)
 170                     && !compiledToEarly;
 171             if (shouldBeFired) {
 172                 testMethod.invoke(null, !testCase.predicate);
 173             }
 174         } catch (ReflectiveOperationException e) {
 175             throw new Error("Test case should be generated, loaded and executed"
 176                     + " without any issues.", e);
 177         }
 178 
 179         shouldBeFired &= shouldBeEmitted;
 180 
 181         Properties properties = new Properties();
 182         properties.setProperty(Verifier.VERIFICATION_SHOULD_BE_SKIPPED,
 183                 Boolean.toString(compiledToEarly));
 184         properties.setProperty(Verifier.UNCOMMON_TRAP_SHOULD_EMITTED,
 185                 Boolean.toString(shouldBeEmitted));
 186         properties.setProperty(Verifier.UNCOMMON_TRAP_SHOULD_FIRED,
 187                 Boolean.toString(shouldBeFired));
 188         properties.setProperty(Verifier.UNCOMMON_TRAP_NAME, testCase.name);
 189         properties.setProperty(Verifier.UNCOMMON_TRAP_COMMENT,
 190                 testCase.comment);
 191         properties.setProperty(Verifier.UNCOMMON_TRAP_BCI,
 192                 Integer.toString(unstableIfLocation.getOffset()));
 193 
 194         properties.list(System.out);
 195 
 196         File f = new File(WB.getStringVMFlag("LogFile") +
 197                 Verifier.PROPERTIES_FILE_SUFFIX);
 198         try (FileWriter wr = new FileWriter(f)) {
 199             properties.store(wr, "");
 200         } catch (IOException e) {
 201             throw new Error("Unable to store test properties.", e);
 202         }
 203     }
 204 
 205     private static boolean isMethodCompiledByC2(Method m) {
 206         boolean isTiered = WB.getBooleanVMFlag("TieredCompilation");
 207         boolean isMethodCompiled = WB.isMethodCompiled(m);
 208         boolean isMethodCompiledAtMaxTier
 209                 = WB.getMethodCompilationLevel(m) == MAX_TIER;
 210 
 211         return Platform.isServer() && isMethodCompiled
 212                 && (!isTiered || isMethodCompiledAtMaxTier);
 213     }
 214 
 215     /**
 216      * Generates class with name {@code CLASS_NAME}, which will contain a
 217      * static method {@code METHOD_NAME}:
 218      *
 219      * <pre>{@code
 220      * public abstract class UnstableIfExecutable {
 221      *   private static int field = 0;
 222      *
 223      *   public static void test(boolean alwaysTrue) {
 224      *     if (alwaysTrue) {
 225      *       field++;
 226      *     } else {
 227      *       field--;
 228      *     }
 229      *   }
 230      * }
 231      * }</pre>
 232      *
 233      * @return generated bytecode.
 234      */
 235     private static byte[] generateTest(Label unstableIfLocation) {
 236         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 237 
 238         cw.visit(CLASS_FILE_VERSION, ACC_PUBLIC | ACC_ABSTRACT, CLASS_NAME,
 239                 null, "java/lang/Object", null);
 240 
 241         cw.visitField(ACC_PUBLIC | ACC_STATIC | ACC_VOLATILE, FIELD_NAME,
 242                 "I", null, Integer.valueOf(0));
 243 
 244         generateTestMethod(cw, unstableIfLocation);
 245 
 246         return cw.toByteArray();
 247     }
 248 
 249     private static void generateTestMethod(ClassVisitor cv,
 250             Label unstableIfLocation) {
 251         MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_STATIC, METHOD_NAME,
 252                 "(Z)V", null, null);
 253         mv.visitCode();
 254 
 255         Label end = new Label();
 256         Label falseBranch = new Label();
 257 
 258         // push "field" field's value and 1 to stack
 259         mv.visitFieldInsn(GETSTATIC, CLASS_NAME, FIELD_NAME, "I");
 260         mv.visitInsn(ICONST_1);
 261         // load argument's value
 262         mv.visitVarInsn(ILOAD, 0); // alwaysTrue
 263         // here is our unstable if
 264         mv.visitLabel(unstableIfLocation);
 265         mv.visitJumpInsn(IFEQ, falseBranch);
 266         // increment on "true"
 267         mv.visitInsn(IADD);
 268         mv.visitJumpInsn(GOTO, end);
 269         // decrement on "false"
 270         mv.visitLabel(falseBranch);
 271         mv.visitInsn(ISUB);
 272         mv.visitLabel(end);
 273         // bye bye
 274         mv.visitInsn(RETURN);
 275 
 276         mv.visitMaxs(0, 0);
 277         mv.visitEnd();
 278     }
 279 }
 280