1 /*
2  * Copyright (c) 2011, 2019, Oracle and/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 package org.graalvm.compiler.replacements.test;
26 
27 import java.util.Random;
28 
29 import org.graalvm.compiler.api.directives.GraalDirectives;
30 import org.graalvm.compiler.core.phases.HighTier;
31 import org.graalvm.compiler.core.test.GraalCompilerTest;
32 import org.graalvm.compiler.nodes.ValueNode;
33 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext;
34 import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin;
35 import org.graalvm.compiler.options.OptionValues;
36 import org.graalvm.compiler.phases.common.AbstractInliningPhase;
37 import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
38 import org.graalvm.compiler.api.test.ExportingClassLoader;
39 import org.junit.Assert;
40 import org.junit.Assume;
41 import org.junit.Test;
42 import org.objectweb.asm.ClassWriter;
43 import org.objectweb.asm.Label;
44 import org.objectweb.asm.MethodVisitor;
45 import org.objectweb.asm.Opcodes;
46 
47 import jdk.vm.ci.code.InstalledCode;
48 import jdk.vm.ci.meta.DeoptimizationReason;
49 import jdk.vm.ci.meta.ResolvedJavaMethod;
50 
51 /**
52  * Tests that deoptimization upon exception handling works.
53  */
54 public class DeoptimizeOnExceptionTest extends GraalCompilerTest {
55 
DeoptimizeOnExceptionTest()56     public DeoptimizeOnExceptionTest() {
57         createSuites(getInitialOptions()).getHighTier().findPhase(AbstractInliningPhase.class).remove();
58     }
59 
raiseException(String m1, String m2, String m3, String m4, String m5)60     private static void raiseException(String m1, String m2, String m3, String m4, String m5) {
61         throw new RuntimeException(m1 + m2 + m3 + m4 + m5);
62     }
63 
64     @Test
test1()65     public void test1() {
66         test("test1Snippet", "m1", "m2", "m3", "m4", "m5");
67     }
68 
69     // no local exception handler - will deopt
test1Snippet(String m1, String m2, String m3, String m4, String m5)70     public static String test1Snippet(String m1, String m2, String m3, String m4, String m5) {
71         if (m1 != null) {
72             raiseException(m1, m2, m3, m4, m5);
73         }
74         return m1 + m2 + m3 + m4 + m5;
75     }
76 
77     @Test
test2()78     public void test2() {
79         test("test2Snippet");
80     }
81 
test2Snippet()82     public String test2Snippet() throws Exception {
83         try {
84             ClassLoader testCl = new MyClassLoader();
85             @SuppressWarnings("unchecked")
86             Class<Runnable> c = (Class<Runnable>) testCl.loadClass(name);
87             Runnable r = c.getDeclaredConstructor().newInstance();
88             ct = Long.MAX_VALUE;
89             // warmup
90             for (int i = 0; i < 100; i++) {
91                 r.run();
92             }
93             // compile
94             ResolvedJavaMethod method = getResolvedJavaMethod(c, "run");
95             getCode(method);
96             ct = 0;
97             r.run();
98         } catch (Throwable e) {
99             e.printStackTrace(System.out);
100             Assert.fail();
101         }
102         return "SUCCESS";
103     }
104 
105     @Test
test3()106     public void test3() {
107         Assume.assumeTrue("Only works on jdk8 right now", JavaVersionUtil.JAVA_SPEC <= 8);
108         ResolvedJavaMethod method = getResolvedJavaMethod("test3Snippet");
109 
110         for (int i = 0; i < 2; i++) {
111             Result actual;
112             boolean expectedCompiledCode = (method.getProfilingInfo().getDeoptimizationCount(DeoptimizationReason.NotCompiledExceptionHandler) != 0);
113             InstalledCode code = getCode(method, null, false, true, new OptionValues(getInitialOptions(), HighTier.Options.Inline, false));
114             assertTrue(code.isValid());
115 
116             try {
117                 actual = new Result(code.executeVarargs(false), null);
118             } catch (Exception e) {
119                 actual = new Result(null, e);
120             }
121 
122             assertTrue(i > 0 == expectedCompiledCode, "expect compiled code to stay around after the first iteration");
123             assertEquals(new Result(expectedCompiledCode, null), actual);
124             assertTrue(expectedCompiledCode == code.isValid());
125         }
126     }
127 
128     @Override
bytecodeParserShouldInlineInvoke(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args)129     protected InlineInvokePlugin.InlineInfo bytecodeParserShouldInlineInvoke(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args) {
130         if (method.getName().equals("throwException")) {
131             if (b.getMethod().getProfilingInfo().getDeoptimizationCount(DeoptimizationReason.NotCompiledExceptionHandler) != 0) {
132                 return InlineInvokePlugin.InlineInfo.DO_NOT_INLINE_WITH_EXCEPTION;
133             } else {
134                 return InlineInvokePlugin.InlineInfo.DO_NOT_INLINE_NO_EXCEPTION;
135             }
136         }
137         return super.bytecodeParserShouldInlineInvoke(b, method, args);
138     }
139 
throwException()140     private static void throwException() throws Exception {
141         throw new Exception();
142     }
143 
144     static int footprint;
145 
test3Snippet(boolean rethrowException)146     public static boolean test3Snippet(boolean rethrowException) throws Exception {
147         try {
148             footprint = 1;
149             throwException();
150         } catch (Exception e) {
151             footprint = 2;
152             if (rethrowException) {
153                 throw e;
154             }
155         }
156 
157         return GraalDirectives.inCompiledCode();
158     }
159 
160     public static class MyClassLoader extends ExportingClassLoader {
161         @Override
findClass(String className)162         protected Class<?> findClass(String className) throws ClassNotFoundException {
163             return defineClass(name.replace('/', '.'), clazz, 0, clazz.length);
164         }
165     }
166 
methodB()167     public static void methodB() {
168         Random r = new Random(System.currentTimeMillis());
169         while (r.nextFloat() > .03f) {
170             // Empty
171         }
172 
173         return;
174     }
175 
methodA()176     public static void methodA() {
177         Random r = new Random(System.currentTimeMillis());
178         while (r.nextDouble() > .05) {
179             // Empty
180         }
181         return;
182     }
183 
184     private static Object m = new Object();
185     static long ct = Long.MAX_VALUE;
186 
getM()187     public static Object getM() {
188         if (ct-- > 0) {
189             return m;
190         } else {
191             return null;
192         }
193     }
194 
195     private static String name = "t/TestJSR";
196 
197     private static final byte[] clazz = makeClazz();
198 
makeClazz()199     private static byte[] makeClazz() {
200         // Code generated the class below using asm.
201         String clazzName = DeoptimizeOnExceptionTest.class.getName().replace('.', '/');
202         final ClassWriter w = new ClassWriter(0);
203         w.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC,
204                         "t/TestJSR", null, "java/lang/Object",
205                         new String[]{"java/lang/Runnable"});
206         MethodVisitor mv = w.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, new String[]{});
207         mv.visitCode();
208         mv.visitVarInsn(Opcodes.ALOAD, 0);
209         mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
210         mv.visitInsn(Opcodes.RETURN);
211         mv.visitMaxs(10, 10);
212         mv.visitEnd();
213 
214         mv = w.visitMethod(Opcodes.ACC_PUBLIC, "run", "()V", null, null);
215         mv.visitCode();
216         mv.visitMethodInsn(Opcodes.INVOKESTATIC, clazzName, "getM", "()Ljava/lang/Object;", false);
217         Label l1 = new Label();
218         mv.visitJumpInsn(Opcodes.JSR, l1);
219         mv.visitInsn(Opcodes.RETURN);
220 
221         mv.visitLabel(l1);
222         mv.visitVarInsn(Opcodes.ASTORE, 1);
223 
224         Label lElse = new Label();
225         Label lEnd = new Label();
226         mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
227         mv.visitInsn(Opcodes.POP2);
228         mv.visitMethodInsn(Opcodes.INVOKESTATIC, clazzName, "getM", "()Ljava/lang/Object;", false);
229         mv.visitInsn(Opcodes.DUP);
230         mv.visitJumpInsn(Opcodes.IFNULL, lElse);
231         mv.visitMethodInsn(Opcodes.INVOKESTATIC, clazzName, "methodA", "()V", false);
232         mv.visitJumpInsn(Opcodes.GOTO, lEnd);
233         mv.visitLabel(lElse);
234         mv.visitMethodInsn(Opcodes.INVOKESTATIC, clazzName, "methodB", "()V", false);
235         mv.visitLabel(lEnd);
236 
237         mv.visitVarInsn(Opcodes.RET, 1);
238         mv.visitMaxs(10, 10);
239         mv.visitEnd();
240         return w.toByteArray();
241     }
242 }
243