Skip to content

Commit a6c5ba3

Browse files
[clr-interp] Add support for unmanaged thiscall calling convention (#122600)
- Notably, on Windows platforms, the thiscall calling convention changes the argument order of the return buffer, and changes the rules where the return buffer pointer is placed - This should also be good prep for the Swift calling convention work, which also needs to identify the swift calling convention at this point - Also a drive by fix for the behavior of explicit this managed function pointers which return values in the return buffer, we were also missing a test cases for that scenario. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent c6af44d commit a6c5ba3

File tree

7 files changed

+375
-9
lines changed

7 files changed

+375
-9
lines changed

src/coreclr/vm/callconvbuilder.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,35 @@ HRESULT CallConv::TryGetUnmanagedCallingConventionFromModOpt(
354354
}
355355
IfFailRet(sigPtr.GetData(NULL)); // arg count
356356

357+
#ifdef DEBUG
357358
PCCOR_SIGNATURE pWalk = sigPtr.GetPtr();
358359
_ASSERTE(pWalk <= pSig + cSig);
360+
#endif
361+
362+
return TryGetUnmanagedCallingConventionFromModOptSigStartingAtRetType(
363+
pModule,
364+
sigPtr,
365+
builder,
366+
errorResID);
367+
}
368+
369+
HRESULT CallConv::TryGetUnmanagedCallingConventionFromModOptSigStartingAtRetType(
370+
_In_ CORINFO_MODULE_HANDLE pModule,
371+
_In_ SigPointer sig,
372+
_Inout_ CallConvBuilder* builder,
373+
_Out_ UINT* errorResID)
374+
{
375+
CONTRACTL
376+
{
377+
STANDARD_VM_CHECK;
378+
PRECONDITION(builder != NULL);
379+
PRECONDITION(errorResID != NULL);
380+
}
381+
CONTRACTL_END;
382+
PCCOR_SIGNATURE pSig;
383+
uint32_t cSig;
384+
sig.GetSignature(&pSig, &cSig);
385+
PCCOR_SIGNATURE pWalk = pSig;
359386

360387
CallConvBuilder& callConvBuilder = *builder;
361388
while ((pWalk < (pSig + cSig)) && ((*pWalk == ELEMENT_TYPE_CMOD_OPT) || (*pWalk == ELEMENT_TYPE_CMOD_REQD) || (*pWalk == ELEMENT_TYPE_CMOD_INTERNAL)))

src/coreclr/vm/callconvbuilder.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@ namespace CallConv
8080
_Inout_ CallConvBuilder *builder,
8181
_Out_ UINT* errorResID);
8282

83+
//-------------------------------------------------------------------------
84+
// Gets the unmanaged calling convention by reading any modopts, starting the sig
85+
// walk at the return type in the signature
86+
//
87+
// Returns:
88+
// S_OK - No errors
89+
// COR_E_BADIMAGEFORMAT - Signature had an invalid format
90+
// COR_E_INVALIDPROGRAM - Program is considered invalid (more
91+
// than one calling convention specified)
92+
//-------------------------------------------------------------------------
93+
HRESULT TryGetUnmanagedCallingConventionFromModOptSigStartingAtRetType(
94+
_In_ CORINFO_MODULE_HANDLE pModule,
95+
_In_ SigPointer sig,
96+
_Inout_ CallConvBuilder* builder,
97+
_Out_ UINT* errorResID);
98+
8399
//-------------------------------------------------------------------------
84100
// Gets the calling convention from the UnmanagedCallConv attribute
85101
//

src/coreclr/vm/callstubgenerator.cpp

Lines changed: 176 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
#if defined(FEATURE_INTERPRETER) && !defined(TARGET_WASM)
55

66
#include "callstubgenerator.h"
7+
#include "callconvbuilder.hpp"
78
#include "ecall.h"
9+
#include "dllimport.h"
810

911
extern "C" void InjectInterpStackAlign();
1012
extern "C" void Load_Stack();
@@ -2267,7 +2269,7 @@ CallStubHeader *CallStubGenerator::GenerateCallStub(MethodDesc *pMD, AllocMemTra
22672269
PCODE *pRoutines = (PCODE*)alloca(tempStorageSize);
22682270
memset(pRoutines, 0, tempStorageSize);
22692271

2270-
ComputeCallStub(sig, pRoutines);
2272+
ComputeCallStub(sig, pRoutines, pMD);
22712273

22722274
LoaderAllocator *pLoaderAllocator = pMD->GetLoaderAllocator();
22732275
S_SIZE_T finalStubSize(sizeof(CallStubHeader) + m_routineIndex * sizeof(PCODE));
@@ -2364,7 +2366,7 @@ CallStubHeader *CallStubGenerator::GenerateCallStubForSig(MetaSig &sig)
23642366

23652367
m_interpreterToNative = true; // We always generate the interpreter to native call stub here
23662368

2367-
ComputeCallStub(sig, pRoutines);
2369+
ComputeCallStub(sig, pRoutines, NULL);
23682370

23692371
xxHash hashState;
23702372
for (int i = 0; i < m_routineIndex; i++)
@@ -2451,8 +2453,179 @@ void CallStubGenerator::TerminateCurrentRoutineIfNotOfNewType(RoutineType type,
24512453
return;
24522454
}
24532455

2454-
void CallStubGenerator::ComputeCallStub(MetaSig &sig, PCODE *pRoutines)
2456+
//---------------------------------------------------------------------------
2457+
// isNativePrimitiveStructType:
2458+
// Check if the given struct type is an intrinsic type that should be treated as though
2459+
// it is not a struct at the unmanaged ABI boundary.
2460+
//
2461+
// Arguments:
2462+
// pMT - the handle for the struct type.
2463+
//
2464+
// Return Value:
2465+
// true if the given struct type should be treated as a primitive for unmanaged calls,
2466+
// false otherwise.
2467+
//
2468+
bool isNativePrimitiveStructType(MethodTable* pMT)
24552469
{
2470+
if (!pMT->IsIntrinsicType())
2471+
{
2472+
return false;
2473+
}
2474+
const char* namespaceName = nullptr;
2475+
const char* typeName = pMT->GetFullyQualifiedNameInfo(&namespaceName);
2476+
2477+
if ((namespaceName == NULL) || (typeName == NULL))
2478+
{
2479+
return false;
2480+
}
2481+
2482+
if (strcmp(namespaceName, "System.Runtime.InteropServices") != 0)
2483+
{
2484+
return false;
2485+
}
2486+
2487+
return strcmp(typeName, "CLong") == 0 || strcmp(typeName, "CULong") == 0 || strcmp(typeName, "NFloat") == 0;
2488+
}
2489+
2490+
void CallStubGenerator::ComputeCallStub(MetaSig &sig, PCODE *pRoutines, MethodDesc *pMD)
2491+
{
2492+
bool rewriteMetaSigFromExplicitThisToHasThis = false;
2493+
bool unmanagedThisCallConv = false;
2494+
2495+
bool hasUnmanagedCallConv = false;
2496+
CorInfoCallConvExtension unmanagedCallConv = CorInfoCallConvExtension::C;
2497+
2498+
if (pMD != NULL && (pMD->IsPInvoke()))
2499+
{
2500+
PInvoke::GetCallingConvention_IgnoreErrors(pMD, &unmanagedCallConv, NULL);
2501+
hasUnmanagedCallConv = true;
2502+
}
2503+
else if (pMD != NULL && pMD->HasUnmanagedCallersOnlyAttribute())
2504+
{
2505+
if (CallConv::TryGetCallingConventionFromUnmanagedCallersOnly(pMD, &unmanagedCallConv))
2506+
{
2507+
if (sig.GetCallingConvention() == IMAGE_CEE_CS_CALLCONV_VARARG)
2508+
{
2509+
unmanagedCallConv = CorInfoCallConvExtension::C;
2510+
}
2511+
}
2512+
else
2513+
{
2514+
unmanagedCallConv = CallConv::GetDefaultUnmanagedCallingConvention();
2515+
}
2516+
hasUnmanagedCallConv = true;
2517+
}
2518+
else
2519+
{
2520+
switch (sig.GetCallingConvention())
2521+
{
2522+
case IMAGE_CEE_CS_CALLCONV_THISCALL:
2523+
unmanagedCallConv = CorInfoCallConvExtension::Thiscall;
2524+
hasUnmanagedCallConv = true;
2525+
break;
2526+
case IMAGE_CEE_CS_CALLCONV_UNMANAGED:
2527+
unmanagedCallConv = GetUnmanagedCallConvExtension(&sig);
2528+
hasUnmanagedCallConv = true;
2529+
break;
2530+
}
2531+
}
2532+
2533+
if (hasUnmanagedCallConv)
2534+
{
2535+
switch (unmanagedCallConv)
2536+
{
2537+
case CorInfoCallConvExtension::Thiscall:
2538+
case CorInfoCallConvExtension::CMemberFunction:
2539+
case CorInfoCallConvExtension::StdcallMemberFunction:
2540+
case CorInfoCallConvExtension::FastcallMemberFunction:
2541+
unmanagedThisCallConv = true;
2542+
break;
2543+
default:
2544+
break;
2545+
}
2546+
}
2547+
2548+
#if defined(TARGET_WINDOWS)
2549+
// On these platforms, when making a ThisCall, or other call using a C++ MemberFunction calling convention,
2550+
// the "this" pointer is passed in the first argument slot.
2551+
bool rewriteReturnTypeToForceRetBuf = false;
2552+
if (unmanagedThisCallConv)
2553+
{
2554+
rewriteMetaSigFromExplicitThisToHasThis = true;
2555+
// Also, any struct type other than a few special cases is returned via return buffer for unmanaged calls
2556+
CorElementType retType = sig.GetReturnType();
2557+
sig.Reset();
2558+
2559+
if (retType == ELEMENT_TYPE_VALUETYPE)
2560+
{
2561+
TypeHandle thRetType = sig.GetRetTypeHandleThrowing();
2562+
MethodTable* pMTRetType = thRetType.AsMethodTable();
2563+
2564+
if (pMTRetType->GetInternalCorElementType() == ELEMENT_TYPE_VALUETYPE && !isNativePrimitiveStructType(pMTRetType))
2565+
{
2566+
rewriteReturnTypeToForceRetBuf = true;
2567+
}
2568+
}
2569+
}
2570+
#endif // defined(TARGET_WINDOWS)
2571+
2572+
// Rewrite ExplicitThis to HasThis. This allows us to use ArgIterator which is unaware of ExplicitThis
2573+
// in the places where it is needed such as computation of return buffers.
2574+
if (sig.GetCallingConventionInfo() & IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS)
2575+
{
2576+
#if LOG_COMPUTE_CALL_STUB
2577+
printf("Managed ExplicitThis to HasThis conversion needed\n");
2578+
#endif // LOG_COMPUTE_CALL_STUB
2579+
rewriteMetaSigFromExplicitThisToHasThis = true;
2580+
}
2581+
2582+
SigBuilder sigBuilder;
2583+
if (rewriteMetaSigFromExplicitThisToHasThis)
2584+
{
2585+
#if LOG_COMPUTE_CALL_STUB
2586+
printf("Rewriting ExplicitThis to implicit this\n");
2587+
#endif // LOG_COMPUTE_CALL_STUB
2588+
sigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_DEFAULT_HASTHIS);
2589+
if ((sig.NumFixedArgs() == 0) || (sig.HasThis() && !sig.HasExplicitThis()))
2590+
{
2591+
ThrowHR(COR_E_BADIMAGEFORMAT);
2592+
}
2593+
sigBuilder.AppendData(sig.NumFixedArgs() - 1);
2594+
TypeHandle thRetType = sig.GetRetTypeHandleThrowing();
2595+
#if defined(TARGET_WINDOWS)
2596+
if (rewriteReturnTypeToForceRetBuf)
2597+
{
2598+
// Change the return type to type large enough it will always need to be returned via return buffer
2599+
thRetType = CoreLibBinder::GetClass(CLASS__STACKFRAMEITERATOR);
2600+
_ASSERTE(thRetType.IsValueType());
2601+
_ASSERTE(thRetType.GetSize() > 64);
2602+
sigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL);
2603+
sigBuilder.AppendPointer(thRetType.AsPtr());
2604+
}
2605+
else
2606+
#endif
2607+
{
2608+
SigPointer pReturn = sig.GetReturnProps();
2609+
pReturn.ConvertToInternalExactlyOne(sig.GetModule(), sig.GetSigTypeContext(), &sigBuilder);
2610+
}
2611+
2612+
// Skip the explicit this argument
2613+
sig.NextArg();
2614+
2615+
// Copy rest of the arguments
2616+
sig.NextArg();
2617+
SigPointer pArgs = sig.GetArgProps();
2618+
for (unsigned i = 1; i < sig.NumFixedArgs(); i++)
2619+
{
2620+
pArgs.ConvertToInternalExactlyOne(sig.GetModule(), sig.GetSigTypeContext(), &sigBuilder);
2621+
}
2622+
2623+
DWORD cSig;
2624+
PCCOR_SIGNATURE pNewSig = (PCCOR_SIGNATURE)sigBuilder.GetSignature(&cSig);
2625+
MetaSig newSig(pNewSig, cSig, sig.GetModule(), NULL, MetaSig::sigMember);
2626+
sig = newSig;
2627+
}
2628+
24562629
ArgIterator argIt(&sig);
24572630
int32_t interpreterStackOffset = 0;
24582631

src/coreclr/vm/callstubgenerator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class CallStubGenerator
179179
// The size of the routines array is three times the number of arguments plus one slot for the target method pointer.
180180
return sizeof(CallStubHeader) + ((numArgs + 1) * 3 + 1) * sizeof(PCODE);
181181
}
182-
void ComputeCallStub(MetaSig &sig, PCODE *pRoutines);
182+
void ComputeCallStub(MetaSig &sig, PCODE *pRoutines, MethodDesc *pMD);
183183

184184
void TerminateCurrentRoutineIfNotOfNewType(RoutineType type, PCODE *pRoutines);
185185
};

src/coreclr/vm/siginfo.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include <corhlprpriv.h>
2424
#include "argdestination.h"
2525
#include "multicorejit.h"
26+
#include "callconvbuilder.hpp"
27+
#include "dynamicmethod.h"
2628

2729
/*******************************************************************/
2830
const CorTypeInfo::CorTypeInfoEntry CorTypeInfo::info[ELEMENT_TYPE_MAX] =
@@ -5903,3 +5905,26 @@ BOOL CompareTypeLayout(mdToken tk1, mdToken tk2, Module *pModule1, Module *pModu
59035905

59045906
return TRUE;
59055907
}
5908+
5909+
#ifndef DACCESS_COMPILE
5910+
CorInfoCallConvExtension GetUnmanagedCallConvExtension(MetaSig* pSig)
5911+
{
5912+
STANDARD_VM_CONTRACT;
5913+
CallConvBuilder builder;
5914+
UINT errorResID;
5915+
5916+
HRESULT hr = CallConv::TryGetUnmanagedCallingConventionFromModOptSigStartingAtRetType(GetScopeHandle(pSig->GetModule()), pSig->GetReturnProps(), &builder, &errorResID);
5917+
5918+
if (FAILED(hr))
5919+
COMPlusThrowHR(hr, errorResID);
5920+
5921+
CorInfoCallConvExtension callConvLocal = builder.GetCurrentCallConv();
5922+
5923+
if (callConvLocal == CallConvBuilder::UnsetValue)
5924+
{
5925+
callConvLocal = CallConv::GetDefaultUnmanagedCallingConvention();
5926+
}
5927+
5928+
return callConvLocal;
5929+
}
5930+
#endif // DACCESS_COMPILE

src/coreclr/vm/siginfo.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,8 @@ BOOL CompareTypeDefsForEquivalence(mdToken tk1, mdToken tk2, Module *pModule1, M
12081208
BOOL IsTypeDefEquivalent(mdToken tk, Module *pModule);
12091209
BOOL IsTypeDefExternallyVisible(mdToken tk, Module *pModule, DWORD dwAttrs);
12101210

1211+
CorInfoCallConvExtension GetUnmanagedCallConvExtension(MetaSig* pSig);
1212+
12111213
void ReportPointersFromValueType(promote_func *fn, ScanContext *sc, PTR_MethodTable pMT, PTR_VOID pSrc);
12121214

12131215
#endif /* _H_SIGINFO */

0 commit comments

Comments
 (0)