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