diff --git a/src/hotspot/share/opto/callGenerator.cpp b/src/hotspot/share/opto/callGenerator.cpp index db03dd9d80c40..33d3c04ea89f7 100644 --- a/src/hotspot/share/opto/callGenerator.cpp +++ b/src/hotspot/share/opto/callGenerator.cpp @@ -526,6 +526,7 @@ bool LateInlineVirtualCallGenerator::do_late_inline_check(Compile* C, JVMState* jvms, allow_inline, _prof_factor, + nullptr /*receiver_type*/, nullptr /*speculative_receiver_type*/, true /*allow_intrinsics*/); @@ -1103,11 +1104,12 @@ CallGenerator* CallGenerator::for_method_handle_inline(JVMState* jvms, ciMethod* int vtable_index = Method::invalid_vtable_index; bool call_does_dispatch = false; + const TypeOopPtr* receiver_type = nullptr; ciKlass* speculative_receiver_type = nullptr; if (is_virtual_or_interface) { ciInstanceKlass* klass = target->holder(); - Node* receiver_node = kit.argument(0); - const TypeOopPtr* receiver_type = gvn.type(receiver_node)->isa_oopptr(); + Node* receiver_node = kit.argument(0); + receiver_type = gvn.type(receiver_node)->isa_oopptr(); // call_does_dispatch and vtable_index are out-parameters. They might be changed. // optimize_virtual_call() takes 2 different holder // arguments for a corner case that doesn't apply here (see @@ -1123,6 +1125,7 @@ CallGenerator* CallGenerator::for_method_handle_inline(JVMState* jvms, ciMethod* CallGenerator* cg = C->call_generator(target, vtable_index, call_does_dispatch, jvms, allow_inline, PROB_ALWAYS, + receiver_type, speculative_receiver_type); return cg; } else { diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index 45a3a4f548fa0..af3d5236b38c9 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -981,7 +981,9 @@ class Compile : public Phase { // Decide how to build a call. // The profile factor is a discount to apply to this site's interp. profile. CallGenerator* call_generator(ciMethod* call_method, int vtable_index, bool call_does_dispatch, - JVMState* jvms, bool allow_inline, float profile_factor, ciKlass* speculative_receiver_type = nullptr, + JVMState* jvms, bool allow_inline, float profile_factor, + const TypeOopPtr* receiver_type = nullptr, + ciKlass* speculative_receiver_type = nullptr, bool allow_intrinsics = true); bool should_delay_inlining(ciMethod* call_method, JVMState* jvms) { return C->directive()->should_delay_inline(call_method) || diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index e4418631d17e6..478481fdadf1a 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -87,8 +87,9 @@ static void trace_type_profile(Compile* C, ciMethod* method, JVMState* jvms, } CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool call_does_dispatch, - JVMState* jvms, bool allow_inline, - float prof_factor, ciKlass* speculative_receiver_type, + JVMState* jvms, bool allow_inline, float prof_factor, + const TypeOopPtr* receiver_type, + ciKlass* speculative_receiver_type, bool allow_intrinsics) { assert(callee != nullptr, "failed method resolution"); @@ -151,8 +152,8 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool if (cg != nullptr) { if (cg->is_predicated()) { // Code without intrinsic but, hopefully, inlined. - CallGenerator* inline_cg = this->call_generator(callee, - vtable_index, call_does_dispatch, jvms, allow_inline, prof_factor, speculative_receiver_type, false); + CallGenerator* inline_cg = this->call_generator(callee, vtable_index, call_does_dispatch, jvms, allow_inline, + prof_factor, receiver_type, speculative_receiver_type, false); if (inline_cg != nullptr) { cg = CallGenerator::for_predicated_intrinsic(cg, inline_cg); } @@ -325,56 +326,64 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool } // 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 + // 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. + // they are roughly equivalent to Object, but receiver type can be used + // to narrow context type beyond declared_interface, so a more specific + // interface with a unique implementor can be used instead. if (call_does_dispatch && is_interface) { - ciInstanceKlass* declared_interface = nullptr; + ciInstanceKlass* context_intf = nullptr; + // Use declared interface class as a starting point. if (orig_callee->intrinsic_id() == vmIntrinsics::_linkToInterface) { // MemberName doesn't keep information about resolved interface class (REFC) once // resolution is over, but resolved method holder (DECC) can be used as a // conservative approximation. - declared_interface = callee->holder(); + context_intf = callee->holder(); } else { assert(!orig_callee->is_method_handle_intrinsic(), "not allowed"); - declared_interface = caller->get_declared_method_holder_at_bci(bci)->as_instance_klass(); + context_intf = caller->get_declared_method_holder_at_bci(bci)->as_instance_klass(); + } + assert(context_intf->is_interface(), "required"); + + // Based on receiver info, narrow context to one of most specific superinterfaces with a unique implementor. + if (receiver_type != nullptr && receiver_type->isa_instptr()) { + ciInstanceKlass* intf = receiver_type->is_instptr()->has_unique_implementor(context_intf); + if (intf != nullptr && intf != context_intf) { + assert(intf->is_subtype_of(context_intf), "not related"); + assert(intf->unique_implementor() != nullptr, "no unique implementor"); + context_intf = intf; + } } - assert(declared_interface->is_interface(), "required"); - ciInstanceKlass* singleton = declared_interface->unique_implementor(); + ciInstanceKlass* singleton = context_intf->unique_implementor(); if (singleton != nullptr) { - assert(singleton != declared_interface, "not a unique implementor"); + assert(singleton != context_intf, "not a unique implementor"); ciMethod* cha_monomorphic_target = - callee->find_monomorphic_target(caller->holder(), declared_interface, singleton, check_access); + callee->find_monomorphic_target(caller->holder(), context_intf, singleton, check_access); if (cha_monomorphic_target != nullptr && 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); + 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); + Deoptimization::Reason_class_check, + Deoptimization::Action_none); ciKlass* constraint = (holder->is_subclass_of(singleton) ? holder : singleton); // avoid upcasts CallGenerator* cg = CallGenerator::for_guarded_call(constraint, miss_cg, hit_cg); if (hit_cg != nullptr && cg != nullptr) { - dependencies()->assert_unique_implementor(declared_interface, singleton); - dependencies()->assert_unique_concrete_method(declared_interface, cha_monomorphic_target, declared_interface, callee); + dependencies()->assert_unique_implementor(context_intf, singleton); + dependencies()->assert_unique_concrete_method(context_intf, cha_monomorphic_target, context_intf, callee); return cg; } } @@ -595,10 +604,11 @@ void Parse::do_call() { bool call_does_dispatch = false; // Speculative type of the receiver if any + const TypeOopPtr* receiver_type = nullptr; ciKlass* speculative_receiver_type = nullptr; if (is_virtual_or_interface) { - Node* receiver_node = stack(sp() - nargs); - const TypeOopPtr* receiver_type = _gvn.type(receiver_node)->isa_oopptr(); + Node* receiver_node = stack(sp() - nargs); + receiver_type = _gvn.type(receiver_node)->isa_oopptr(); // call_does_dispatch and vtable_index are out-parameters. They might be changed. // For arrays, klass below is Object. When vtable calls are used, // resolving the call with Object would allow an illegal call to @@ -609,7 +619,7 @@ void Parse::do_call() { callee = C->optimize_virtual_call(method(), klass, holder, orig_callee, receiver_type, is_virtual, call_does_dispatch, vtable_index); // out-parameters - speculative_receiver_type = receiver_type != nullptr ? receiver_type->speculative_type() : nullptr; + speculative_receiver_type = (receiver_type != nullptr ? receiver_type->speculative_type() : nullptr); } // Additional receiver subtype checks for interface calls via invokespecial or invokeinterface. @@ -655,7 +665,8 @@ void Parse::do_call() { // Decide call tactic. // This call checks with CHA, the interpreter profile, intrinsics table, etc. // It decides whether inlining is desirable or not. - CallGenerator* cg = C->call_generator(callee, vtable_index, call_does_dispatch, jvms, try_inline, prof_factor(), speculative_receiver_type); + CallGenerator* cg = C->call_generator(callee, vtable_index, call_does_dispatch, jvms, try_inline, prof_factor(), + receiver_type, speculative_receiver_type); // NOTE: Don't use orig_callee and callee after this point! Use cg->method() instead. orig_callee = callee = nullptr; @@ -700,7 +711,9 @@ void Parse::do_call() { // the call site, perhaps because it did not match a pattern the // intrinsic was expecting to optimize. Should always be possible to // get a normal java call that may inline in that case - cg = C->call_generator(cg->method(), vtable_index, call_does_dispatch, jvms, try_inline, prof_factor(), speculative_receiver_type, /* allow_intrinsics= */ false); + cg = C->call_generator(cg->method(), vtable_index, call_does_dispatch, jvms, try_inline, prof_factor(), + receiver_type, speculative_receiver_type, + false /*allow_intrinsics*/); new_jvms = cg->generate(jvms); if (new_jvms == nullptr) { guarantee(failing(), "call failed to generate: calls should work"); diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index ecb8c2c1cd825..5c956f05b1863 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -3438,6 +3438,22 @@ bool TypeInterfaces::has_non_array_interface() const { return !TypeAryPtr::_array_interfaces->contains(this); } +// Look for a leaf interface with unique implementor under context class. Report one of possibly many. +ciInstanceKlass* TypeInterfaces::has_unique_implementor(ciInstanceKlass* context) const { + assert(context->is_interface(), "not an interface"); + ciInstanceKlass* candidate = nullptr; + for (int i = 0; i < _interfaces.length(); i++) { + ciInstanceKlass* intf = _interfaces.at(i); + if (intf->is_subtype_of(context) && (intf->unique_implementor() != nullptr)) { + if (candidate == nullptr || intf->is_subtype_of(candidate)) { + assert(candidate == nullptr || (candidate->unique_implementor() != nullptr), "not a candidate"); + candidate = intf; + } + } + } + return candidate; +} + //------------------------------TypeOopPtr------------------------------------- TypeOopPtr::TypeOopPtr(TYPES t, PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, int offset, int instance_id, const TypePtr* speculative, int inline_depth) @@ -4547,6 +4563,19 @@ const TypeKlassPtr* TypeInstPtr::as_klass_type(bool try_for_exact) const { return TypeInstKlassPtr::make(xk ? TypePtr::Constant : TypePtr::NotNull, klass(), _interfaces, 0); } +// Does the type represent an interface instance? +bool TypeInstPtr::is_interface() const { + return (instance_klass() == ciEnv::current()->Object_klass()) && !_interfaces->empty(); +} + +// For an interface instance reports one of most specific superinterfaces with a unique implementor. +ciInstanceKlass* TypeInstPtr::has_unique_implementor(ciInstanceKlass* context_intf) const { + if (is_interface() && context_intf->is_interface()) { + return _interfaces->has_unique_implementor(context_intf); + } + return nullptr; +} + template bool TypePtr::is_meet_subtype_of_helper_for_instance(const T1* this_one, const T2* other, bool this_xk, bool other_xk) { static_assert(std::is_base_of::value, ""); diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp index 4666cfbcf2d14..d83b22e068fb9 100644 --- a/src/hotspot/share/opto/type.hpp +++ b/src/hotspot/share/opto/type.hpp @@ -1128,6 +1128,8 @@ class TypeInterfaces : public Type { bool singleton(void) const; bool has_non_array_interface() const; + + ciInstanceKlass* has_unique_implementor(ciInstanceKlass* context) const; }; //------------------------------TypePtr---------------------------------------- @@ -1585,6 +1587,11 @@ class TypeInstPtr : public TypeOopPtr { const TypeKlassPtr* as_klass_type(bool try_for_exact = false) const; + bool is_interface() const; + + // Find one (of possibly many) most specific superinterfaces with a unique implementor. + ciInstanceKlass* has_unique_implementor(ciInstanceKlass* context_intf) const; + // Convenience common pre-built types. static const TypeInstPtr *NOTNULL; static const TypeInstPtr *BOTTOM; diff --git a/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java b/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java index 5e2dc2e3d56b1..5909a481877a7 100644 --- a/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java +++ b/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java @@ -23,9 +23,7 @@ /* * @test - * @requires !vm.graal.enabled - * @requires vm.opt.StressMethodHandleLinkerInlining == null | !vm.opt.StressMethodHandleLinkerInlining - * @requires vm.opt.StressUnstableIfTraps == null | !vm.opt.StressUnstableIfTraps + * @requires vm.flagless * @modules java.base/jdk.internal.misc * java.base/jdk.internal.vm.annotation * @library /testlibrary/asm @@ -58,8 +56,10 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.util.concurrent.Callable; import static compiler.cha.Utils.*; +import static jdk.test.lib.Asserts.assertTrue; public class StrengthReduceInterfaceCall { public static void main(String[] args) { @@ -82,6 +82,11 @@ public static void main(String[] args) { run(ThreeLevelDefaultHierarchy1.TestMH.class, ThreeLevelDefaultHierarchy1.class); } + // Receiver type information is only available in C2. + if (!jdk.test.whitebox.code.Compiler.isC1Enabled()) { + run(MultipleInterfacesOnReceiver.class); + run(MultipleInterfacesOnReceiver.TestMH.class, MultipleInterfacesOnReceiver.class); + } System.out.println("TEST PASSED"); } @@ -462,16 +467,10 @@ public void testMega1() { assertCompiled(); // No deopt on not-yet-seen receiver // 2. No dependency invalidation: different context - if (contextClass() == I.class) { - 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(); - } else if (contextClass() == J.class) { - // no classes to initialize w/o breaking a dependency - } else { - throw new InternalError("unsupported context: " + contextClass()); - } + 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 @@ -556,10 +555,6 @@ public void checkInvalidReceiver() { assertCompiled(); } - Class contextClass() { - return I.class; - } - public static class TestMH extends ThreeLevelHierarchyLinear { static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); @@ -568,11 +563,6 @@ public void checkInvalidReceiver() { // receiver type check failures trigger nmethod invalidation } - @Override - Class contextClass() { - return J.class; - } - @Override public Object test(I obj) throws Throwable { return TEST_MH.invokeExact(obj); // invokeinterface I.m() @@ -738,17 +728,11 @@ public void testMega() { assertCompiled(); // 2. No dependency invalidation - if (contextClass() == I.class) { - 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 - DK3.class); // DK3.m <: intf K3.m DEFAULT <: intf J.m ABSTRACT - assertCompiled(); - } else if (contextClass() == J.class) { - // no classes to initialize w/o breaking a dependency - } else { - throw new InternalError("unsupported context: " + contextClass()); - } + 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 + DK3.class); // DK3.m <: intf K3.m DEFAULT <: intf J.m ABSTRACT + assertCompiled(); // 3. Dependency invalidation initialize(DI.class); // DI.m <: intf I <: intf J.m ABSTRACT @@ -775,10 +759,6 @@ public void checkInvalidReceiver() { assertCompiled(); } - Class contextClass() { - return I.class; - } - public static class TestMH extends ThreeLevelDefaultHierarchy { static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); @@ -787,11 +767,6 @@ public void checkInvalidReceiver() { // receiver type check failures trigger nmethod invalidation } - @Override - Class contextClass() { - return J.class; - } - @Override public Object test(I obj) throws Throwable { return TEST_MH.invokeExact(obj); // invokeinterface I.m() @@ -836,20 +811,11 @@ public void testMega() { assertCompiled(); // 2. No dependency invalidation - if (contextClass() == I.class) { - initialize(DJ1.class, // DJ1.m <: intf J1 - DJ2.class, // DJ2.m <: intf J2.m ABSTRACT - K1.class, // intf K1 <: intf I <: intf J1, intf J2.m ABSTRACT - K2.class, // intf K2.m ABSTRACT <: intf I <: intf J1, intf J2.m ABSTRACT - K3.class); // intf K3.m DEFAULT <: intf I <: intf J1, intf J2.m ABSTRACT - } else if (contextClass() == J2.class) { - initialize(DJ1.class, // DJ1.m <: intf J1 - K1.class, // intf K1 <: intf I <: intf J1, intf J2.m ABSTRACT - K2.class, // intf K2.m ABSTRACT <: intf I <: intf J1, intf J2.m ABSTRACT - K3.class); // intf K3.m DEFAULT <: intf I <: intf J1, intf J2.m ABSTRACT - } else { - throw new InternalError("unsupported context: " + contextClass()); - } + initialize(DJ1.class, // DJ1.m <: intf J1 + DJ2.class, // DJ2.m <: intf J2.m ABSTRACT + K1.class, // intf K1 <: intf I <: intf J1, intf J2.m ABSTRACT + K2.class, // intf K2.m ABSTRACT <: intf I <: intf J1, intf J2.m ABSTRACT + K3.class); // intf K3.m DEFAULT <: intf I <: intf J1, intf J2.m ABSTRACT assertCompiled(); // 3. Dependency invalidation @@ -883,10 +849,6 @@ public void checkInvalidReceiver() { assertCompiled(); } - Class contextClass() { - return I.class; - } - public static class TestMH extends ThreeLevelDefaultHierarchy1 { static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); @@ -896,13 +858,98 @@ public void checkInvalidReceiver() { } @Override - Class contextClass() { - return J2.class; + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() } + } + } + + public static class MultipleInterfacesOnReceiver extends ATest { + public MultipleInterfacesOnReceiver() { super(I.class, D.class); } + + interface L1 {} + interface L2 {} + + interface J { Object m(); } + interface I extends J {} + + interface I1 extends I {} + interface I2 extends I {} + interface I3 extends I {} + interface I4 extends I {} + + interface K1 extends I1 {} + interface K2 extends I2 {} + interface K3 extends I3 {} + interface K4 extends I4 {} + + static class C implements I { public Object m() { return WRONG; }} + + static class D implements K1, K2, K3, K4, L1, L2 { public Object m() { return CORRECT; }} + + static class F1 implements K1 { public Object m() { return WRONG; }} + static class F2 implements K2 { public Object m() { return WRONG; }} + static class F3 implements K3 { public Object m() { return WRONG; }} + static class F4 implements K4 { public Object m() { return WRONG; }} + + @DontInline + public Object test(I i) throws Throwable { + // K1, K2, K3, and K4 are interfaces with unique implementor D until F1, ..., F4 are loaded. + if (i instanceof K1 && i instanceof K2 && i instanceof K3 && i instanceof K4 && + i instanceof L1 && i instanceof L2) { + return i.m(); // I <: J.m ABSTRACT; i <: (K1 & K2 & K3 & K4 & L1 & L2) + } else { + return WRONG; + } + } + + @TestCase + public void testMega() throws Exception { + assertTrue(!jdk.test.whitebox.code.Compiler.isC1Enabled(), "C2 only"); + + initialize(C.class); // exclude I + + // NB! Avoid accessing context classes directly to delay their loading until context invalidation. + for (Callable context : new Callable[] { () -> F1.class, () -> F2.class, + () -> F3.class, () -> F4.class }) { + // Trigger compilation of a megamorphic call site + resetCompiledState(); + compile(megamorphic()); + assertCompiled(); + + // Dependency: type = unique_concrete_method, context = F1...F4, method = D.m + + initialize((Class)context.call()); // context class doesn't have a unique implementor anymore, + // but we can't say which one is chosen as a context by the JVM + } + assertNotCompiled(); + + compile(megamorphic()); + assertCompiled(); + + initialize(L1.class, L2.class); // unrelated interfaces + assertCompiled(); + + repeat(100, () -> call(new D() {})); // new implementor + assertCompiled(); + } + + @Override + public void checkInvalidReceiver() { + // nothing to check; test has instanceof guards + } + + public static class TestMH extends MultipleInterfacesOnReceiver { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); @Override - public Object test(I obj) throws Throwable { - return TEST_MH.invokeExact(obj); // invokeinterface I.m() + public Object test(I i) throws Throwable { + if (i instanceof K1 && i instanceof K2 && i instanceof K3 && i instanceof K4 && + i instanceof L1 && i instanceof L2) { + return TEST_MH.invokeExact(i); // invokeinterface I.m(); i <: (K1 & K2 & K3 & K4 & L1 & L2) + } else { + return WRONG; + } } } } diff --git a/test/hotspot/jtreg/compiler/cha/Utils.java b/test/hotspot/jtreg/compiler/cha/Utils.java index 0e0a9bfbeccd0..4deb18770ab01 100644 --- a/test/hotspot/jtreg/compiler/cha/Utils.java +++ b/test/hotspot/jtreg/compiler/cha/Utils.java @@ -158,6 +158,11 @@ public void assertCompiled() { prevNM = curNM; // update nmethod info } + + public void resetCompiledState() { + prevNM = null; + } + @Override public void call(T i) { try {