Performance improvements in invoke-polymorphic runtime support
Fineses the runtime support for invoke-polymorphic to improve
MethodHandle benchmark measurements by an average of 14%.
- Faster conversion checks for non-exact invokes.
- Avoid conversion checks for CallerTransformers and Transforms.
- Fast path for exact invokes avoiding conversion checks.
- Use cheaper constructs where possible.
Most of these changes are driven by simulation in callgrind.
Bug: 30550796
Test: m test-art-host
Change-Id: I38722b0d41db8e528f2730a826b1710b9fd1f6a9
diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc
index 58c5d17..bd7c4ad 100644
--- a/runtime/method_handles.cc
+++ b/runtime/method_handles.cc
@@ -49,10 +49,19 @@
bool GetUnboxedPrimitiveType(ObjPtr<mirror::Class> klass, Primitive::Type* type)
REQUIRES_SHARED(Locks::mutator_lock_) {
ScopedAssertNoThreadSuspension ants(__FUNCTION__);
-#define LOOKUP_PRIMITIVE(primitive, _, __, ___) \
- if (klass->DescriptorEquals(Primitive::BoxedDescriptor(primitive))) { \
- *type = primitive; \
- return true; \
+ std::string storage;
+ const char* descriptor = klass->GetDescriptor(&storage);
+ static const char kJavaLangPrefix[] = "Ljava/lang/";
+ static const size_t kJavaLangPrefixSize = sizeof(kJavaLangPrefix) - 1;
+ if (strncmp(descriptor, kJavaLangPrefix, kJavaLangPrefixSize) != 0) {
+ return false;
+ }
+
+ descriptor += kJavaLangPrefixSize;
+#define LOOKUP_PRIMITIVE(primitive, _, java_name, ___) \
+ if (strcmp(descriptor, #java_name ";") == 0) { \
+ *type = primitive; \
+ return true; \
}
PRIMITIVES_LIST(LOOKUP_PRIMITIVE);
@@ -141,21 +150,23 @@
if (from->DescriptorEquals("Ljava/lang/Object;")) {
// Object might be converted into a primitive during unboxing.
return true;
- } else if (Primitive::IsNumericType(to_primitive) &&
- from->DescriptorEquals("Ljava/lang/Number;")) {
+ }
+
+ if (Primitive::IsNumericType(to_primitive) && from->DescriptorEquals("Ljava/lang/Number;")) {
// Number might be unboxed into any of the number primitive types.
return true;
}
+
Primitive::Type unboxed_type;
if (GetUnboxedPrimitiveType(from, &unboxed_type)) {
if (unboxed_type == to_primitive) {
// Straightforward unboxing conversion such as Boolean => boolean.
return true;
- } else {
- // Check if widening operations for numeric primitives would work,
- // such as Byte => byte => long.
- return Primitive::IsWidenable(unboxed_type, to_primitive);
}
+
+ // Check if widening operations for numeric primitives would work,
+ // such as Byte => byte => long.
+ return Primitive::IsWidenable(unboxed_type, to_primitive);
}
}
@@ -372,25 +383,18 @@
static inline size_t GetInsForProxyOrNativeMethod(ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(method->IsNative() || method->IsProxyMethod());
-
method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
- size_t num_ins = 0;
- // Separate accounting for the receiver, which isn't a part of the
- // shorty.
- if (!method->IsStatic()) {
- ++num_ins;
- }
+ uint32_t shorty_length = 0;
+ const char* shorty = method->GetShorty(&shorty_length);
- uint32_t shorty_len = 0;
- const char* shorty = method->GetShorty(&shorty_len);
- for (size_t i = 1; i < shorty_len; ++i) {
- const char c = shorty[i];
- ++num_ins;
- if (c == 'J' || c == 'D') {
+ // Static methods do not include the receiver. The receiver isn't included
+ // in the shorty_length though the return value is.
+ size_t num_ins = method->IsStatic() ? shorty_length - 1 : shorty_length;
+ for (const char* c = shorty + 1; *c != '\0'; ++c) {
+ if (*c == 'J' || *c == 'D') {
++num_ins;
}
}
-
return num_ins;
}
@@ -402,7 +406,10 @@
ObjPtr<mirror::ObjectArray<mirror::Class>> param_types(callsite_type->GetPTypes());
if (param_types->GetLength() == 1) {
ObjPtr<mirror::Class> param(param_types->GetWithoutChecks(0));
- return param == WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_EmulatedStackFrame);
+ // NB Comparing descriptor here as it appears faster in cycle simulation than using:
+ // param == WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_EmulatedStackFrame)
+ // Costs are 98 vs 173 cycles per invocation.
+ return param->DescriptorEquals("Ldalvik/system/EmulatedStackFrame;");
}
return false;
@@ -416,35 +423,8 @@
ShadowFrame& shadow_frame,
const uint32_t (&args)[Instruction::kMaxVarArgRegs],
uint32_t first_arg,
- JValue* result,
- const mirror::MethodHandle::Kind handle_kind)
+ JValue* result)
REQUIRES_SHARED(Locks::mutator_lock_) {
- // For virtual and interface methods ensure called_method points to
- // the actual method to invoke.
- if (handle_kind == mirror::MethodHandle::Kind::kInvokeVirtual ||
- handle_kind == mirror::MethodHandle::Kind::kInvokeInterface) {
- uint32_t receiver_reg = is_range ? first_arg : args[0];
- ObjPtr<mirror::Object> receiver(shadow_frame.GetVRegReference(receiver_reg));
- if (IsCallerTransformer(callsite_type)) {
- // The current receiver is an emulated stack frame, the method's
- // receiver needs to be fetched from there as the emulated frame
- // will be unpacked into a new frame.
- receiver = ObjPtr<mirror::EmulatedStackFrame>::DownCast(receiver)->GetReceiver();
- }
-
- ObjPtr<mirror::Class> declaring_class(called_method->GetDeclaringClass());
- if (receiver == nullptr || receiver->GetClass() != declaring_class) {
- // Verify that _vRegC is an object reference and of the type expected by
- // the receiver.
- if (!VerifyObjectIsClass(receiver, declaring_class)) {
- DCHECK(self->IsExceptionPending());
- return false;
- }
- called_method = receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(
- called_method, kRuntimePointerSize);
- }
- }
-
// Compute method information.
const DexFile::CodeItem* code_item = called_method->GetCodeItem();
@@ -513,17 +493,23 @@
result->SetL(0);
return false;
}
- } else if (!ConvertAndCopyArgumentsFromCallerFrame<is_range>(self,
- callsite_type,
- target_type,
- shadow_frame,
- args,
- first_arg,
- first_dest_reg,
- new_shadow_frame)) {
- DCHECK(self->IsExceptionPending());
- result->SetL(0);
- return false;
+ } else {
+ if (!callsite_type->IsConvertible(target_type.Get())) {
+ ThrowWrongMethodTypeException(target_type.Get(), callsite_type.Get());
+ return false;
+ }
+ if (!ConvertAndCopyArgumentsFromCallerFrame<is_range>(self,
+ callsite_type,
+ target_type,
+ shadow_frame,
+ args,
+ first_arg,
+ first_dest_reg,
+ new_shadow_frame)) {
+ DCHECK(self->IsExceptionPending());
+ result->SetL(0);
+ return false;
+ }
}
}
}
@@ -548,13 +534,13 @@
if (ConvertReturnValue(emulated_stack_type, target_type, &local_result)) {
emulated_stack_frame->SetReturnValue(self, local_result);
return true;
- } else {
- DCHECK(self->IsExceptionPending());
- return false;
}
- } else {
- return ConvertReturnValue(callsite_type, target_type, result);
+
+ DCHECK(self->IsExceptionPending());
+ return false;
}
+
+ return ConvertReturnValue(callsite_type, target_type, result);
}
template <bool is_range>
@@ -650,98 +636,130 @@
return klass;
}
+ArtMethod* RefineTargetMethod(Thread* self,
+ ShadowFrame& shadow_frame,
+ const mirror::MethodHandle::Kind& handle_kind,
+ Handle<mirror::MethodType> handle_type,
+ Handle<mirror::MethodType> callsite_type,
+ const uint32_t receiver_reg,
+ ArtMethod* target_method)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (handle_kind == mirror::MethodHandle::Kind::kInvokeVirtual ||
+ handle_kind == mirror::MethodHandle::Kind::kInvokeInterface) {
+ // For virtual and interface methods ensure target_method points to
+ // the actual method to invoke.
+ ObjPtr<mirror::Object> receiver(shadow_frame.GetVRegReference(receiver_reg));
+ if (IsCallerTransformer(callsite_type)) {
+ // The current receiver is an emulated stack frame, the method's
+ // receiver needs to be fetched from there as the emulated frame
+ // will be unpacked into a new frame.
+ receiver = ObjPtr<mirror::EmulatedStackFrame>::DownCast(receiver)->GetReceiver();
+ }
+
+ ObjPtr<mirror::Class> declaring_class(target_method->GetDeclaringClass());
+ if (receiver == nullptr || receiver->GetClass() != declaring_class) {
+ // Verify that _vRegC is an object reference and of the type expected by
+ // the receiver.
+ if (!VerifyObjectIsClass(receiver, declaring_class)) {
+ DCHECK(self->IsExceptionPending());
+ return nullptr;
+ }
+ return receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(
+ target_method, kRuntimePointerSize);
+ }
+ } else if (handle_kind == mirror::MethodHandle::Kind::kInvokeDirect) {
+ // String constructors are a special case, they are replaced with
+ // StringFactory methods.
+ if (target_method->IsConstructor() && target_method->GetDeclaringClass()->IsStringClass()) {
+ DCHECK(handle_type->GetRType()->IsStringClass());
+ return WellKnownClasses::StringInitToStringFactory(target_method);
+ }
+ } else if (handle_kind == mirror::MethodHandle::Kind::kInvokeSuper) {
+ ObjPtr<mirror::Class> declaring_class = target_method->GetDeclaringClass();
+
+ // Note that we're not dynamically dispatching on the type of the receiver
+ // here. We use the static type of the "receiver" object that we've
+ // recorded in the method handle's type, which will be the same as the
+ // special caller that was specified at the point of lookup.
+ ObjPtr<mirror::Class> referrer_class = handle_type->GetPTypes()->Get(0);
+ if (!declaring_class->IsInterface()) {
+ ObjPtr<mirror::Class> super_class = referrer_class->GetSuperClass();
+ uint16_t vtable_index = target_method->GetMethodIndex();
+ DCHECK(super_class != nullptr);
+ DCHECK(super_class->HasVTable());
+ // Note that super_class is a super of referrer_class and target_method
+ // will always be declared by super_class (or one of its super classes).
+ DCHECK_LT(vtable_index, super_class->GetVTableLength());
+ return super_class->GetVTableEntry(vtable_index, kRuntimePointerSize);
+ } else {
+ return referrer_class->FindVirtualMethodForInterfaceSuper(target_method, kRuntimePointerSize);
+ }
+ }
+ return target_method;
+}
+
template <bool is_range>
-bool DoInvokePolymorphicUnchecked(Thread* self,
- ShadowFrame& shadow_frame,
- Handle<mirror::MethodHandle> method_handle,
- Handle<mirror::MethodType> callsite_type,
- const uint32_t (&args)[Instruction::kMaxVarArgRegs],
- uint32_t first_arg,
- JValue* result)
+bool DoInvokePolymorphicMethod(Thread* self,
+ ShadowFrame& shadow_frame,
+ Handle<mirror::MethodHandle> method_handle,
+ Handle<mirror::MethodType> callsite_type,
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+ uint32_t first_arg,
+ JValue* result)
REQUIRES_SHARED(Locks::mutator_lock_) {
StackHandleScope<1> hs(self);
Handle<mirror::MethodType> handle_type(hs.NewHandle(method_handle->GetMethodType()));
const mirror::MethodHandle::Kind handle_kind = method_handle->GetHandleKind();
- if (IsInvoke(handle_kind)) {
- // Get the method we're actually invoking along with the kind of
- // invoke that is desired. We don't need to perform access checks at this
- // point because they would have been performed on our behalf at the point
- // of creation of the method handle.
- ArtMethod* called_method = method_handle->GetTargetMethod();
- CHECK(called_method != nullptr);
+ DCHECK(IsInvoke(handle_kind));
- if (handle_kind == mirror::MethodHandle::Kind::kInvokeVirtual ||
- handle_kind == mirror::MethodHandle::Kind::kInvokeInterface) {
- // TODO: Unfortunately, we have to postpone dynamic receiver based checks
- // because the receiver might be cast or might come from an emulated stack
- // frame, which means that it is unknown at this point. We perform these
- // checks inside DoCallPolymorphic right before we do the actual invoke.
- } else if (handle_kind == mirror::MethodHandle::Kind::kInvokeDirect) {
- // String constructors are a special case, they are replaced with StringFactory
- // methods.
- if (called_method->IsConstructor() && called_method->GetDeclaringClass()->IsStringClass()) {
- DCHECK(handle_type->GetRType()->IsStringClass());
- called_method = WellKnownClasses::StringInitToStringFactory(called_method);
- }
- } else if (handle_kind == mirror::MethodHandle::Kind::kInvokeSuper) {
- ObjPtr<mirror::Class> declaring_class = called_method->GetDeclaringClass();
+ // Get the method we're actually invoking along with the kind of
+ // invoke that is desired. We don't need to perform access checks at this
+ // point because they would have been performed on our behalf at the point
+ // of creation of the method handle.
+ ArtMethod* target_method = method_handle->GetTargetMethod();
+ uint32_t receiver_reg = is_range ? first_arg: args[0];
+ ArtMethod* called_method = RefineTargetMethod(self,
+ shadow_frame,
+ handle_kind,
+ handle_type,
+ callsite_type,
+ receiver_reg,
+ target_method);
+ if (called_method == nullptr) {
+ DCHECK(self->IsExceptionPending());
+ return false;
+ }
- // Note that we're not dynamically dispatching on the type of the receiver
- // here. We use the static type of the "receiver" object that we've
- // recorded in the method handle's type, which will be the same as the
- // special caller that was specified at the point of lookup.
- ObjPtr<mirror::Class> referrer_class = handle_type->GetPTypes()->Get(0);
- if (!declaring_class->IsInterface()) {
- ObjPtr<mirror::Class> super_class = referrer_class->GetSuperClass();
- uint16_t vtable_index = called_method->GetMethodIndex();
- DCHECK(super_class != nullptr);
- DCHECK(super_class->HasVTable());
- // Note that super_class is a super of referrer_class and called_method
- // will always be declared by super_class (or one of its super classes).
- DCHECK_LT(vtable_index, super_class->GetVTableLength());
- called_method = super_class->GetVTableEntry(vtable_index, kRuntimePointerSize);
- } else {
- called_method = referrer_class->FindVirtualMethodForInterfaceSuper(
- called_method, kRuntimePointerSize);
- }
- CHECK(called_method != nullptr);
- }
- if (IsInvokeTransform(handle_kind)) {
- // There are two cases here - method handles representing regular
- // transforms and those representing call site transforms. Method
- // handles for call site transforms adapt their MethodType to match
- // the call site. For these, the |callee_type| is the same as the
- // |callsite_type|. The VarargsCollector is such a tranform, its
- // method type depends on the call site, ie. x(a) or x(a, b), or
- // x(a, b, c). The VarargsCollector invokes a variable arity method
- // with the arity arguments in an array.
- Handle<mirror::MethodType> callee_type =
- (handle_kind == mirror::MethodHandle::Kind::kInvokeCallSiteTransform) ? callsite_type
- : handle_type;
- return DoCallTransform<is_range>(called_method,
+ if (IsInvokeTransform(handle_kind)) {
+ // There are two cases here - method handles representing regular
+ // transforms and those representing call site transforms. Method
+ // handles for call site transforms adapt their MethodType to match
+ // the call site. For these, the |callee_type| is the same as the
+ // |callsite_type|. The VarargsCollector is such a tranform, its
+ // method type depends on the call site, ie. x(a) or x(a, b), or
+ // x(a, b, c). The VarargsCollector invokes a variable arity method
+ // with the arity arguments in an array.
+ Handle<mirror::MethodType> callee_type =
+ (handle_kind == mirror::MethodHandle::Kind::kInvokeCallSiteTransform) ? callsite_type
+ : handle_type;
+ return DoCallTransform<is_range>(called_method,
+ callsite_type,
+ callee_type,
+ self,
+ shadow_frame,
+ method_handle /* receiver */,
+ args,
+ first_arg,
+ result);
+ } else {
+ return DoCallPolymorphic<is_range>(called_method,
callsite_type,
- callee_type,
+ handle_type,
self,
shadow_frame,
- method_handle /* receiver */,
args,
first_arg,
result);
-
- } else {
- return DoCallPolymorphic<is_range>(called_method,
- callsite_type,
- handle_type,
- self,
- shadow_frame,
- args,
- first_arg,
- result,
- handle_kind);
- }
- } else {
- LOG(FATAL) << "Unreachable: " << handle_kind;
- UNREACHABLE();
}
}
@@ -948,55 +966,30 @@
ObjPtr<mirror::MethodType> handle_type(method_handle->GetMethodType());
CHECK(handle_type != nullptr);
- if (!IsInvokeTransform(handle_kind)) {
- if (UNLIKELY(!IsCallerTransformer(callsite_type) &&
- !callsite_type->IsConvertible(handle_type.Ptr()))) {
+ if (IsFieldAccess(handle_kind)) {
+ DCHECK(!callsite_type->IsExactMatch(handle_type.Ptr()));
+ if (!callsite_type->IsConvertible(handle_type.Ptr())) {
ThrowWrongMethodTypeException(handle_type.Ptr(), callsite_type.Get());
return false;
}
+ const bool do_convert = true;
+ return DoInvokePolymorphicFieldAccess<is_range, do_convert>(
+ self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
}
- if (IsFieldAccess(handle_kind)) {
- if (UNLIKELY(callsite_type->IsExactMatch(handle_type.Ptr()))) {
- const bool do_convert = false;
- return DoInvokePolymorphicFieldAccess<is_range, do_convert>(
- self,
- shadow_frame,
- method_handle,
- callsite_type,
- args,
- first_arg,
- result);
- } else {
- const bool do_convert = true;
- return DoInvokePolymorphicFieldAccess<is_range, do_convert>(
- self,
- shadow_frame,
- method_handle,
- callsite_type,
- args,
- first_arg,
- result);
- }
- }
-
- if (UNLIKELY(callsite_type->IsExactMatch(handle_type.Ptr()))) {
- return DoInvokePolymorphicUnchecked<is_range>(self,
- shadow_frame,
- method_handle,
- callsite_type,
- args,
- first_arg,
- result);
- } else {
- return DoInvokePolymorphicUnchecked<is_range>(self,
- shadow_frame,
- method_handle,
- callsite_type,
- args,
- first_arg,
- result);
- }
+ return DoInvokePolymorphicMethod<is_range>(self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
}
template <bool is_range>
@@ -1008,32 +1001,9 @@
uint32_t first_arg,
JValue* result)
REQUIRES_SHARED(Locks::mutator_lock_) {
- // We need to check the nominal type of the handle in addition to the
- // real type. The "nominal" type is present when MethodHandle.asType is
- // called any handle, and results in the declared type of the handle
- // changing.
- ObjPtr<mirror::MethodType> nominal_type(method_handle->GetNominalType());
- if (UNLIKELY(nominal_type != nullptr)) {
- if (UNLIKELY(!callsite_type->IsExactMatch(nominal_type.Ptr()))) {
- ThrowWrongMethodTypeException(nominal_type.Ptr(), callsite_type.Get());
- return false;
- }
- return DoInvokePolymorphicNonExact<is_range>(self,
- shadow_frame,
- method_handle,
- callsite_type,
- args,
- first_arg,
- result);
- }
-
- ObjPtr<mirror::MethodType> handle_type(method_handle->GetMethodType());
- if (UNLIKELY(!callsite_type->IsExactMatch(handle_type.Ptr()))) {
- ThrowWrongMethodTypeException(handle_type.Ptr(), callsite_type.Get());
- return false;
- }
-
+ StackHandleScope<1> hs(self);
const mirror::MethodHandle::Kind handle_kind = method_handle->GetHandleKind();
+ Handle<mirror::MethodType> method_handle_type(hs.NewHandle(method_handle->GetMethodType()));
if (IsFieldAccess(handle_kind)) {
const bool do_convert = false;
return DoInvokePolymorphicFieldAccess<is_range, do_convert>(
@@ -1046,13 +1016,68 @@
result);
}
- return DoInvokePolymorphicUnchecked<is_range>(self,
+ // Slow-path check.
+ if (IsInvokeTransform(handle_kind) || IsCallerTransformer(callsite_type)) {
+ return DoInvokePolymorphicMethod<is_range>(self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+ }
+
+ // On the fast-path. This is equivalent to DoCallPolymoprhic without the conversion paths.
+ ArtMethod* target_method = method_handle->GetTargetMethod();
+ uint32_t receiver_reg = is_range ? first_arg : args[0];
+ ArtMethod* called_method = RefineTargetMethod(self,
shadow_frame,
- method_handle,
+ handle_kind,
+ method_handle_type,
callsite_type,
- args,
- first_arg,
- result);
+ receiver_reg,
+ target_method);
+ if (called_method == nullptr) {
+ DCHECK(self->IsExceptionPending());
+ return false;
+ }
+
+ // Compute method information.
+ const DexFile::CodeItem* code_item = called_method->GetCodeItem();
+ uint16_t num_regs;
+ size_t num_input_regs;
+ size_t first_dest_reg;
+ if (LIKELY(code_item != nullptr)) {
+ num_regs = code_item->registers_size_;
+ first_dest_reg = num_regs - code_item->ins_size_;
+ num_input_regs = code_item->ins_size_;
+ // Parameter registers go at the end of the shadow frame.
+ DCHECK_NE(first_dest_reg, (size_t)-1);
+ } else {
+ // No local regs for proxy and native methods.
+ DCHECK(called_method->IsNative() || called_method->IsProxyMethod());
+ num_regs = num_input_regs = GetInsForProxyOrNativeMethod(called_method);
+ first_dest_reg = 0;
+ }
+
+ // Allocate shadow frame on the stack.
+ const char* old_cause = self->StartAssertNoThreadSuspension("DoCallCommon");
+ ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
+ CREATE_SHADOW_FRAME(num_regs, &shadow_frame, called_method, /* dex pc */ 0);
+ ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
+ CopyArgumentsFromCallerFrame<is_range>(shadow_frame,
+ new_shadow_frame,
+ args,
+ first_arg,
+ first_dest_reg,
+ num_input_regs);
+ self->EndAssertNoThreadSuspension(old_cause);
+
+ PerformCall(self, code_item, shadow_frame.GetMethod(), first_dest_reg, new_shadow_frame, result);
+ if (self->IsExceptionPending()) {
+ return false;
+ }
+ return true;
}
} // namespace
@@ -1067,7 +1092,35 @@
uint32_t first_arg,
JValue* result)
REQUIRES_SHARED(Locks::mutator_lock_) {
+ ObjPtr<mirror::MethodType> method_handle_type = method_handle->GetMethodType();
if (IsMethodHandleInvokeExact(invoke_method)) {
+ // We need to check the nominal type of the handle in addition to the
+ // real type. The "nominal" type is present when MethodHandle.asType is
+ // called any handle, and results in the declared type of the handle
+ // changing.
+ ObjPtr<mirror::MethodType> nominal_type(method_handle->GetNominalType());
+ if (UNLIKELY(nominal_type != nullptr)) {
+ if (UNLIKELY(!callsite_type->IsExactMatch(nominal_type.Ptr()))) {
+ ThrowWrongMethodTypeException(nominal_type.Ptr(), callsite_type.Get());
+ return false;
+ }
+
+ if (LIKELY(!nominal_type->IsExactMatch(method_handle_type.Ptr()))) {
+ // Different nominal type means we have to treat as non-exact.
+ return DoInvokePolymorphicNonExact<is_range>(self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+ }
+ }
+
+ if (!callsite_type->IsExactMatch(method_handle_type.Ptr())) {
+ ThrowWrongMethodTypeException(method_handle_type.Ptr(), callsite_type.Get());
+ return false;
+ }
return DoInvokePolymorphicExact<is_range>(self,
shadow_frame,
method_handle,
@@ -1076,6 +1129,16 @@
first_arg,
result);
} else {
+ if (UNLIKELY(callsite_type->IsExactMatch(method_handle_type.Ptr()))) {
+ // A non-exact invoke that can be invoked exactly.
+ return DoInvokePolymorphicExact<is_range>(self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+ }
return DoInvokePolymorphicNonExact<is_range>(self,
shadow_frame,
method_handle,