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