| /* |
| * Copyright Amazon.com Inc. 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 8275908 |
| * @summary Record null_check traps for calls and array_check traps in the interpreter |
| * |
| * @requires vm.compiler2.enabled & vm.compMode != "Xcomp" |
| * @requires vm.opt.DeoptimizeALot != true |
| * |
| * @library /test/lib |
| * @build jdk.test.whitebox.WhiteBox |
| * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox |
| * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI |
| * -Xbatch -XX:-UseOnStackReplacement -XX:-TieredCompilation |
| * -XX:CompileCommand=compileonly,compiler.exceptions.OptimizeImplicitExceptions::throwImplicitException |
| * compiler.exceptions.OptimizeImplicitExceptions |
| */ |
| |
| package compiler.exceptions; |
| |
| import java.lang.reflect.Method; |
| import java.util.HashMap; |
| |
| import jdk.test.lib.Asserts; |
| import jdk.test.whitebox.WhiteBox; |
| |
| public class OptimizeImplicitExceptions { |
| // ImplicitException represents the various implicit (aka. 'built-in') exceptions |
| // which can be thrown implicitely by the JVM when executing bytecodes. |
| public enum ImplicitException { |
| // NullPointerException during field access |
| NULL_POINTER_EXCEPTION("null_check"), |
| // NullPointerException during invoke |
| INVOKE_NULL_POINTER_EXCEPTION("null_check"), |
| ARITHMETIC_EXCEPTION("div0_check"), |
| ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION("range_check"), |
| ARRAY_STORE_EXCEPTION("array_check"), |
| CLASS_CAST_EXCEPTION("class_check"); |
| private final String reason; |
| ImplicitException(String reason) { |
| this.reason = reason; |
| } |
| public String getReason() { |
| return reason; |
| } |
| } |
| // TestMode represents a specific combination of the OmitStackTraceInFastThrow command line options. |
| // They will be set up in 'setFlags(TestMode testMode)' before a new test run starts. |
| public enum TestMode { |
| OMIT_STACKTRACES_IN_FASTTHROW, |
| STACKTRACES_IN_FASTTHROW |
| } |
| |
| private static final WhiteBox WB = WhiteBox.getWhiteBox(); |
| // The number of deoptimizations after which a method will be made not-entrant |
| private static final int PerBytecodeTrapLimit = WB.getIntxVMFlag("PerBytecodeTrapLimit").intValue(); |
| // The number of interpreter invocations after which a decompiled method will be re-compiled. |
| private static final int Tier0InvokeNotifyFreq = (int)Math.pow(2, WB.getIntxVMFlag("Tier0InvokeNotifyFreqLog")); |
| // The following variables are used to track the value of the global deopt counters between the various test phases. |
| private static int oldDeoptCount = 0; |
| private static HashMap<String, Integer> oldDeoptCountReason = new HashMap<String, Integer>(ImplicitException.values().length); |
| // The following two objects are declared statically to simplify the test method. |
| private static String[] string_a = new String[1]; |
| private static final Object o = new Object(); |
| |
| // This is the main test method. It will repeatedly called with the same ImplicitException 'type' to |
| // JIT-compile it, deoptimized it, re-compile it again and do various checks on the way. |
| // This process will be repeated then for each kind of ImplicitException 'type'. |
| public static Object throwImplicitException(ImplicitException type, Object[] object_a) { |
| switch (type) { |
| case NULL_POINTER_EXCEPTION: { |
| return object_a.length; |
| } |
| case INVOKE_NULL_POINTER_EXCEPTION: { |
| return object_a.hashCode(); |
| } |
| case ARITHMETIC_EXCEPTION: { |
| return ((42 / (object_a.length - 1)) > 2) ? null : object_a[0]; |
| } |
| case ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION: { |
| return object_a[5]; |
| } |
| case ARRAY_STORE_EXCEPTION: { |
| return (object_a[0] = o); |
| } |
| case CLASS_CAST_EXCEPTION: { |
| return (ImplicitException[])object_a; |
| } |
| } |
| return null; |
| } |
| |
| // Completely unload (i.e. make "not-entrant"->free) a JIT-compiled |
| // version of a method and clear the method's profiling counters. |
| private static void unloadAndClean(Method m) { |
| WB.deoptimizeMethod(m); // Makes the nmethod "not entrant". |
| System.gc(); |
| WB.clearMethodState(m); |
| } |
| |
| // Set '-XX' flags according to 'TestMode' |
| private static void setFlags(TestMode testMode) { |
| if (testMode == TestMode.OMIT_STACKTRACES_IN_FASTTHROW) { |
| WB.setBooleanVMFlag("OmitStackTraceInFastThrow", true); |
| } else { |
| WB.setBooleanVMFlag("OmitStackTraceInFastThrow", false); |
| } |
| |
| System.out.println("=========================================================="); |
| System.out.println("testMode=" + testMode + |
| " OmitStackTraceInFastThrow=" + WB.getBooleanVMFlag("OmitStackTraceInFastThrow")); |
| System.out.println("=========================================================="); |
| } |
| |
| private static void printCounters(TestMode testMode, ImplicitException impExcp, Method throwImplicitException_m, int invocations) { |
| System.out.println("testMode=" + testMode + " exception=" + impExcp + " invocations=" + invocations + "\n" + |
| "decompilecount=" + WB.getMethodDecompileCount(throwImplicitException_m) + " " + |
| "trapCount=" + WB.getMethodTrapCount(throwImplicitException_m) + " " + |
| "trapCount(" + impExcp.getReason() + ")=" + |
| WB.getMethodTrapCount(throwImplicitException_m, impExcp.getReason()) + " " + |
| "globalDeoptCount=" + WB.getDeoptCount() + " " + |
| "globalDeoptCount(" + impExcp.getReason() + ")=" + WB.getDeoptCount(impExcp.getReason(), null)); |
| System.out.println("method compiled=" + WB.isMethodCompiled(throwImplicitException_m)); |
| } |
| |
| // Checks after the test method has been JIT-compiled but before the compiled version has been invoked. |
| private static void checkSimple(TestMode testMode, ImplicitException impExcp, Exception ex, Method throwImplicitException_m, int invocations) { |
| |
| printCounters(testMode, impExcp, throwImplicitException_m, invocations); |
| // At this point, throwImplicitException() has been compiled but the compiled version has not been invoked yet. |
| Asserts.assertEQ(WB.getMethodCompilationLevel(throwImplicitException_m), 4, "Method should be compiled at level 4."); |
| |
| int trapCount = WB.getMethodTrapCount(throwImplicitException_m); |
| int trapCountSpecific = WB.getMethodTrapCount(throwImplicitException_m, impExcp.getReason()); |
| Asserts.assertEQ(trapCount, invocations, "Trap count must much invocation count."); |
| Asserts.assertEQ(trapCountSpecific, invocations, "Trap count must much invocation count."); |
| Asserts.assertNotNull(ex.getMessage(), "Exceptions thrown in the interpreter should have a message."); |
| } |
| |
| // Checks after the JIT-compiled test method has been invoked 'invocations' times. |
| private static void check(TestMode testMode, ImplicitException impExcp, Exception ex, |
| Method throwImplicitException_m, int invocations, int totalInvocations) { |
| |
| printCounters(testMode, impExcp, throwImplicitException_m, totalInvocations); |
| // At this point, the compiled version of 'throwImplicitException()' has been invoked 'invocations' times. |
| Asserts.assertEQ(WB.getMethodCompilationLevel(throwImplicitException_m), 4, "Method should be compiled at level 4."); |
| int deoptCount = WB.getDeoptCount(); |
| int deoptCountReason = WB.getDeoptCount(impExcp.getReason(), null/*action*/); |
| if (testMode == TestMode.OMIT_STACKTRACES_IN_FASTTHROW) { |
| // No deoptimizations for '-XX:+OmitStackTraceInFastThrow' |
| Asserts.assertEQ(oldDeoptCount, deoptCount, "Wrong number of deoptimizations."); |
| Asserts.assertEQ(oldDeoptCountReason.get(impExcp.getReason()), deoptCountReason, "Wrong number of deoptimizations."); |
| // '-XX:+OmitStackTraceInFastThrow' never has message because it is using a global singleton exception. |
| Asserts.assertNull(ex.getMessage(), "Optimized exceptions have no message."); |
| } else if (testMode == TestMode.STACKTRACES_IN_FASTTHROW) { |
| // We always deoptimize for '-XX:-OmitStackTraceInFastThrow |
| Asserts.assertEQ(oldDeoptCount + invocations, deoptCount, "Wrong number of deoptimizations."); |
| Asserts.assertEQ(oldDeoptCountReason.get(impExcp.getReason()) + invocations, deoptCountReason, "Wrong number of deoptimizations."); |
| Asserts.assertNotNull(ex.getMessage(), "Exceptions thrown in the interpreter should have a message."); |
| } else { |
| Asserts.fail("Unknown test mode."); |
| } |
| oldDeoptCount = deoptCount; |
| oldDeoptCountReason.put(impExcp.getReason(), deoptCountReason); |
| } |
| |
| public static void main(String[] args) throws Exception { |
| |
| if (!WB.getBooleanVMFlag("ProfileTraps")) { |
| // The fast-throw optimzation only works if we're running with -XX:+ProfileTraps |
| return; |
| } |
| |
| // The following options are both develop, or nops in product build. |
| // If they are set, disable them for test stability. It's fine because we use /othervm above. |
| WB.setBooleanVMFlag("DeoptimizeALot", false); |
| WB.setBooleanVMFlag("DeoptimizeRandom", false); |
| |
| // Initialize global deopt counts to zero. |
| for (ImplicitException impExcp : ImplicitException.values()) { |
| oldDeoptCountReason.put(impExcp.getReason(), 0); |
| } |
| // Get a handle of the test method for usage with the WhiteBox API. |
| Method throwImplicitException_m = OptimizeImplicitExceptions.class |
| .getDeclaredMethod("throwImplicitException", new Class[] { ImplicitException.class, Object[].class}); |
| |
| for (TestMode testMode : TestMode.values()) { |
| setFlags(testMode); |
| for (ImplicitException impExcp : ImplicitException.values()) { |
| int invocations = 0; |
| Exception lastException = null; |
| |
| // Warmup and compile, but don't invoke compiled code. |
| while(!WB.isMethodCompiled(throwImplicitException_m)) { |
| invocations++; |
| try { |
| throwImplicitException(impExcp, impExcp.getReason().equals("null_check") ? null : string_a); |
| } catch (Exception catchedExcp) { |
| lastException = catchedExcp; |
| continue; |
| } |
| throw new Exception("Should not happen"); |
| } |
| |
| checkSimple(testMode, impExcp, lastException, throwImplicitException_m, invocations); |
| |
| // Invoke compiled code 'PerBytecodeTrapLimit' times. |
| for (int i = 0; i < PerBytecodeTrapLimit; i++) { |
| invocations++; |
| try { |
| throwImplicitException(impExcp, impExcp.getReason().equals("null_check") ? null : string_a); |
| } catch (Exception catchedExcp) { |
| lastException = catchedExcp; |
| continue; |
| } |
| throw new Exception("Should not happen"); |
| } |
| |
| check(testMode, impExcp, lastException, throwImplicitException_m, PerBytecodeTrapLimit, invocations); |
| |
| // Invoke compiled code 'Tier0InvokeNotifyFreq' times. |
| // If the method was de-compiled before, this will re-compile it again. |
| for (int i = 0; i < Tier0InvokeNotifyFreq; i++) { |
| invocations++; |
| try { |
| throwImplicitException(impExcp, impExcp.getReason().equals("null_check") ? null : string_a); |
| } catch (Exception catchedExcp) { |
| lastException = catchedExcp; |
| continue; |
| } |
| throw new Exception("Should not happen"); |
| } |
| |
| check(testMode, impExcp, lastException, throwImplicitException_m, Tier0InvokeNotifyFreq, invocations); |
| |
| // Invoke compiled code 'PerBytecodeTrapLimit' times. |
| for (int i = 0; i < PerBytecodeTrapLimit; i++) { |
| invocations++; |
| try { |
| throwImplicitException(impExcp, impExcp.getReason().equals("null_check") ? null : string_a); |
| } catch (Exception catchedExcp) { |
| lastException = catchedExcp; |
| continue; |
| } |
| throw new Exception("Should not happen"); |
| } |
| |
| check(testMode, impExcp, lastException, throwImplicitException_m, PerBytecodeTrapLimit, invocations); |
| |
| System.out.println("------------------------------------------------------------------"); |
| |
| unloadAndClean(throwImplicitException_m); |
| } |
| } |
| } |
| } |