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