/* * 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 oldDeoptCountReason = new HashMap(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); } } } }