/* * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 8030976 8059226 * @library /test/lib / * @modules java.base/jdk.internal.org.objectweb.asm * java.base/jdk.internal.misc * java.compiler * java.management * jdk.jvmstat/sun.jvmstat.monitor * * @build sun.hotspot.WhiteBox * @run driver ClassFileInstaller sun.hotspot.WhiteBox * sun.hotspot.WhiteBox$WhiteBoxPermission * @run main/othervm -Xbatch -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI -XX:+LogCompilation * -XX:CompileCommand=compileonly,UnstableIfExecutable.test * -XX:LogFile=always_taken_not_fired.xml * compiler.uncommontrap.TestUnstableIfTrap ALWAYS_TAKEN false * @run main/othervm -Xbatch -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI -XX:+LogCompilation * -XX:CompileCommand=compileonly,UnstableIfExecutable.test * -XX:LogFile=always_taken_fired.xml * compiler.uncommontrap.TestUnstableIfTrap ALWAYS_TAKEN true * @run main/othervm -Xbatch -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI -XX:+LogCompilation * -XX:CompileCommand=compileonly,UnstableIfExecutable.test * -XX:LogFile=never_taken_not_fired.xml * compiler.uncommontrap.TestUnstableIfTrap NEVER_TAKEN false * @run main/othervm -Xbatch -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI -XX:+LogCompilation * -XX:CompileCommand=compileonly,UnstableIfExecutable.test * -XX:LogFile=never_taken_fired.xml * compiler.uncommontrap.TestUnstableIfTrap NEVER_TAKEN true * @run driver compiler.testlibrary.uncommontrap.Verifier always_taken_not_fired.xml * always_taken_fired.xml * never_taken_not_fired.xml * never_taken_fired.xml */ package compiler.uncommontrap; import compiler.testlibrary.uncommontrap.Verifier; import jdk.internal.org.objectweb.asm.ClassVisitor; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.Label; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.test.lib.ByteCodeLoader; import jdk.test.lib.Platform; import sun.hotspot.WhiteBox; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Method; import java.util.Properties; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VOLATILE; import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC; import static jdk.internal.org.objectweb.asm.Opcodes.GOTO; import static jdk.internal.org.objectweb.asm.Opcodes.IADD; import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_1; import static jdk.internal.org.objectweb.asm.Opcodes.IFEQ; import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD; import static jdk.internal.org.objectweb.asm.Opcodes.ISUB; import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; public class TestUnstableIfTrap { private static final WhiteBox WB = WhiteBox.getWhiteBox(); private static final String CLASS_NAME = "UnstableIfExecutable"; private static final String METHOD_NAME = "test"; private static final String FIELD_NAME = "field"; private static final int ITERATIONS = 1_000_000; // There is no dependency on particular class file version, so it could be // set to any version (if you're updating this test for Java 42). private static final int CLASS_FILE_VERSION = 49; private static final int MAX_TIER = 4; // This test aimed to verify that uncommon trap with reason "unstable_if" // is emitted when method that contain control-flow divergence such that // one of two branches is never taken (and other one is taken always). // C2 will made a decision whether or not the branch was ever taken // depending on method's profile. // If profile was collected for a few method's invocations, then C2 will not // trust in branches' probabilities and the tested trap won't be emitted. // In fact, a method has to be invoked at least 40 time at the day when this // comment was written (see Parse::dynamic_branch_prediction for an actual // value). It would be to implementation dependent to use "40" as // a threshold value in the test, so in order to improve test's robustness // the threshold value is 1000: if the tested method was compiled by C2 // before it was invoked 1000 times, then we won't verify that trap was // emitted and fired. private static final int MIN_INVOCATIONS_BEFORE_C2_COMPILATION = 1000; /** * Description of test case parameters and uncommon trap that will * be emitted during tested method compilation. */ private static enum TestCaseName { ALWAYS_TAKEN(false, "taken always"), NEVER_TAKEN(true, "taken never"); TestCaseName(boolean predicate, String comment) { this.predicate = predicate; this.comment = comment; } public final boolean predicate; public final String name = "unstable_if"; public final String comment; } public static void main(String args[]) { if (args.length != 2) { throw new Error("Expected two arguments: test case name and a " + "boolean determining if uncommon trap should be fired."); } test(TestCaseName.valueOf(args[0]), Boolean.valueOf(args[1])); } private static void test(TestCaseName testCase, boolean shouldBeFired) { Method testMethod; Label unstableIfLocation = new Label(); boolean shouldBeEmitted; boolean compiledToEarly = false; try { Class testClass = ByteCodeLoader.load(CLASS_NAME, generateTest(unstableIfLocation)); testMethod = testClass.getDeclaredMethod(METHOD_NAME, boolean.class); for (int i = 0; i < ITERATIONS; i++) { testMethod.invoke(null, testCase.predicate); if (i < MIN_INVOCATIONS_BEFORE_C2_COMPILATION && isMethodCompiledByC2(testMethod)) { compiledToEarly = true; // There is no sense in further invocations: we already // decided to avoid verification. break; } } // We're checking that trap should be emitted (i.e. it was compiled // by C2) before the trap is fired, because otherwise the nmethod // will be deoptimized and isMethodCompiledByC2 will return false. shouldBeEmitted = isMethodCompiledByC2(testMethod) && !compiledToEarly; if (shouldBeFired) { testMethod.invoke(null, !testCase.predicate); } } catch (ReflectiveOperationException e) { throw new Error("Test case should be generated, loaded and executed" + " without any issues.", e); } shouldBeFired &= shouldBeEmitted; Properties properties = new Properties(); properties.setProperty(Verifier.VERIFICATION_SHOULD_BE_SKIPPED, Boolean.toString(compiledToEarly)); properties.setProperty(Verifier.UNCOMMON_TRAP_SHOULD_EMITTED, Boolean.toString(shouldBeEmitted)); properties.setProperty(Verifier.UNCOMMON_TRAP_SHOULD_FIRED, Boolean.toString(shouldBeFired)); properties.setProperty(Verifier.UNCOMMON_TRAP_NAME, testCase.name); properties.setProperty(Verifier.UNCOMMON_TRAP_COMMENT, testCase.comment); properties.setProperty(Verifier.UNCOMMON_TRAP_BCI, Integer.toString(unstableIfLocation.getOffset())); properties.list(System.out); File f = new File(WB.getStringVMFlag("LogFile") + Verifier.PROPERTIES_FILE_SUFFIX); try (FileWriter wr = new FileWriter(f)) { properties.store(wr, ""); } catch (IOException e) { throw new Error("Unable to store test properties.", e); } } private static boolean isMethodCompiledByC2(Method m) { boolean isTiered = WB.getBooleanVMFlag("TieredCompilation"); boolean isMethodCompiled = WB.isMethodCompiled(m); boolean isMethodCompiledAtMaxTier = WB.getMethodCompilationLevel(m) == MAX_TIER; return Platform.isServer() && isMethodCompiled && (!isTiered || isMethodCompiledAtMaxTier); } /** * Generates class with name {@code CLASS_NAME}, which will contain a * static method {@code METHOD_NAME}: * * <pre>{@code * public abstract class UnstableIfExecutable { * private static int field = 0; * * public static void test(boolean alwaysTrue) { * if (alwaysTrue) { * field++; * } else { * field--; * } * } * } * }</pre> * * @return generated bytecode. */ private static byte[] generateTest(Label unstableIfLocation) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); cw.visit(CLASS_FILE_VERSION, ACC_PUBLIC | ACC_ABSTRACT, CLASS_NAME, null, "java/lang/Object", null); cw.visitField(ACC_PUBLIC | ACC_STATIC | ACC_VOLATILE, FIELD_NAME, "I", null, Integer.valueOf(0)); generateTestMethod(cw, unstableIfLocation); return cw.toByteArray(); } private static void generateTestMethod(ClassVisitor cv, Label unstableIfLocation) { MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_STATIC, METHOD_NAME, "(Z)V", null, null); mv.visitCode(); Label end = new Label(); Label falseBranch = new Label(); // push "field" field's value and 1 to stack mv.visitFieldInsn(GETSTATIC, CLASS_NAME, FIELD_NAME, "I"); mv.visitInsn(ICONST_1); // load argument's value mv.visitVarInsn(ILOAD, 0); // alwaysTrue // here is our unstable if mv.visitLabel(unstableIfLocation); mv.visitJumpInsn(IFEQ, falseBranch); // increment on "true" mv.visitInsn(IADD); mv.visitJumpInsn(GOTO, end); // decrement on "false" mv.visitLabel(falseBranch); mv.visitInsn(ISUB); mv.visitLabel(end); // bye bye mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } }