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