diff --git a/eng/pipelines/coreclr/jit-cfg.yml b/eng/pipelines/coreclr/jit-cfg.yml index a99a9edb89db66..5cc80c787c1f67 100644 --- a/eng/pipelines/coreclr/jit-cfg.yml +++ b/eng/pipelines/coreclr/jit-cfg.yml @@ -16,8 +16,6 @@ extends: template: /eng/pipelines/coreclr/templates/jit-outerloop-pipeline.yml parameters: platforms: - - linux_arm64 - - linux_x64 - windows_x64 - windows_arm64 testGroup: jit-cfg \ No newline at end of file diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index a4ba0a37c235b1..2383adbb13de22 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -573,6 +573,7 @@ enum CorInfoHelpFunc CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT_TRACK_TRANSITIONS, // Transition to preemptive mode and track transitions in reverse P/Invoke prolog. CORINFO_HELP_GVMLOOKUP_FOR_SLOT, // Resolve a generic virtual method target from this pointer and runtime method handle + CORINFO_HELP_INTERFACELOOKUP_FOR_SLOT, // Resolve a non-generic interface method from this pointer and dispatch cell CORINFO_HELP_STACK_PROBE, // Probes each page of the allocated stack frame diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index d426f8003599d3..26ab011000ff25 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* d1188f9b-d81e-487a-b525-cedb10d15c8e */ - 0xd1188f9b, - 0xd81e, - 0x487a, - {0xb5, 0x25, 0xce, 0xdb, 0x10, 0xd1, 0x5c, 0x8e} +constexpr GUID JITEEVersionIdentifier = { /* 7EA1AD2F-5830-4802-AD99-A6DA4A5269AB */ + 0x7ea1ad2f, + 0x5830, + 0x4802, + {0xad, 0x99, 0xa6, 0xda, 0x4a, 0x52, 0x69, 0xab} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index 24ce3b00085129..4f862c21105bb0 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -332,6 +332,11 @@ JITHELPER(CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT_TRACK_TRANSITIONS, JIT_ReversePInvokeExitTrackTransitions, METHOD__NIL) JITHELPER(CORINFO_HELP_GVMLOOKUP_FOR_SLOT, NULL, METHOD__NIL) +#ifdef FEATURE_RESOLVE_HELPER_DISPATCH + JITHELPER(CORINFO_HELP_INTERFACELOOKUP_FOR_SLOT, JIT_InterfaceLookupForSlot, METHOD__NIL) +#else + JITHELPER(CORINFO_HELP_INTERFACELOOKUP_FOR_SLOT, NULL, METHOD__NIL) +#endif #if !defined(TARGET_ARM64) && !defined(TARGET_LOONGARCH64) && !defined(TARGET_RISCV64) JITHELPER(CORINFO_HELP_STACK_PROBE, JIT_StackProbe, METHOD__NIL) diff --git a/src/coreclr/inc/switches.h b/src/coreclr/inc/switches.h index 9d6f7083f53fa4..a5ccfee195e612 100644 --- a/src/coreclr/inc/switches.h +++ b/src/coreclr/inc/switches.h @@ -170,6 +170,12 @@ #define FEATURE_PORTABLE_SHUFFLE_THUNKS #endif +// Dispatch interface calls via resolve helper followed by an indirect call. +// Slow functional implementation, only used for stress-testing of DOTNET_JitForceControlFlowGuard=1. +#if defined(TARGET_WINDOWS) && (defined(TARGET_AMD64) || defined(TARGET_ARM64)) +#define FEATURE_RESOLVE_HELPER_DISPATCH +#endif + // If this is uncommented, leaves a file "StubLog_.log" with statistics on the behavior // of stub-based interface dispatch. //#define STUB_LOGGING diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 4113e40f1d44be..fd85663bd45b8d 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -766,6 +766,11 @@ regMaskTP Compiler::compHelperCallKillSet(CorInfoHelpFunc helper) case CORINFO_HELP_VALIDATE_INDIRECT_CALL: return RBM_VALIDATE_INDIRECT_CALL_TRASH; +#ifdef RBM_INTERFACELOOKUP_FOR_SLOT_TRASH + case CORINFO_HELP_INTERFACELOOKUP_FOR_SLOT: + return RBM_INTERFACELOOKUP_FOR_SLOT_TRASH; +#endif + default: return RBM_CALLEE_TRASH; } @@ -5849,6 +5854,7 @@ void CodeGen::genDefinePendingCallLabel(GenTreeCall* call) { case CORINFO_HELP_VALIDATE_INDIRECT_CALL: case CORINFO_HELP_VIRTUAL_FUNC_PTR: + case CORINFO_HELP_INTERFACELOOKUP_FOR_SLOT: case CORINFO_HELP_MEMSET: case CORINFO_HELP_MEMCPY: return; diff --git a/src/coreclr/jit/emit.cpp b/src/coreclr/jit/emit.cpp index 2482c1b22c6ce8..bd9a5dc655f638 100644 --- a/src/coreclr/jit/emit.cpp +++ b/src/coreclr/jit/emit.cpp @@ -10690,13 +10690,13 @@ const char* emitter::emitOffsetToLabel(unsigned offs) regMaskTP emitter::emitGetGCRegsSavedOrModified(CORINFO_METHOD_HANDLE methHnd) { // Is it a helper with a special saved set? - bool isNoGCHelper = emitNoGChelper(methHnd); + bool isNoGCHelper = emitNoGChelper(methHnd); + CorInfoHelpFunc helper = Compiler::eeGetHelperNum(methHnd); + if (isNoGCHelper) { - CorInfoHelpFunc helpFunc = Compiler::eeGetHelperNum(methHnd); - // Get the set of registers that this call kills and remove it from the saved set. - regMaskTP savedSet = RBM_ALLINT & ~emitGetGCRegsKilledByNoGCCall(helpFunc); + regMaskTP savedSet = RBM_ALLINT & ~emitGetGCRegsKilledByNoGCCall(helper); #ifdef DEBUG if (emitComp->verbose) @@ -10709,6 +10709,13 @@ regMaskTP emitter::emitGetGCRegsSavedOrModified(CORINFO_METHOD_HANDLE methHnd) #endif return savedSet; } +#ifdef RBM_INTERFACELOOKUP_FOR_SLOT_TRASH + else if (helper == CORINFO_HELP_INTERFACELOOKUP_FOR_SLOT) + { + // This one is not no-gc, but it preserves arg registers. + return RBM_ALLINT & ~RBM_INTERFACELOOKUP_FOR_SLOT_TRASH; + } +#endif else { // This is the saved set of registers after a normal call. diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 05edb2adb5f170..ffde65198e6e37 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -1897,6 +1897,47 @@ void CallArgs::RemoveUnsafe(CallArg* arg) assert(!"Did not find arg to remove in CallArgs::Remove"); } +//--------------------------------------------------------------- +// RemoveLate: Remove an argument from the argument list and late argument list. +// +// Parameters: +// arg - The arg to remove. +// +// Remarks: +// This can be used to remove arguments after ABI determination and after morph. +// It removes the argument from both the early and late list. However, no ABI +// information is updated. Caller needs to know what they are doing. +// +void CallArgs::RemoveLate(CallArg* arg) +{ + CallArg** slot = &m_lateHead; + while (*slot != nullptr) + { + if (*slot == arg) + { + *slot = arg->GetLateNext(); + break; + } + + slot = &(*slot)->LateNextRef(); + } + + slot = &m_head; + while (*slot != nullptr) + { + if (*slot == arg) + { + *slot = arg->GetNext(); + RemovedWellKnownArg(arg->GetWellKnownArg()); + return; + } + + slot = &(*slot)->NextRef(); + } + + assert(!"Did not find arg to remove in CallArgs::RemoveLate"); +} + #ifdef TARGET_XARCH //--------------------------------------------------------------- // NeedsVzeroupper: Determines if the call needs a vzeroupper emitted before it is invoked diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 0726b9b9f8846f..c5156f53c15a3d 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -4840,6 +4840,7 @@ class CallArgs CallArg* InsertAfterThisOrFirst(Compiler* comp, const NewCallArg& arg); void PushLateBack(CallArg* arg); void Remove(CallArg* arg); + void RemoveLate(CallArg* arg); void RemoveUnsafe(CallArg* arg); template diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 118d7b75b320e1..d01b47982692d3 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -3627,8 +3627,99 @@ void Lowering::LowerCFGCall(GenTreeCall* call) { return; } + auto cloneUse = [=](LIR::Use& use, bool cloneConsts) -> GenTree* { + bool canClone = cloneConsts && use.Def()->IsCnsIntOrI(); + if (!canClone && use.Def()->OperIs(GT_LCL_VAR)) + { + canClone = !comp->lvaGetDesc(use.Def()->AsLclVarCommon())->IsAddressExposed(); + } + + if (canClone) + { + return comp->gtCloneExpr(use.Def()); + } + else + { + unsigned newLcl = use.ReplaceWithLclVar(comp); + return comp->gtNewLclvNode(newLcl, TYP_I_IMPL); + } + }; GenTree* callTarget = call->gtCallType == CT_INDIRECT ? call->gtCallAddr : call->gtControlExpr; + + if (call->IsVirtualStub()) + { + // VSDs go through a resolver instead which skips double validation and + // indirection. + CallArg* vsdCellArg = call->gtArgs.FindWellKnownArg(WellKnownArg::VirtualStubCell); + CallArg* thisArg = call->gtArgs.GetThisArg(); + + assert((vsdCellArg != nullptr) && (thisArg != nullptr)); + assert(thisArg->GetNode()->OperIs(GT_PUTARG_REG)); + LIR::Use thisArgUse(BlockRange(), &thisArg->GetNode()->AsOp()->gtOp1, thisArg->GetNode()); + GenTree* thisArgClone = cloneUse(thisArgUse, true); + + // The VSD cell is not needed for the original call when going through the resolver. + // It can be removed without further fixups because it has fixed ABI assignment. + call->gtArgs.RemoveLate(vsdCellArg); + assert(vsdCellArg->GetNode()->OperIs(GT_PUTARG_REG)); + // Also PUTARG_REG can be removed. + BlockRange().Remove(vsdCellArg->GetNode()); + // The actual cell we need for the resolver. + GenTree* vsdCellArgNode = vsdCellArg->GetNode()->gtGetOp1(); + + GenTreeCall* resolve = comp->gtNewHelperCallNode(CORINFO_HELP_INTERFACELOOKUP_FOR_SLOT, TYP_I_IMPL); + + // Use a placeholder for the cell since the cell is already inserted in + // LIR. + GenTree* vsdCellPlaceholder = comp->gtNewZeroConNode(TYP_I_IMPL); + resolve->gtArgs.PushFront(comp, + NewCallArg::Primitive(vsdCellPlaceholder).WellKnown(WellKnownArg::VirtualStubCell)); + + // 'this' arg clone is not inserted, so no need to use a placeholder for that. + resolve->gtArgs.PushFront(comp, NewCallArg::Primitive(thisArgClone)); + + comp->fgMorphTree(resolve); + + LIR::Range resolveRange = LIR::SeqTree(comp, resolve); + GenTree* resolveFirst = resolveRange.FirstNode(); + GenTree* resolveLast = resolveRange.LastNode(); + // Resolution comes with a null check, so it must happen after all + // arguments are evaluated, hence we insert it right before the call. + BlockRange().InsertBefore(call, std::move(resolveRange)); + + // Swap out the VSD cell argument. + LIR::Use vsdCellUse; + bool gotUse = BlockRange().TryGetUse(vsdCellPlaceholder, &vsdCellUse); + assert(gotUse); + vsdCellUse.ReplaceWith(vsdCellArgNode); + vsdCellPlaceholder->SetUnusedValue(); + + // Now we can lower the resolver. + LowerRange(resolveFirst, resolveLast); + + // That inserted new PUTARG nodes right before the call, so we need to + // legalize the existing call's PUTARG_REG nodes. + MovePutArgNodesUpToCall(call); + + // Finally update the call target + call->gtCallType = CT_INDIRECT; + call->gtFlags &= ~GTF_CALL_VIRT_STUB; + call->gtCallAddr = resolve; + call->gtCallCookie = nullptr; +#ifdef FEATURE_READYTORUN + call->gtEntryPoint.addr = nullptr; + call->gtEntryPoint.accessType = IAT_VALUE; +#endif + + if (callTarget != nullptr) + { + callTarget->SetUnusedValue(); + } + + callTarget = resolve; + } + if (callTarget == nullptr) { assert((call->gtCallType != CT_INDIRECT) && (!call->IsVirtual() || call->IsVirtualStubRelativeIndir())); @@ -3655,7 +3746,7 @@ void Lowering::LowerCFGCall(GenTreeCall* call) cloneConsts = true; #endif - GenTree* indirCellClone; + GenTree* indirCellClone = cloneUse(indirCellArgUse, cloneConsts); if (indirCellArgUse.Def()->OperIs(GT_LCL_VAR) || (cloneConsts && indirCellArgUse.Def()->IsCnsIntOrI())) { @@ -7352,7 +7443,7 @@ GenTree* Lowering::LowerVirtualStubCall(GenTreeCall* call) // fgMorphArgs will have created trees to pass the address in VirtualStubParam.reg. // All we have to do here is add an indirection to generate the actual call target. - GenTree* ind = Ind(call->gtCallAddr); + GenTree* ind = comp->gtNewIndir(TYP_I_IMPL, call->gtCallAddr, GTF_IND_NONFAULTING); BlockRange().InsertAfter(call->gtCallAddr, ind); call->gtCallAddr = ind; @@ -7394,7 +7485,7 @@ GenTree* Lowering::LowerVirtualStubCall(GenTreeCall* call) if (!shouldOptimizeVirtualStubCall) { - result = Ind(addr); + result = comp->gtNewIndir(TYP_I_IMPL, addr, GTF_IND_NONFAULTING); } } } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index c9d29a447aeca5..a607178ae22efd 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -1021,12 +1021,12 @@ void CallArgs::ArgsComplete(Compiler* comp, GenTreeCall* call) } } - // When CFG is enabled and this is a delegate call or vtable call we must + // When CFG is enabled and this is a delegate call or virtual call we must // compute the call target before all late args. However this will // effectively null-check 'this', which should happen only after all // arguments are evaluated. Thus we must evaluate all args with side // effects to a temp. - if (comp->opts.IsCFGEnabled() && (call->IsVirtualVtable() || call->IsDelegateInvoke())) + if (comp->opts.IsCFGEnabled() && (call->IsVirtual() || call->IsDelegateInvoke())) { // Always evaluate 'this' to temp. assert(HasThisPointer()); diff --git a/src/coreclr/jit/targetamd64.h b/src/coreclr/jit/targetamd64.h index a298b9c989fcdf..9b287dd6906605 100644 --- a/src/coreclr/jit/targetamd64.h +++ b/src/coreclr/jit/targetamd64.h @@ -540,6 +540,8 @@ // The registers trashed by the CORINFO_HELP_INIT_PINVOKE_FRAME helper. #define RBM_INIT_PINVOKE_FRAME_TRASH RBM_CALLEE_TRASH + #define RBM_INTERFACELOOKUP_FOR_SLOT_TRASH (RBM_RAX | RBM_R10 | RBM_R11) + #define RBM_VALIDATE_INDIRECT_CALL_TRASH (RBM_INT_CALLEE_TRASH & ~(RBM_R10 | RBM_RCX)) #define RBM_VALIDATE_INDIRECT_CALL_TRASH_ALL (RBM_INT_CALLEE_TRASH_ALL & ~(RBM_R10 | RBM_RCX)) #define REG_VALIDATE_INDIRECT_CALL_ADDR REG_RCX diff --git a/src/coreclr/jit/targetarm64.h b/src/coreclr/jit/targetarm64.h index c11844ddba5b18..ae25b459322922 100644 --- a/src/coreclr/jit/targetarm64.h +++ b/src/coreclr/jit/targetarm64.h @@ -263,6 +263,8 @@ // The registers trashed by the CORINFO_HELP_INIT_PINVOKE_FRAME helper. #define RBM_INIT_PINVOKE_FRAME_TRASH RBM_CALLEE_TRASH + #define RBM_INTERFACELOOKUP_FOR_SLOT_TRASH (REG_R0 | REG_R12 | REG_R13 | REG_R14 | REG_R15) + #define RBM_VALIDATE_INDIRECT_CALL_TRASH (RBM_INT_CALLEE_TRASH & ~(RBM_R0 | RBM_R1 | RBM_R2 | RBM_R3 | RBM_R4 | RBM_R5 | RBM_R6 | RBM_R7 | RBM_R8 | RBM_R15)) #define REG_VALIDATE_INDIRECT_CALL_ADDR REG_R15 #define REG_DISPATCH_INDIRECT_CALL_ADDR REG_R9 diff --git a/src/coreclr/nativeaot/Runtime/AsmOffsets.h b/src/coreclr/nativeaot/Runtime/AsmOffsets.h index e903dec5fefaaa..62156959948f2d 100644 --- a/src/coreclr/nativeaot/Runtime/AsmOffsets.h +++ b/src/coreclr/nativeaot/Runtime/AsmOffsets.h @@ -72,8 +72,10 @@ ASM_OFFSET( 4, 8, InterfaceDispatchCell, m_pCache) #ifdef INTERFACE_DISPATCH_CACHE_HAS_CELL_BACKPOINTER ASM_OFFSET( 8, 0, InterfaceDispatchCache, m_pCell) #endif +ASM_OFFSET( C, 18, InterfaceDispatchCache, m_cEntries) ASM_OFFSET( 10, 20, InterfaceDispatchCache, m_rgEntries) ASM_SIZEOF( 8, 10, InterfaceDispatchCacheEntry) +ASM_CONST( 3, 3, IDC_CACHE_POINTER_MASK) #endif // Undefine macros that are only used in this header for convenience. diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt index a700315c6d7ab6..b0c1bf540b3984 100644 --- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt +++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt @@ -241,6 +241,14 @@ if (CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_ARM64) ) endif () +if(CLR_CMAKE_TARGET_WIN32) + if (CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_ARM64) + list(APPEND RUNTIME_SOURCES_ARCH_ASM + ${ARCH_SOURCES_DIR}/DispatchResolve.${ASM_SUFFIX} + ) + endif() +endif() + # Add architecture specific folder for looking up headers. convert_to_absolute_path(ARCH_SOURCES_DIR ${ARCH_SOURCES_DIR}) include_directories(${ARCH_SOURCES_DIR}) diff --git a/src/coreclr/nativeaot/Runtime/EHHelpers.cpp b/src/coreclr/nativeaot/Runtime/EHHelpers.cpp index 19ad512805012c..6f8d0873282a15 100644 --- a/src/coreclr/nativeaot/Runtime/EHHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/EHHelpers.cpp @@ -276,6 +276,9 @@ static bool InWriteBarrierHelper(uintptr_t faultingIP) return false; } +#if (defined(HOST_AMD64) || defined(HOST_ARM64)) && defined(HOST_WINDOWS) +EXTERN_C CODE_LOCATION RhpResolveInterfaceMethodFast; +#endif EXTERN_C CODE_LOCATION RhpInitialInterfaceDispatch; EXTERN_C CODE_LOCATION RhpInterfaceDispatchAVLocation1; EXTERN_C CODE_LOCATION RhpInterfaceDispatchAVLocation2; @@ -290,6 +293,9 @@ static bool InInterfaceDispatchHelper(uintptr_t faultingIP) #ifndef FEATURE_PORTABLE_HELPERS static uintptr_t interfaceDispatchAVLocations[] = { +#if (defined(HOST_AMD64) || defined(HOST_ARM64)) && defined(HOST_WINDOWS) + (uintptr_t)&RhpResolveInterfaceMethodFast, +#endif (uintptr_t)&RhpInitialInterfaceDispatch, (uintptr_t)&RhpInterfaceDispatchAVLocation1, (uintptr_t)&RhpInterfaceDispatchAVLocation2, diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp index d921e3cc07c491..a7db02a03eeb82 100644 --- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp +++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp @@ -33,6 +33,9 @@ #if !defined(FEATURE_PORTABLE_HELPERS) // @TODO: these are (currently) only implemented in assembly helpers EXTERN_C CODE_LOCATION ReturnFromUniversalTransitionTailCall; +#if (defined(HOST_AMD64) || defined(HOST_ARM64)) && defined(HOST_WINDOWS) +EXTERN_C CODE_LOCATION ReturnFromUniversalTransitionReturnResult; +#endif EXTERN_C CODE_LOCATION RhpCallCatchFunclet2; EXTERN_C CODE_LOCATION RhpCallFinallyFunclet2; @@ -2231,6 +2234,12 @@ StackFrameIterator::ReturnAddressCategory StackFrameIterator::CategorizeUnadjust { return InUniversalTransitionThunk; } +#if (defined(HOST_AMD64) || defined(HOST_ARM64)) && defined(HOST_WINDOWS) + if (EQUALS_RETURN_ADDRESS(returnAddress, ReturnFromUniversalTransitionReturnResult)) + { + return InUniversalTransitionThunk; + } +#endif if (EQUALS_RETURN_ADDRESS(returnAddress, RhpThrowEx2) || EQUALS_RETURN_ADDRESS(returnAddress, RhpThrowHwEx2) || diff --git a/src/coreclr/nativeaot/Runtime/amd64/DispatchResolve.asm b/src/coreclr/nativeaot/Runtime/amd64/DispatchResolve.asm new file mode 100644 index 00000000000000..d311fc5bf0994d --- /dev/null +++ b/src/coreclr/nativeaot/Runtime/amd64/DispatchResolve.asm @@ -0,0 +1,63 @@ +;; Licensed to the .NET Foundation under one or more agreements. +;; The .NET Foundation licenses this file to you under the MIT license. + +include AsmMacros.inc + + +ifdef FEATURE_CACHED_INTERFACE_DISPATCH + +EXTERN RhpCidResolve : PROC +EXTERN RhpUniversalTransitionReturnResult : PROC + +;; Fast version of RhpResolveInterfaceMethod +LEAF_ENTRY RhpResolveInterfaceMethodFast, _TEXT + + ;; Load the MethodTable from the object instance in rcx. + ;; Trigger an AV if we're dispatching on a null this. + ;; The exception handling infrastructure is aware of the fact that this is the first + ;; instruction of RhpResolveInterfaceMethodFast and uses it to translate an AV here + ;; to a NullReferenceException at the callsite. + mov rax, [rcx] + + ;; r11 currently contains the indirection cell address. + ;; load r10 to point to the cache block. + mov r10, [r11 + OFFSETOF__InterfaceDispatchCell__m_pCache] + test r10b, IDC_CACHE_POINTER_MASK + jne RhpResolveInterfaceMethodFast_SlowPath + + lea r10, [r10 + OFFSETOF__InterfaceDispatchCache__m_rgEntries] + cmp qword ptr [r10], rax + jne RhpResolveInterfaceMethodFast_Polymorphic + mov rax, qword ptr [r10 + 8] + ret + + RhpResolveInterfaceMethodFast_Polymorphic: + ;; load the count of cache entries into edx + ;; r11 points to the first cache entry so to get to m_cEntries, we need to subtract m_rgEntries first + push rdx + mov edx, dword ptr [r10 - OFFSETOF__InterfaceDispatchCache__m_rgEntries + OFFSETOF__InterfaceDispatchCache__m_cEntries] + + RhpResolveInterfaceMethodFast_NextEntry: + add r10, SIZEOF__InterfaceDispatchCacheEntry + dec edx + jz RhpResolveInterfaceMethodFast_SlowPath_Pop + + cmp qword ptr [r10], rax + jne RhpResolveInterfaceMethodFast_NextEntry + + mov rax, qword ptr [r10 + 8] + pop rdx + ret + + RhpResolveInterfaceMethodFast_SlowPath_Pop: + pop rdx + RhpResolveInterfaceMethodFast_SlowPath: + ;; r11 contains indirection cell address + lea r10, RhpCidResolve + jmp RhpUniversalTransitionReturnResult + +LEAF_END RhpResolveInterfaceMethodFast, _TEXT + +endif ;; FEATURE_CACHED_INTERFACE_DISPATCH + +end diff --git a/src/coreclr/nativeaot/Runtime/amd64/UniversalTransition.asm b/src/coreclr/nativeaot/Runtime/amd64/UniversalTransition.asm index e548a84ca86dea..f70a0c6f65708e 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/UniversalTransition.asm +++ b/src/coreclr/nativeaot/Runtime/amd64/UniversalTransition.asm @@ -82,7 +82,7 @@ DISTANCE_FROM_CHILDSP_TO_CALLERSP equ DISTANCE_FROM_CHILDSP_TO_RET ; everything between the base of the ReturnBlock and the top of the StackPassedArgs. ; -UNIVERSAL_TRANSITION macro FunctionName +UNIVERSAL_TRANSITION macro FunctionName, ExitSequence NESTED_ENTRY Rhp&FunctionName, _TEXT @@ -144,12 +144,13 @@ ALTERNATE_ENTRY ReturnFrom&FunctionName ; Pop the space that was allocated between the ChildSP and the caller return address. add rsp, DISTANCE_FROM_CHILDSP_TO_RETADDR - TAILJMP_RAX + ExitSequence NESTED_END Rhp&FunctionName, _TEXT endm - UNIVERSAL_TRANSITION UniversalTransitionTailCall + UNIVERSAL_TRANSITION UniversalTransitionTailCall, TAILJMP_RAX + UNIVERSAL_TRANSITION UniversalTransitionReturnResult, ret end diff --git a/src/coreclr/nativeaot/Runtime/arm64/DispatchResolve.asm b/src/coreclr/nativeaot/Runtime/arm64/DispatchResolve.asm new file mode 100644 index 00000000000000..338bba7eeceac1 --- /dev/null +++ b/src/coreclr/nativeaot/Runtime/arm64/DispatchResolve.asm @@ -0,0 +1,60 @@ +;; Licensed to the .NET Foundation under one or more agreements. +;; The .NET Foundation licenses this file to you under the MIT license. + +#include "AsmMacros.h" + + TEXTAREA + +#ifdef FEATURE_CACHED_INTERFACE_DISPATCH + + EXTERN RhpCidResolve + EXTERN RhpUniversalTransitionReturnResult + + NESTED_ENTRY RhpResolveInterfaceMethodFast, _TEXT + + ;; Load the MethodTable from the object instance in x0. + ;; Trigger an AV if we're dispatching on a null this. + ;; The exception handling infrastructure is aware of the fact that this is the first + ;; instruction of RhpResolveInterfaceMethodFast and uses it to translate an AV here + ;; to a NullReferenceException at the callsite. + ldr x12, [x0] + + ;; x11 currently contains the indirection cell address. + ;; load x13 to point to the cache block. + ldr x13, [x11, #OFFSETOF__InterfaceDispatchCell__m_pCache] + and x14, x13, #IDC_CACHE_POINTER_MASK + cbnz x14, RhpResolveInterfaceMethodFast_SlowPath + + add x14, x13, #OFFSETOF__InterfaceDispatchCache__m_rgEntries + ldr x15, [x14] + cmp x15, x12 + bne RhpResolveInterfaceMethodFast_Polymorphic + ldur x0, [x14, #8] + ret + +RhpResolveInterfaceMethodFast_Polymorphic + ldr w13, [x13, #OFFSETOF__InterfaceDispatchCache__m_cEntries] + +RhpResolveInterfaceMethodFast_NextEntry + add x14, x14, #SIZEOF__InterfaceDispatchCacheEntry + sub w13, w13, #1 + cmp w13, #0 + beq RhpResolveInterfaceMethodFast_SlowPath + + ldr x15, [x14] + cmp x15, x12 + bne RhpResolveInterfaceMethodFast_NextEntry + + ldur x0, [x14, #8] + ret + +RhpResolveInterfaceMethodFast_SlowPath + ldr xip0, =RhpCidResolve + mov xip1, x11 + b RhpUniversalTransitionReturnResult + + NESTED_END RhpResolveInterfaceMethodFast + +#endif // FEATURE_CACHED_INTERFACE_DISPATCH + + END diff --git a/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm b/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm index 65974a87af1b2b..994c8e0c8c8f78 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm +++ b/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm @@ -89,7 +89,7 @@ TEXTAREA MACRO - UNIVERSAL_TRANSITION $FunctionName + UNIVERSAL_TRANSITION $FunctionName, $ReturnResult NESTED_ENTRY Rhp$FunctionName @@ -142,13 +142,20 @@ ;; Restore FP and LR registers, and free the allocated stack block EPILOG_RESTORE_REG_PAIR fp, lr, #STACK_SIZE! + IF $ReturnResult == 0 ;; Tailcall to the target address. EPILOG_NOP br x12 + ELSE + ;; Return target address + EPILOG_NOP mov x0, x12 + ret + ENDIF NESTED_END Rhp$FunctionName MEND - UNIVERSAL_TRANSITION UniversalTransitionTailCall + UNIVERSAL_TRANSITION UniversalTransitionTailCall, 0 + UNIVERSAL_TRANSITION UniversalTransitionReturnResult, 1 END diff --git a/src/coreclr/nativeaot/Runtime/inc/rhbinder.h b/src/coreclr/nativeaot/Runtime/inc/rhbinder.h index 963942ce6d846b..12f6565d06982c 100644 --- a/src/coreclr/nativeaot/Runtime/inc/rhbinder.h +++ b/src/coreclr/nativeaot/Runtime/inc/rhbinder.h @@ -241,6 +241,8 @@ struct InterfaceDispatchCell } }; +#define IDC_CACHE_POINTER_MASK (InterfaceDispatchCell::Flags::IDC_CachePointerMask) + #endif // FEATURE_CACHED_INTERFACE_DISPATCH #ifdef TARGET_ARM diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs index 55faa2e79b64de..87c743f1680b76 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs @@ -264,6 +264,7 @@ which is the right helper to use to allocate an object of a given type. */ CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT_TRACK_TRANSITIONS, // Transition to preemptive mode and track transitions in reverse P/Invoke prolog. CORINFO_HELP_GVMLOOKUP_FOR_SLOT, // Resolve a generic virtual method target from this pointer and runtime method handle + CORINFO_HELP_INTERFACELOOKUP_FOR_SLOT, // Resolve a non-generic interface method from this pointer and dispatch cell CORINFO_HELP_STACK_PROBE, // Probes each page of the allocated stack frame diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 5186f812e6d93f..43e116ade2c554 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -772,6 +772,8 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum) case CorInfoHelpFunc.CORINFO_HELP_GVMLOOKUP_FOR_SLOT: id = ReadyToRunHelper.GVMLookupForSlot; break; + case CorInfoHelpFunc.CORINFO_HELP_INTERFACELOOKUP_FOR_SLOT: + return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpResolveInterfaceMethodFast"u8)); case CorInfoHelpFunc.CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE_MAYBENULL: id = ReadyToRunHelper.TypeHandleToRuntimeType; diff --git a/src/coreclr/vm/FrameTypes.h b/src/coreclr/vm/FrameTypes.h index 83a4e0aa6de455..1588290d9064cc 100644 --- a/src/coreclr/vm/FrameTypes.h +++ b/src/coreclr/vm/FrameTypes.h @@ -30,6 +30,9 @@ FRAME_TYPE_NAME(HijackFrame) FRAME_TYPE_NAME(PrestubMethodFrame) FRAME_TYPE_NAME(CallCountingHelperFrame) FRAME_TYPE_NAME(StubDispatchFrame) +#ifdef FEATURE_RESOLVE_HELPER_DISPATCH +FRAME_TYPE_NAME(ResolveHelperFrame) +#endif // FEATURE_RESOLVE_HELPER_DISPATCH FRAME_TYPE_NAME(ExternalMethodFrame) FRAME_TYPE_NAME(DynamicHelperFrame) FRAME_TYPE_NAME(ProtectValueClassFrame) diff --git a/src/coreclr/vm/amd64/AsmHelpers.asm b/src/coreclr/vm/amd64/AsmHelpers.asm index 83e53a633ded54..125143377f0ecf 100644 --- a/src/coreclr/vm/amd64/AsmHelpers.asm +++ b/src/coreclr/vm/amd64/AsmHelpers.asm @@ -405,7 +405,6 @@ NESTED_ENTRY JIT_Patchpoint, _TEXT call JIT_PatchpointWorkerWorkerWithPolicy EPILOG_WITH_TRANSITION_BLOCK_RETURN - TAILJMP_RAX NESTED_END JIT_Patchpoint, _TEXT ; first arg register holds iloffset, which needs to be moved to the second register, and the first register filled with NULL diff --git a/src/coreclr/vm/amd64/VirtualCallStubAMD64.asm b/src/coreclr/vm/amd64/VirtualCallStubAMD64.asm index 6170adf6d4f20b..249c5dd91f5539 100644 --- a/src/coreclr/vm/amd64/VirtualCallStubAMD64.asm +++ b/src/coreclr/vm/amd64/VirtualCallStubAMD64.asm @@ -9,6 +9,7 @@ ifdef FEATURE_VIRTUAL_STUB_DISPATCH CHAIN_SUCCESS_COUNTER equ g_dispatch_cache_chain_success_counter extern VSD_ResolveWorker:proc + extern VSD_ResolveWorkerForInterfaceLookupSlot:proc extern CHAIN_SUCCESS_COUNTER:dword BACKPATCH_FLAG equ 1 ;; Also known as SDF_ResolveBackPatch in the EE @@ -85,5 +86,25 @@ Fail: LEAF_END ResolveWorkerChainLookupAsmStub, _TEXT +;; On Input: +;; rcx contains object 'this' pointer +;; r11 contains the address of the indirection cell (with the flags in the low bits) +;; +;; Preserves all argument registers +NESTED_ENTRY JIT_InterfaceLookupForSlot, _TEXT + + PROLOG_WITH_TRANSITION_BLOCK + + lea rcx, [rsp + __PWTB_TransitionBlock] ; pTransitionBlock + mov rdx, r11 ; indirection cell + + call VSD_ResolveWorkerForInterfaceLookupSlot + + RESTORE_FLOAT_ARGUMENT_REGISTERS __PWTB_FloatArgumentRegisters + RESTORE_ARGUMENT_REGISTERS __PWTB_ArgumentRegisters + EPILOG_WITH_TRANSITION_BLOCK_RETURN + +NESTED_END JIT_InterfaceLookupForSlot, _TEXT + endif ;; FEATURE_VIRTUAL_STUB_DISPATCH end diff --git a/src/coreclr/vm/amd64/cgenamd64.cpp b/src/coreclr/vm/amd64/cgenamd64.cpp index aed4226fcbf6d7..977c97ed77b808 100644 --- a/src/coreclr/vm/amd64/cgenamd64.cpp +++ b/src/coreclr/vm/amd64/cgenamd64.cpp @@ -43,6 +43,25 @@ void UpdateRegDisplayFromCalleeSavedRegisters(REGDISPLAY * pRD, CalleeSavedRegis #undef CALLEE_SAVED_REGISTER } +#ifdef TARGET_WINDOWS +void UpdateRegDisplayFromArgumentRegisters(REGDISPLAY * pRD, ArgumentRegisters* pRegs) +{ + LIMITED_METHOD_CONTRACT; + + T_CONTEXT * pContext = pRD->pCurrentContext; + pContext->Rcx = pRegs->RCX; + pContext->Rdx = pRegs->RDX; + pContext->R8 = pRegs->R8; + pContext->R9 = pRegs->R9; + + KNONVOLATILE_CONTEXT_POINTERS * pContextPointers = pRD->pCurrentContextPointers; + pContextPointers->Rcx = (PULONG64)&pRegs->RCX; + pContextPointers->Rdx = (PULONG64)&pRegs->RDX; + pContextPointers->R8 = (PULONG64)&pRegs->R8; + pContextPointers->R9 = (PULONG64)&pRegs->R9; +} +#endif + void ClearRegDisplayArgumentAndScratchRegisters(REGDISPLAY * pRD) { LIMITED_METHOD_CONTRACT; @@ -88,6 +107,37 @@ void TransitionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFl LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK TransitionFrame::UpdateRegDisplay_Impl(rip:%p, rsp:%p)\n", pRD->ControlPC, pRD->SP)); } +#ifdef FEATURE_RESOLVE_HELPER_DISPATCH +void ResolveHelperFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats) +{ + LIMITED_METHOD_CONTRACT; + +#ifndef DACCESS_COMPILE + if (updateFloats) + { + UpdateFloatingPointRegisters(pRD, GetSP()); + _ASSERTE(pRD->pCurrentContext->Rip == GetReturnAddress()); + _ASSERTE(pRD->pCurrentContext->Rsp == GetSP()); + } +#endif // DACCESS_COMPILE + + pRD->IsCallerContextValid = FALSE; + pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. + + pRD->pCurrentContext->Rip = GetReturnAddress(); + pRD->pCurrentContext->Rsp = GetSP(); + + UpdateRegDisplayFromCalleeSavedRegisters(pRD, GetCalleeSavedRegisters()); + ClearRegDisplayArgumentAndScratchRegisters(pRD); + + UpdateRegDisplayFromArgumentRegisters(pRD, GetArgumentRegisters()); + + SyncRegDisplayToCurrentContext(pRD); + + LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK ResolveHelperFrame::UpdateRegDisplay_Impl(rip:%p, rsp:%p)\n", pRD->ControlPC, pRD->SP)); +} +#endif // FEATURE_RESOLVE_HELPER_DISPATCH + void InlinedCallFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats) { CONTRACTL diff --git a/src/coreclr/vm/arm/virtualcallstubcpu.hpp b/src/coreclr/vm/arm/virtualcallstubcpu.hpp index 49023d7f84cd31..ede098dde691bf 100644 --- a/src/coreclr/vm/arm/virtualcallstubcpu.hpp +++ b/src/coreclr/vm/arm/virtualcallstubcpu.hpp @@ -366,8 +366,6 @@ extern size_t g_poly_call_counter; extern size_t g_poly_miss_counter; #endif -TADDR StubDispatchFrame_MethodFrameVPtr; - LookupHolder* LookupHolder::FromLookupEntry(PCODE lookupEntry) { lookupEntry = lookupEntry & ~THUMB_CODE; diff --git a/src/coreclr/vm/arm64/asmhelpers.asm b/src/coreclr/vm/arm64/asmhelpers.asm index 281ca3bd0e85bc..6245881ee0cef1 100644 --- a/src/coreclr/vm/arm64/asmhelpers.asm +++ b/src/coreclr/vm/arm64/asmhelpers.asm @@ -10,6 +10,7 @@ IMPORT PInvokeImportWorker #ifdef FEATURE_VIRTUAL_STUB_DISPATCH IMPORT VSD_ResolveWorker + IMPORT VSD_ResolveWorkerForInterfaceLookupSlot #endif IMPORT ComPreStubWorker IMPORT COMToCLRWorker @@ -760,6 +761,34 @@ Fail EPILOG_BRANCH_REG x9 NESTED_END + + +;; On Input: +;; x0 contains object 'this' pointer +;; x11 contains the address of the indirection cell (with the flags in the low bits) +;; +;; Preserves all argument registers + NESTED_ENTRY JIT_InterfaceLookupForSlot + + PROLOG_WITH_TRANSITION_BLOCK + + add x0, sp, #__PWTB_TransitionBlock ; pTransitionBlock + mov x1, x11 ; indirection cell + + bl VSD_ResolveWorkerForInterfaceLookupSlot + + ;; Move the result (the target address) to x12 so it doesn't get overridden when we restore the + ;; argument registers. + mov x12, x0 + + EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + + EPILOG_NOP mov x0, x12 + + EPILOG_RETURN + + NESTED_END + #endif // FEATURE_VIRTUAL_STUB_DISPATCH #ifdef FEATURE_READYTORUN diff --git a/src/coreclr/vm/arm64/stubs.cpp b/src/coreclr/vm/arm64/stubs.cpp index a14d52045f34ee..ec6377012cef5a 100644 --- a/src/coreclr/vm/arm64/stubs.cpp +++ b/src/coreclr/vm/arm64/stubs.cpp @@ -193,6 +193,25 @@ void UpdateRegDisplayFromCalleeSavedRegisters(REGDISPLAY * pRD, CalleeSavedRegis pContextPointers->Lr = (PDWORD64)&pCalleeSaved->x30; } +void UpdateRegDisplayFromArgumentRegisters(REGDISPLAY * pRD, ArgumentRegisters* pRegs) +{ + LIMITED_METHOD_CONTRACT; + + T_CONTEXT * pContext = pRD->pCurrentContext; + for (int i = 0; i < 8; i++) + { + pContext->X[i] = pRegs->x[i]; + } + pContext->X[8] = *(&pRegs->x[0] - 1); // m_x8RetBuffReg + + Arm64VolatileContextPointer * pContextPointers = &pRD->volatileCurrContextPointers; + for (int i = 0; i < 8; i++) + { + pContextPointers->X[i] = (PDWORD64)&pRegs->x[i]; + } + pContextPointers->X[8] = (PDWORD64)(&pRegs->x[0] - 1); // m_x8RetBuffReg +} + void TransitionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats) { #ifndef DACCESS_COMPILE @@ -224,6 +243,41 @@ void TransitionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFl LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK TransitionFrame::UpdateRegDisplay_Impl(pc:%p, sp:%p)\n", pRD->ControlPC, pRD->SP)); } +#ifdef FEATURE_RESOLVE_HELPER_DISPATCH +void ResolveHelperFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats) +{ +#ifndef DACCESS_COMPILE + if (updateFloats) + { + UpdateFloatingPointRegisters(pRD, GetSP()); + _ASSERTE(pRD->pCurrentContext->Pc == GetReturnAddress()); + } +#endif // DACCESS_COMPILE + + pRD->IsCallerContextValid = FALSE; + pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. + + // copy the callee saved regs + CalleeSavedRegisters *pCalleeSaved = GetCalleeSavedRegisters(); + UpdateRegDisplayFromCalleeSavedRegisters(pRD, pCalleeSaved); + + ClearRegDisplayArgumentAndScratchRegisters(pRD); + + // copy the control registers + pRD->pCurrentContext->Fp = pCalleeSaved->x29; + pRD->pCurrentContext->Lr = pCalleeSaved->x30; + pRD->pCurrentContext->Pc = GetReturnAddress(); + pRD->pCurrentContext->Sp = this->GetSP(); + + UpdateRegDisplayFromArgumentRegisters(pRD, GetArgumentRegisters()); + + // Finally, syncup the regdisplay with the context + SyncRegDisplayToCurrentContext(pRD); + + LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK ResolveHelperFrame::UpdateRegDisplay_Impl(pc:%p, sp:%p)\n", pRD->ControlPC, pRD->SP)); +} +#endif // FEATURE_RESOLVE_HELPER_DISPATCH + void FaultingExceptionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats) { LIMITED_METHOD_DAC_CONTRACT; diff --git a/src/coreclr/vm/contractimpl.h b/src/coreclr/vm/contractimpl.h index 5b3a1f2ee53e8b..6ec975c9a80925 100644 --- a/src/coreclr/vm/contractimpl.h +++ b/src/coreclr/vm/contractimpl.h @@ -299,7 +299,6 @@ struct DispatchToken explicit DispatchToken(UINT_PTR token) { - CONSISTENCY_CHECK(token != INVALID_TOKEN); m_token = token; } diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 5491e238918411..e6a732b84caaf4 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -54,6 +54,12 @@ // | | to either a EE runtime helper function or // | | a framed method. // | | +#ifdef FEATURE_RESOLVE_HELPER_DISPATCH +// | | +// | + ResolveHelperFrame - represents a call to interface resolve helper +// | | +#endif // FEATURE_RESOLVE_HELPER_DISPATCH +// | | // | +-FramedMethodFrame - this abstract frame represents a call to a method // | | that generates a full-fledged frame. // | | @@ -894,6 +900,43 @@ class TransitionFrame : public Frame #endif }; +#ifdef FEATURE_RESOLVE_HELPER_DISPATCH + +typedef DPTR(class ResolveHelperFrame) PTR_ResolveHelperFrame; + +// Represents a call to interface resolve helper +// +// This frame saves all argument registers and leaves GC reporting them to the callsite. +// +class ResolveHelperFrame : public TransitionFrame +{ + TADDR m_pTransitionBlock; + +public: +#ifndef DACCESS_COMPILE + ResolveHelperFrame(TransitionBlock* pTransitionBlock) + : TransitionFrame(FrameIdentifier::ResolveHelperFrame), m_pTransitionBlock(dac_cast(pTransitionBlock)) + { + } +#endif + + TADDR GetTransitionBlock_Impl() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pTransitionBlock; + } + + unsigned GetFrameAttribs_Impl() + { + LIMITED_METHOD_DAC_CONTRACT; + return FRAME_ATTR_RESUMABLE; // Treat the next frame as the top frame. + } + + void UpdateRegDisplay_Impl(const PREGDISPLAY, bool updateFloats = false); +}; + +#endif // FEATURE_RESOLVE_HELPER_DISPATCH + //----------------------------------------------------------------------- // TransitionFrames for exceptions //----------------------------------------------------------------------- diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 9edf5b97eb33a1..c44bad435d110f 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -2475,10 +2475,12 @@ HCIMPL1_RAW(void, JIT_ReversePInvokeExit, ReversePInvokeFrame* frame) } HCIMPLEND_RAW -// These two do take args but have a custom calling convention. +// These do take args but have a custom calling convention. EXTERN_C void JIT_ValidateIndirectCall(); EXTERN_C void JIT_DispatchIndirectCall(); +EXTERN_C void JIT_InterfaceLookupForSlot(); + //======================================================================== // // JIT HELPERS INITIALIZATION diff --git a/src/coreclr/vm/virtualcallstub.cpp b/src/coreclr/vm/virtualcallstub.cpp index 5ddf8e6438403e..c86bdc4a79c8cf 100644 --- a/src/coreclr/vm/virtualcallstub.cpp +++ b/src/coreclr/vm/virtualcallstub.cpp @@ -1697,6 +1697,89 @@ PCODE VSD_ResolveWorker(TransitionBlock * pTransitionBlock, return target; } +#ifdef FEATURE_RESOLVE_HELPER_DISPATCH +PCODE VSD_ResolveWorkerForInterfaceLookupSlot(TransitionBlock * pTransitionBlock, TADDR siteAddrForRegisterIndirect) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + INJECT_FAULT(COMPlusThrowOM();); + PRECONDITION(CheckPointer(pTransitionBlock)); + MODE_COOPERATIVE; + } CONTRACTL_END; + + MAKE_CURRENT_THREAD_AVAILABLE(); + +#ifdef _DEBUG + Thread::ObjectRefFlush(CURRENT_THREAD); +#endif + + ResolveHelperFrame frame(pTransitionBlock); + ResolveHelperFrame * pSDFrame = &frame; + + StubCallSite callSite(siteAddrForRegisterIndirect, pTransitionBlock->m_ReturnAddress); + + OBJECTREF *protectedObj = pSDFrame->GetThisPtr(); + _ASSERTE(protectedObj != NULL); + OBJECTREF pObj = *protectedObj; + + PCODE target = (PCODE)NULL; + + bool propagateExceptionToNativeCode = IsCallDescrWorkerInternalReturnAddress(pTransitionBlock->m_ReturnAddress); + + if (pObj == NULL) { + pSDFrame->Push(CURRENT_THREAD); + INSTALL_MANAGED_EXCEPTION_DISPATCHER_EX; + INSTALL_UNWIND_AND_CONTINUE_HANDLER_EX; + COMPlusThrow(kNullReferenceException); + UNINSTALL_UNWIND_AND_CONTINUE_HANDLER_EX(propagateExceptionToNativeCode); + UNINSTALL_MANAGED_EXCEPTION_DISPATCHER_EX(propagateExceptionToNativeCode); + _ASSERTE(!"Throw returned"); + } + + DispatchToken representativeToken = DispatchToken(VirtualCallStubManager::GetTokenFromStub(callSite.GetSiteTarget(), NULL)); + + MethodTable * pRepresentativeMT = pObj->GetMethodTable(); + if (representativeToken.IsTypedToken()) + { + pRepresentativeMT = AppDomain::GetCurrentDomain()->LookupType(representativeToken.GetTypeID()); + CONSISTENCY_CHECK(CheckPointer(pRepresentativeMT)); + } + + pSDFrame->Push(CURRENT_THREAD); + + INSTALL_MANAGED_EXCEPTION_DISPATCHER_EX; + INSTALL_UNWIND_AND_CONTINUE_HANDLER_EX; + + GCPROTECT_BEGIN(pObj); + + // For Virtual Delegates the m_siteAddr is a field of a managed object + // Thus we have to report it as an interior pointer, + // so that it is updated during a gc + GCPROTECT_BEGININTERIOR( *(callSite.GetIndirectCellAddress()) ); + + GCStress::MaybeTrigger(); + + PCODE callSiteTarget = callSite.GetSiteTarget(); + CONSISTENCY_CHECK(callSiteTarget != (PCODE)NULL); + + StubCodeBlockKind stubKind = STUB_CODE_BLOCK_UNKNOWN; + VirtualCallStubManager *pMgr = VirtualCallStubManager::FindStubManager(callSiteTarget, &stubKind); + _ASSERTE(pMgr != NULL); + + target = pMgr->ResolveWorker(&callSite, &pObj, representativeToken, stubKind); + + GCPROTECT_END(); + GCPROTECT_END(); + + UNINSTALL_UNWIND_AND_CONTINUE_HANDLER_EX(propagateExceptionToNativeCode); + UNINSTALL_MANAGED_EXCEPTION_DISPATCHER_EX(propagateExceptionToNativeCode); + pSDFrame->Pop(CURRENT_THREAD); + + return target; +} +#endif // FEATURE_RESOLVE_HELPER_DISPATCH + void VirtualCallStubManager::BackPatchWorkerStatic(PCODE returnAddress, TADDR siteAddrForRegisterIndirect) { CONTRACTL { diff --git a/src/coreclr/vm/virtualcallstub.h b/src/coreclr/vm/virtualcallstub.h index bfa3280f4597a7..243fb311ed1278 100644 --- a/src/coreclr/vm/virtualcallstub.h +++ b/src/coreclr/vm/virtualcallstub.h @@ -44,6 +44,7 @@ extern "C" PCODE STDCALL VSD_ResolveWorker(TransitionBlock * pTransitionBlock, #endif ); +extern "C" PCODE STDCALL VSD_ResolveWorkerForInterfaceLookupSlot(TransitionBlock * pTransitionBlock, TADDR siteAddrForRegisterIndirect); ///////////////////////////////////////////////////////////////////////////////////// #if defined(TARGET_X86) || defined(TARGET_AMD64) diff --git a/src/tests/nativeaot/SmokeTests/ControlFlowGuard/ControlFlowGuard.cs b/src/tests/nativeaot/SmokeTests/ControlFlowGuard/ControlFlowGuard.cs index 5e9189ac90a572..28c4a72e73a23f 100644 --- a/src/tests/nativeaot/SmokeTests/ControlFlowGuard/ControlFlowGuard.cs +++ b/src/tests/nativeaot/SmokeTests/ControlFlowGuard/ControlFlowGuard.cs @@ -35,6 +35,9 @@ static int Main(string[] args) // Are we running the control program? if (args.Length == 0) { + TestExceptionFromDispatch.Run(); + TestInterfaceDispatch.Run(); + // Dry run - execute all scenarios while s_armed is false. // // The replaced call target will not be considered invalid by CFG and none of this @@ -84,6 +87,120 @@ static int Main(string[] args) return 10; } + class TestExceptionFromDispatch + { + class CastableObject : IDynamicInterfaceCastable + { + public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType) => throw new Exception(); + public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented) => true; + } + + public static void Run() + { + bool caughtException = false; + + IDisposable obj = (IDisposable)new CastableObject(); + try + { + obj.Dispose(); + } + catch (Exception) + { + caughtException = true; + } + + if (!caughtException) + throw new Exception(); + } + } + + internal class TestInterfaceDispatch + { + interface IFoo + { + int Call(int x, int y); + } + + interface IFoo + { + int Call(int x, int y); + } + + class C1 : IFoo, IFoo + { + public int Call(int x, int y) => x + y; + } + + class C2 : IFoo, IFoo + { + public int Call(int x, int y) => x - y; + } + class C3 : IFoo, IFoo + { + public int Call(int x, int y) => x * y; + } + + class C4 : IFoo, IFoo + { + public int Call(int x, int y) => x / y; + } + + class C5 : IFoo, IFoo + { + public int Call(int x, int y) => x % y; + } + + public static void Run() + { + if (Call(new C1(), 10, 20) != (10 + 20)) + throw new Exception(); + if (Call(new C1(), 11, 22) != (11 + 22)) + throw new Exception(); + if (Call(new C2(), 10, 20) != (10 - 20)) + throw new Exception(); + if (Call(new C2(), 11, 22) != (11 - 22)) + throw new Exception(); + if (Call(new C3(), 10, 20) != (10 * 20)) + throw new Exception(); + if (Call(new C3(), 11, 22) != (11 * 22)) + throw new Exception(); + if (Call(new C4(), 10, 20) != (10 / 20)) + throw new Exception(); + if (Call(new C5(), 10, 20) != (10 % 20)) + throw new Exception(); + + if (CallGen(new C1(), 10, 20) != (10 + 20)) + throw new Exception(); + if (CallGen(new C2(), 11, 22) != (11 - 22)) + throw new Exception(); + if (CallGen(new C3(), 11, 22) != (11 * 22)) + throw new Exception(); + if (CallGen(new C4(), 10, 20) != (10 / 20)) + throw new Exception(); + if (CallGen(new C5(), 10, 20) != (10 % 20)) + throw new Exception(); + + bool caught = false; + try + { + Call(null, 10, 20); + } + catch (NullReferenceException) + { + caught = true; + } + + if (!caught) + throw new Exception(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Call(IFoo f, int x, int y) => f.Call(x, y); + + [MethodImpl(MethodImplOptions.NoInlining)] + static int CallGen(IFoo f, int x, int y) => f.Call(x, y); + } + class TestFunctionPointer { public static int Run()