/* * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.jfr.event.compiler; import jdk.internal.org.objectweb.asm.*; import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedMethod; import jdk.jfr.consumer.RecordedObject; import jdk.test.lib.Asserts; import jdk.test.lib.Platform; import jdk.test.lib.jfr.EventNames; import jdk.test.lib.jfr.Events; import sun.hotspot.WhiteBox; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.util.*; import java.util.stream.IntStream; /* * @test CompilerInliningTest * @bug 8073607 * @key jfr * @summary Verifies that corresponding JFR events are emitted in case of inlining. * * @requires vm.opt.Inline == true | vm.opt.Inline == null * @library /test/lib * @modules java.base/jdk.internal.org.objectweb.asm * jdk.jfr * * @build sun.hotspot.WhiteBox * @run main ClassFileInstaller sun.hotspot.WhiteBox * sun.hotspot.WhiteBox$WhiteBoxPermission * @run main/othervm -Xbootclasspath/a:. * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI * -Xbatch jdk.jfr.event.compiler.TestCompilerInlining */ public class TestCompilerInlining { private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); private static final int LEVEL_SIMPLE = 1; private static final int LEVEL_FULL_OPTIMIZATION = 4; private static final Executable ENTRY_POINT = getConstructor(TestCase.class); private static final String TEST_CASE_CLASS_NAME = TestCase.class.getName().replace('.', '/'); public static void main(String[] args) throws Exception { InlineCalls inlineCalls = new InlineCalls(TestCase.class); inlineCalls.disableInline(getConstructor(Object.class)); inlineCalls.disableInline(getMethod(TestCase.class, "qux", boolean.class)); inlineCalls.forceInline(getMethod(TestCase.class, "foo")); inlineCalls.forceInline(getMethod(TestCase.class, "foo", int.class)); inlineCalls.forceInline(getMethod(TestCase.class, "bar")); inlineCalls.forceInline(getMethod(TestCase.class, "baz")); Map result = inlineCalls.getExpected(ENTRY_POINT); for (int level : determineAvailableLevels()) { testLevel(result, level); } } private static void testLevel(Map expectedResult, int level) throws IOException { System.out.println("****** Testing level " + level + " *******"); Recording r = new Recording(); r.enable(EventNames.CompilerInlining); r.start(); WHITE_BOX.enqueueMethodForCompilation(ENTRY_POINT, level); WHITE_BOX.deoptimizeMethod(ENTRY_POINT); r.stop(); System.out.println("Expected:"); List events = Events.fromRecording(r); Set foundEvents = new HashSet<>(); int foundRelevantEvent = 0; for (RecordedEvent event : events) { RecordedMethod callerObject = event.getValue("caller"); RecordedObject calleeObject = event.getValue("callee"); MethodDesc caller = methodToMethodDesc(callerObject); MethodDesc callee = ciMethodToMethodDesc(calleeObject); // only TestCase.* -> TestCase.* OR TestCase.* -> Object. are tested/filtered if (caller.className.equals(TEST_CASE_CLASS_NAME) && (callee.className.equals(TEST_CASE_CLASS_NAME) || (callee.className.equals("java/lang/Object") && callee.methodName.equals("")))) { System.out.println(event); boolean succeeded = (boolean) event.getValue("succeeded"); int bci = Events.assertField(event, "bci").atLeast(0).getValue(); Call call = new Call(caller, callee, bci); foundRelevantEvent++; Boolean expected = expectedResult.get(call); Asserts.assertNotNull(expected, "Unexpected inlined call : " + call); Asserts.assertEquals(expected, succeeded, "Incorrect result for " + call); Asserts.assertTrue(foundEvents.add(call), "repeated event for " + call); } } Asserts.assertEquals(foundRelevantEvent, expectedResult.size(), String.format("not all events found at lavel %d. " + "found = '%s'. expected = '%s'", level, events, expectedResult.keySet())); System.out.println(); System.out.println(); } private static int[] determineAvailableLevels() { if (WHITE_BOX.getBooleanVMFlag("TieredCompilation")) { return IntStream.rangeClosed(LEVEL_SIMPLE, WHITE_BOX.getIntxVMFlag("TieredStopAtLevel").intValue()).toArray(); } if (Platform.isServer() && !Platform.isEmulatedClient()) { return new int[] { LEVEL_FULL_OPTIMIZATION }; } if (Platform.isClient() || Platform.isEmulatedClient()) { return new int[] { LEVEL_SIMPLE }; } throw new Error("TESTBUG: unknown VM"); } private static MethodDesc methodToMethodDesc(RecordedMethod method) { String internalClassName = method.getType().getName().replace('.', '/'); String methodName = method.getValue("name"); String methodDescriptor = method.getValue("descriptor"); return new MethodDesc(internalClassName, methodName, methodDescriptor); } private static MethodDesc ciMethodToMethodDesc(RecordedObject ciMethod) { String internalClassName = ciMethod.getValue("type"); String methodName = ciMethod.getValue("name"); String methodDescriptor = ciMethod.getValue("descriptor"); return new MethodDesc(internalClassName, methodName, methodDescriptor); } private static Method getMethod(Class aClass, String name, Class... params) { try { return aClass.getDeclaredMethod(name, params); } catch (NoSuchMethodException | SecurityException e) { throw new Error("TESTBUG : cannot get method " + name + Arrays.toString(params), e); } } private static Constructor getConstructor(Class aClass, Class... params) { try { return aClass.getDeclaredConstructor(params); } catch (NoSuchMethodException | SecurityException e) { throw new Error("TESTBUG : cannot get constructor" + Arrays.toString(params), e); } } } class TestCase { public TestCase() { foo(); } public void foo() { qux(true); bar(); foo(2); } private void foo(int i) { } private void bar() { baz(); qux(false); qux(true); } protected static double baz() { qux(false); return .0; } private static int qux(boolean b) { qux(b); return 0; } } /** * data structure for method call */ class Call { public final MethodDesc caller; public final MethodDesc callee; public final int bci; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || !(o instanceof Call)) return false; Call call = (Call) o; if (bci != call.bci) return false; if (!callee.equals(call.callee)) return false; if (!caller.equals(call.caller)) return false; return true; } @Override public int hashCode() { int result = caller.hashCode(); result = 31 * result + callee.hashCode(); result = 47 * result + bci; return result; } public Call(MethodDesc caller, MethodDesc callee, int bci) { Objects.requireNonNull(caller); Objects.requireNonNull(callee); this.caller = caller; this.callee = callee; this.bci = bci; } @Override public String toString() { return String.format("Call{caller='%s', callee='%s', bci=%d}", caller, callee, bci); } } /** * data structure for method description */ class MethodDesc { public final String className; public final String methodName; public final String descriptor; public MethodDesc(Class aClass, String methodName, String descriptor) { this(aClass.getName().replace('.', '/'), methodName, descriptor); } public MethodDesc(String className, String methodName, String descriptor) { Objects.requireNonNull(className); Objects.requireNonNull(methodName); Objects.requireNonNull(descriptor); this.className = className.replace('.', '/'); this.methodName = methodName; this.descriptor = descriptor; } public MethodDesc(Executable executable) { Class aClass = executable.getDeclaringClass(); className = Type.getInternalName(aClass).replace('.', '/'); if (executable instanceof Constructor) { methodName = ""; descriptor = Type.getConstructorDescriptor((Constructor) executable); } else { methodName = executable.getName(); descriptor = Type.getMethodDescriptor((Method) executable); } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MethodDesc that = (MethodDesc) o; if (!className.equals(that.className)) return false; if (!methodName.equals(that.methodName)) return false; if (!descriptor.equals(that.descriptor)) return false; return true; } @Override public int hashCode() { int result = className.hashCode(); result = 31 * result + methodName.hashCode(); result = 47 * result + descriptor.hashCode(); return result; } @Override public String toString() { return String.format("MethodDesc{className='%s', methodName='%s', descriptor='%s'}", className, methodName, descriptor); } } /** * Aux class to get all calls in an arbitrary class. */ class InlineCalls { private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); private final Collection calls; private final Map inline; public InlineCalls(Class aClass) { calls = getCalls(aClass); inline = new HashMap<>(); } /** * @return expected inline events */ public Map getExpected(Executable entry) { Map result = new HashMap<>(); Queue methods = new ArrayDeque<>(); Set finished = new HashSet<>(); methods.add(new MethodDesc(entry)); while (!methods.isEmpty()) { MethodDesc method = methods.poll(); if (finished.add(method)) { inline.entrySet().stream().filter(k -> k.getKey().caller.equals(method)).forEach(k -> { result.put(k.getKey(), k.getValue()); if (k.getValue()) { methods.add(k.getKey().callee); } }); } } return result; } public void disableInline(Executable executable) { WHITE_BOX.testSetDontInlineMethod(executable, true); MethodDesc md = new MethodDesc(executable); calls.stream().filter(c -> c.callee.equals(md)).forEach(c -> inline.put(c, false)); } public void forceInline(Executable executable) { WHITE_BOX.testSetForceInlineMethod(executable, true); MethodDesc md = new MethodDesc(executable); calls.stream().filter(c -> c.callee.equals(md)).forEach(c -> inline.putIfAbsent(c, true)); } private static Collection getCalls(Class aClass) { List calls = new ArrayList<>(); ClassWriter cw; ClassReader cr; try { cr = new ClassReader(aClass.getName()); } catch (IOException e) { throw new Error("TESTBUG : unexpected IOE during class reading", e); } cw = new ClassWriter(cr, 0); ClassVisitor cv = new ClassVisitor(Opcodes.ASM5, cw) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String descriptor, String[] exceptions) { System.out.println("Method: " +name); MethodVisitor mv = super.visitMethod(access, name, desc, descriptor, exceptions); return new CallTracer(aClass, name, desc, mv, calls); } }; cr.accept(cv, 0); return calls; } private static class CallTracer extends MethodVisitor { private final MethodDesc caller; private Collection calls; public CallTracer(Class aClass, String name, String desc, MethodVisitor mv, Collection calls) { super(Opcodes.ASM5, mv); caller = new MethodDesc(aClass.getName(), name, desc); this.calls = calls; } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { Label label = new Label(); visitLabel(label); super.visitMethodInsn(opcode, owner, name, desc, itf); calls.add(new Call(caller, new MethodDesc(owner, name, desc), label.getOffset())); } } }