1 /*
2  * Copyright (c) 2015, 2018, 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.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.jfr.event.compiler;
27 
28 import jdk.internal.org.objectweb.asm.*;
29 import jdk.jfr.Recording;
30 import jdk.jfr.consumer.RecordedEvent;
31 import jdk.jfr.consumer.RecordedMethod;
32 import jdk.jfr.consumer.RecordedObject;
33 import jdk.test.lib.Asserts;
34 import jdk.test.lib.Platform;
35 import jdk.test.lib.jfr.EventNames;
36 import jdk.test.lib.jfr.Events;
37 import sun.hotspot.WhiteBox;
38 
39 import java.io.IOException;
40 import java.lang.reflect.Constructor;
41 import java.lang.reflect.Executable;
42 import java.lang.reflect.Method;
43 import java.util.*;
44 import java.util.stream.IntStream;
45 
46 /**
47  * @test CompilerInliningTest
48  * @bug 8073607
49  * @key jfr
50  * @summary Verifies that corresponding JFR events are emitted in case of inlining.
51  * @requires vm.hasJFR
52  *
53  * @requires vm.opt.Inline == true | vm.opt.Inline == null
54  * @library /test/lib
55  * @modules java.base/jdk.internal.org.objectweb.asm
56  *          jdk.jfr
57  *
58  * @build sun.hotspot.WhiteBox
59  * @run main ClassFileInstaller sun.hotspot.WhiteBox
60  *     sun.hotspot.WhiteBox$WhiteBoxPermission
61  * @run main/othervm -Xbootclasspath/a:.
62  *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
63  *     -Xbatch jdk.jfr.event.compiler.TestCompilerInlining
64  */
65 public class TestCompilerInlining {
66     private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
67     private static final int LEVEL_SIMPLE = 1;
68     private static final int LEVEL_FULL_OPTIMIZATION = 4;
69     private static final Executable ENTRY_POINT = getConstructor(TestCase.class);
70     private static final String TEST_CASE_CLASS_NAME = TestCase.class.getName().replace('.', '/');
71 
main(String[] args)72     public static void main(String[] args) throws Exception {
73         InlineCalls inlineCalls = new InlineCalls(TestCase.class);
74         inlineCalls.disableInline(getConstructor(Object.class));
75         inlineCalls.disableInline(getMethod(TestCase.class, "qux", boolean.class));
76         inlineCalls.forceInline(getMethod(TestCase.class, "foo"));
77         inlineCalls.forceInline(getMethod(TestCase.class, "foo", int.class));
78         inlineCalls.forceInline(getMethod(TestCase.class, "bar"));
79         inlineCalls.forceInline(getMethod(TestCase.class, "baz"));
80 
81         Map<Call, Boolean> result = inlineCalls.getExpected(ENTRY_POINT);
82         for (int level : determineAvailableLevels()) {
83             testLevel(result, level);
84         }
85     }
86 
testLevel(Map<Call, Boolean> expectedResult, int level)87     private static void testLevel(Map<Call, Boolean> expectedResult, int level) throws IOException {
88         System.out.println("****** Testing level " + level + " *******");
89         Recording r = new Recording();
90         r.enable(EventNames.CompilerInlining);
91         r.start();
92         WHITE_BOX.enqueueMethodForCompilation(ENTRY_POINT, level);
93         WHITE_BOX.deoptimizeMethod(ENTRY_POINT);
94         r.stop();
95         System.out.println("Expected:");
96 
97         List<RecordedEvent> events = Events.fromRecording(r);
98         Set<Call> foundEvents = new HashSet<>();
99         int foundRelevantEvent = 0;
100         for (RecordedEvent event : events) {
101             RecordedMethod callerObject = event.getValue("caller");
102             RecordedObject calleeObject = event.getValue("callee");
103             MethodDesc caller = methodToMethodDesc(callerObject);
104             MethodDesc callee = ciMethodToMethodDesc(calleeObject);
105             // only TestCase.* -> TestCase.* OR TestCase.* -> Object.<init> are tested/filtered
106             if (caller.className.equals(TEST_CASE_CLASS_NAME) && (callee.className.equals(TEST_CASE_CLASS_NAME)
107                     || (callee.className.equals("java/lang/Object") && callee.methodName.equals("<init>")))) {
108                 System.out.println(event);
109                 boolean succeeded = (boolean) event.getValue("succeeded");
110                 int bci = Events.assertField(event, "bci").atLeast(0).getValue();
111                 Call call = new Call(caller, callee, bci);
112                 foundRelevantEvent++;
113                 Boolean expected = expectedResult.get(call);
114                 Asserts.assertNotNull(expected, "Unexpected inlined call : " + call);
115                 Asserts.assertEquals(expected, succeeded, "Incorrect result for " + call);
116                 Asserts.assertTrue(foundEvents.add(call), "repeated event for " + call);
117             }
118         }
119         Asserts.assertEquals(foundRelevantEvent, expectedResult.size(), String.format("not all events found at lavel %d. " + "found = '%s'. expected = '%s'", level, events, expectedResult.keySet()));
120         System.out.println();
121         System.out.println();
122     }
123 
determineAvailableLevels()124     private static int[] determineAvailableLevels() {
125         if (WHITE_BOX.getBooleanVMFlag("TieredCompilation")) {
126             return IntStream.rangeClosed(LEVEL_SIMPLE, WHITE_BOX.getIntxVMFlag("TieredStopAtLevel").intValue()).toArray();
127         }
128         if (Platform.isServer() && !Platform.isEmulatedClient()) {
129             return new int[] { LEVEL_FULL_OPTIMIZATION };
130         }
131         if (Platform.isClient() || Platform.isEmulatedClient()) {
132             return new int[] { LEVEL_SIMPLE };
133         }
134         throw new Error("TESTBUG: unknown VM");
135     }
136 
methodToMethodDesc(RecordedMethod method)137     private static MethodDesc methodToMethodDesc(RecordedMethod method) {
138         String internalClassName = method.getType().getName().replace('.', '/');
139         String methodName = method.getValue("name");
140         String methodDescriptor = method.getValue("descriptor");
141         return new MethodDesc(internalClassName, methodName, methodDescriptor);
142     }
143 
ciMethodToMethodDesc(RecordedObject ciMethod)144     private static MethodDesc ciMethodToMethodDesc(RecordedObject ciMethod) {
145         String internalClassName = ciMethod.getValue("type");
146         String methodName = ciMethod.getValue("name");
147         String methodDescriptor = ciMethod.getValue("descriptor");
148         return new MethodDesc(internalClassName, methodName, methodDescriptor);
149     }
150 
getMethod(Class<?> aClass, String name, Class<?>... params)151     private static Method getMethod(Class<?> aClass, String name, Class<?>... params) {
152         try {
153             return aClass.getDeclaredMethod(name, params);
154         } catch (NoSuchMethodException | SecurityException e) {
155             throw new Error("TESTBUG : cannot get method " + name + Arrays.toString(params), e);
156         }
157     }
158 
getConstructor(Class<?> aClass, Class<?>... params)159     private static Constructor<?> getConstructor(Class<?> aClass, Class<?>... params) {
160         try {
161             return aClass.getDeclaredConstructor(params);
162         } catch (NoSuchMethodException | SecurityException e) {
163             throw new Error("TESTBUG : cannot get constructor" + Arrays.toString(params), e);
164         }
165     }
166 }
167 
168 class TestCase {
TestCase()169     public TestCase() {
170         foo();
171     }
172 
foo()173     public void foo() {
174         qux(true);
175         bar();
176         foo(2);
177     }
178 
foo(int i)179     private void foo(int i) {
180     }
181 
bar()182     private void bar() {
183         baz();
184         qux(false);
185         qux(true);
186     }
187 
baz()188     protected static double baz() {
189         qux(false);
190         return .0;
191     }
192 
qux(boolean b)193     private static int qux(boolean b) {
194         qux(b);
195         return 0;
196     }
197 }
198 
199 /**
200  * data structure for method call
201  */
202 class Call {
203     public final MethodDesc caller;
204     public final MethodDesc callee;
205     public final int bci;
206 
207     @Override
equals(Object o)208     public boolean equals(Object o) {
209         if (this == o)
210             return true;
211         if (o == null || !(o instanceof Call))
212             return false;
213 
214         Call call = (Call) o;
215 
216         if (bci != call.bci)
217             return false;
218         if (!callee.equals(call.callee))
219             return false;
220         if (!caller.equals(call.caller))
221             return false;
222 
223         return true;
224     }
225 
226     @Override
hashCode()227     public int hashCode() {
228         int result = caller.hashCode();
229         result = 31 * result + callee.hashCode();
230         result = 47 * result + bci;
231         return result;
232     }
233 
Call(MethodDesc caller, MethodDesc callee, int bci)234     public Call(MethodDesc caller, MethodDesc callee, int bci) {
235         Objects.requireNonNull(caller);
236         Objects.requireNonNull(callee);
237         this.caller = caller;
238         this.callee = callee;
239         this.bci = bci;
240     }
241 
242     @Override
toString()243     public String toString() {
244         return String.format("Call{caller='%s', callee='%s', bci=%d}", caller, callee, bci);
245     }
246 }
247 
248 /**
249  * data structure for method description
250  */
251 class MethodDesc {
252     public final String className;
253     public final String methodName;
254     public final String descriptor;
255 
MethodDesc(Class<?> aClass, String methodName, String descriptor)256     public MethodDesc(Class<?> aClass, String methodName, String descriptor) {
257         this(aClass.getName().replace('.', '/'), methodName, descriptor);
258     }
259 
MethodDesc(String className, String methodName, String descriptor)260     public MethodDesc(String className, String methodName, String descriptor) {
261         Objects.requireNonNull(className);
262         Objects.requireNonNull(methodName);
263         Objects.requireNonNull(descriptor);
264         this.className = className.replace('.', '/');
265         this.methodName = methodName;
266         this.descriptor = descriptor;
267     }
268 
MethodDesc(Executable executable)269     public MethodDesc(Executable executable) {
270         Class<?> aClass = executable.getDeclaringClass();
271         className = Type.getInternalName(aClass).replace('.', '/');
272 
273         if (executable instanceof Constructor<?>) {
274             methodName = "<init>";
275             descriptor = Type.getConstructorDescriptor((Constructor<?>) executable);
276         } else {
277             methodName = executable.getName();
278             descriptor = Type.getMethodDescriptor((Method) executable);
279         }
280 
281     }
282 
283     @Override
equals(Object o)284     public boolean equals(Object o) {
285         if (this == o)
286             return true;
287         if (o == null || getClass() != o.getClass())
288             return false;
289 
290         MethodDesc that = (MethodDesc) o;
291 
292         if (!className.equals(that.className))
293             return false;
294         if (!methodName.equals(that.methodName))
295             return false;
296         if (!descriptor.equals(that.descriptor))
297             return false;
298 
299         return true;
300     }
301 
302     @Override
hashCode()303     public int hashCode() {
304         int result = className.hashCode();
305         result = 31 * result + methodName.hashCode();
306         result = 47 * result + descriptor.hashCode();
307         return result;
308     }
309 
310     @Override
toString()311     public String toString() {
312         return String.format("MethodDesc{className='%s', methodName='%s', descriptor='%s'}", className, methodName, descriptor);
313     }
314 }
315 
316 /**
317  * Aux class to get all calls in an arbitrary class.
318  */
319 class InlineCalls {
320     private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
321 
322     private final Collection<Call> calls;
323     private final Map<Call, Boolean> inline;
324 
InlineCalls(Class<?> aClass)325     public InlineCalls(Class<?> aClass) {
326         calls = getCalls(aClass);
327         inline = new HashMap<>();
328     }
329 
330     /**
331      * @return expected inline events
332      */
getExpected(Executable entry)333     public Map<Call, Boolean> getExpected(Executable entry) {
334         Map<Call, Boolean> result = new HashMap<>();
335         Queue<MethodDesc> methods = new ArrayDeque<>();
336         Set<MethodDesc> finished = new HashSet<>();
337         methods.add(new MethodDesc(entry));
338         while (!methods.isEmpty()) {
339             MethodDesc method = methods.poll();
340             if (finished.add(method)) {
341                 inline.entrySet().stream().filter(k -> k.getKey().caller.equals(method)).forEach(k -> {
342                     result.put(k.getKey(), k.getValue());
343                     if (k.getValue()) {
344                         methods.add(k.getKey().callee);
345                     }
346                 });
347             }
348         }
349 
350         return result;
351     }
352 
disableInline(Executable executable)353     public void disableInline(Executable executable) {
354         WHITE_BOX.testSetDontInlineMethod(executable, true);
355         MethodDesc md = new MethodDesc(executable);
356         calls.stream().filter(c -> c.callee.equals(md)).forEach(c -> inline.put(c, false));
357     }
358 
forceInline(Executable executable)359     public void forceInline(Executable executable) {
360         WHITE_BOX.testSetForceInlineMethod(executable, true);
361         MethodDesc md = new MethodDesc(executable);
362         calls.stream().filter(c -> c.callee.equals(md)).forEach(c -> inline.putIfAbsent(c, true));
363     }
364 
getCalls(Class<?> aClass)365     private static Collection<Call> getCalls(Class<?> aClass) {
366         List<Call> calls = new ArrayList<>();
367         ClassWriter cw;
368         ClassReader cr;
369         try {
370             cr = new ClassReader(aClass.getName());
371         } catch (IOException e) {
372             throw new Error("TESTBUG : unexpected IOE during class reading", e);
373         }
374         cw = new ClassWriter(cr, 0);
375         ClassVisitor cv = new ClassVisitor(Opcodes.ASM7, cw) {
376             @Override
377             public MethodVisitor visitMethod(int access, String name, String desc, String descriptor, String[] exceptions) {
378                 System.out.println("Method: " +name);
379                 MethodVisitor mv = super.visitMethod(access, name, desc, descriptor, exceptions);
380                 return new CallTracer(aClass, name, desc, mv, calls);
381             }
382         };
383         cr.accept(cv, 0);
384 
385         return calls;
386     }
387 
388     private static class CallTracer extends MethodVisitor {
389         private final MethodDesc caller;
390         private Collection<Call> calls;
391 
CallTracer(Class<?> aClass, String name, String desc, MethodVisitor mv, Collection<Call> calls)392         public CallTracer(Class<?> aClass, String name, String desc, MethodVisitor mv, Collection<Call> calls) {
393             super(Opcodes.ASM7, mv);
394             caller = new MethodDesc(aClass.getName(), name, desc);
395             this.calls = calls;
396         }
397 
398         @Override
visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)399         public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
400             Label label = new Label();
401             visitLabel(label);
402             super.visitMethodInsn(opcode, owner, name, desc, itf);
403             calls.add(new Call(caller, new MethodDesc(owner, name, desc), label.getOffset()));
404         }
405     }
406 }
407