1 /*
2 * Copyright Amazon.com Inc. 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 8275908
27 * @summary Record null_check traps for calls and array_check traps in the interpreter
28 *
29 * @requires vm.compiler2.enabled & vm.compMode != "Xcomp"
30 * @requires vm.opt.DeoptimizeALot != true
31 *
32 * @library /test/lib
33 * @build jdk.test.whitebox.WhiteBox
34 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
35 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
36 * -Xbatch -XX:-UseOnStackReplacement -XX:-TieredCompilation
37 * -XX:CompileCommand=compileonly,compiler.exceptions.OptimizeImplicitExceptions::throwImplicitException
38 * compiler.exceptions.OptimizeImplicitExceptions
39 */
40
41 package compiler.exceptions;
42
43 import java.lang.reflect.Method;
44 import java.util.HashMap;
45
46 import jdk.test.lib.Asserts;
47 import jdk.test.whitebox.WhiteBox;
48
49 public class OptimizeImplicitExceptions {
50 // ImplicitException represents the various implicit (aka. 'built-in') exceptions
51 // which can be thrown implicitely by the JVM when executing bytecodes.
52 public enum ImplicitException {
53 // NullPointerException during field access
54 NULL_POINTER_EXCEPTION("null_check"),
55 // NullPointerException during invoke
56 INVOKE_NULL_POINTER_EXCEPTION("null_check"),
57 ARITHMETIC_EXCEPTION("div0_check"),
58 ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION("range_check"),
59 ARRAY_STORE_EXCEPTION("array_check"),
60 CLASS_CAST_EXCEPTION("class_check");
61 private final String reason;
62 ImplicitException(String reason) {
63 this.reason = reason;
64 }
65 public String getReason() {
66 return reason;
67 }
68 }
69 // TestMode represents a specific combination of the OmitStackTraceInFastThrow command line options.
70 // They will be set up in 'setFlags(TestMode testMode)' before a new test run starts.
71 public enum TestMode {
72 OMIT_STACKTRACES_IN_FASTTHROW,
73 STACKTRACES_IN_FASTTHROW
74 }
75
76 private static final WhiteBox WB = WhiteBox.getWhiteBox();
77 // The number of deoptimizations after which a method will be made not-entrant
78 private static final int PerBytecodeTrapLimit = WB.getIntxVMFlag("PerBytecodeTrapLimit").intValue();
79 // The number of interpreter invocations after which a decompiled method will be re-compiled.
80 private static final int Tier0InvokeNotifyFreq = (int)Math.pow(2, WB.getIntxVMFlag("Tier0InvokeNotifyFreqLog"));
81 // The following variables are used to track the value of the global deopt counters between the various test phases.
82 private static int oldDeoptCount = 0;
83 private static HashMap<String, Integer> oldDeoptCountReason = new HashMap<String, Integer>(ImplicitException.values().length);
84 // The following two objects are declared statically to simplify the test method.
85 private static String[] string_a = new String[1];
86 private static final Object o = new Object();
87
88 // This is the main test method. It will repeatedly called with the same ImplicitException 'type' to
89 // JIT-compile it, deoptimized it, re-compile it again and do various checks on the way.
90 // This process will be repeated then for each kind of ImplicitException 'type'.
91 public static Object throwImplicitException(ImplicitException type, Object[] object_a) {
92 switch (type) {
93 case NULL_POINTER_EXCEPTION: {
94 return object_a.length;
95 }
96 case INVOKE_NULL_POINTER_EXCEPTION: {
97 return object_a.hashCode();
98 }
99 case ARITHMETIC_EXCEPTION: {
100 return ((42 / (object_a.length - 1)) > 2) ? null : object_a[0];
101 }
102 case ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION: {
103 return object_a[5];
104 }
105 case ARRAY_STORE_EXCEPTION: {
106 return (object_a[0] = o);
107 }
108 case CLASS_CAST_EXCEPTION: {
109 return (ImplicitException[])object_a;
110 }
111 }
112 return null;
113 }
114
115 // Completely unload (i.e. make "not-entrant"->free) a JIT-compiled
116 // version of a method and clear the method's profiling counters.
117 private static void unloadAndClean(Method m) {
118 WB.deoptimizeMethod(m); // Makes the nmethod "not entrant".
119 System.gc();
120 WB.clearMethodState(m);
121 }
122
123 // Set '-XX' flags according to 'TestMode'
124 private static void setFlags(TestMode testMode) {
125 if (testMode == TestMode.OMIT_STACKTRACES_IN_FASTTHROW) {
126 WB.setBooleanVMFlag("OmitStackTraceInFastThrow", true);
127 } else {
128 WB.setBooleanVMFlag("OmitStackTraceInFastThrow", false);
129 }
130
131 System.out.println("==========================================================");
132 System.out.println("testMode=" + testMode +
133 " OmitStackTraceInFastThrow=" + WB.getBooleanVMFlag("OmitStackTraceInFastThrow"));
134 System.out.println("==========================================================");
135 }
136
137 private static void printCounters(TestMode testMode, ImplicitException impExcp, Method throwImplicitException_m, int invocations) {
138 System.out.println("testMode=" + testMode + " exception=" + impExcp + " invocations=" + invocations + "\n" +
139 "decompilecount=" + WB.getMethodDecompileCount(throwImplicitException_m) + " " +
140 "trapCount=" + WB.getMethodTrapCount(throwImplicitException_m) + " " +
141 "trapCount(" + impExcp.getReason() + ")=" +
142 WB.getMethodTrapCount(throwImplicitException_m, impExcp.getReason()) + " " +
143 "globalDeoptCount=" + WB.getDeoptCount() + " " +
144 "globalDeoptCount(" + impExcp.getReason() + ")=" + WB.getDeoptCount(impExcp.getReason(), null));
145 System.out.println("method compiled=" + WB.isMethodCompiled(throwImplicitException_m));
146 }
147
148 // Checks after the test method has been JIT-compiled but before the compiled version has been invoked.
149 private static void checkSimple(TestMode testMode, ImplicitException impExcp, Exception ex, Method throwImplicitException_m, int invocations) {
150
151 printCounters(testMode, impExcp, throwImplicitException_m, invocations);
152 // At this point, throwImplicitException() has been compiled but the compiled version has not been invoked yet.
153 Asserts.assertEQ(WB.getMethodCompilationLevel(throwImplicitException_m), 4, "Method should be compiled at level 4.");
154
155 int trapCount = WB.getMethodTrapCount(throwImplicitException_m);
156 int trapCountSpecific = WB.getMethodTrapCount(throwImplicitException_m, impExcp.getReason());
157 Asserts.assertEQ(trapCount, invocations, "Trap count must match invocation count.");
158 Asserts.assertEQ(trapCountSpecific, invocations, "Trap count must match invocation count.");
159 Asserts.assertNotNull(ex.getMessage(), "Exceptions thrown in the interpreter should have a message.");
160 }
161
162 // Checks after the JIT-compiled test method has been invoked 'invocations' times.
163 private static void check(TestMode testMode, ImplicitException impExcp, Exception ex,
164 Method throwImplicitException_m, int invocations, int totalInvocations) {
165
166 printCounters(testMode, impExcp, throwImplicitException_m, totalInvocations);
167 // At this point, the compiled version of 'throwImplicitException()' has been invoked 'invocations' times.
168 Asserts.assertEQ(WB.getMethodCompilationLevel(throwImplicitException_m), 4, "Method should be compiled at level 4.");
169 int deoptCount = WB.getDeoptCount();
170 int deoptCountReason = WB.getDeoptCount(impExcp.getReason(), null/*action*/);
171 if (testMode == TestMode.OMIT_STACKTRACES_IN_FASTTHROW) {
172 // No deoptimizations for '-XX:+OmitStackTraceInFastThrow'
173 Asserts.assertEQ(oldDeoptCount, deoptCount, "Wrong number of deoptimizations.");
174 Asserts.assertEQ(oldDeoptCountReason.get(impExcp.getReason()), deoptCountReason, "Wrong number of deoptimizations.");
175 // '-XX:+OmitStackTraceInFastThrow' never has message because it is using a global singleton exception.
176 Asserts.assertNull(ex.getMessage(), "Optimized exceptions have no message.");
177 } else if (testMode == TestMode.STACKTRACES_IN_FASTTHROW) {
178 // We always deoptimize for '-XX:-OmitStackTraceInFastThrow
179 Asserts.assertEQ(oldDeoptCount + invocations, deoptCount, "Wrong number of deoptimizations.");
180 Asserts.assertEQ(oldDeoptCountReason.get(impExcp.getReason()) + invocations, deoptCountReason, "Wrong number of deoptimizations.");
181 Asserts.assertNotNull(ex.getMessage(), "Exceptions thrown in the interpreter should have a message.");
182 } else {
183 Asserts.fail("Unknown test mode.");
184 }
185 oldDeoptCount = deoptCount;
186 oldDeoptCountReason.put(impExcp.getReason(), deoptCountReason);
187 }
188
189 public static void main(String[] args) throws Exception {
190
191 if (!WB.getBooleanVMFlag("ProfileTraps")) {
192 // The fast-throw optimzation only works if we're running with -XX:+ProfileTraps
193 return;
194 }
195
196 // The following options are both develop, or nops in product build.
197 // If they are set, disable them for test stability. It's fine because we use /othervm above.
198 WB.setBooleanVMFlag("DeoptimizeALot", false);
199 WB.setBooleanVMFlag("DeoptimizeRandom", false);
200
201 // Initialize global deopt counts to zero.
202 for (ImplicitException impExcp : ImplicitException.values()) {
203 oldDeoptCountReason.put(impExcp.getReason(), 0);
204 }
205 // Get a handle of the test method for usage with the WhiteBox API.
206 Method throwImplicitException_m = OptimizeImplicitExceptions.class
207 .getDeclaredMethod("throwImplicitException", new Class[] { ImplicitException.class, Object[].class});
208
209 for (TestMode testMode : TestMode.values()) {
210 setFlags(testMode);
211 for (ImplicitException impExcp : ImplicitException.values()) {
212 int invocations = 0;
213 Exception lastException = null;
214
215 // Warmup and compile, but don't invoke compiled code.
216 while(!WB.isMethodCompiled(throwImplicitException_m)) {
217 invocations++;
218 try {
219 throwImplicitException(impExcp, impExcp.getReason().equals("null_check") ? null : string_a);
220 } catch (Exception catchedExcp) {
221 lastException = catchedExcp;
222 continue;
223 }
224 throw new Exception("Should not happen");
225 }
226
227 checkSimple(testMode, impExcp, lastException, throwImplicitException_m, invocations);
228
229 // Invoke compiled code 'PerBytecodeTrapLimit' times.
230 for (int i = 0; i < PerBytecodeTrapLimit; i++) {
231 invocations++;
232 try {
233 throwImplicitException(impExcp, impExcp.getReason().equals("null_check") ? null : string_a);
234 } catch (Exception catchedExcp) {
235 lastException = catchedExcp;
236 continue;
237 }
238 throw new Exception("Should not happen");
239 }
240
241 check(testMode, impExcp, lastException, throwImplicitException_m, PerBytecodeTrapLimit, invocations);
242
243 // Invoke compiled code 'Tier0InvokeNotifyFreq' times.
244 // If the method was de-compiled before, this will re-compile it again.
245 for (int i = 0; i < Tier0InvokeNotifyFreq; i++) {
246 invocations++;
247 try {
248 throwImplicitException(impExcp, impExcp.getReason().equals("null_check") ? null : string_a);
249 } catch (Exception catchedExcp) {
250 lastException = catchedExcp;
251 continue;
252 }
253 throw new Exception("Should not happen");
254 }
255
256 check(testMode, impExcp, lastException, throwImplicitException_m, Tier0InvokeNotifyFreq, invocations);
257
258 // Invoke compiled code 'PerBytecodeTrapLimit' times.
259 for (int i = 0; i < PerBytecodeTrapLimit; i++) {
260 invocations++;
261 try {
262 throwImplicitException(impExcp, impExcp.getReason().equals("null_check") ? null : string_a);
263 } catch (Exception catchedExcp) {
264 lastException = catchedExcp;
265 continue;
266 }
267 throw new Exception("Should not happen");
268 }
269
270 check(testMode, impExcp, lastException, throwImplicitException_m, PerBytecodeTrapLimit, invocations);
271
272 System.out.println("------------------------------------------------------------------");
273
274 unloadAndClean(throwImplicitException_m);
275 }
276 }
277 }
278 }