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 much invocation count.");
158         Asserts.assertEQ(trapCountSpecific, invocations, "Trap count must much 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 }