--- /dev/null 2019-01-28 17:48:54.000000000 +0800 +++ new/test/jfr/event/compiler/TestCompilerInlining.java 2019-01-28 17:48:54.000000000 +0800 @@ -0,0 +1,404 @@ +/* + * Copyright (c) 2015, 2019, 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 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 com.oracle.java.testlibrary.Asserts; +import com.oracle.java.testlibrary.Platform; +import com.oracle.java.testlibrary.jfr.EventNames; +import com.oracle.java.testlibrary.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 + * @summary Verifies that corresponding JFR events are emitted in case of inlining. + * + * @requires vm.opt.Inline == true | vm.opt.Inline == null + * @library /testlibrary /testlibrary/whitebox + * @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 -XX:+EnableJFR 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())); + } + } +}