diff options
22 files changed, 378 insertions, 143 deletions
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc index 3b1086e88e..068d3a964b 100644 --- a/compiler/optimizing/instruction_builder.cc +++ b/compiler/optimizing/instruction_builder.cc @@ -1111,6 +1111,7 @@ bool HInstructionBuilder::BuildInvoke(const Instruction& instruction, HInvokeStaticOrDirect::DispatchInfo dispatch_info = HSharpening::SharpenLoadMethod(resolved_method, has_method_id, + /* for_interface_call= */ false, code_generator_); if (dispatch_info.code_ptr_location == CodePtrLocation::kCallCriticalNative) { graph_->SetHasDirectCriticalNativeCall(true); @@ -1147,8 +1148,11 @@ bool HInstructionBuilder::BuildInvoke(const Instruction& instruction, ScopedObjectAccess soa(Thread::Current()); DCHECK(resolved_method->GetDeclaringClass()->IsInterface()); } - MethodLoadKind load_kind = - HSharpening::SharpenLoadMethod(resolved_method, /* has_method_id= */ true, code_generator_) + MethodLoadKind load_kind = HSharpening::SharpenLoadMethod( + resolved_method, + /* has_method_id= */ true, + /* for_interface_call= */ true, + code_generator_) .method_load_kind; invoke = new (allocator_) HInvokeInterface(allocator_, number_of_arguments, diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc index 96f82f61ca..8886f14726 100644 --- a/compiler/optimizing/instruction_simplifier.cc +++ b/compiler/optimizing/instruction_simplifier.cc @@ -2310,8 +2310,11 @@ void InstructionSimplifierVisitor::SimplifySystemArrayCopy(HInvoke* instruction) // the invoke, as we would need to look it up in the current dex file, and it // is unlikely that it exists. The most usual situation for such typed // arraycopy methods is a direct pointer to the boot image. - invoke->SetDispatchInfo( - HSharpening::SharpenLoadMethod(method, /* has_method_id= */ true, codegen_)); + invoke->SetDispatchInfo(HSharpening::SharpenLoadMethod( + method, + /* has_method_id= */ true, + /* for_interface_call= */ false, + codegen_)); } } } diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc index 3ffb24b852..1fd76f7029 100644 --- a/compiler/optimizing/sharpening.cc +++ b/compiler/optimizing/sharpening.cc @@ -58,7 +58,10 @@ static bool BootImageAOTCanEmbedMethod(ArtMethod* method, const CompilerOptions& } HInvokeStaticOrDirect::DispatchInfo HSharpening::SharpenLoadMethod( - ArtMethod* callee, bool has_method_id, CodeGenerator* codegen) { + ArtMethod* callee, + bool has_method_id, + bool for_interface_call, + CodeGenerator* codegen) { if (kIsDebugBuild) { ScopedObjectAccess soa(Thread::Current()); // Required for GetDeclaringClass below. DCHECK(callee != nullptr); @@ -83,8 +86,14 @@ HInvokeStaticOrDirect::DispatchInfo HSharpening::SharpenLoadMethod( // We don't optimize for debuggable as it would prevent us from obsoleting the method in some // situations. const CompilerOptions& compiler_options = codegen->GetCompilerOptions(); - if (callee == codegen->GetGraph()->GetArtMethod() && !codegen->GetGraph()->IsDebuggable()) { - // Recursive call. + if (callee == codegen->GetGraph()->GetArtMethod() && + !codegen->GetGraph()->IsDebuggable() && + // The runtime expects the canonical interface method being passed as + // hidden argument when doing an invokeinterface. Because default methods + // can be called through invokevirtual, we may get a copied method if we + // load 'recursively'. + (!for_interface_call || !callee->IsDefault())) { + // Recursive load. method_load_kind = MethodLoadKind::kRecursive; code_ptr_location = CodePtrLocation::kCallSelf; } else if (compiler_options.IsBootImage() || compiler_options.IsBootImageExtension()) { diff --git a/compiler/optimizing/sharpening.h b/compiler/optimizing/sharpening.h index f71d9b5056..975366918c 100644 --- a/compiler/optimizing/sharpening.h +++ b/compiler/optimizing/sharpening.h @@ -31,7 +31,10 @@ class HSharpening { public: // Used by the builder and InstructionSimplifier. static HInvokeStaticOrDirect::DispatchInfo SharpenLoadMethod( - ArtMethod* callee, bool has_method_id, CodeGenerator* codegen); + ArtMethod* callee, + bool has_method_id, + bool for_interface_call, + CodeGenerator* codegen); // Used by the builder and the inliner. static HLoadClass::LoadKind ComputeLoadClassKind(HLoadClass* load_class, diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index 291438284f..b2a27f36d2 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -423,10 +423,8 @@ inline uint16_t ArtMethod::GetCounter() { } inline uint32_t ArtMethod::GetImtIndex() { - if (LIKELY(IsAbstract() && imt_index_ != 0)) { - uint16_t imt_index = ~imt_index_; - DCHECK_EQ(imt_index, ImTable::GetImtIndex(this)) << PrettyMethod(); - return imt_index; + if (LIKELY(IsAbstract())) { + return imt_index_; } else { return ImTable::GetImtIndex(this); } @@ -434,7 +432,7 @@ inline uint32_t ArtMethod::GetImtIndex() { inline void ArtMethod::CalculateAndSetImtIndex() { DCHECK(IsAbstract()) << PrettyMethod(); - imt_index_ = ~ImTable::GetImtIndex(this); + imt_index_ = ImTable::GetImtIndex(this); } } // namespace art diff --git a/runtime/art_method.h b/runtime/art_method.h index b21a18ddba..31b81d40df 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -792,8 +792,7 @@ class ArtMethod final { // Non-abstract methods: The hotness we measure for this method. Not atomic, // as we allow missing increments: if the method is hot, we will see it eventually. uint16_t hotness_count_; - // Abstract methods: IMT index (bitwise negated) or zero if it was not cached. - // The negation is needed to distinguish zero index and missing cached entry. + // Abstract methods: IMT index. uint16_t imt_index_; }; diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 197e8ef22b..49dfba64eb 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -2447,6 +2447,7 @@ extern "C" TwoWordReturn artInvokeInterfaceTrampoline(ArtMethod* interface_metho DCHECK(!interface_method->GetDeclaringClass()->IsObjectClass()); DCHECK(interface_method->GetDeclaringClass()->IsInterface()); DCHECK(!interface_method->IsRuntimeMethod()); + DCHECK(!interface_method->IsCopied()); ObjPtr<mirror::Object> obj_this = raw_this_object; ObjPtr<mirror::Class> cls = obj_this->GetClass(); diff --git a/runtime/image.cc b/runtime/image.cc index 6f88481f89..f5898136c0 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -29,7 +29,8 @@ namespace art { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -const uint8_t ImageHeader::kImageVersion[] = { '0', '8', '8', '\0' }; // Remove DexCache arrays. +// Last change: IMT index for default methods. +const uint8_t ImageHeader::kImageVersion[] = { '0', '9', '0', '\0' }; ImageHeader::ImageHeader(uint32_t image_reservation_size, uint32_t component_count, diff --git a/runtime/imtable-inl.h b/runtime/imtable-inl.h index 21e3eb1f23..a0e56dd830 100644 --- a/runtime/imtable-inl.h +++ b/runtime/imtable-inl.h @@ -83,6 +83,13 @@ inline void ImTable::GetImtHashComponents(ArtMethod* method, } inline uint32_t ImTable::GetImtIndex(ArtMethod* method) { + DCHECK(!method->IsCopied()); + if (!method->IsAbstract()) { + // For default methods, where we cannot store the imt_index, we use the + // method_index instead. We mask it with the closest power of two to + // simplify the interpreter. + return method->GetMethodIndex() & (ImTable::kSizeTruncToPowerOfTwo - 1); + } uint32_t class_hash, name_hash, signature_hash; GetImtHashComponents(method, &class_hash, &name_hash, &signature_hash); diff --git a/runtime/imtable.h b/runtime/imtable.h index 5db3d07ec2..df10cda816 100644 --- a/runtime/imtable.h +++ b/runtime/imtable.h @@ -17,6 +17,7 @@ #ifndef ART_RUNTIME_IMTABLE_H_ #define ART_RUNTIME_IMTABLE_H_ +#include "base/bit_utils.h" #include "base/casts.h" #include "base/enums.h" #include "base/locks.h" @@ -34,6 +35,10 @@ class ImTable { // (non-marker) interfaces. // When this value changes, old images become incompatible, so image file version must change too. static constexpr size_t kSize = 43; + // Default methods cannot store the imt_index, so instead we make its IMT index depend on the + // method_index and mask it with the closest power of 2 of kSize - 1. This + // is to simplify fetching it in the interpreter. + static constexpr size_t kSizeTruncToPowerOfTwo = TruncToPowerOfTwo(kSize); uint8_t* AddressOfElement(size_t index, PointerSize pointer_size) { return reinterpret_cast<uint8_t*>(this) + OffsetOfElement(index, pointer_size); diff --git a/runtime/interpreter/mterp/arm64ng/invoke.S b/runtime/interpreter/mterp/arm64ng/invoke.S index 4a7ec49582..ac45a35e6c 100644 --- a/runtime/interpreter/mterp/arm64ng/invoke.S +++ b/runtime/interpreter/mterp/arm64ng/invoke.S @@ -72,7 +72,7 @@ %def invoke_interface(range=""): EXPORT_PC // Fast-path which gets the method from thread-local cache. - FETCH_FROM_THREAD_CACHE x0, 2f + FETCH_FROM_THREAD_CACHE x26, 5f 1: // First argument is the 'this' pointer. FETCH w1, 2 @@ -82,26 +82,42 @@ GET_VREG w1, w1 // Note: if w1 is null, this will be handled by our SIGSEGV handler. ldr w2, [x1, #MIRROR_OBJECT_CLASS_OFFSET] + // Test the first two bits of the fetched ArtMethod: + // - If the first bit is set, this is a method on j.l.Object + // - If the second bit is set, this is a default method. + tst w26, #0x3 + b.ne 3f + ldrh w3, [x26, #ART_METHOD_IMT_INDEX_OFFSET] +2: ldr x2, [x2, #MIRROR_CLASS_IMT_PTR_OFFSET_64] - ldr x0, [x2, w0, uxtw #3] + ldr x0, [x2, w3, uxtw #3] .if $range b NterpCommonInvokeInterfaceRange .else b NterpCommonInvokeInterface .endif -2: +3: + tbnz w26, #0, 4f + and x26, x26, #-4 + ldrh w3, [x26, #ART_METHOD_METHOD_INDEX_OFFSET] + and w3, w3, #ART_METHOD_IMT_MASK + b 2b +4: + lsr w26, w26, #16 + add w2, w2, #MIRROR_CLASS_VTABLE_OFFSET_64 + ldr x0, [x2, w26, uxtw #3] + .if $range + b NterpCommonInvokeInstanceRange + .else + b NterpCommonInvokeInstance + .endif +5: mov x0, xSELF ldr x1, [sp] mov x2, xPC bl nterp_get_method - // For j.l.Object interface calls, the high bit is set. Also the method index is 16bits. - tbz w0, #31, 1b - and w0, w0, #0xffff - .if $range - b NterpHandleInvokeInterfaceOnObjectMethodRange - .else - b NterpHandleInvokeInterfaceOnObjectMethod - .endif + mov x26, x0 + b 1b %def op_invoke_interface(): % invoke_interface(range="0") diff --git a/runtime/interpreter/mterp/arm64ng/main.S b/runtime/interpreter/mterp/arm64ng/main.S index 8223a1bcd0..b6d9db696f 100644 --- a/runtime/interpreter/mterp/arm64ng/main.S +++ b/runtime/interpreter/mterp/arm64ng/main.S @@ -862,6 +862,7 @@ END \name // - xINST contains shorty (in callee-save to switch over return value after call). // - x0 contains method // - x1 contains 'this' pointer for instance method. + // - for interface calls, x26 contains the interface method. add x9, xINST, #1 // shorty + 1 ; ie skip return arg character FETCH w11, 2 // arguments .if \is_string_init @@ -903,10 +904,8 @@ END \name bl art_quick_invoke_custom .else .if \is_interface - // Setup hidden argument. As we don't have access to the interface method, - // just pass the method from the IMT. If the method is the conflict trampoline, - // this will make the stub go to runtime, otherwise the hidden argument is unused. - mov ip2, x0 + // Setup hidden argument. + mov ip2, x26 .endif ldr lr, [x0, #ART_METHOD_QUICK_CODE_OFFSET_64] blr lr @@ -1102,6 +1101,7 @@ END \name // - xINST contains shorty (in callee-save to switch over return value after call). // - x0 contains method // - x1 contains 'this' pointer for instance method. + // - for interface calls, x26 contains the interface method. add x9, xINST, #1 // shorty + 1 ; ie skip return arg character FETCH w10, 2 // arguments .if \is_string_init @@ -1154,10 +1154,8 @@ END \name bl art_quick_invoke_custom .else .if \is_interface - // Setup hidden argument. As we don't have access to the interface method, - // just pass the method from the IMT. If the method is the conflict trampoline, - // this will make the stub go to runtime, otherwise the hidden argument is unused. - mov ip2, x0 + // Setup hidden argument. + mov ip2, x26 .endif ldr lr, [x0, #ART_METHOD_QUICK_CODE_OFFSET_64] blr lr @@ -1582,27 +1580,6 @@ NterpHandleHotnessOverflow: // Jump to the compiled code. br xFP -NterpHandleInvokeInterfaceOnObjectMethodRange: - // First argument is the 'this' pointer. - FETCH w1, 2 - GET_VREG w1, w1 - // Note: x1 is null, this will be handled by our SIGSEGV handler. - ldr w2, [x1, #MIRROR_OBJECT_CLASS_OFFSET] - add w2, w2, #MIRROR_CLASS_VTABLE_OFFSET_64 - ldr x0, [x2, w0, sxtw #3] - b NterpCommonInvokeInstanceRange - -NterpHandleInvokeInterfaceOnObjectMethod: - // First argument is the 'this' pointer. - FETCH w1, 2 - and w1, w1, #0xf - GET_VREG w1, w1 - // Note: x1 is null, this will be handled by our SIGSEGV handler. - ldr w2, [x1, #MIRROR_OBJECT_CLASS_OFFSET] - add w2, w2, #MIRROR_CLASS_VTABLE_OFFSET_64 - ldr x0, [x2, w0, sxtw #3] - b NterpCommonInvokeInstance - // This is the logical end of ExecuteNterpImpl, where the frame info applies. // EndExecuteNterpImpl includes the methods below as we want the runtime to // see them as part of the Nterp PCs. diff --git a/runtime/interpreter/mterp/armng/invoke.S b/runtime/interpreter/mterp/armng/invoke.S index 18f6303fb4..3dfdab16ba 100644 --- a/runtime/interpreter/mterp/armng/invoke.S +++ b/runtime/interpreter/mterp/armng/invoke.S @@ -74,10 +74,11 @@ b NterpCommonInvokePolymorphicRange %def invoke_interface(range=""): +% slow_path = add_helper(lambda: op_invoke_interface_slow_path()) EXPORT_PC // Fast-path which gets the method from thread-local cache. - FETCH_FROM_THREAD_CACHE r0, 2f -1: + FETCH_FROM_THREAD_CACHE r4, ${slow_path} +.L${opcode}_resume: // First argument is the 'this' pointer. FETCH r1, 2 .if !$range @@ -86,27 +87,44 @@ GET_VREG r1, r1 // Note: if r1 is null, this will be handled by our SIGSEGV handler. ldr r2, [r1, #MIRROR_OBJECT_CLASS_OFFSET] + // Test the first two bits of the fetched ArtMethod: + // - If the first bit is set, this is a method on j.l.Object + // - If the second bit is set, this is a default method. + tst r4, #3 + bne 2f + ldrh r3, [r4, #ART_METHOD_IMT_INDEX_OFFSET] +1: ldr r2, [r2, #MIRROR_CLASS_IMT_PTR_OFFSET_32] - ldr r0, [r2, r0, lsl #2] + ldr r0, [r2, r3, lsl #2] .if $range b NterpCommonInvokeInterfaceRange .else b NterpCommonInvokeInterface .endif 2: + tst r4, #1 + bne 3f + and r4, r4, #-4 + ldrh r3, [r4, #ART_METHOD_METHOD_INDEX_OFFSET] + and r3, r3, #ART_METHOD_IMT_MASK + b 1b +3: + lsr r4, r4, #16 + add r2, r2, #MIRROR_CLASS_VTABLE_OFFSET_32 + ldr r0, [r2, r4, lsl #2] + .if $range + b NterpCommonInvokeInstanceRange + .else + b NterpCommonInvokeInstance + .endif + +%def op_invoke_interface_slow_path(): mov r0, rSELF ldr r1, [sp] mov r2, rPC bl nterp_get_method - // For j.l.Object interface calls, the high bit is set. Also the method index is 16bits. - cmp r0, #0 - bge 1b - ubfx r0, r0, #0, #16 - .if $range - b NterpHandleInvokeInterfaceOnObjectMethodRange - .else - b NterpHandleInvokeInterfaceOnObjectMethod - .endif + mov r4, r0 + b .L${opcode}_resume %def op_invoke_interface(): % invoke_interface(range="0") diff --git a/runtime/interpreter/mterp/armng/main.S b/runtime/interpreter/mterp/armng/main.S index 680c8ad1d0..7095f58e53 100644 --- a/runtime/interpreter/mterp/armng/main.S +++ b/runtime/interpreter/mterp/armng/main.S @@ -961,6 +961,10 @@ END \name .endif .Lcall_compiled_code_\suffix: + .if \is_interface + // Save hidden argument. + vmov s16, r4 + .endif GET_SHORTY rINST, \is_interface, \is_polymorphic, \is_custom // From this point: // - rINST contains shorty (in callee-save to switch over return value after call). @@ -1022,10 +1026,8 @@ END \name bl art_quick_invoke_custom .else .if \is_interface - // Setup hidden argument. As we don't have access to the interface method, - // just pass the method from the IMT. If the method is the conflict trampoline, - // this will make the stub go to runtime, otherwise the hidden argument is unused. - mov ip, r0 + // Setup hidden argument. + vmov ip, s16 .endif ldr lr, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32] blx lr @@ -1160,6 +1162,10 @@ END \name .endif .Lcall_compiled_code_range_\suffix: + .if \is_interface + // Save hidden argument. + vmov s16, r4 + .endif GET_SHORTY rINST, \is_interface, \is_polymorphic, \is_custom // From this point: // - rINST contains shorty (in callee-save to switch over return value after call). @@ -1217,10 +1223,8 @@ END \name bl art_quick_invoke_custom .else .if \is_interface - // Setup hidden argument. As we don't have access to the interface method, - // just pass the method from the IMT. If the method is the conflict trampoline, - // this will make the stub go to runtime, otherwise the hidden argument is unused. - mov ip, r0 + // Setup hidden argument. + vmov ip, s16 .endif ldr lr, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32] blx lr @@ -1551,26 +1555,6 @@ NterpNewArray: bl art_quick_read_barrier_mark_reg00 b 1b -NterpHandleInvokeInterfaceOnObjectMethodRange: - // First argument is the 'this' pointer. - FETCH r1, 2 - GET_VREG r1, r1 - // Note: if r1 is null, this will be handled by our SIGSEGV handler. - ldr r2, [r1, #MIRROR_OBJECT_CLASS_OFFSET] - add r2, r2, #MIRROR_CLASS_VTABLE_OFFSET_32 - ldr r0, [r2, r0, lsl #2] - b NterpCommonInvokeInstanceRange - -NterpHandleInvokeInterfaceOnObjectMethod: - // First argument is the 'this' pointer. - FETCH r1, 2 - and r1, r1, #0xf - GET_VREG r1, r1 - // Note: if r1 is null, this will be handled by our SIGSEGV handler. - ldr r2, [r1, #MIRROR_OBJECT_CLASS_OFFSET] - add r2, r2, #MIRROR_CLASS_VTABLE_OFFSET_32 - ldr r0, [r2, r0, lsl #2] - b NterpCommonInvokeInstance NterpHandleHotnessOverflow: add r1, rPC, rINST, lsl #1 diff --git a/runtime/interpreter/mterp/nterp.cc b/runtime/interpreter/mterp/nterp.cc index 48f483f462..627aa1d00c 100644 --- a/runtime/interpreter/mterp/nterp.cc +++ b/runtime/interpreter/mterp/nterp.cc @@ -384,18 +384,24 @@ extern "C" size_t NterpGetMethod(Thread* self, ArtMethod* caller, uint16_t* dex_ } if (invoke_type == kInterface) { + size_t result = 0u; if (resolved_method->GetDeclaringClass()->IsObjectClass()) { - // Don't update the cache and return a value with high bit set to notify the - // interpreter it should do a vtable call instead. + // Set the low bit to notify the interpreter it should do a vtable call. DCHECK_LT(resolved_method->GetMethodIndex(), 0x10000); - return resolved_method->GetMethodIndex() | (1U << 31); + result = (resolved_method->GetMethodIndex() << 16) | 1U; } else { DCHECK(resolved_method->GetDeclaringClass()->IsInterface()); - UpdateCache(self, dex_pc_ptr, resolved_method->GetImtIndex()); - // TODO: We should pass the resolved method, and have nterp fetch the IMT - // index. Unfortunately, this doesn't work for default methods. - return resolved_method->GetImtIndex(); + DCHECK(!resolved_method->IsCopied()); + if (!resolved_method->IsAbstract()) { + // Set the second bit to notify the interpreter this is a default + // method. + result = reinterpret_cast<size_t>(resolved_method) | 2U; + } else { + result = reinterpret_cast<size_t>(resolved_method); + } } + UpdateCache(self, dex_pc_ptr, result); + return result; } else if (resolved_method->GetDeclaringClass()->IsStringClass() && !resolved_method->IsStatic() && resolved_method->IsConstructor()) { diff --git a/runtime/interpreter/mterp/x86_64ng/invoke.S b/runtime/interpreter/mterp/x86_64ng/invoke.S index b7885b4b68..ebe2fcf4cf 100644 --- a/runtime/interpreter/mterp/x86_64ng/invoke.S +++ b/runtime/interpreter/mterp/x86_64ng/invoke.S @@ -72,10 +72,11 @@ jmp NterpCommonInvokePolymorphicRange %def invoke_interface(helper="", range=""): +% slow_path = add_helper(lambda: op_invoke_interface_slow_path()) EXPORT_PC - // Fast-path which gets the method from thread-local cache. - FETCH_FROM_THREAD_CACHE %rax, 2f -1: + // Fast-path which gets the interface method from thread-local cache. + FETCH_FROM_THREAD_CACHE %rax, ${slow_path} +.L${opcode}_resume: // First argument is the 'this' pointer. movzwl 4(rPC), %r11d // arguments .if !$range @@ -83,23 +84,35 @@ .endif movl (rFP, %r11, 4), %esi movl MIRROR_OBJECT_CLASS_OFFSET(%esi), %edx + // Test the first two bits of the fetched ArtMethod: + // - If the first bit is set, this is a method on j.l.Object + // - If the second bit is set, this is a default method. + testl $$3, %eax + jne 2f + movzw ART_METHOD_IMT_INDEX_OFFSET(%rax), %ecx +1: movq MIRROR_CLASS_IMT_PTR_OFFSET_64(%edx), %rdx - movq (%rdx, %rax, 8), %rdi + movq (%rdx, %rcx, 8), %rdi jmp $helper 2: + testl $$1, %eax + .if $range + jne NterpHandleInvokeInterfaceOnObjectMethodRange + .else + jne NterpHandleInvokeInterfaceOnObjectMethod + .endif + // Default method + andq $$-4, %rax + movzw ART_METHOD_METHOD_INDEX_OFFSET(%rax), %ecx + andl $$ART_METHOD_IMT_MASK, %ecx + jmp 1b + +%def op_invoke_interface_slow_path(): movq rSELF:THREAD_SELF_OFFSET, %rdi movq 0(%rsp), %rsi movq rPC, %rdx call nterp_get_method - testl %eax, %eax - jns 1b - // For j.l.Object interface calls, the high bit is set. Also the method index is 16bits. - andl LITERAL(0xffff), %eax - .if $range - jmp NterpHandleInvokeInterfaceOnObjectMethodRange - .else - jmp NterpHandleInvokeInterfaceOnObjectMethod - .endif + jmp .L${opcode}_resume %def op_invoke_interface(): % invoke_interface(helper="NterpCommonInvokeInterface", range="0") diff --git a/runtime/interpreter/mterp/x86_64ng/main.S b/runtime/interpreter/mterp/x86_64ng/main.S index 1627b5d41a..20dc760ab3 100644 --- a/runtime/interpreter/mterp/x86_64ng/main.S +++ b/runtime/interpreter/mterp/x86_64ng/main.S @@ -464,7 +464,7 @@ END_FUNCTION \name // Uses rax as temporary. .macro LOOP_OVER_SHORTY_LOADING_GPRS gpr_reg64, gpr_reg32, inst, shorty, arg_index, finished 1: // LOOP - movb (REG_VAR(shorty)), %al // bl := *shorty + movb (REG_VAR(shorty)), %al // al := *shorty addq MACRO_LITERAL(1), REG_VAR(shorty) // shorty++ cmpb MACRO_LITERAL(0), %al // if (al == '\0') goto finished je VAR(finished) @@ -531,7 +531,7 @@ END_FUNCTION \name // Uses rax as temporary. .macro LOOP_RANGE_OVER_SHORTY_LOADING_XMMS xmm_reg, shorty, arg_index, stack_index, finished 1: // LOOP - movb (REG_VAR(shorty)), %al // bl := *shorty + movb (REG_VAR(shorty)), %al // al := *shorty addq MACRO_LITERAL(1), REG_VAR(shorty) // shorty++ cmpb MACRO_LITERAL(0), %al // if (al == '\0') goto finished je VAR(finished) @@ -605,7 +605,7 @@ END_FUNCTION \name // Uses rax as temporary. .macro LOOP_RANGE_OVER_SHORTY_LOADING_GPRS gpr_reg64, gpr_reg32, shorty, arg_index, stack_index, finished 1: // LOOP - movb (REG_VAR(shorty)), %al // bl := *shorty + movb (REG_VAR(shorty)), %al // al := *shorty addq MACRO_LITERAL(1), REG_VAR(shorty) // shorty++ cmpb MACRO_LITERAL(0), %al // if (al == '\0') goto finished je VAR(finished) @@ -706,7 +706,7 @@ END_FUNCTION \name // Uses rax as temporary. .macro LOOP_OVER_SHORTY_STORING_GPRS gpr_reg64, gpr_reg32, shorty, arg_index, regs, refs, finished 1: // LOOP - movb (REG_VAR(shorty)), %al // bl := *shorty + movb (REG_VAR(shorty)), %al // al := *shorty addq MACRO_LITERAL(1), REG_VAR(shorty) // shorty++ cmpb MACRO_LITERAL(0), %al // if (al == '\0') goto finished je VAR(finished) @@ -746,7 +746,7 @@ END_FUNCTION \name // (The trade-off is different for passing arguments and receiving them.) .macro LOOP_OVER_FPs shorty, arg_index, regs, stack_ptr, finished 1: // LOOP - movb (REG_VAR(shorty)), %al // bl := *shorty + movb (REG_VAR(shorty)), %al // al := *shorty addq MACRO_LITERAL(1), REG_VAR(shorty) // shorty++ cmpb MACRO_LITERAL(0), %al // if (al == '\0') goto finished je VAR(finished) @@ -778,7 +778,7 @@ END_FUNCTION \name // Uses rax as temporary. .macro LOOP_OVER_INTs shorty, arg_index, regs, refs, stack_ptr, finished 1: // LOOP - movb (REG_VAR(shorty)), %al // bl := *shorty + movb (REG_VAR(shorty)), %al // al := *shorty addq MACRO_LITERAL(1), REG_VAR(shorty) // shorty++ cmpb MACRO_LITERAL(0), %al // if (al == '\0') goto finished je VAR(finished) @@ -1041,10 +1041,11 @@ END_FUNCTION \name movq %rax, \dest .endm +// Uses r9 as temporary. .macro DO_ENTRY_POINT_CHECK call_compiled_code // On entry, the method is %rdi, the instance is %rsi - leaq ExecuteNterpImpl(%rip), %rax - cmpq %rax, ART_METHOD_QUICK_CODE_OFFSET_64(%rdi) + leaq ExecuteNterpImpl(%rip), %r9 + cmpq %r9, ART_METHOD_QUICK_CODE_OFFSET_64(%rdi) jne VAR(call_compiled_code) movq ART_METHOD_DATA_OFFSET_64(%rdi), %rax @@ -1084,6 +1085,10 @@ END_FUNCTION \name .endif .Lcall_compiled_code_\suffix: + .if \is_interface + // Save interface method, used for conflict resolution, in a callee-save register. + movq %rax, %xmm12 + .endif GET_SHORTY rINSTq, \is_interface, \is_polymorphic, \is_custom // From this point: // - rISNTq contains shorty (in callee-save to switch over return value after call). @@ -1130,10 +1135,7 @@ END_FUNCTION \name call SYMBOL(art_quick_invoke_custom) .else .if \is_interface - // Setup hidden argument. As we don't have access to the interface method, - // just pass the method from the IMT. If the method is the conflict trampoline, - // this will make the stub go to runtime, otherwise the hidden argument is unused. - movq %rdi, %rax + movq %xmm12, %rax .endif call *ART_METHOD_QUICK_CODE_OFFSET_64(%rdi) // Call the method. .endif @@ -1180,6 +1182,10 @@ END_FUNCTION \name .endif .Lcall_compiled_code_range_\suffix: + .if \is_interface + // Save interface method, used for conflict resolution, in a callee-saved register. + movq %rax, %xmm12 + .endif GET_SHORTY rINSTq, \is_interface, \is_polymorphic, \is_custom // From this point: // - rINSTq contains shorty (in callee-save to switch over return value after call). @@ -1232,10 +1238,8 @@ END_FUNCTION \name call SYMBOL(art_quick_invoke_custom) .else .if \is_interface - // Setup hidden argument. As we don't have access to the interface method, - // just pass the method from the IMT. If the method is the conflict trampoline, - // this will make the stub go to runtime, otherwise the hidden argument is unused. - movq %rdi, %rax + // Set the hidden argument for conflict resolution. + movq %xmm12, %rax .endif call *ART_METHOD_QUICK_CODE_OFFSET_64(%rdi) // Call the method. .endif @@ -1987,21 +1991,12 @@ NterpHandleHotnessOverflow: jmp *%rbx NterpHandleInvokeInterfaceOnObjectMethodRange: - // First argument is the 'this' pointer. - movzwl 4(rPC), %r11d // arguments - movl (rFP, %r11, 4), %esi - // Note: if esi is null, this will be handled by our SIGSEGV handler. - movl MIRROR_OBJECT_CLASS_OFFSET(%esi), %edx + shrl $$16, %eax movq MIRROR_CLASS_VTABLE_OFFSET_64(%edx, %eax, 8), %rdi jmp NterpCommonInvokeInstanceRange NterpHandleInvokeInterfaceOnObjectMethod: - // First argument is the 'this' pointer. - movzwl 4(rPC), %r11d // arguments - andq MACRO_LITERAL(0xf), %r11 - movl (rFP, %r11, 4), %esi - // Note: if esi is null, this will be handled by our SIGSEGV handler. - movl MIRROR_OBJECT_CLASS_OFFSET(%esi), %edx + shrl $$16, %eax movq MIRROR_CLASS_VTABLE_OFFSET_64(%edx, %eax, 8), %rdi jmp NterpCommonInvokeInstance diff --git a/test/815-invokeinterface-default/expected-stderr.txt b/test/815-invokeinterface-default/expected-stderr.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/815-invokeinterface-default/expected-stderr.txt diff --git a/test/815-invokeinterface-default/expected-stdout.txt b/test/815-invokeinterface-default/expected-stdout.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/815-invokeinterface-default/expected-stdout.txt diff --git a/test/815-invokeinterface-default/info.txt b/test/815-invokeinterface-default/info.txt new file mode 100644 index 0000000000..7d6c696825 --- /dev/null +++ b/test/815-invokeinterface-default/info.txt @@ -0,0 +1,2 @@ +Regression test for the compiler where we used to pass a copied method as hidden +argument for invokeinterface, which isn't supported. diff --git a/test/815-invokeinterface-default/src/Main.java b/test/815-invokeinterface-default/src/Main.java new file mode 100644 index 0000000000..db3612b646 --- /dev/null +++ b/test/815-invokeinterface-default/src/Main.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.Method; + +// An interface with enough methods to trigger a conflict. +interface Itf { + public void method0a(); + public void method0b(); + public void method0c(); + public void method0d(); + public void method0e(); + public void method0f(); + public void method0g(); + public void method0h(); + public void method0i(); + public void method0j(); + public void method0k(); + public void method0l(); + public void method0m(); + public void method0n(); + public void method0o(); + public void method0p(); + public void method0q(); + public void method0r(); + public void method0s(); + public void method0t(); + public void method0u(); + public void method0v(); + public void method0w(); + public void method0x(); + public void method0y(); + public void method0z(); + public void method1a(); + public void method1b(); + public void method1c(); + public void method1d(); + public void method1e(); + public void method1f(); + public void method1g(); + public void method1h(); + public void method1i(); + public void method1j(); + public void method1k(); + public void method1l(); + public void method1m(); + public void method1n(); + public void method1o(); + public void method1p(); + public void method1q(); + public void method1r(); + public void method1s(); + public void method1t(); + public void method1u(); + public void method1v(); + public void method1w(); + public void method1x(); + public void method1y(); + public void method1z(); + public void method2a(); + public void method2b(); + public void method2c(); + public void method2d(); + public void method2e(); + public void method2f(); + public void method2g(); + public void method2h(); + public void method2i(); + public void method2j(); + public void method2k(); + public void method2l(); + public void method2m(); + public void method2n(); + public void method2o(); + public void method2p(); + public void method2q(); + public void method2r(); + public void method2s(); + public void method2t(); + public void method2u(); + public void method2v(); + public void method2w(); + public void method2x(); + public void method2y(); + public void method2z(); + + public default void $noinline$defaultRecursiveMethod(boolean callRecursive) { + if (callRecursive) { + $noinline$defaultRecursiveMethod(false); + } + } +} + +public class Main implements Itf { + public static void main(String[] args) throws Exception { + Main main = new Main(); + main.$noinline$defaultRecursiveMethod(true); + } + + public void method0a() {} + public void method0b() {} + public void method0c() {} + public void method0d() {} + public void method0e() {} + public void method0f() {} + public void method0g() {} + public void method0h() {} + public void method0i() {} + public void method0j() {} + public void method0k() {} + public void method0l() {} + public void method0m() {} + public void method0n() {} + public void method0o() {} + public void method0p() {} + public void method0q() {} + public void method0r() {} + public void method0s() {} + public void method0t() {} + public void method0u() {} + public void method0v() {} + public void method0w() {} + public void method0x() {} + public void method0y() {} + public void method0z() {} + public void method1a() {} + public void method1b() {} + public void method1c() {} + public void method1d() {} + public void method1e() {} + public void method1f() {} + public void method1g() {} + public void method1h() {} + public void method1i() {} + public void method1j() {} + public void method1k() {} + public void method1l() {} + public void method1m() {} + public void method1n() {} + public void method1o() {} + public void method1p() {} + public void method1q() {} + public void method1r() {} + public void method1s() {} + public void method1t() {} + public void method1u() {} + public void method1v() {} + public void method1w() {} + public void method1x() {} + public void method1y() {} + public void method1z() {} + public void method2a() {} + public void method2b() {} + public void method2c() {} + public void method2d() {} + public void method2e() {} + public void method2f() {} + public void method2g() {} + public void method2h() {} + public void method2i() {} + public void method2j() {} + public void method2k() {} + public void method2l() {} + public void method2m() {} + public void method2n() {} + public void method2o() {} + public void method2p() {} + public void method2q() {} + public void method2r() {} + public void method2s() {} + public void method2t() {} + public void method2u() {} + public void method2v() {} + public void method2w() {} + public void method2x() {} + public void method2y() {} + public void method2z() {} +} diff --git a/tools/cpp-define-generator/art_method.def b/tools/cpp-define-generator/art_method.def index 097d4662e1..7b5606f935 100644 --- a/tools/cpp-define-generator/art_method.def +++ b/tools/cpp-define-generator/art_method.def @@ -16,12 +16,15 @@ #if ASM_DEFINE_INCLUDE_DEPENDENCIES #include "art_method.h" +#include "imtable.h" #endif ASM_DEFINE(ART_METHOD_ACCESS_FLAGS_OFFSET, art::ArtMethod::AccessFlagsOffset().Int32Value()) ASM_DEFINE(ART_METHOD_IS_STATIC_FLAG, art::kAccStatic) +ASM_DEFINE(ART_METHOD_IMT_MASK, + art::ImTable::kSizeTruncToPowerOfTwo - 1) ASM_DEFINE(ART_METHOD_DECLARING_CLASS_OFFSET, art::ArtMethod::DeclaringClassOffset().Int32Value()) ASM_DEFINE(ART_METHOD_JNI_OFFSET_32, |