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