blob: c66e8c3b606cb7a6b517239443759f69cbf3520e [file] [log] [blame]
/*
* 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.hasJFR
*
* @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<Call, Boolean> result = inlineCalls.getExpected(ENTRY_POINT);
for (int level : determineAvailableLevels()) {
testLevel(result, level);
}
}
private static void testLevel(Map<Call, Boolean> 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<RecordedEvent> events = Events.fromRecording(r);
Set<Call> 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.<init> 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("<init>")))) {
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 = "<init>";
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<Call> calls;
private final Map<Call, Boolean> inline;
public InlineCalls(Class<?> aClass) {
calls = getCalls(aClass);
inline = new HashMap<>();
}
/**
* @return expected inline events
*/
public Map<Call, Boolean> getExpected(Executable entry) {
Map<Call, Boolean> result = new HashMap<>();
Queue<MethodDesc> methods = new ArrayDeque<>();
Set<MethodDesc> 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<Call> getCalls(Class<?> aClass) {
List<Call> 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<Call> calls;
public CallTracer(Class<?> aClass, String name, String desc, MethodVisitor mv, Collection<Call> 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()));
}
}
}