8065760: CHA: Improve abstract method support
Reviewed-by: neliasso, thartmann
This commit is contained in:
parent
68f3b3acce
commit
20479c3de9
src/hotspot/share
test/hotspot/jtreg/compiler/cha
@ -675,23 +675,26 @@ ciMethod* ciMethod::find_monomorphic_target(ciInstanceKlass* caller,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ciMethod* root_m = resolve_invoke(caller, actual_recv, check_access);
|
||||
ciMethod* root_m = resolve_invoke(caller, actual_recv, check_access, true /* allow_abstract */);
|
||||
if (root_m == NULL) {
|
||||
// Something went wrong looking up the actual receiver method.
|
||||
return NULL;
|
||||
}
|
||||
assert(!root_m->is_abstract(), "resolve_invoke promise");
|
||||
|
||||
// Make certain quick checks even if UseCHA is false.
|
||||
|
||||
// Is it private or final?
|
||||
if (root_m->can_be_statically_bound()) {
|
||||
assert(!root_m->is_abstract(), "sanity");
|
||||
return root_m;
|
||||
}
|
||||
|
||||
if (actual_recv->is_leaf_type() && actual_recv == root_m->holder()) {
|
||||
// Easy case. There is no other place to put a method, so don't bother
|
||||
// to go through the VM_ENTRY_MARK and all the rest.
|
||||
if (root_m->is_abstract()) {
|
||||
return NULL;
|
||||
}
|
||||
return root_m;
|
||||
}
|
||||
|
||||
@ -720,6 +723,9 @@ ciMethod* ciMethod::find_monomorphic_target(ciInstanceKlass* caller,
|
||||
callee_holder->get_Klass(),
|
||||
this->get_Method()));
|
||||
} else {
|
||||
if (root_m->is_abstract()) {
|
||||
return NULL; // not supported
|
||||
}
|
||||
target = methodHandle(THREAD, Dependencies::find_unique_concrete_method(context, root_m->get_Method()));
|
||||
}
|
||||
assert(target() == NULL || !target()->is_abstract(), "not allowed");
|
||||
@ -753,7 +759,6 @@ ciMethod* ciMethod::find_monomorphic_target(ciInstanceKlass* caller,
|
||||
// with the same name but different vtable indexes.
|
||||
return NULL;
|
||||
}
|
||||
assert(!target()->is_abstract(), "not allowed");
|
||||
return CURRENT_THREAD_ENV->get_method(target());
|
||||
}
|
||||
|
||||
@ -770,49 +775,47 @@ bool ciMethod::can_be_statically_bound(ciInstanceKlass* context) const {
|
||||
//
|
||||
// Given a known receiver klass, find the target for the call.
|
||||
// Return NULL if the call has no target or the target is abstract.
|
||||
ciMethod* ciMethod::resolve_invoke(ciKlass* caller, ciKlass* exact_receiver, bool check_access) {
|
||||
check_is_loaded();
|
||||
VM_ENTRY_MARK;
|
||||
ciMethod* ciMethod::resolve_invoke(ciKlass* caller, ciKlass* exact_receiver, bool check_access, bool allow_abstract) {
|
||||
check_is_loaded();
|
||||
VM_ENTRY_MARK;
|
||||
|
||||
Klass* caller_klass = caller->get_Klass();
|
||||
Klass* recv = exact_receiver->get_Klass();
|
||||
Klass* resolved = holder()->get_Klass();
|
||||
Symbol* h_name = name()->get_symbol();
|
||||
Symbol* h_signature = signature()->get_symbol();
|
||||
Klass* caller_klass = caller->get_Klass();
|
||||
Klass* recv = exact_receiver->get_Klass();
|
||||
Klass* resolved = holder()->get_Klass();
|
||||
Symbol* h_name = name()->get_symbol();
|
||||
Symbol* h_signature = signature()->get_symbol();
|
||||
|
||||
LinkInfo link_info(resolved, h_name, h_signature, caller_klass,
|
||||
check_access ? LinkInfo::AccessCheck::required : LinkInfo::AccessCheck::skip,
|
||||
check_access ? LinkInfo::LoaderConstraintCheck::required : LinkInfo::LoaderConstraintCheck::skip);
|
||||
Method* m = NULL;
|
||||
// Only do exact lookup if receiver klass has been linked. Otherwise,
|
||||
// the vtable has not been setup, and the LinkResolver will fail.
|
||||
if (recv->is_array_klass()
|
||||
||
|
||||
(InstanceKlass::cast(recv)->is_linked() && !exact_receiver->is_interface())) {
|
||||
if (holder()->is_interface()) {
|
||||
m = LinkResolver::resolve_interface_call_or_null(recv, link_info);
|
||||
} else {
|
||||
m = LinkResolver::resolve_virtual_call_or_null(recv, link_info);
|
||||
}
|
||||
}
|
||||
LinkInfo link_info(resolved, h_name, h_signature, caller_klass,
|
||||
check_access ? LinkInfo::AccessCheck::required : LinkInfo::AccessCheck::skip,
|
||||
check_access ? LinkInfo::LoaderConstraintCheck::required : LinkInfo::LoaderConstraintCheck::skip);
|
||||
Method* m = NULL;
|
||||
// Only do exact lookup if receiver klass has been linked. Otherwise,
|
||||
// the vtable has not been setup, and the LinkResolver will fail.
|
||||
if (recv->is_array_klass()
|
||||
||
|
||||
(InstanceKlass::cast(recv)->is_linked() && !exact_receiver->is_interface())) {
|
||||
if (holder()->is_interface()) {
|
||||
m = LinkResolver::resolve_interface_call_or_null(recv, link_info);
|
||||
} else {
|
||||
m = LinkResolver::resolve_virtual_call_or_null(recv, link_info);
|
||||
}
|
||||
}
|
||||
|
||||
if (m == NULL) {
|
||||
// Return NULL only if there was a problem with lookup (uninitialized class, etc.)
|
||||
return NULL;
|
||||
}
|
||||
if (m == NULL) {
|
||||
// Return NULL only if there was a problem with lookup (uninitialized class, etc.)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ciMethod* result = this;
|
||||
if (m != get_Method()) {
|
||||
result = CURRENT_THREAD_ENV->get_method(m);
|
||||
}
|
||||
ciMethod* result = this;
|
||||
if (m != get_Method()) {
|
||||
result = CURRENT_THREAD_ENV->get_method(m);
|
||||
}
|
||||
|
||||
// Don't return abstract methods because they aren't
|
||||
// optimizable or interesting.
|
||||
if (result->is_abstract()) {
|
||||
return NULL;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
if (result->is_abstract() && !allow_abstract) {
|
||||
// Don't return abstract methods because they aren't optimizable or interesting.
|
||||
return NULL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
@ -300,7 +300,7 @@ class ciMethod : public ciMetadata {
|
||||
|
||||
// Given a known receiver klass, find the target for the call.
|
||||
// Return NULL if the call has no target or is abstract.
|
||||
ciMethod* resolve_invoke(ciKlass* caller, ciKlass* exact_receiver, bool check_access = true);
|
||||
ciMethod* resolve_invoke(ciKlass* caller, ciKlass* exact_receiver, bool check_access = true, bool allow_abstract = false);
|
||||
|
||||
// Find the proper vtable index to invoke this method.
|
||||
int resolve_vtable_index(ciKlass* caller, ciKlass* receiver);
|
||||
|
@ -1881,10 +1881,12 @@ Method* Dependencies::find_unique_concrete_method(InstanceKlass* ctxk, Method* m
|
||||
assert(fm == NULL || !fm->is_abstract(), "sanity");
|
||||
// Old CHA conservatively reports concrete methods in abstract classes
|
||||
// irrespective of whether they have concrete subclasses or not.
|
||||
// Also, abstract root method case is not fully supported.
|
||||
#ifdef ASSERT
|
||||
Klass* uniqp = NULL;
|
||||
Method* uniqm = Dependencies::find_unique_concrete_method(ctxk, m, &uniqp);
|
||||
assert(uniqm == NULL || uniqm == fm ||
|
||||
m->is_abstract() ||
|
||||
uniqm->method_holder()->is_abstract() ||
|
||||
(fm == NULL && uniqm != NULL && uniqp != NULL && !InstanceKlass::cast(uniqp)->is_linked()),
|
||||
"sanity");
|
||||
|
197
test/hotspot/jtreg/compiler/cha/AbstractRootMethod.java
Normal file
197
test/hotspot/jtreg/compiler/cha/AbstractRootMethod.java
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires !vm.graal.enabled & vm.opt.final.UseVtableBasedCHA == true
|
||||
* @modules java.base/jdk.internal.org.objectweb.asm
|
||||
* java.base/jdk.internal.misc
|
||||
* java.base/jdk.internal.vm.annotation
|
||||
* @library /test/lib /
|
||||
* @compile Utils.java
|
||||
* @build sun.hotspot.WhiteBox
|
||||
* @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox
|
||||
*
|
||||
* @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions
|
||||
* -XX:+PrintCompilation -XX:+PrintInlining -XX:+TraceDependencies -verbose:class -XX:CompileCommand=quiet
|
||||
* -XX:CompileCommand=compileonly,*::m
|
||||
* -XX:CompileCommand=compileonly,*::test -XX:CompileCommand=dontinline,*::test
|
||||
* -Xbatch -Xmixed -XX:+WhiteBoxAPI
|
||||
* -XX:-TieredCompilation
|
||||
* compiler.cha.AbstractRootMethod
|
||||
*
|
||||
* @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions
|
||||
* -XX:+PrintCompilation -XX:+PrintInlining -XX:+TraceDependencies -verbose:class -XX:CompileCommand=quiet
|
||||
* -XX:CompileCommand=compileonly,*::m
|
||||
* -XX:CompileCommand=compileonly,*::test -XX:CompileCommand=dontinline,*::test
|
||||
* -Xbatch -Xmixed -XX:+WhiteBoxAPI
|
||||
* -XX:+TieredCompilation -XX:TieredStopAtLevel=1
|
||||
* compiler.cha.AbstractRootMethod
|
||||
*/
|
||||
package compiler.cha;
|
||||
|
||||
import static compiler.cha.Utils.*;
|
||||
|
||||
public class AbstractRootMethod {
|
||||
public static void main(String[] args) {
|
||||
run(AbstractClass.class);
|
||||
run(AbstractInterface.class);
|
||||
}
|
||||
|
||||
public static class AbstractClass extends ATest<AbstractClass.C> {
|
||||
public AbstractClass() {
|
||||
super(C.class, D.class);
|
||||
}
|
||||
|
||||
interface I1 { Object m(); }
|
||||
interface I2 { default Object m() { return "I2.m"; } }
|
||||
|
||||
static abstract class C { public abstract Object m(); }
|
||||
|
||||
static abstract class D extends C {
|
||||
final Object ret = CORRECT;
|
||||
public Object m() {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static abstract class E1 extends C { /* empty */ }
|
||||
static abstract class E2 extends C { public abstract Object m(); }
|
||||
static abstract class E3 extends C { public Object m() { return "E3.m"; } }
|
||||
|
||||
static abstract class F1 extends C implements I1 { }
|
||||
static abstract class F2 extends C implements I2 { }
|
||||
|
||||
static class G extends C { public Object m() { return CORRECT; } }
|
||||
|
||||
@Override
|
||||
public Object test(C obj) {
|
||||
return obj.m(); // invokevirtual C.m()
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkInvalidReceiver() {
|
||||
// nothing to do: concrete class types are enforced by the verifier
|
||||
}
|
||||
|
||||
@TestCase
|
||||
public void test() {
|
||||
// 0. Trigger compilation of a megamorphic call site
|
||||
compile(megamorphic()); // Dn <: D.m <: C.m ABSTRACT
|
||||
assertCompiled();
|
||||
|
||||
// Dependency: type = unique_concrete_method, context = C, method = D.m
|
||||
|
||||
// 1. No invalidation: abstract classes don't participate in CHA.
|
||||
initialize(E1.class, // ABSTRACT E1 <: C.m ABSTRACT
|
||||
E2.class, // ABSTRACT E2.m ABSTRACT <: C.m ABSTRACT
|
||||
E3.class, // ABSTRACT E3.m <: C.m ABSTRACT
|
||||
F1.class, // ABSTRACT F1 <: C.m ABSTRACT, I1.m ABSTRACT
|
||||
F2.class); // ABSTRACT F2 <: C.m ABSTRACT, I2.m DEFAULT
|
||||
assertCompiled();
|
||||
|
||||
// 2. Dependency invalidation: G.m <: C.m ABSTRACT
|
||||
load(G.class);
|
||||
assertCompiled();
|
||||
|
||||
// 3. Dependency invalidation: G.m <: C.m ABSTRACT
|
||||
initialize(G.class);
|
||||
assertNotCompiled();
|
||||
|
||||
// 4. Recompilation: no inlining, no dependencies
|
||||
compile(megamorphic());
|
||||
call(new C() { public Object m() { return CORRECT; } }); // Cn.m <: C.m ABSTRACT
|
||||
call(new G() { public Object m() { return CORRECT; } }); // Gn <: G.m <: C.m ABSTRACT
|
||||
assertCompiled();
|
||||
}
|
||||
}
|
||||
public static class AbstractInterface extends ATest<AbstractInterface.C> {
|
||||
public AbstractInterface() {
|
||||
super(C.class, D.class);
|
||||
}
|
||||
|
||||
interface I1 { Object m(); }
|
||||
interface I2 extends I { default Object m() { return "I2.m"; } }
|
||||
|
||||
interface I { Object m(); }
|
||||
|
||||
static abstract class C implements I { /* inherited from I */}
|
||||
|
||||
static abstract class D extends C {
|
||||
final Object ret = CORRECT;
|
||||
public Object m() {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static abstract class E1 extends C { /* empty */ }
|
||||
static abstract class E2 extends C { public abstract Object m(); }
|
||||
static abstract class E3 extends C { public Object m() { return "E3.m"; } }
|
||||
|
||||
static abstract class F1 extends C implements I1 { }
|
||||
static abstract class F2 extends C implements I2 { }
|
||||
|
||||
static class G extends C { public Object m() { return CORRECT; } }
|
||||
|
||||
@Override
|
||||
public Object test(C obj) {
|
||||
return obj.m(); // invokevirtual C.m()
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkInvalidReceiver() {
|
||||
// nothing to do: concrete class types are enforced by the verifier
|
||||
}
|
||||
|
||||
@TestCase
|
||||
public void test() {
|
||||
// 0. Trigger compilation of a megamorphic call site
|
||||
compile(megamorphic()); // Dn <: D.m <: C <: I.m ABSTRACT
|
||||
assertCompiled();
|
||||
|
||||
// Dependency: type = unique_concrete_method, context = C, method = D.m
|
||||
|
||||
// 1. No invalidation: abstract classes don't participate in CHA.
|
||||
initialize(E1.class, // ABSTRACT E1 <: C <: I.m ABSTRACT
|
||||
E2.class, // ABSTRACT E2.m ABSTRACT <: C <: I.m ABSTRACT
|
||||
E3.class, // ABSTRACT E3.m <: C <: I.m ABSTRACT
|
||||
F1.class, // ABSTRACT F1 <: C <: I.m ABSTRACT, I1.m ABSTRACT
|
||||
F2.class); // ABSTRACT F2 <: C <: I.m ABSTRACT, I2.m DEFAULT
|
||||
assertCompiled();
|
||||
|
||||
// 2. Dependency invalidation: G.m <: C <: I.m ABSTRACT
|
||||
load(G.class);
|
||||
assertCompiled();
|
||||
|
||||
// 3. Dependency invalidation: G.m <: C <: I.m ABSTRACT
|
||||
initialize(G.class);
|
||||
assertNotCompiled();
|
||||
|
||||
// 4. Recompilation: no inlining, no dependencies
|
||||
compile(megamorphic());
|
||||
call(new C() { public Object m() { return CORRECT; } }); // Cn.m <: C <: I.m ABSTRACT
|
||||
call(new G() { public Object m() { return CORRECT; } }); // Gn <: G.m <: C <: I.m ABSTRACT
|
||||
assertCompiled();
|
||||
}
|
||||
}
|
||||
}
|
@ -28,44 +28,33 @@
|
||||
* java.base/jdk.internal.misc
|
||||
* java.base/jdk.internal.vm.annotation
|
||||
* @library /test/lib /
|
||||
* @compile Utils.java
|
||||
* @build sun.hotspot.WhiteBox
|
||||
* @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox
|
||||
*
|
||||
* @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions
|
||||
* -XX:+PrintCompilation -XX:+PrintInlining -XX:+TraceDependencies -verbose:class -XX:CompileCommand=quiet
|
||||
* -XX:CompileCommand=compileonly,*::test -XX:CompileCommand=compileonly,*::m -XX:CompileCommand=dontinline,*::test
|
||||
* -Xbatch -XX:+WhiteBoxAPI -Xmixed
|
||||
* -XX:CompileCommand=compileonly,*::m
|
||||
* -XX:CompileCommand=compileonly,*::test -XX:CompileCommand=dontinline,*::test
|
||||
* -XX:CompileCommand=compileonly,*::testHelper -XX:CompileCommand=inline,*::testHelper
|
||||
* -Xbatch -Xmixed -XX:+WhiteBoxAPI
|
||||
* -XX:-TieredCompilation
|
||||
* compiler.cha.StrengthReduceInterfaceCall
|
||||
*
|
||||
* @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions
|
||||
* -XX:+PrintCompilation -XX:+PrintInlining -XX:+TraceDependencies -verbose:class -XX:CompileCommand=quiet
|
||||
* -XX:CompileCommand=compileonly,*::test -XX:CompileCommand=compileonly,*::m -XX:CompileCommand=dontinline,*::test
|
||||
* -Xbatch -XX:+WhiteBoxAPI -Xmixed
|
||||
* -XX:CompileCommand=compileonly,*::m
|
||||
* -XX:CompileCommand=compileonly,*::test -XX:CompileCommand=dontinline,*::test
|
||||
* -XX:CompileCommand=compileonly,*::testHelper -XX:CompileCommand=inline,*::testHelper
|
||||
* -Xbatch -Xmixed -XX:+WhiteBoxAPI
|
||||
* -XX:+TieredCompilation -XX:TieredStopAtLevel=1
|
||||
* compiler.cha.StrengthReduceInterfaceCall
|
||||
*/
|
||||
package compiler.cha;
|
||||
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.org.objectweb.asm.ClassWriter;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.internal.vm.annotation.DontInline;
|
||||
import sun.hotspot.WhiteBox;
|
||||
import sun.hotspot.code.NMethod;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static jdk.test.lib.Asserts.*;
|
||||
import static jdk.internal.org.objectweb.asm.ClassWriter.*;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import static compiler.cha.Utils.*;
|
||||
|
||||
public class StrengthReduceInterfaceCall {
|
||||
public static void main(String[] args) {
|
||||
@ -76,6 +65,7 @@ public class StrengthReduceInterfaceCall {
|
||||
run(ThreeLevelHierarchyAbstractVsDefault.class);
|
||||
run(ThreeLevelDefaultHierarchy.class);
|
||||
run(ThreeLevelDefaultHierarchy1.class);
|
||||
System.out.println("TEST PASSED");
|
||||
}
|
||||
|
||||
public static class ObjectToString extends ATest<ObjectToString.I> {
|
||||
@ -96,7 +86,7 @@ public class StrengthReduceInterfaceCall {
|
||||
static class DJ2 implements J { public String toString() { return "DJ2"; }}
|
||||
|
||||
@Override
|
||||
public Object test(I i) { return ObjectToStringHelper.test(i); /* invokeinterface I.toString() */ }
|
||||
public Object test(I i) { return ObjectToStringHelper.testHelper(i); /* invokeinterface I.toString() */ }
|
||||
|
||||
@TestCase
|
||||
public void testMono() {
|
||||
@ -185,7 +175,7 @@ public class StrengthReduceInterfaceCall {
|
||||
|
||||
@Override
|
||||
public Object test(I i) {
|
||||
return ObjectHashCodeHelper.test(i); /* invokeinterface I.hashCode() */
|
||||
return ObjectHashCodeHelper.testHelper(i); /* invokeinterface I.hashCode() */
|
||||
}
|
||||
|
||||
@TestCase
|
||||
@ -735,291 +725,4 @@ public class StrengthReduceInterfaceCall {
|
||||
assertCompiled();
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================================================== */
|
||||
|
||||
interface Action {
|
||||
int run();
|
||||
}
|
||||
|
||||
public static final Unsafe U = Unsafe.getUnsafe();
|
||||
|
||||
interface Test<T> {
|
||||
void call(T o);
|
||||
T receiver(int id);
|
||||
|
||||
default Runnable monomophic() {
|
||||
return () -> {
|
||||
call(receiver(0)); // 100%
|
||||
};
|
||||
}
|
||||
|
||||
default Runnable bimorphic() {
|
||||
return () -> {
|
||||
call(receiver(0)); // 50%
|
||||
call(receiver(1)); // 50%
|
||||
};
|
||||
}
|
||||
|
||||
default Runnable polymorphic() {
|
||||
return () -> {
|
||||
for (int i = 0; i < 23; i++) {
|
||||
call(receiver(0)); // 92%
|
||||
}
|
||||
call(receiver(1)); // 4%
|
||||
call(receiver(2)); // 4%
|
||||
};
|
||||
}
|
||||
|
||||
default Runnable megamorphic() {
|
||||
return () -> {
|
||||
call(receiver(0)); // 33%
|
||||
call(receiver(1)); // 33%
|
||||
call(receiver(2)); // 33%
|
||||
};
|
||||
}
|
||||
|
||||
default void initialize(Class<?>... cs) {
|
||||
for (Class<?> c : cs) {
|
||||
U.ensureClassInitialized(c);
|
||||
}
|
||||
}
|
||||
|
||||
default void repeat(int cnt, Runnable r) {
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class ATest<T> implements Test<T> {
|
||||
public static final WhiteBox WB = WhiteBox.getWhiteBox();
|
||||
|
||||
public static final Object CORRECT = new Object();
|
||||
public static final Object WRONG = new Object();
|
||||
|
||||
final Method TEST;
|
||||
private final Class<T> declared;
|
||||
private final Class<?> receiver;
|
||||
|
||||
private final HashMap<Integer, T> receivers = new HashMap<>();
|
||||
|
||||
public ATest(Class<T> declared, Class<?> receiver) {
|
||||
this.declared = declared;
|
||||
this.receiver = receiver;
|
||||
TEST = compute(() -> this.getClass().getDeclaredMethod("test", declared));
|
||||
}
|
||||
|
||||
@DontInline
|
||||
public abstract Object test(T i);
|
||||
|
||||
public abstract void checkInvalidReceiver();
|
||||
|
||||
public T receiver(int id) {
|
||||
return receivers.computeIfAbsent(id, (i -> {
|
||||
try {
|
||||
MyClassLoader cl = (MyClassLoader) receiver.getClassLoader();
|
||||
Class<?> sub = cl.subclass(receiver, i);
|
||||
return (T)sub.getDeclaredConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
public void compile(Runnable r) {
|
||||
while (!WB.isMethodCompiled(TEST)) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
assertCompiled(); // record nmethod info
|
||||
}
|
||||
|
||||
private NMethod prevNM = null;
|
||||
|
||||
public void assertNotCompiled() {
|
||||
NMethod curNM = NMethod.get(TEST, false);
|
||||
assertTrue(prevNM != null); // was previously compiled
|
||||
assertTrue(curNM == null || prevNM.compile_id != curNM.compile_id); // either no nmethod present or recompiled
|
||||
prevNM = curNM; // update nmethod info
|
||||
}
|
||||
|
||||
public void assertCompiled() {
|
||||
NMethod curNM = NMethod.get(TEST, false);
|
||||
assertTrue(curNM != null); // nmethod is present
|
||||
assertTrue(prevNM == null || prevNM.compile_id == curNM.compile_id); // no recompilations if nmethod present
|
||||
prevNM = curNM; // update nmethod info
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(T i) {
|
||||
assertTrue(test(i) != WRONG);
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
public @interface TestCase {}
|
||||
|
||||
static void run(Class<?> test) {
|
||||
try {
|
||||
for (Method m : test.getDeclaredMethods()) {
|
||||
if (m.isAnnotationPresent(TestCase.class)) {
|
||||
System.out.println(m.toString());
|
||||
ClassLoader cl = new MyClassLoader(test);
|
||||
Class<?> c = cl.loadClass(test.getName());
|
||||
c.getMethod(m.getName()).invoke(c.getDeclaredConstructor().newInstance());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static class ObjectToStringHelper {
|
||||
static Object test(Object o) {
|
||||
throw new Error("not used");
|
||||
}
|
||||
}
|
||||
static class ObjectHashCodeHelper {
|
||||
static int test(Object o) {
|
||||
throw new Error("not used");
|
||||
}
|
||||
}
|
||||
|
||||
static final class MyClassLoader extends ClassLoader {
|
||||
private final Class<?> test;
|
||||
|
||||
MyClassLoader(Class<?> test) {
|
||||
this.test = test;
|
||||
}
|
||||
|
||||
static String intl(String s) {
|
||||
return s.replace('.', '/');
|
||||
}
|
||||
|
||||
Class<?> subclass(Class<?> c, int id) {
|
||||
String name = c.getName() + id;
|
||||
Class<?> sub = findLoadedClass(name);
|
||||
if (sub == null) {
|
||||
ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
|
||||
cw.visit(52, ACC_PUBLIC | ACC_SUPER, intl(name), null, intl(c.getName()), null);
|
||||
|
||||
{ // Default constructor: <init>()V
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, intl(c.getName()), "<init>", "()V", false);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
byte[] classFile = cw.toByteArray();
|
||||
return defineClass(name, classFile, 0, classFile.length);
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
|
||||
protected Class<?> loadClass(String name, boolean resolve)
|
||||
throws ClassNotFoundException
|
||||
{
|
||||
// First, check if the class has already been loaded
|
||||
Class<?> c = findLoadedClass(name);
|
||||
if (c == null) {
|
||||
try {
|
||||
c = getParent().loadClass(name);
|
||||
if (name.endsWith("ObjectToStringHelper")) {
|
||||
ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
|
||||
cw.visit(52, ACC_PUBLIC | ACC_SUPER, intl(name), null, "java/lang/Object", null);
|
||||
|
||||
{
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "(Ljava/lang/Object;)Ljava/lang/Object;", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, intl(test.getName()) + "$I", "toString", "()Ljava/lang/String;", true);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
byte[] classFile = cw.toByteArray();
|
||||
return defineClass(name, classFile, 0, classFile.length);
|
||||
} else if (name.endsWith("ObjectHashCodeHelper")) {
|
||||
ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
|
||||
cw.visit(52, ACC_PUBLIC | ACC_SUPER, intl(name), null, "java/lang/Object", null);
|
||||
|
||||
{
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "(Ljava/lang/Object;)I", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, intl(test.getName()) + "$I", "hashCode", "()I", true);
|
||||
mv.visitInsn(IRETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
byte[] classFile = cw.toByteArray();
|
||||
return defineClass(name, classFile, 0, classFile.length);
|
||||
} else if (c == test || name.startsWith(test.getName())) {
|
||||
try {
|
||||
String path = name.replace('.', '/') + ".class";
|
||||
byte[] classFile = getParent().getResourceAsStream(path).readAllBytes();
|
||||
return defineClass(name, classFile, 0, classFile.length);
|
||||
} catch (IOException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// ClassNotFoundException thrown if class not found
|
||||
// from the non-null parent class loader
|
||||
}
|
||||
|
||||
if (c == null) {
|
||||
// If still not found, then invoke findClass in order
|
||||
// to find the class.
|
||||
c = findClass(name);
|
||||
}
|
||||
}
|
||||
if (resolve) {
|
||||
resolveClass(c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
public interface RunnableWithException {
|
||||
void run() throws Throwable;
|
||||
}
|
||||
|
||||
public static void shouldThrow(Class<? extends Throwable> expectedException, RunnableWithException r) {
|
||||
try {
|
||||
r.run();
|
||||
throw new AssertionError("Exception not thrown: " + expectedException.getName());
|
||||
} catch(Throwable e) {
|
||||
if (expectedException == e.getClass()) {
|
||||
// success: proper exception is thrown
|
||||
} else {
|
||||
throw new Error(expectedException.getName() + " is expected", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static MethodHandle unsafeCastMH(Class<?> cls) {
|
||||
try {
|
||||
MethodHandle mh = MethodHandles.identity(Object.class);
|
||||
return MethodHandles.explicitCastArguments(mh, mh.type().changeReturnType(cls));
|
||||
} catch (Throwable e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static <T> T compute(Callable<T> c) {
|
||||
try {
|
||||
return c.call();
|
||||
} catch (Exception e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
331
test/hotspot/jtreg/compiler/cha/Utils.java
Normal file
331
test/hotspot/jtreg/compiler/cha/Utils.java
Normal file
@ -0,0 +1,331 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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.
|
||||
*
|
||||
* 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 compiler.cha;
|
||||
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.org.objectweb.asm.ClassWriter;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.internal.vm.annotation.DontInline;
|
||||
import sun.hotspot.WhiteBox;
|
||||
import sun.hotspot.code.NMethod;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
|
||||
import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import static jdk.test.lib.Asserts.assertTrue;
|
||||
|
||||
public class Utils {
|
||||
public static final Unsafe U = Unsafe.getUnsafe();
|
||||
|
||||
interface Test<T> {
|
||||
void call(T o);
|
||||
T receiver(int id);
|
||||
|
||||
default Runnable monomophic() {
|
||||
return () -> {
|
||||
call(receiver(0)); // 100%
|
||||
};
|
||||
}
|
||||
|
||||
default Runnable bimorphic() {
|
||||
return () -> {
|
||||
call(receiver(0)); // 50%
|
||||
call(receiver(1)); // 50%
|
||||
};
|
||||
}
|
||||
|
||||
default Runnable polymorphic() {
|
||||
return () -> {
|
||||
for (int i = 0; i < 23; i++) {
|
||||
call(receiver(0)); // 92%
|
||||
}
|
||||
call(receiver(1)); // 4%
|
||||
call(receiver(2)); // 4%
|
||||
};
|
||||
}
|
||||
|
||||
default Runnable megamorphic() {
|
||||
return () -> {
|
||||
call(receiver(0)); // 33%
|
||||
call(receiver(1)); // 33%
|
||||
call(receiver(2)); // 33%
|
||||
};
|
||||
}
|
||||
|
||||
default void load(Class<?>... cs) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
default void initialize(Class<?>... cs) {
|
||||
for (Class<?> c : cs) {
|
||||
U.ensureClassInitialized(c);
|
||||
}
|
||||
}
|
||||
|
||||
default void repeat(int cnt, Runnable r) {
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class ATest<T> implements Test<T> {
|
||||
public static final WhiteBox WB = WhiteBox.getWhiteBox();
|
||||
|
||||
public static final Object CORRECT = new Object();
|
||||
public static final Object WRONG = new Object();
|
||||
|
||||
final Method TEST;
|
||||
private final Class<T> declared;
|
||||
private final Class<?> receiver;
|
||||
|
||||
private final HashMap<Integer, T> receivers = new HashMap<>();
|
||||
|
||||
public ATest(Class<T> declared, Class<?> receiver) {
|
||||
this.declared = declared;
|
||||
this.receiver = receiver;
|
||||
TEST = compute(() -> this.getClass().getDeclaredMethod("test", declared));
|
||||
}
|
||||
|
||||
@DontInline
|
||||
public abstract Object test(T i);
|
||||
|
||||
public abstract void checkInvalidReceiver();
|
||||
|
||||
public T receiver(int id) {
|
||||
return receivers.computeIfAbsent(id, (i -> {
|
||||
try {
|
||||
MyClassLoader cl = (MyClassLoader) receiver.getClassLoader();
|
||||
Class<?> sub = cl.subclass(receiver, i);
|
||||
return (T)sub.getDeclaredConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
public void compile(Runnable r) {
|
||||
while (!WB.isMethodCompiled(TEST)) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
assertCompiled(); // record nmethod info
|
||||
}
|
||||
|
||||
private NMethod prevNM = null;
|
||||
|
||||
public void assertNotCompiled() {
|
||||
NMethod curNM = NMethod.get(TEST, false);
|
||||
assertTrue(prevNM != null); // was previously compiled
|
||||
assertTrue(curNM == null || prevNM.compile_id != curNM.compile_id); // either no nmethod present or recompiled
|
||||
prevNM = curNM; // update nmethod info
|
||||
}
|
||||
|
||||
public void assertCompiled() {
|
||||
NMethod curNM = NMethod.get(TEST, false);
|
||||
assertTrue(curNM != null); // nmethod is present
|
||||
assertTrue(prevNM == null || prevNM.compile_id == curNM.compile_id); // no recompilations if nmethod present
|
||||
prevNM = curNM; // update nmethod info
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(T i) {
|
||||
assertTrue(test(i) != WRONG);
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
public @interface TestCase {}
|
||||
|
||||
static void run(Class<?> test) {
|
||||
try {
|
||||
for (Method m : test.getDeclaredMethods()) {
|
||||
if (m.isAnnotationPresent(TestCase.class)) {
|
||||
System.out.println(m.toString());
|
||||
ClassLoader cl = new MyClassLoader(test);
|
||||
Class<?> c = cl.loadClass(test.getName());
|
||||
c.getMethod(m.getName()).invoke(c.getDeclaredConstructor().newInstance());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static class ObjectToStringHelper {
|
||||
static Object testHelper(Object o) {
|
||||
throw new Error("not used");
|
||||
}
|
||||
}
|
||||
static class ObjectHashCodeHelper {
|
||||
static int testHelper(Object o) {
|
||||
throw new Error("not used");
|
||||
}
|
||||
}
|
||||
|
||||
static final class MyClassLoader extends ClassLoader {
|
||||
private final Class<?> test;
|
||||
|
||||
MyClassLoader(Class<?> test) {
|
||||
this.test = test;
|
||||
}
|
||||
|
||||
static String intl(String s) {
|
||||
return s.replace('.', '/');
|
||||
}
|
||||
|
||||
Class<?> subclass(Class<?> c, int id) {
|
||||
String name = c.getName() + id;
|
||||
Class<?> sub = findLoadedClass(name);
|
||||
if (sub == null) {
|
||||
ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
|
||||
cw.visit(52, ACC_PUBLIC | ACC_SUPER, intl(name), null, intl(c.getName()), null);
|
||||
|
||||
{ // Default constructor: <init>()V
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, intl(c.getName()), "<init>", "()V", false);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
byte[] classFile = cw.toByteArray();
|
||||
return defineClass(name, classFile, 0, classFile.length);
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
|
||||
protected Class<?> loadClass(String name, boolean resolve)
|
||||
throws ClassNotFoundException
|
||||
{
|
||||
// First, check if the class has already been loaded
|
||||
Class<?> c = findLoadedClass(name);
|
||||
if (c == null) {
|
||||
try {
|
||||
c = getParent().loadClass(name);
|
||||
if (name.endsWith("ObjectToStringHelper")) {
|
||||
ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
|
||||
cw.visit(52, ACC_PUBLIC | ACC_SUPER, intl(name), null, "java/lang/Object", null);
|
||||
|
||||
{
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "testHelper", "(Ljava/lang/Object;)Ljava/lang/Object;", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, intl(test.getName()) + "$I", "toString", "()Ljava/lang/String;", true);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
byte[] classFile = cw.toByteArray();
|
||||
return defineClass(name, classFile, 0, classFile.length);
|
||||
} else if (name.endsWith("ObjectHashCodeHelper")) {
|
||||
ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
|
||||
cw.visit(52, ACC_PUBLIC | ACC_SUPER, intl(name), null, "java/lang/Object", null);
|
||||
|
||||
{
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "testHelper", "(Ljava/lang/Object;)I", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, intl(test.getName()) + "$I", "hashCode", "()I", true);
|
||||
mv.visitInsn(IRETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
byte[] classFile = cw.toByteArray();
|
||||
return defineClass(name, classFile, 0, classFile.length);
|
||||
} else if (c == test || name.startsWith(test.getName())) {
|
||||
try {
|
||||
String path = name.replace('.', '/') + ".class";
|
||||
byte[] classFile = getParent().getResourceAsStream(path).readAllBytes();
|
||||
return defineClass(name, classFile, 0, classFile.length);
|
||||
} catch (IOException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// ClassNotFoundException thrown if class not found
|
||||
// from the non-null parent class loader
|
||||
}
|
||||
|
||||
if (c == null) {
|
||||
// If still not found, then invoke findClass in order
|
||||
// to find the class.
|
||||
c = findClass(name);
|
||||
}
|
||||
}
|
||||
if (resolve) {
|
||||
resolveClass(c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
public interface RunnableWithException {
|
||||
void run() throws Throwable;
|
||||
}
|
||||
|
||||
public static void shouldThrow(Class<? extends Throwable> expectedException, RunnableWithException r) {
|
||||
try {
|
||||
r.run();
|
||||
throw new AssertionError("Exception not thrown: " + expectedException.getName());
|
||||
} catch(Throwable e) {
|
||||
if (expectedException == e.getClass()) {
|
||||
// success: proper exception is thrown
|
||||
} else {
|
||||
throw new Error(expectedException.getName() + " is expected", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static MethodHandle unsafeCastMH(Class<?> cls) {
|
||||
try {
|
||||
MethodHandle mh = MethodHandles.identity(Object.class);
|
||||
return MethodHandles.explicitCastArguments(mh, mh.type().changeReturnType(cls));
|
||||
} catch (Throwable e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static <T> T compute(Callable<T> c) {
|
||||
try {
|
||||
return c.call();
|
||||
} catch (Exception e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user