From eb54507f9df73a593b42ab2b6439481ca545946c Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 22 Jan 2026 23:55:05 +0000 Subject: [PATCH 1/2] [clr-interp] Handle unmanaged thiscall on Windows Arm64 Windows Arm64 has a peculiar calling convention for C++ member functions. Notably, the return buffer is passed as the second parameter to the method (not using the normal X8 return buffer argument), and like other Windows platforms, the return buffer is needed for more forms of aggregates. --- src/coreclr/vm/arm64/asmhelpers.asm | 33 ++++++++++++++++++ src/coreclr/vm/callingconvention.h | 45 +++++++++++++++++++++---- src/coreclr/vm/callstubgenerator.cpp | 42 ++++++++++++++++------- src/coreclr/vm/callstubgenerator.h | 3 ++ src/coreclr/vm/reflectioninvocation.cpp | 5 +++ 5 files changed, 110 insertions(+), 18 deletions(-) diff --git a/src/coreclr/vm/arm64/asmhelpers.asm b/src/coreclr/vm/arm64/asmhelpers.asm index c919004d76e746..b2974be836bde5 100644 --- a/src/coreclr/vm/arm64/asmhelpers.asm +++ b/src/coreclr/vm/arm64/asmhelpers.asm @@ -1176,6 +1176,18 @@ HaveInterpThreadContext EPILOG_RETURN NESTED_END InterpreterStubRetBuff + NESTED_ENTRY InterpreterStubRetBuffX1 + PROLOG_SAVE_REG_PAIR fp, lr, #-16! + ; The +16 is for the fp, lr above + add x0, sp, #__PWTB_TransitionBlock + 16 + mov x1, x19 ; the IR bytecode pointer + ; Load the return buffer address + mov x2, x1 + bl ExecuteInterpretedMethod + EPILOG_RESTORE_REG_PAIR fp, lr, #16! + EPILOG_RETURN + NESTED_END InterpreterStubRetBuffX1 + NESTED_ENTRY InterpreterStubRet2I8 PROLOG_SAVE_REG_PAIR fp, lr, #-16! ; The +16 is for the fp, lr above @@ -2599,6 +2611,27 @@ CopyLoop EPILOG_RETURN NESTED_END CallJittedMethodRetBuff + ; X0 - routines array + ; X1 - interpreter stack args location + ; X2 - interpreter stack return value location + ; X3 - stack arguments size (properly aligned) + ; X4 - address of continuation return value + NESTED_ENTRY CallJittedMethodRetBuffX1 + PROLOG_SAVE_REG_PAIR fp, lr, #-32! + str x4, [fp, #16] + sub sp, sp, x3 + mov x10, x0 + mov x9, x1 + mov x1, x2 + ldr x11, [x10], #8 + blr x11 + ldr x4, [fp, #16] + str x2, [x4] + EPILOG_STACK_RESTORE + EPILOG_RESTORE_REG_PAIR fp, lr, #32! + EPILOG_RETURN + NESTED_END CallJittedMethodRetBuffX1 + ; X0 - routines array ; X1 - interpreter stack args location ; X2 - interpreter stack return value location diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index 2100e180403f0c..2a609a7ad4e604 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -1091,7 +1091,15 @@ int ArgIteratorTemplate::GetRetBuffArgOffset() // x86 is special as always ret += this->HasThis() ? offsetof(ArgumentRegisters, EDX) : offsetof(ArgumentRegisters, ECX); #elif TARGET_ARM64 - ret = TransitionBlock::GetOffsetOfRetBuffArgReg(); + if (this->IsRetBuffPassedAsFirstArg()) + { + if (this->HasThis()) + ret += TARGET_REGISTER_SIZE; + } + else + { + ret = TransitionBlock::GetOffsetOfRetBuffArgReg(); + } #else if (this->HasThis()) ret += TARGET_REGISTER_SIZE; @@ -1119,7 +1127,7 @@ int ArgIteratorTemplate::GetVASigCookieOffset() ret += TARGET_REGISTER_SIZE; } - if (this->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) + if (this->HasRetBuffArg() && this->IsRetBuffPassedAsFirstArg()) { ret += TARGET_REGISTER_SIZE; } @@ -1172,7 +1180,7 @@ int ArgIteratorTemplate::GetParamTypeArgOffset() ret += TARGET_REGISTER_SIZE; } - if (this->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) + if (this->HasRetBuffArg() && this->IsRetBuffPassedAsFirstArg()) { ret += TARGET_REGISTER_SIZE; } @@ -1229,7 +1237,7 @@ int ArgIteratorTemplate::GetAsyncContinuationArgOffset() ret += TARGET_REGISTER_SIZE; } - if (this->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) + if (this->HasRetBuffArg() && this->IsRetBuffPassedAsFirstArg()) { ret += TARGET_REGISTER_SIZE; } @@ -1265,7 +1273,7 @@ int ArgIteratorTemplate::GetNextOffset() if (this->HasThis()) numRegistersUsed++; - if (this->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) + if (this->HasRetBuffArg() && this->IsRetBuffPassedAsFirstArg()) numRegistersUsed++; _ASSERTE(!this->IsVarArg() || !this->HasParamType()); @@ -2070,7 +2078,7 @@ void ArgIteratorTemplate::ForceSigWalk() if (this->HasThis()) numRegistersUsed++; - if (this->HasRetBuffArg() && IsRetBuffPassedAsFirstArg()) + if (this->HasRetBuffArg() && this->IsRetBuffPassedAsFirstArg()) numRegistersUsed++; if (this->IsVarArg()) @@ -2267,6 +2275,11 @@ class ArgIteratorBase #endif // defined(UNIX_AMD64_ABI) public: + FORCEINLINE BOOL IsRetBuffPassedAsFirstArg() + { + return ::IsRetBuffPassedAsFirstArg(); + } + BOOL HasThis() { LIMITED_METHOD_CONTRACT; @@ -2361,6 +2374,26 @@ class PInvokeArgIterator : public ArgIteratorTemplate } }; +#if defined(TARGET_ARM64) && defined(TARGET_WINDOWS) +class ArgIteratorBaseForWindowsArm64PInvokeThisCall : public ArgIteratorBaseForPInvoke +{ +public: + FORCEINLINE BOOL IsRetBuffPassedAsFirstArg() + { + return TRUE; + } +}; + +class WindowsArm64PInvokeThisCallArgIterator : public ArgIteratorTemplate +{ +public: + WindowsArm64PInvokeThisCallArgIterator(MetaSig* pSig) + { + m_pSig = pSig; + } +}; +#endif // defined(TARGET_ARM64) && defined(TARGET_WINDOWS) + // Conventience helper inline BOOL HasRetBuffArg(MetaSig * pSig) { diff --git a/src/coreclr/vm/callstubgenerator.cpp b/src/coreclr/vm/callstubgenerator.cpp index 8eb213a634866a..e0ed49d3bd1de8 100644 --- a/src/coreclr/vm/callstubgenerator.cpp +++ b/src/coreclr/vm/callstubgenerator.cpp @@ -1995,6 +1995,10 @@ extern "C" void InterpreterStubRetBuffRDI(); extern "C" void InterpreterStubRetBuffRSI(); #endif // TARGET_WINDOWS #else // TARGET_AMD64 +#if defined(TARGET_ARM64) && defined(TARGET_WINDOWS) +extern "C" void CallJittedMethodRetBuffX1(PCODE *routines, int8_t*pArgs, int8_t*pRet, int totalStackSize, PTR_PTR_Object pContinuation); +extern "C" void InterpreterStubRetBuffX1(); +#endif extern "C" void CallJittedMethodRetBuff(PCODE *routines, int8_t*pArgs, int8_t*pRet, int totalStackSize, PTR_PTR_Object pContinuation); extern "C" void InterpreterStubRetBuff(); #endif // TARGET_AMD64 @@ -2094,6 +2098,10 @@ CallStubHeader::InvokeFunctionPtr CallStubGenerator::GetInvokeFunctionPtr(CallSt INVOKE_FUNCTION_PTR(CallJittedMethodRetBuffRSI); #endif // TARGET_WINDOWS #else // TARGET_AMD64 +#if defined(TARGET_ARM64) && defined(TARGET_WINDOWS) + case ReturnTypeBuffArg2: + INVOKE_FUNCTION_PTR(CallJittedMethodRetBuffX1); +#endif case ReturnTypeBuff: INVOKE_FUNCTION_PTR(CallJittedMethodRetBuff); #endif // TARGET_AMD64 @@ -2193,6 +2201,10 @@ PCODE CallStubGenerator::GetInterpreterReturnTypeHandler(CallStubGenerator::Retu RETURN_TYPE_HANDLER(InterpreterStubRetBuffRSI); #endif #else // TARGET_AMD64 +#if defined(TARGET_ARM64) && defined(TARGET_WINDOWS) + case ReturnTypeBuffArg2: + RETURN_TYPE_HANDLER(InterpreterStubRetBuffX1); +#endif case ReturnTypeBuff: RETURN_TYPE_HANDLER(InterpreterStubRetBuff); #endif // TARGET_AMD64 @@ -2559,7 +2571,16 @@ void CallStubGenerator::ComputeCallStub(MetaSig &sig, PCODE *pRoutines, MethodDe if (hasUnmanagedCallConv) { - ComputeCallStubWorker(hasUnmanagedCallConv, unmanagedCallConv, sig, pRoutines, pMD); +#if defined(TARGET_ARM64) && defined(TARGET_WINDOWS) + if (callConvIsInstanceMethodCallConv(unmanagedCallConv)) + { + ComputeCallStubWorker(hasUnmanagedCallConv, unmanagedCallConv, sig, pRoutines, pMD); + } + else +#endif // defined(TARGET_ARM64) && defined(TARGET_WINDOWS) + { + ComputeCallStubWorker(hasUnmanagedCallConv, unmanagedCallConv, sig, pRoutines, pMD); + } } else { @@ -2575,17 +2596,7 @@ void CallStubGenerator::ComputeCallStubWorker(bool hasUnmanagedCallConv, CorInfo if (hasUnmanagedCallConv) { - switch (unmanagedCallConv) - { - case CorInfoCallConvExtension::Thiscall: - case CorInfoCallConvExtension::CMemberFunction: - case CorInfoCallConvExtension::StdcallMemberFunction: - case CorInfoCallConvExtension::FastcallMemberFunction: - unmanagedThisCallConv = true; - break; - default: - break; - } + unmanagedThisCallConv = callConvIsInstanceMethodCallConv(unmanagedCallConv); } #if defined(TARGET_WINDOWS) @@ -3060,6 +3071,13 @@ CallStubGenerator::ReturnType CallStubGenerator::GetReturnType(ArgIteratorType * return ReturnTypeBuffArg1; } #else +#if defined(TARGET_WINDOWS) && defined(TARGET_ARM64) + if (pArgIt->IsRetBuffPassedAsFirstArg()) + { + _ASSERTE(pArgIt->HasThis()); + return ReturnTypeBuffArg2; + } +#endif return ReturnTypeBuff; #endif // TARGET_AMD64 } diff --git a/src/coreclr/vm/callstubgenerator.h b/src/coreclr/vm/callstubgenerator.h index 6491613d50c884..026b191e6bcc37 100644 --- a/src/coreclr/vm/callstubgenerator.h +++ b/src/coreclr/vm/callstubgenerator.h @@ -79,6 +79,9 @@ class CallStubGenerator ReturnTypeBuffArg1, ReturnTypeBuffArg2, #else +#if defined(TARGET_ARM64) && defined(TARGET_WINDOWS) + ReturnTypeBuffArg2, +#endif ReturnTypeBuff, #endif #ifdef UNIX_AMD64_ABI diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 736954fac9f78e..461dbf1a4a1d01 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -238,6 +238,11 @@ class ArgIteratorBaseForMethodInvoke #endif // defined(UNIX_AMD64_ABI) public: + FORCEINLINE BOOL IsRetBuffPassedAsFirstArg() + { + return ::IsRetBuffPassedAsFirstArg(); + } + BOOL HasThis() { LIMITED_METHOD_CONTRACT; From 4c36eb7b6c0e84cbba77816ab5d2c9c71987565e Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 23 Jan 2026 09:43:24 -0800 Subject: [PATCH 2/2] Update src/coreclr/vm/arm64/asmhelpers.asm Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/vm/arm64/asmhelpers.asm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/arm64/asmhelpers.asm b/src/coreclr/vm/arm64/asmhelpers.asm index b2974be836bde5..7d051454457471 100644 --- a/src/coreclr/vm/arm64/asmhelpers.asm +++ b/src/coreclr/vm/arm64/asmhelpers.asm @@ -1180,9 +1180,9 @@ HaveInterpThreadContext PROLOG_SAVE_REG_PAIR fp, lr, #-16! ; The +16 is for the fp, lr above add x0, sp, #__PWTB_TransitionBlock + 16 - mov x1, x19 ; the IR bytecode pointer - ; Load the return buffer address + ; Load the return buffer address from incoming x1 before clobbering x1 mov x2, x1 + mov x1, x19 ; the IR bytecode pointer bl ExecuteInterpretedMethod EPILOG_RESTORE_REG_PAIR fp, lr, #16! EPILOG_RETURN