--- a/src/hotspot/share/c1/c1_GraphBuilder.cpp Thu Jan 31 17:48:25 2019 -0800
+++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp Thu Jan 31 17:48:29 2019 -0800
@@ -1942,7 +1942,8 @@
// Use CHA on the receiver to select a more precise method.
cha_monomorphic_target = target->find_monomorphic_target(calling_klass, callee_holder, actual_recv);
} else if (code == Bytecodes::_invokeinterface && callee_holder->is_loaded() && receiver != NULL) {
- // if there is only one implementor of this interface then we
+ assert(callee_holder->is_interface(), "invokeinterface to non interface?");
+ // If there is only one implementor of this interface then we
// may be able bind this invoke directly to the implementing
// klass but we need both a dependence on the single interface
// and on the method we bind to. Additionally since all we know
@@ -1950,53 +1951,44 @@
// interface we have to insert a check that it's the class we
// expect. Interface types are not checked by the verifier so
// they are roughly equivalent to Object.
+ // The number of implementors for declared_interface is less or
+ // equal to the number of implementors for target->holder() so
+ // if number of implementors of target->holder() == 1 then
+ // number of implementors for decl_interface is 0 or 1. If
+ // it's 0 then no class implements decl_interface and there's
+ // no point in inlining.
ciInstanceKlass* singleton = NULL;
- if (target->holder()->nof_implementors() == 1) {
- singleton = target->holder()->implementor();
- assert(singleton != NULL && singleton != target->holder(),
- "just checking");
-
- assert(holder->is_interface(), "invokeinterface to non interface?");
- ciInstanceKlass* decl_interface = (ciInstanceKlass*)holder;
- // the number of implementors for decl_interface is less or
- // equal to the number of implementors for target->holder() so
- // if number of implementors of target->holder() == 1 then
- // number of implementors for decl_interface is 0 or 1. If
- // it's 0 then no class implements decl_interface and there's
- // no point in inlining.
- if (!holder->is_loaded() || decl_interface->nof_implementors() != 1 || decl_interface->has_nonstatic_concrete_methods()) {
- singleton = NULL;
- }
- }
- if (singleton) {
- cha_monomorphic_target = target->find_monomorphic_target(calling_klass, target->holder(), singleton);
+ ciInstanceKlass* declared_interface = callee_holder;
+ if (declared_interface->nof_implementors() == 1 &&
+ (!target->is_default_method() || target->is_overpass()) /* CHA doesn't support default methods yet. */) {
+ singleton = declared_interface->implementor();
+ assert(singleton != NULL && singleton != declared_interface, "");
+ cha_monomorphic_target = target->find_monomorphic_target(calling_klass, declared_interface, singleton);
if (cha_monomorphic_target != NULL) {
- // If CHA is able to bind this invoke then update the class
- // to match that class, otherwise klass will refer to the
- // interface.
- klass = cha_monomorphic_target->holder();
- actual_recv = target->holder();
-
- // insert a check it's really the expected class.
- CheckCast* c = new CheckCast(klass, receiver, copy_state_for_exception());
- c->set_incompatible_class_change_check();
- c->set_direct_compare(klass->is_final());
- // pass the result of the checkcast so that the compiler has
- // more accurate type info in the inlinee
- better_receiver = append_split(c);
+ if (cha_monomorphic_target->holder() != compilation()->env()->Object_klass()) {
+ // If CHA is able to bind this invoke then update the class
+ // to match that class, otherwise klass will refer to the
+ // interface.
+ klass = cha_monomorphic_target->holder();
+ actual_recv = declared_interface;
+
+ // insert a check it's really the expected class.
+ CheckCast* c = new CheckCast(klass, receiver, copy_state_for_exception());
+ c->set_incompatible_class_change_check();
+ c->set_direct_compare(klass->is_final());
+ // pass the result of the checkcast so that the compiler has
+ // more accurate type info in the inlinee
+ better_receiver = append_split(c);
+ } else {
+ cha_monomorphic_target = NULL; // subtype check against Object is useless
+ }
}
}
}
}
if (cha_monomorphic_target != NULL) {
- if (cha_monomorphic_target->is_abstract()) {
- // Do not optimize for abstract methods
- cha_monomorphic_target = NULL;
- }
- }
-
- if (cha_monomorphic_target != NULL) {
+ assert(!cha_monomorphic_target->is_abstract(), "");
if (!(target->is_final_method())) {
// If we inlined because CHA revealed only a single target method,
// then we are dependent on that target method not getting overridden
--- a/src/hotspot/share/ci/ciInstanceKlass.hpp Thu Jan 31 17:48:25 2019 -0800
+++ b/src/hotspot/share/ci/ciInstanceKlass.hpp Thu Jan 31 17:48:29 2019 -0800
@@ -72,7 +72,7 @@
// The possible values of the _implementor fall into following three cases:
// NULL: no implementor.
// A ciInstanceKlass that's not itself: one implementor.
- // Itsef: more than one implementors.
+ // Itself: more than one implementor.
ciInstanceKlass* _implementor;
void compute_injected_fields();
--- a/src/hotspot/share/ci/ciMethod.cpp Thu Jan 31 17:48:25 2019 -0800
+++ b/src/hotspot/share/ci/ciMethod.cpp Thu Jan 31 17:48:29 2019 -0800
@@ -90,6 +90,7 @@
_is_c2_compilable = !h_m()->is_not_c2_compilable();
_can_be_parsed = true;
_has_reserved_stack_access = h_m()->has_reserved_stack_access();
+ _is_overpass = h_m()->is_overpass();
// Lazy fields, filled in on demand. Require allocation.
_code = NULL;
_exception_handlers = NULL;
@@ -719,7 +720,7 @@
VM_ENTRY_MARK;
// Disable CHA for default methods for now
- if (root_m->get_Method()->is_default_method()) {
+ if (root_m->is_default_method()) {
return NULL;
}
@@ -759,6 +760,7 @@
// with the same name but different vtable indexes.
return NULL;
}
+ assert(!target()->is_abstract(), "not allowed");
return CURRENT_THREAD_ENV->get_method(target());
}
@@ -875,6 +877,14 @@
}
// ------------------------------------------------------------------
+ciKlass* ciMethod::get_declared_method_holder_at_bci(int bci) {
+ ciBytecodeStream iter(this);
+ iter.reset_to_bci(bci);
+ iter.next();
+ return iter.get_declared_method_holder();
+}
+
+// ------------------------------------------------------------------
// Adjust a CounterData count to be commensurate with
// interpreter_invocation_count. If the MDO exists for
// only 25% of the time the method exists, then the
--- a/src/hotspot/share/ci/ciMethod.hpp Thu Jan 31 17:48:25 2019 -0800
+++ b/src/hotspot/share/ci/ciMethod.hpp Thu Jan 31 17:48:29 2019 -0800
@@ -89,6 +89,7 @@
bool _can_be_parsed;
bool _can_be_statically_bound;
bool _has_reserved_stack_access;
+ bool _is_overpass;
// Lazy fields, filled in on demand
address _code;
@@ -265,6 +266,8 @@
return get_method_at_bci(bci, ignored_will_link, &ignored_declared_signature);
}
+ ciKlass* get_declared_method_holder_at_bci(int bci);
+
ciSignature* get_declared_signature_at_bci(int bci) {
bool ignored_will_link;
ciSignature* declared_signature;
@@ -333,6 +336,9 @@
bool is_empty_method() const;
bool is_vanilla_constructor() const;
bool is_final_method() const { return is_final() || holder()->is_final(); }
+ bool is_default_method() const { return !is_abstract() && !is_private() &&
+ holder()->is_interface(); }
+ bool is_overpass () const { check_is_loaded(); return _is_overpass; }
bool has_loops () const;
bool has_jsrs () const;
bool is_getter () const;
--- a/src/hotspot/share/ci/ciStreams.cpp Thu Jan 31 17:48:25 2019 -0800
+++ b/src/hotspot/share/ci/ciStreams.cpp Thu Jan 31 17:48:29 2019 -0800
@@ -315,18 +315,7 @@
// If this is a method invocation bytecode, get the constant pool
// index of the invoked method.
int ciBytecodeStream::get_method_index() {
-#ifdef ASSERT
- switch (cur_bc()) {
- case Bytecodes::_invokeinterface:
- case Bytecodes::_invokevirtual:
- case Bytecodes::_invokespecial:
- case Bytecodes::_invokestatic:
- case Bytecodes::_invokedynamic:
- break;
- default:
- ShouldNotReachHere();
- }
-#endif
+ assert(Bytecodes::is_invoke(cur_bc()), "invalid bytecode: %s", Bytecodes::name(cur_bc()));
if (has_index_u4())
return get_index_u4(); // invokedynamic
return get_index_u2_cpcache();
--- a/src/hotspot/share/opto/callGenerator.cpp Thu Jan 31 17:48:25 2019 -0800
+++ b/src/hotspot/share/opto/callGenerator.cpp Thu Jan 31 17:48:29 2019 -0800
@@ -652,11 +652,13 @@
CallGenerator* _if_missed;
CallGenerator* _if_hit;
float _hit_prob;
+ bool _exact_check;
public:
PredictedCallGenerator(ciKlass* predicted_receiver,
CallGenerator* if_missed,
- CallGenerator* if_hit, float hit_prob)
+ CallGenerator* if_hit, bool exact_check,
+ float hit_prob)
: CallGenerator(if_missed->method())
{
// The call profile data may predict the hit_prob as extreme as 0 or 1.
@@ -668,6 +670,7 @@
_if_missed = if_missed;
_if_hit = if_hit;
_hit_prob = hit_prob;
+ _exact_check = exact_check;
}
virtual bool is_virtual() const { return true; }
@@ -682,9 +685,16 @@
CallGenerator* if_missed,
CallGenerator* if_hit,
float hit_prob) {
- return new PredictedCallGenerator(predicted_receiver, if_missed, if_hit, hit_prob);
+ return new PredictedCallGenerator(predicted_receiver, if_missed, if_hit,
+ /*exact_check=*/true, hit_prob);
}
+CallGenerator* CallGenerator::for_guarded_call(ciKlass* guarded_receiver,
+ CallGenerator* if_missed,
+ CallGenerator* if_hit) {
+ return new PredictedCallGenerator(guarded_receiver, if_missed, if_hit,
+ /*exact_check=*/false, PROB_ALWAYS);
+}
JVMState* PredictedCallGenerator::generate(JVMState* jvms) {
GraphKit kit(jvms);
@@ -695,8 +705,8 @@
Node* receiver = kit.argument(0);
CompileLog* log = kit.C->log();
if (log != NULL) {
- log->elem("predicted_call bci='%d' klass='%d'",
- jvms->bci(), log->identify(_predicted_receiver));
+ log->elem("predicted_call bci='%d' exact='%d' klass='%d'",
+ jvms->bci(), (_exact_check ? 1 : 0), log->identify(_predicted_receiver));
}
receiver = kit.null_check_receiver_before_call(method());
@@ -708,10 +718,15 @@
ReplacedNodes replaced_nodes = kit.map()->replaced_nodes();
replaced_nodes.clone();
- Node* exact_receiver = receiver; // will get updated in place...
- Node* slow_ctl = kit.type_check_receiver(receiver,
- _predicted_receiver, _hit_prob,
- &exact_receiver);
+ Node* casted_receiver = receiver; // will get updated in place...
+ Node* slow_ctl = NULL;
+ if (_exact_check) {
+ slow_ctl = kit.type_check_receiver(receiver, _predicted_receiver, _hit_prob,
+ &casted_receiver);
+ } else {
+ slow_ctl = kit.subtype_check_receiver(receiver, _predicted_receiver,
+ &casted_receiver);
+ }
SafePointNode* slow_map = NULL;
JVMState* slow_jvms = NULL;
@@ -736,7 +751,7 @@
}
// fall through if the instance exactly matches the desired type
- kit.replace_in_map(receiver, exact_receiver);
+ kit.replace_in_map(receiver, casted_receiver);
// Make the hot call:
JVMState* new_jvms = _if_hit->generate(kit.sync_jvms());
--- a/src/hotspot/share/opto/callGenerator.hpp Thu Jan 31 17:48:25 2019 -0800
+++ b/src/hotspot/share/opto/callGenerator.hpp Thu Jan 31 17:48:29 2019 -0800
@@ -144,6 +144,10 @@
CallGenerator* if_hit,
float hit_prob);
+ static CallGenerator* for_guarded_call(ciKlass* predicted_receiver,
+ CallGenerator* if_missed,
+ CallGenerator* if_hit);
+
// How to make a call that optimistically assumes a MethodHandle target:
static CallGenerator* for_predicted_dynamic_call(ciMethodHandle* predicted_method_handle,
CallGenerator* if_missed,
--- a/src/hotspot/share/opto/doCall.cpp Thu Jan 31 17:48:25 2019 -0800
+++ b/src/hotspot/share/opto/doCall.cpp Thu Jan 31 17:48:29 2019 -0800
@@ -292,6 +292,51 @@
}
}
}
+
+ // If there is only one implementor of this interface then we
+ // may be able to bind this invoke directly to the implementing
+ // klass but we need both a dependence on the single interface
+ // and on the method we bind to. Additionally since all we know
+ // about the receiver type is that it's supposed to implement the
+ // interface we have to insert a check that it's the class we
+ // expect. Interface types are not checked by the verifier so
+ // they are roughly equivalent to Object.
+ // The number of implementors for declared_interface is less or
+ // equal to the number of implementors for target->holder() so
+ // if number of implementors of target->holder() == 1 then
+ // number of implementors for decl_interface is 0 or 1. If
+ // it's 0 then no class implements decl_interface and there's
+ // no point in inlining.
+ if (call_does_dispatch && bytecode == Bytecodes::_invokeinterface) {
+ ciInstanceKlass* declared_interface =
+ caller->get_declared_method_holder_at_bci(bci)->as_instance_klass();
+
+ if (declared_interface->nof_implementors() == 1 &&
+ (!callee->is_default_method() || callee->is_overpass()) /* CHA doesn't support default methods yet */) {
+ ciInstanceKlass* singleton = declared_interface->implementor();
+ ciMethod* cha_monomorphic_target =
+ callee->find_monomorphic_target(caller->holder(), declared_interface, singleton);
+
+ if (cha_monomorphic_target != NULL &&
+ cha_monomorphic_target->holder() != env()->Object_klass()) { // subtype check against Object is useless
+ ciKlass* holder = cha_monomorphic_target->holder();
+
+ // Try to inline the method found by CHA. Inlined method is guarded by the type check.
+ CallGenerator* hit_cg = call_generator(cha_monomorphic_target,
+ vtable_index, !call_does_dispatch, jvms, allow_inline, prof_factor);
+
+ // Deoptimize on type check fail. The interpreter will throw ICCE for us.
+ CallGenerator* miss_cg = CallGenerator::for_uncommon_trap(callee,
+ Deoptimization::Reason_class_check, Deoptimization::Action_none);
+
+ CallGenerator* cg = CallGenerator::for_guarded_call(holder, miss_cg, hit_cg);
+ if (hit_cg != NULL && cg != NULL) {
+ dependencies()->assert_unique_concrete_method(declared_interface, cha_monomorphic_target);
+ return cg;
+ }
+ }
+ }
+ }
}
// Nothing claimed the intrinsic, we go with straight-forward inlining
--- a/src/hotspot/share/opto/graphKit.cpp Thu Jan 31 17:48:25 2019 -0800
+++ b/src/hotspot/share/opto/graphKit.cpp Thu Jan 31 17:48:29 2019 -0800
@@ -2794,6 +2794,22 @@
return fail;
}
+//------------------------------subtype_check_receiver-------------------------
+Node* GraphKit::subtype_check_receiver(Node* receiver, ciKlass* klass,
+ Node** casted_receiver) {
+ const TypeKlassPtr* tklass = TypeKlassPtr::make(klass);
+ Node* recv_klass = load_object_klass(receiver);
+ Node* want_klass = makecon(tklass);
+
+ Node* slow_ctl = gen_subtype_check(recv_klass, want_klass);
+
+ // Cast receiver after successful check
+ const TypeOopPtr* recv_type = tklass->cast_to_exactness(false)->is_klassptr()->as_instance_type();
+ Node* cast = new CheckCastPPNode(control(), receiver, recv_type);
+ (*casted_receiver) = _gvn.transform(cast);
+
+ return slow_ctl;
+}
//------------------------------seems_never_null-------------------------------
// Use null_seen information if it is available from the profile.
--- a/src/hotspot/share/opto/graphKit.hpp Thu Jan 31 17:48:25 2019 -0800
+++ b/src/hotspot/share/opto/graphKit.hpp Thu Jan 31 17:48:29 2019 -0800
@@ -830,6 +830,10 @@
Node* type_check_receiver(Node* receiver, ciKlass* klass, float prob,
Node* *casted_receiver);
+ // Inexact type check used for predicted calls.
+ Node* subtype_check_receiver(Node* receiver, ciKlass* klass,
+ Node** casted_receiver);
+
// implementation of object creation
Node* set_output_for_allocation(AllocateNode* alloc,
const TypeOopPtr* oop_type,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java Thu Jan 31 17:48:29 2019 -0800
@@ -0,0 +1,970 @@
+/*
+ * Copyright (c) 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.
+ *
+ * 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
+ * @modules java.base/jdk.internal.org.objectweb.asm
+ * java.base/jdk.internal.misc
+ * java.base/jdk.internal.vm.annotation
+ * @library /test/lib /
+ * @build sun.hotspot.WhiteBox
+ * @run driver ClassFileInstaller sun.hotspot.WhiteBox
+ * sun.hotspot.WhiteBox$WhiteBoxPermission
+ *
+ * @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:-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:+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 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.*;
+
+public class StrengthReduceInterfaceCall {
+ public static void main(String[] args) {
+ run(ObjectToString.class);
+ run(ObjectHashCode.class);
+ run(TwoLevelHierarchyLinear.class);
+ run(ThreeLevelHierarchyLinear.class);
+ run(ThreeLevelHierarchyAbstractVsDefault.class);
+ run(ThreeLevelDefaultHierarchy.class);
+ run(ThreeLevelDefaultHierarchy1.class);
+ }
+
+ public static class ObjectToString extends ATest<ObjectToString.I> {
+ public ObjectToString() { super(I.class, C.class); }
+
+ interface J { String toString(); }
+ interface I extends J {}
+
+ static class C implements I {}
+
+ interface K1 extends I {}
+ interface K2 extends I { String toString(); } // K2.tS() ABSTRACT
+ // interface K3 extends I { default String toString() { return "K3"; } // K2.tS() DEFAULT
+
+ static class D implements I { public String toString() { return "D"; }}
+
+ static class DJ1 implements J {}
+ static class DJ2 implements J { public String toString() { return "DJ2"; }}
+
+ @Override
+ public Object test(I i) { return ObjectToStringHelper.test(i); /* invokeinterface I.toString() */ }
+
+ @TestCase
+ public void testMono() {
+ // 0. Trigger compilation of a monomorphic call site
+ compile(monomophic()); // C1 <: C <: intf I <: intf J <: Object.toString()
+ assertCompiled();
+
+ // Dependency: none
+
+ call(new C() { public String toString() { return "Cn"; }}); // Cn.tS <: C.tS <: intf I
+ assertCompiled();
+ }
+
+ @TestCase
+ public void testBi() {
+ // 0. Trigger compilation of a bimorphic call site
+ compile(bimorphic()); // C1 <: C <: intf I <: intf J <: Object.toString()
+ assertCompiled();
+
+ // Dependency: none
+
+ call(new C() { public String toString() { return "Cn"; }}); // Cn.tS <: C.tS <: intf I
+ assertCompiled();
+ }
+
+ @TestCase
+ public void testMega() {
+ // 0. Trigger compilation of a megamorphic call site
+ compile(megamorphic()); // C1,C2,C3 <: C <: intf I <: intf J <: Object.toString()
+ assertCompiled();
+
+ // Dependency: none
+ // compiler.cha.StrengthReduceInterfaceCall$ObjectToString::test (5 bytes)
+ // @ 1 compiler.cha.StrengthReduceInterfaceCall$ObjectToStringHelper::test (7 bytes) inline (hot)
+ // @ 1 java.lang.Object::toString (36 bytes) virtual call
+
+ // No dependency - no invalidation
+ repeat(100, () -> call(new C(){})); // Cn <: C <: intf I
+ assertCompiled();
+
+ initialize(K1.class, // intf K1 <: intf I <: intf J
+ K2.class, // intf K2.tS ABSTRACT <: intf I <: intf J
+ DJ1.class, // DJ1 <: intf J
+ DJ2.class); // DJ2.tS <: intf J
+ assertCompiled();
+
+ initialize(D.class); // D.tS <: intf I <: intf J
+ assertCompiled();
+
+ call(new C() { public String toString() { return "Cn"; }}); // Cn.tS <: C.tS <: intf I
+ assertCompiled();
+ }
+
+ @Override
+ public void checkInvalidReceiver() {
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
+ test(o);
+ });
+ assertCompiled();
+
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J() {}); // super interface
+ test(j);
+ });
+ assertCompiled();
+ }
+ }
+
+ public static class ObjectHashCode extends ATest<ObjectHashCode.I> {
+ public ObjectHashCode() { super(I.class, C.class); }
+
+ interface J {}
+ interface I extends J {}
+
+ static class C implements I {}
+
+ interface K1 extends I {}
+ interface K2 extends I { int hashCode(); } // K2.hC() ABSTRACT
+ // interface K3 extends I { default int hashCode() { return CORRECT; } // K2.hC() DEFAULT
+
+ static class D implements I { public int hashCode() { return super.hashCode(); }}
+
+ static class DJ1 implements J {}
+ static class DJ2 implements J { public int hashCode() { return super.hashCode(); }}
+
+ @Override
+ public Object test(I i) {
+ return ObjectHashCodeHelper.test(i); /* invokeinterface I.hashCode() */
+ }
+
+ @TestCase
+ public void testMono() {
+ // 0. Trigger compilation of a monomorphic call site
+ compile(monomophic()); // C1 <: C <: intf I <: intf J <: Object.hashCode()
+ assertCompiled();
+
+ // Dependency: none
+
+ call(new C() { public int hashCode() { return super.hashCode(); }}); // Cn.hC <: C.hC <: intf I
+ assertCompiled();
+ }
+
+ @TestCase
+ public void testBi() {
+ // 0. Trigger compilation of a bimorphic call site
+ compile(bimorphic()); // C1 <: C <: intf I <: intf J <: Object.toString()
+ assertCompiled();
+
+ // Dependency: none
+
+ call(new C() { public int hashCode() { return super.hashCode(); }}); // Cn.hC <: C.hC <: intf I
+ assertCompiled();
+ }
+
+ @TestCase
+ public void testMega() {
+ // 0. Trigger compilation of a megamorphic call site
+ compile(megamorphic()); // C1,C2,C3 <: C <: intf I <: intf J <: Object.hashCode()
+ assertCompiled();
+
+ // Dependency: none
+
+ // No dependency - no invalidation
+ repeat(100, () -> call(new C(){})); // Cn <: C <: intf I
+ assertCompiled();
+
+ initialize(K1.class, // intf K1 <: intf I <: intf J
+ K2.class, // intf K2.hC ABSTRACT <: intf I <: intf J
+ DJ1.class, // DJ1 <: intf J
+ DJ2.class); // DJ2.hC <: intf J
+ assertCompiled();
+
+ initialize(D.class); // D.hC <: intf I <: intf J
+ assertCompiled();
+
+ call(new C() { public int hashCode() { return super.hashCode(); }}); // Cn.hC <: C.hC <: intf I
+ assertCompiled();
+ }
+
+ @Override
+ public void checkInvalidReceiver() {
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
+ test(o);
+ });
+ assertCompiled();
+
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J() {}); // super interface
+ test(j);
+ });
+ assertCompiled();
+ }
+ }
+
+ public static class TwoLevelHierarchyLinear extends ATest<TwoLevelHierarchyLinear.I> {
+ public TwoLevelHierarchyLinear() { super(I.class, C.class); }
+
+ interface J { default Object m() { return WRONG; } }
+
+ interface I extends J { Object m(); }
+ static class C implements I { public Object m() { return CORRECT; }}
+
+ interface K1 extends I {}
+ interface K2 extends I { Object m(); }
+ interface K3 extends I { default Object m() { return WRONG; }}
+
+ static class D implements I { public Object m() { return WRONG; }}
+
+ static class DJ1 implements J {}
+ static class DJ2 implements J { public Object m() { return WRONG; }}
+
+ @DontInline
+ public Object test(I i) {
+ return i.m();
+ }
+
+ @TestCase
+ public void testMega1() {
+ // 0. Trigger compilation of a megamorphic call site
+ compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I.m ABSTRACT <: intf J.m ABSTRACT
+ assertCompiled();
+
+ // Dependency: type = unique_concrete_method, context = I, method = C.m
+
+ checkInvalidReceiver(); // ensure proper type check is preserved
+
+ // 1. No deoptimization/invalidation on not-yet-seen receiver
+ repeat(100, () -> call(new C(){})); // Cn <: C.m <: intf I.m ABSTRACT <: intf J.m DEFAULT
+ assertCompiled();
+
+ // 2. No dependency invalidation on class loading of unrelated classes: different context
+ initialize(K1.class, // intf K1 <: intf I.m ABSTRACT <: intf J.m DEFAULT
+ K2.class, // intf K2.m ABSTRACT <: intf I.m ABSTRACT <: intf J.m DEFAULT
+ DJ1.class, // DJ1 <: intf J.m DEFAULT
+ DJ2.class); // DJ2.m <: intf J.m DEFAULT
+ assertCompiled();
+
+ // 3. Dependency invalidation on D <: I
+ initialize(D.class); // D.m <: intf I.m ABSTRACT <: intf J.m DEFAULT
+ assertNotCompiled();
+
+ // 4. Recompilation: no inlining, no dependencies
+ compile(megamorphic());
+ call(new C() { public Object m() { return CORRECT; }}); // Cn.m <: C.m <: intf I.m ABSTRACT <: intf J.m DEFAULT
+ assertCompiled();
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+ }
+
+ @TestCase
+ public void testMega2() {
+ // 0. Trigger compilation of a megamorphic call site
+ compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I.m ABSTRACT <: intf J.m DEFAULT
+ assertCompiled();
+
+ // Dependency: type = unique_concrete_method, context = I, method = C.m
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+
+ // 1. Dependency invalidation
+ initialize(K3.class); // intf K3.m DEFAULT <: intf I.m ABSTRACT <: intf J.m DEFAULT
+ assertNotCompiled();
+
+ // 2. Recompilation: still inlines
+ // FIXME: no default method support in CHA yet
+ compile(megamorphic());
+ call(new K3() { public Object m() { return CORRECT; }}); // K3n.m <: intf K3.m DEFAULT <: intf I.m ABSTRACT <: intf J.m ABSTRACT
+ assertNotCompiled();
+
+ // 3. Recompilation: no inlining, no dependencies
+ compile(megamorphic());
+ call(new K3() { public Object m() { return CORRECT; }}); // Kn.m <: intf K3.m DEFAULT <: intf I.m ABSTRACT <: intf J.m DEFAULT
+ assertCompiled();
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+ }
+
+ @Override
+ public void checkInvalidReceiver() {
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
+ test(o);
+ });
+ assertCompiled();
+
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J() {}); // super interface
+ test(j);
+ });
+ assertCompiled();
+ }
+ }
+
+ public static class ThreeLevelHierarchyLinear extends ATest<ThreeLevelHierarchyLinear.I> {
+ public ThreeLevelHierarchyLinear() { super(I.class, C.class); }
+
+ interface J { Object m(); }
+ interface I extends J {}
+
+ interface K1 extends I {}
+ interface K2 extends I { Object m(); }
+ interface K3 extends I { default Object m() { return WRONG; }}
+
+ static class C implements I { public Object m() { return CORRECT; }}
+
+ static class DI implements I { public Object m() { return WRONG; }}
+ static class DJ implements J { public Object m() { return WRONG; }}
+
+ @DontInline
+ public Object test(I i) {
+ return i.m(); // I <: J.m ABSTRACT
+ }
+
+ @TestCase
+ public void testMega1() {
+ // 0. Trigger compilation of a megamorphic call site
+ compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I <: intf J.m ABSTRACT
+ assertCompiled();
+
+ // Dependency: type = unique_concrete_method, context = I, method = C.m
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+
+ // 1. No deoptimization/invalidation on not-yet-seen receiver
+ repeat(100, () -> call(new C(){})); // Cn <: C.m <: intf I
+ assertCompiled(); // No deopt on not-yet-seen receiver
+
+ // 2. No dependency invalidation: different context
+ initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT
+ K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT
+ K2.class); // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT
+ assertCompiled();
+
+ // 3. Dependency invalidation: DI.m <: I
+ initialize(DI.class); // DI.m <: intf I <: intf J.m ABSTRACT
+ assertNotCompiled();
+
+ // 4. Recompilation w/o a dependency
+ compile(megamorphic());
+ call(new C() { public Object m() { return CORRECT; }}); // Cn.m <: C.m <: intf I <: intf J.m ABSTRACT
+ assertCompiled(); // no dependency
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+ }
+
+ @TestCase
+ public void testMega2() {
+ compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I <: intf J.m ABSTRACT
+ assertCompiled();
+
+ // Dependency: type = unique_concrete_method, context = I, method = C.m
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+
+ // Dependency invalidation
+ initialize(K3.class); // intf K3.m DEFAULT <: intf I;
+ assertNotCompiled(); // FIXME: default methods in sub-interfaces shouldn't be taken into account by CHA
+
+ // Recompilation with a dependency
+ compile(megamorphic());
+ assertCompiled();
+
+ // Dependency: type = unique_concrete_method, context = I, method = C.m
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+
+ call(new K3() { public Object m() { return CORRECT; }}); // Kn.m <: K3.m DEFAULT <: intf I <: intf J.m ABSTRACT
+ assertNotCompiled();
+
+ // Recompilation w/o a dependency
+ compile(megamorphic());
+ // Dependency: none
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+ call(new C() { public Object m() { return CORRECT; }}); // Cn.m <: C.m <: intf I <: intf J.m ABSTRACT
+ assertCompiled();
+ }
+
+ @Override
+ public void checkInvalidReceiver() {
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
+ test(o);
+ });
+ assertCompiled();
+
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J() { public Object m() { return WRONG; }}); // super interface
+ test(j);
+ });
+ assertCompiled();
+ }
+ }
+
+ public static class ThreeLevelHierarchyAbstractVsDefault extends ATest<ThreeLevelHierarchyAbstractVsDefault.I> {
+ public ThreeLevelHierarchyAbstractVsDefault() { super(I.class, C.class); }
+
+ interface J1 { default Object m() { return WRONG; } } // intf J1.m DEFAULT
+ interface J2 extends J1 { Object m(); } // intf J2.m ABSTRACT <: intf J1
+ interface I extends J1, J2 {} // intf I.m OVERPASS <: intf J1,J2
+
+ static class C implements I { public Object m() { return CORRECT; }}
+
+ @DontInline
+ public Object test(I i) {
+ return i.m(); // intf I.m OVERPASS
+ }
+
+ static class DI implements I { public Object m() { return WRONG; }}
+
+ static class DJ11 implements J1 {}
+ static class DJ12 implements J1 { public Object m() { return WRONG; }}
+
+ static class DJ2 implements J2 { public Object m() { return WRONG; }}
+
+ interface K11 extends J1 {}
+ interface K12 extends J1 { Object m(); }
+ interface K13 extends J1 { default Object m() { return WRONG; }}
+ interface K21 extends J2 {}
+ interface K22 extends J2 { Object m(); }
+ interface K23 extends J2 { default Object m() { return WRONG; }}
+
+
+ public void testMega1() {
+ // 0. Trigger compilation of megamorphic call site
+ compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I.m OVERPASS <: intf J2.m ABSTRACT <: intf J1.m DEFAULT
+ assertCompiled();
+
+ // Dependency: type = unique_concrete_method, context = I, method = C.m
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+
+ // 1. No deopt/invalidation on not-yet-seen receiver
+ repeat(100, () -> call(new C(){})); // Cn <: C.m <: intf I.m OVERPASS <: intf J2.m ABSTRACT <: intf J1.m DEFAULT
+ assertCompiled();
+
+ // 2. No dependency invalidation: different context
+ initialize(K11.class, K12.class, K13.class,
+ K21.class, K22.class, K23.class);
+
+ // 3. Dependency invalidation: Cn.m <: C <: I
+ call(new C() { public Object m() { return CORRECT; }}); // Cn.m <: C.m <: intf I.m OVERPASS <: intf J2.m ABSTRACT <: intf J1.m DEFAULT
+ assertNotCompiled();
+
+ // 4. Recompilation w/o a dependency
+ compile(megamorphic());
+ call(new C() { public Object m() { return CORRECT; }});
+ assertCompiled(); // no inlining
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+ }
+
+ public void testMega2() {
+ // 0. Trigger compilation of a megamorphic call site
+ compile(megamorphic());
+ assertCompiled();
+
+ // Dependency: type = unique_concrete_method, context = I, method = C.m
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+
+ // 1. No dependency invalidation: different context
+ initialize(DJ11.class,
+ DJ12.class,
+ DJ2.class);
+ assertCompiled();
+
+ // 2. Dependency invalidation: DI.m <: I
+ initialize(DI.class);
+ assertNotCompiled();
+
+ // 3. Recompilation w/o a dependency
+ compile(megamorphic());
+ call(new C() { public Object m() { return CORRECT; }});
+ assertCompiled(); // no inlining
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+ }
+
+ @Override
+ public void checkInvalidReceiver() {
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
+ test(o);
+ });
+ assertCompiled();
+
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J1() {}); // super interface
+ test(j);
+ });
+ assertCompiled();
+
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J2() { public Object m() { return WRONG; }}); // super interface
+ test(j);
+ });
+ assertCompiled();
+ }
+ }
+
+ public static class ThreeLevelDefaultHierarchy extends ATest<ThreeLevelDefaultHierarchy.I> {
+ public ThreeLevelDefaultHierarchy() { super(I.class, C.class); }
+
+ interface J { default Object m() { return WRONG; }}
+ interface I extends J {}
+
+ static class C implements I { public Object m() { return CORRECT; }}
+
+ interface K1 extends I {}
+ interface K2 extends I { Object m(); }
+ interface K3 extends I { default Object m() { return WRONG; }}
+
+ static class DI implements I { public Object m() { return WRONG; }}
+ static class DJ implements J { public Object m() { return WRONG; }}
+
+ @DontInline
+ public Object test(I i) {
+ return i.m(); // no inlining since J.m is a default method
+ }
+
+ @TestCase
+ public void testMega() {
+ // 0. Trigger compilation of a megamorphic call site
+ compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I <: intf J.m ABSTRACT
+ assertCompiled();
+
+ // Dependency: none
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+
+ // 1. No deoptimization/invalidation on not-yet-seen receiver
+ repeat(100, () -> call(new C() {}));
+ assertCompiled();
+
+ // 2. No dependency and no inlining
+ initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT
+ DI.class, // DI.m <: intf I <: intf J.m ABSTRACT
+ K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT
+ K2.class); // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT
+ assertCompiled();
+ }
+
+ @Override
+ public void checkInvalidReceiver() {
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
+ test(o);
+ });
+ assertCompiled();
+
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J() {}); // super interface
+ test(j);
+ });
+ assertCompiled();
+ }
+ }
+
+ public static class ThreeLevelDefaultHierarchy1 extends ATest<ThreeLevelDefaultHierarchy1.I> {
+ public ThreeLevelDefaultHierarchy1() { super(I.class, C.class); }
+
+ interface J1 { Object m();}
+ interface J2 extends J1 { default Object m() { return WRONG; } }
+ interface I extends J1, J2 {}
+
+ static class C implements I { public Object m() { return CORRECT; }}
+
+ interface K1 extends I {}
+ interface K2 extends I { Object m(); }
+ interface K3 extends I { default Object m() { return WRONG; }}
+
+ static class DI implements I { public Object m() { return WRONG; }}
+ static class DJ1 implements J1 { public Object m() { return WRONG; }}
+ static class DJ2 implements J2 { public Object m() { return WRONG; }}
+
+ @DontInline
+ public Object test(I i) {
+ return i.m(); // no inlining since J.m is a default method
+ }
+
+ @TestCase
+ public void testMega() {
+ // 0. Trigger compilation of a megamorphic call site
+ compile(megamorphic());
+ assertCompiled();
+
+ // Dependency: none
+
+ checkInvalidReceiver(); // ensure proper type check on receiver is preserved
+
+ // 1. No deoptimization/invalidation on not-yet-seen receiver
+ repeat(100, () -> call(new C() {}));
+ assertCompiled();
+
+ // 2. No dependency, no inlining
+ // CHA doesn't support default methods yet.
+ initialize(DJ1.class,
+ DJ2.class,
+ DI.class,
+ K1.class,
+ K2.class,
+ K3.class);
+ assertCompiled();
+ }
+
+ @Override
+ public void checkInvalidReceiver() {
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I o = (I) unsafeCastMH(I.class).invokeExact(new Object()); // unrelated
+ test(o);
+ });
+ assertCompiled();
+
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J1() { public Object m() { return WRONG; } }); // super interface
+ test(j);
+ });
+ assertCompiled();
+
+ shouldThrow(IncompatibleClassChangeError.class, () -> {
+ I j = (I) unsafeCastMH(I.class).invokeExact((Object)new J2() {}); // super interface
+ test(j);
+ });
+ assertCompiled();
+ }
+ }
+
+ /* =========================================================== */
+
+ interface Action {
+ int run();
+ }
+
+ public static final Unsafe U = Unsafe.getUnsafe();
+
+ interface Test<T> {
+ boolean isCompiled();
+ void assertNotCompiled();
+ void assertCompiled();
+
+ 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 compile(Runnable r) {
+ assertNotCompiled();
+ while(!isCompiled()) {
+ r.run();
+ }
+ assertCompiled();
+ }
+
+ 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);
+ }
+ }));
+ }
+
+ @Override
+ public boolean isCompiled() { return WB.isMethodCompiled(TEST); }
+
+ @Override
+ public void assertNotCompiled() { assertFalse(isCompiled()); }
+
+ @Override
+ public void assertCompiled() { assertTrue(isCompiled()); }
+
+ @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);
+ }
+ }
+}