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