From ba1d2bc39b52e28b032a3a963448cc888b3f4300 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 9 Sep 2025 18:40:40 -0700 Subject: [PATCH 01/15] Fix WASM logic for locating correct stubs. The broken scenario was that value types are passed by value if they are <= sizeof(void*), but pass by reference if > sizeof(void*). This needed to be reconciled with the current interpreter ABI that passes values types on the stack. --- src/coreclr/vm/wasm/helpers.cpp | 135 +++++++++++++++++++++++++------- 1 file changed, 105 insertions(+), 30 deletions(-) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index bf41811d8af4bd..ce47f2f3eb0b6c 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -411,7 +411,8 @@ void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, in namespace { // Arguments are passed on the stack with each argument aligned to INTERP_STACK_SLOT_SIZE. -#define ARG(i) *((int32_t*)(pArgs + (i * INTERP_STACK_SLOT_SIZE))) +#define ARG_IND(i) ((int32_t)((int32_t*)(pArgs + (i * INTERP_STACK_SLOT_SIZE)))) +#define ARG(i) (*(int32_t*)ARG_IND(i)) void CallFunc_Void_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) { @@ -461,6 +462,20 @@ namespace *(int32_t*)pRet = (*fptr)(ARG(0), ARG(1), ARG(2)); } + // Special thunks for signatures with indirect arguments. + + void CallFunc_I32IND_I32_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + void (*fptr)(int32_t, int32_t) = (void (*)(int32_t, int32_t))pcode; + (*fptr)(ARG_IND(0), ARG(1)); + } + + void CallFunc_I32IND_I32_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + int32_t (*fptr)(int32_t, int32_t) = (int32_t (*)(int32_t, int32_t))pcode; + *(int32_t*)pRet = (*fptr)(ARG_IND(0), ARG(1)); + } + #undef ARG void* const RetVoidThunks[] = @@ -479,34 +494,78 @@ namespace (void*)&CallFunc_I32_I32_I32_RetI32, }; - bool ConvertibleToI32(CorElementType argType) + enum class ConvertType + { + NotConvertible, + ToI32, + ToI32Indirect + }; + + ConvertType ConvertibleTo(CorElementType argType, MetaSig& sig, bool isReturn) + { + // See https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md + switch (argType) + { + case ELEMENT_TYPE_BOOLEAN: + case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_I1: + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_I2: + case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: + case ELEMENT_TYPE_STRING: + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: + case ELEMENT_TYPE_FNPTR: + case ELEMENT_TYPE_SZARRAY: + return ConvertType::ToI32; + case ELEMENT_TYPE_TYPEDBYREF: + // Typed references are passed indirectly in WASM since they are larger than pointer size. + return ConvertType::ToI32Indirect; + case ELEMENT_TYPE_VALUETYPE: + { + // In WASM, values types that are larger than pointer size are passed indirectly. + TypeHandle vt = isReturn + ? sig.GetRetTypeHandleThrowing() + : sig.GetLastTypeHandleThrowing(); + return vt.GetSize() <= sizeof(uint32_t) + ? ConvertType::ToI32 + : ConvertType::ToI32Indirect; + } + default: + return ConvertType::NotConvertible; + } + } + + void* ComputeCalliSigThunkSpecial(bool isVoidReturn, uint32_t numArgs, ConvertType* args) { - // See https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md - switch (argType) + STANDARD_VM_CONTRACT; + + if (isVoidReturn) + { + if (numArgs == 2 && + args[0] == ConvertType::ToI32Indirect && + args[1] == ConvertType::ToI32) + { + return (void*)&CallFunc_I32IND_I32_RetVoid; + } + } + else + { + if (numArgs == 2 && + args[0] == ConvertType::ToI32Indirect && + args[1] == ConvertType::ToI32) { - case ELEMENT_TYPE_BOOLEAN: - case ELEMENT_TYPE_CHAR: - case ELEMENT_TYPE_I1: - case ELEMENT_TYPE_U1: - case ELEMENT_TYPE_I2: - case ELEMENT_TYPE_U2: - case ELEMENT_TYPE_I4: - case ELEMENT_TYPE_U4: - case ELEMENT_TYPE_STRING: - case ELEMENT_TYPE_PTR: - case ELEMENT_TYPE_BYREF: - case ELEMENT_TYPE_VALUETYPE: - case ELEMENT_TYPE_CLASS: - case ELEMENT_TYPE_ARRAY: - case ELEMENT_TYPE_TYPEDBYREF: - case ELEMENT_TYPE_I: - case ELEMENT_TYPE_U: - case ELEMENT_TYPE_FNPTR: - case ELEMENT_TYPE_SZARRAY: - return true; - default: - return false; + return (void*)&CallFunc_I32IND_I32_RetI32; } + } + + return NULL; } // This is a simple signature computation routine for signatures currently supported in the wasm environment. @@ -530,21 +589,37 @@ namespace return NULL; } - // Check return value + // Check return value. We only support void or i32 return types for now. bool returnsVoid = sig.IsReturnTypeVoid(); - if (!returnsVoid && !ConvertibleToI32(sig.GetReturnType())) + if (!returnsVoid && ConvertibleTo(sig.GetReturnType(), sig, true /* isReturn */) != ConvertType::ToI32) return NULL; + ConvertType args[16]; + _ASSERTE(sig.NumFixedArgs() < ARRAY_SIZE(args)); + + uint32_t i = 0; // Ensure all arguments are wasm i32 compatible types. for (CorElementType argType = sig.NextArg(); argType != ELEMENT_TYPE_END; argType = sig.NextArg()) { - if (!ConvertibleToI32(argType)) + // If we have no conversion, immediately return. + ConvertType type = ConvertibleTo(argType, sig, false /* isReturn */); + if (type == ConvertType::NotConvertible) return NULL; + + args[i++] = type; + } + + uint32_t numArgs = sig.NumFixedArgs(); + + // Check for homogeneous i32 argument types. + for (uint32_t j = 0; j < i; j++) + { + if (args[j] != ConvertType::ToI32) + return ComputeCalliSigThunkSpecial(returnsVoid, numArgs, args); } - UINT numArgs = sig.NumFixedArgs(); void* const * thunks; if (returnsVoid) { From cef7c1433bda224eaf1dd8b3af3d5b8809c3f790 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 9 Sep 2025 20:56:52 -0700 Subject: [PATCH 02/15] Add comments. --- src/coreclr/vm/wasm/helpers.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index ce47f2f3eb0b6c..6959d29cf05553 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -529,10 +529,18 @@ namespace return ConvertType::ToI32Indirect; case ELEMENT_TYPE_VALUETYPE: { - // In WASM, values types that are larger than pointer size are passed indirectly. + // In WASM, values types that are larger than pointer size or have multiple fields are passed indirectly. + // WASMTODO: Single fields may not always be passed as i32. Floats and doubles are passed as f32 and f64 respectively. TypeHandle vt = isReturn ? sig.GetRetTypeHandleThrowing() : sig.GetLastTypeHandleThrowing(); + + if (vt.IsTypeDesc() + && vt.AsMethodTable()->GetNumInstanceFields() >= 2) + { + return ConvertType::ToI32Indirect; + } + return vt.GetSize() <= sizeof(uint32_t) ? ConvertType::ToI32 : ConvertType::ToI32Indirect; @@ -614,7 +622,7 @@ namespace uint32_t numArgs = sig.NumFixedArgs(); // Check for homogeneous i32 argument types. - for (uint32_t j = 0; j < i; j++) + for (uint32_t j = 0; j < numArgs; j++) { if (args[j] != ConvertType::ToI32) return ComputeCalliSigThunkSpecial(returnsVoid, numArgs, args); From f20670bdf89ddc6402bc6b71ee5a906eb680c51b Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 9 Sep 2025 22:07:54 -0700 Subject: [PATCH 03/15] Managed calli should check for associated native code. --- src/coreclr/vm/interpexec.cpp | 10 +++++++++- src/coreclr/vm/wasm/helpers.cpp | 11 ++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index e5bd23c538a2cf..fe74fc864e0724 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -2189,7 +2189,15 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr } else { - InvokeCalliStub(LOCAL_VAR(calliFunctionPointerVar, PCODE), cookie, stack + callArgsOffset, stack + returnOffset); + PCODE calliFunctionPointer = LOCAL_VAR(calliFunctionPointerVar, PCODE); +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + if (!PortableEntryPoint::HasNativeEntryPoint(calliFunctionPointer)) + { + targetMethod = PortableEntryPoint::GetMethodDesc(calliFunctionPointer); + goto CALL_INTERP_METHOD; + } +#endif // FEATURE_PORTABLE_ENTRYPOINTS + InvokeCalliStub(calliFunctionPointer, cookie, stack + callArgsOffset, stack + returnOffset); } break; diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 6959d29cf05553..889ef22d5ea5cd 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -438,6 +438,12 @@ namespace (*fptr)(ARG(0), ARG(1), ARG(2)); } + void CallFunc_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5)); + } + void CallFunc_Void_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet) { int32_t (*fptr)(void) = (int32_t (*)(void))pcode; @@ -484,6 +490,9 @@ namespace (void*)&CallFunc_I32_RetVoid, (void*)&CallFunc_I32_I32_RetVoid, (void*)&CallFunc_I32_I32_I32_RetVoid, + NULL, + NULL, + (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetVoid, }; void* const RetI32Thunks[] = @@ -535,7 +544,7 @@ namespace ? sig.GetRetTypeHandleThrowing() : sig.GetLastTypeHandleThrowing(); - if (vt.IsTypeDesc() + if (!vt.IsTypeDesc() && vt.AsMethodTable()->GetNumInstanceFields() >= 2) { return ConvertType::ToI32Indirect; From 29279181c96261823b207a38b3f57b6cf4c4e9f0 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Wed, 10 Sep 2025 13:41:54 +0200 Subject: [PATCH 04/15] Simplify `INTOP_CALLI` for `FEATURE_PORTABLE_ENTRYPOINTS`` Co-authored-by: Jan Kotas --- src/coreclr/vm/interpexec.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index fe74fc864e0724..c0f8f0f974df1e 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -2189,15 +2189,13 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr } else { - PCODE calliFunctionPointer = LOCAL_VAR(calliFunctionPointerVar, PCODE); #ifdef FEATURE_PORTABLE_ENTRYPOINTS - if (!PortableEntryPoint::HasNativeEntryPoint(calliFunctionPointer)) - { - targetMethod = PortableEntryPoint::GetMethodDesc(calliFunctionPointer); - goto CALL_INTERP_METHOD; - } -#endif // FEATURE_PORTABLE_ENTRYPOINTS + PCODE calliFunctionPointer = LOCAL_VAR(calliFunctionPointerVar, PCODE); + targetMethod = PortableEntryPoint::GetMethodDesc(calliFunctionPointer); + goto CALL_INTERP_METHOD; +#else // FEATURE_PORTABLE_ENTRYPOINTS InvokeCalliStub(calliFunctionPointer, cookie, stack + callArgsOffset, stack + returnOffset); +#endif // FEATURE_PORTABLE_ENTRYPOINTS } break; From 09f6fa17ca925e53eebe1efbe8834d785fd8d317 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 10 Sep 2025 05:59:13 -0700 Subject: [PATCH 05/15] Fix build. --- src/coreclr/vm/interpexec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index c0f8f0f974df1e..ffc113255a53db 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -2189,8 +2189,8 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr } else { -#ifdef FEATURE_PORTABLE_ENTRYPOINTS PCODE calliFunctionPointer = LOCAL_VAR(calliFunctionPointerVar, PCODE); +#ifdef FEATURE_PORTABLE_ENTRYPOINTS targetMethod = PortableEntryPoint::GetMethodDesc(calliFunctionPointer); goto CALL_INTERP_METHOD; #else // FEATURE_PORTABLE_ENTRYPOINTS From 2d05382945301d116c12148e956d87d0680e85f3 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 10 Sep 2025 06:31:01 -0700 Subject: [PATCH 06/15] Fixes --- src/coreclr/vm/interpexec.cpp | 7 +- src/coreclr/vm/jitinterface.cpp | 124 +++++++++++++++++--------------- src/coreclr/vm/jitinterface.h | 2 +- 3 files changed, 73 insertions(+), 60 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index ffc113255a53db..9e6b8322673e89 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -2191,8 +2191,11 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { PCODE calliFunctionPointer = LOCAL_VAR(calliFunctionPointerVar, PCODE); #ifdef FEATURE_PORTABLE_ENTRYPOINTS - targetMethod = PortableEntryPoint::GetMethodDesc(calliFunctionPointer); - goto CALL_INTERP_METHOD; + if (!PortableEntryPoint::HasNativeEntryPoint(calliFunctionPointer)) + { + targetMethod = PortableEntryPoint::GetMethodDesc(calliFunctionPointer); + goto CALL_INTERP_METHOD; + } #else // FEATURE_PORTABLE_ENTRYPOINTS InvokeCalliStub(calliFunctionPointer, cookie, stack + callArgsOffset, stack + returnOffset); #endif // FEATURE_PORTABLE_ENTRYPOINTS diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 7f163a2689bdac..3c94df3b09cc41 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -10749,7 +10749,7 @@ CEECodeGenInfo::CEECodeGenInfo(PrepareCodeConfig* config, MethodDesc* fd, COR_IL void CEECodeGenInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN */ CORINFO_CONST_LOOKUP* pNativeEntrypoint, /* OUT */ - CORINFO_METHOD_HANDLE* pMethod) /* OUT */ + CORINFO_METHOD_HANDLE* pMethodHandle) /* OUT */ { CONTRACTL { @@ -10765,61 +10765,17 @@ void CEECodeGenInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN InfoAccessType accessType; LPVOID targetAddr; - MethodDesc* helperMD = NULL; - VMHELPDEF const& helperDef = hlpFuncTable[ftnNum]; - PCODE pfnHelper = helperDef.pfnHelper; - - DynamicCorInfoHelpFunc dynamicFtnNum; #ifdef FEATURE_PORTABLE_ENTRYPOINTS - accessType = IAT_VALUE; - targetAddr = (LPVOID)VolatileLoad(&hlpFuncEntryPoints[ftnNum]); - if (targetAddr != NULL) - { - // If the target address is already cached, but the caller asked for the method handle - // then we verify the helper is an IL based dynamic helper and load the method handle for it. - if (pMethod != NULL - && helperDef.IsDynamicHelper(&dynamicFtnNum) - && HasILBasedDynamicJitHelper(dynamicFtnNum)) - { - helperMD = GetMethodDescForILBasedDynamicJitHelper(dynamicFtnNum); - _ASSERTE(PortableEntryPoint::GetMethodDesc((PCODE)targetAddr) == helperMD); - } - } - else - { - if (helperDef.IsDynamicHelper(&dynamicFtnNum)) - { - pfnHelper = LoadDynamicJitHelper(dynamicFtnNum); - if (HasILBasedDynamicJitHelper(dynamicFtnNum)) - helperMD = GetMethodDescForILBasedDynamicJitHelper(dynamicFtnNum); - } - - // LoadDynamicJitHelper returns PortableEntryPoint for helpers backed by managed methods. We need to wrap - // the code address by PortableEntryPoint in all other cases. - if (helperMD == NULL) - { - _ASSERTE(pfnHelper != NULL); - AllocMemHolder portableEntryPoint = SystemDomain::GetGlobalLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T{ sizeof(PortableEntryPoint) }); - portableEntryPoint->Init((void*)pfnHelper); - pfnHelper = (PCODE)(PortableEntryPoint*)(portableEntryPoint); - - if (InterlockedCompareExchangeT(&hlpFuncEntryPoints[ftnNum], pfnHelper, (PCODE)NULL) == (PCODE)NULL) - portableEntryPoint.SuppressRelease(); - pfnHelper = hlpFuncEntryPoints[ftnNum]; - } - else - { - VolatileStore(&hlpFuncEntryPoints[ftnNum], pfnHelper); - } - - targetAddr = (LPVOID)pfnHelper; - } + targetAddr = (LPVOID)getHelperFtnStatic(ftnNum, &helperMD); #else // !FEATURE_PORTABLE_ENTRYPOINTS + VMHELPDEF const& helperDef = hlpFuncTable[ftnNum]; + DynamicCorInfoHelpFunc dynamicFtnNum; + PCODE pfnHelper = helperDef.pfnHelper; if (helperDef.IsDynamicHelper(&dynamicFtnNum)) { #if defined(TARGET_AMD64) @@ -10853,7 +10809,7 @@ void CEECodeGenInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN { accessType = IAT_VALUE; targetAddr = finalTierAddr; - if (pMethod != NULL && HasILBasedDynamicJitHelper(dynamicFtnNum)) + if (pMethodHandle != NULL && HasILBasedDynamicJitHelper(dynamicFtnNum)) { helperMD = GetMethodDescForILBasedDynamicJitHelper(dynamicFtnNum); _ASSERT(helperMD != NULL); @@ -10939,30 +10895,84 @@ exit: ; pNativeEntrypoint->addr = targetAddr; } - if (pMethod != NULL) - *pMethod = (CORINFO_METHOD_HANDLE)helperMD; + if (pMethodHandle != NULL) + *pMethodHandle = (CORINFO_METHOD_HANDLE)helperMD; EE_TO_JIT_TRANSITION(); } -PCODE CEECodeGenInfo::getHelperFtnStatic(CorInfoHelpFunc ftnNum) +PCODE CEECodeGenInfo::getHelperFtnStatic(CorInfoHelpFunc ftnNum, MethodDesc** pMethod) { - CONTRACTL { + CONTRACTL + { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; - } CONTRACTL_END; + } + CONTRACTL_END; VMHELPDEF const& helperDef = hlpFuncTable[ftnNum]; - PCODE pfnHelper = helperDef.pfnHelper; + + PCODE pfnHelper; + MethodDesc* helperMD = NULL; + DynamicCorInfoHelpFunc dynamicFtnNum; + +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + pfnHelper = VolatileLoad(&hlpFuncEntryPoints[ftnNum]); + if (pfnHelper != (PCODE)NULL) + { + // If the target address is already cached, but the caller asked for the method handle + // then we verify the helper is an IL based dynamic helper and load the method handle for it. + if (pMethod != NULL + && helperDef.IsDynamicHelper(&dynamicFtnNum) + && HasILBasedDynamicJitHelper(dynamicFtnNum)) + { + helperMD = GetMethodDescForILBasedDynamicJitHelper(dynamicFtnNum); + _ASSERTE(PortableEntryPoint::GetMethodDesc(pfnHelper) == helperMD); + } + } + else + { + pfnHelper = helperDef.pfnHelper; + if (helperDef.IsDynamicHelper(&dynamicFtnNum)) + { + pfnHelper = LoadDynamicJitHelper(dynamicFtnNum); + if (HasILBasedDynamicJitHelper(dynamicFtnNum)) + helperMD = GetMethodDescForILBasedDynamicJitHelper(dynamicFtnNum); + } + + // LoadDynamicJitHelper returns PortableEntryPoint for helpers backed by managed methods. We need to wrap + // the code address by PortableEntryPoint in all other cases. + if (helperMD == NULL) + { + _ASSERTE(pfnHelper != NULL); + AllocMemHolder portableEntryPoint = SystemDomain::GetGlobalLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T{ sizeof(PortableEntryPoint) }); + portableEntryPoint->Init((void*)pfnHelper); + pfnHelper = (PCODE)(PortableEntryPoint*)(portableEntryPoint); + + if (InterlockedCompareExchangeT(&hlpFuncEntryPoints[ftnNum], pfnHelper, (PCODE)NULL) == (PCODE)NULL) + portableEntryPoint.SuppressRelease(); + pfnHelper = hlpFuncEntryPoints[ftnNum]; + } + else + { + VolatileStore(&hlpFuncEntryPoints[ftnNum], pfnHelper); + } + } + +#else // !FEATURE_PORTABLE_ENTRYPOINTS + pfnHelper = helperDef.pfnHelper; // In this case we need to find the actual pfnHelper // using an extra indirection. - DynamicCorInfoHelpFunc dynamicFtnNum; if (helperDef.IsDynamicHelper(&dynamicFtnNum)) { pfnHelper = LoadDynamicJitHelper(dynamicFtnNum); } +#endif // FEATURE_PORTABLE_ENTRYPOINTS + + if (pMethod != NULL) + *pMethod = helperMD; _ASSERTE(pfnHelper != (PCODE)NULL); diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 3067854ac0882f..1af1b2aedc99f3 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -569,7 +569,7 @@ class CEECodeGenInfo : public CEEInfo void getHelperFtn(CorInfoHelpFunc tnNum, /* IN */ CORINFO_CONST_LOOKUP * pNativeEntrypoint, /* OUT */ CORINFO_METHOD_HANDLE * pMethodHandle) override; /* OUT */ - static PCODE getHelperFtnStatic(CorInfoHelpFunc ftnNum); + static PCODE getHelperFtnStatic(CorInfoHelpFunc ftnNum, MethodDesc** pMethod /* OUT */ = NULL); InfoAccessType constructStringLiteral(CORINFO_MODULE_HANDLE scopeHnd, mdToken metaTok, void **ppValue) override; InfoAccessType emptyStringLiteral(void ** ppValue) override; From 05523b7c6b5141bdb2b4276ec5cd5a1fd2445d0b Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Wed, 10 Sep 2025 16:09:24 +0200 Subject: [PATCH 07/15] Fix call of System.Runtime.CompilerServices.RuntimeHelpers::RunClassConstructor --- src/coreclr/vm/wasm/helpers.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 889ef22d5ea5cd..8ba5ccdae7f77b 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -470,6 +470,13 @@ namespace // Special thunks for signatures with indirect arguments. + void CallFunc_I32IND_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + void (*fptr)(int32_t) = (void (*)(int32_t))pcode; + (*fptr)(ARG_IND(0)); + } + + void CallFunc_I32IND_I32_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) { void (*fptr)(int32_t, int32_t) = (void (*)(int32_t, int32_t))pcode; @@ -565,12 +572,22 @@ namespace if (isVoidReturn) { - if (numArgs == 2 && - args[0] == ConvertType::ToI32Indirect && - args[1] == ConvertType::ToI32) + switch(numArgs) { - return (void*)&CallFunc_I32IND_I32_RetVoid; - } + case 1: + if (args[0] == ConvertType::ToI32Indirect) + { + return (void*)&CallFunc_I32IND_RetVoid; + } + break; + case 2: + if (args[0] == ConvertType::ToI32Indirect && + args[1] == ConvertType::ToI32) + { + return (void*)&CallFunc_I32IND_I32_RetVoid; + } + break; + } } else { From e78da21ac6e1f842c18ecb661017882603c2ed60 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Wed, 10 Sep 2025 16:24:57 +0200 Subject: [PATCH 08/15] Fix call of System.RuntimeTypeHandle::ConstructName signature: void *(valuetype System.Runtime.CompilerServices.QCallTypeHandle,valuetype System.TypeNameFormatFlags,valuetype System.Runtime.CompilerServices.StringHandleOnStack) --- src/coreclr/vm/wasm/helpers.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 8ba5ccdae7f77b..1c2b79d8dba237 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -483,6 +483,12 @@ namespace (*fptr)(ARG_IND(0), ARG(1)); } + void CallFunc_I32IND_I32_I32_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_IND(0), ARG(1), ARG_IND(2)); + } + void CallFunc_I32IND_I32_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet) { int32_t (*fptr)(int32_t, int32_t) = (int32_t (*)(int32_t, int32_t))pcode; @@ -587,6 +593,14 @@ namespace return (void*)&CallFunc_I32IND_I32_RetVoid; } break; + case 3: + if (args[0] == ConvertType::ToI32Indirect && + args[1] == ConvertType::ToI32 && + args[1] == ConvertType::ToI32) + { + return (void*)&CallFunc_I32IND_I32_I32_RetVoid; + } + break; } } else From 13c4eae1a9388575e7c116b677dcf262da3dd826 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Wed, 10 Sep 2025 16:32:36 +0200 Subject: [PATCH 09/15] Fix call of System.Reflection.MetadataImport::GetCustomAttributeProps signature: instance void *(int32,int32&,valuetype System.Reflection.ConstArray&) --- src/coreclr/vm/wasm/helpers.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 1c2b79d8dba237..3c667cd3ec5463 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -468,6 +468,12 @@ namespace *(int32_t*)pRet = (*fptr)(ARG(0), ARG(1), ARG(2)); } + void CallFunc_I32_I32_I32_I32_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; + *(int32_t*)pRet = (*fptr)(ARG(0), ARG(1), ARG(2), ARG(3)); + } + // Special thunks for signatures with indirect arguments. void CallFunc_I32IND_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) @@ -514,6 +520,7 @@ namespace (void*)&CallFunc_I32_RetI32, (void*)&CallFunc_I32_I32_RetI32, (void*)&CallFunc_I32_I32_I32_RetI32, + (void*)&CallFunc_I32_I32_I32_I32_RetI32, }; enum class ConvertType From 64e9177fd4e7ae1a5fdd06f472be93f6149a81c3 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Wed, 10 Sep 2025 16:40:43 +0200 Subject: [PATCH 10/15] Fix call of System.ModuleHandle::ResolveType signature: void *(valuetype System.Runtime.CompilerServices.QCallModule,int32,native int*,int32,native int*,int32,valuetype System.Runtime.CompilerServices.ObjectHandleOnStack) plus few fixes --- src/coreclr/vm/wasm/helpers.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 3c667cd3ec5463..da70403c1b022a 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -492,7 +492,13 @@ namespace void CallFunc_I32IND_I32_I32_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) { void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG(1), ARG_IND(2)); + (*fptr)(ARG_IND(0), ARG(1), ARG(2)); + } + + void CallFunc_I32IND_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_IND(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6)); } void CallFunc_I32IND_I32_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet) @@ -603,11 +609,23 @@ namespace case 3: if (args[0] == ConvertType::ToI32Indirect && args[1] == ConvertType::ToI32 && - args[1] == ConvertType::ToI32) + args[2] == ConvertType::ToI32) { return (void*)&CallFunc_I32IND_I32_I32_RetVoid; } break; + case 7: + if (args[0] == ConvertType::ToI32Indirect && + args[1] == ConvertType::ToI32 && + args[2] == ConvertType::ToI32 && + args[3] == ConvertType::ToI32 && + args[4] == ConvertType::ToI32 && + args[5] == ConvertType::ToI32 && + args[6] == ConvertType::ToI32) + { + return (void*)&CallFunc_I32IND_I32_I32_I32_I32_I32_I32_RetVoid; + } + break; } } else From 578e7aa6e64c93e3742ee9a611327d3a89803c46 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Wed, 10 Sep 2025 17:01:14 +0200 Subject: [PATCH 11/15] Fix call of System.ModuleHandle::ResolveMethod signature: valuetype System.RuntimeMethodHandleInternal *(valuetype System.Runtime.CompilerServices.QCallModule,int32,native int*,int32,native int*,int32) --- src/coreclr/vm/wasm/helpers.cpp | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index da70403c1b022a..c4d28e3cba506b 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -507,6 +507,12 @@ namespace *(int32_t*)pRet = (*fptr)(ARG_IND(0), ARG(1)); } + void CallFunc_I32IND_I32_I32_I32_I32_I32_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + *(int32_t*)pRet = (*fptr)(ARG_IND(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5)); + } + #undef ARG void* const RetVoidThunks[] = @@ -626,15 +632,29 @@ namespace return (void*)&CallFunc_I32IND_I32_I32_I32_I32_I32_I32_RetVoid; } break; - } + } } else { - if (numArgs == 2 && - args[0] == ConvertType::ToI32Indirect && - args[1] == ConvertType::ToI32) - { - return (void*)&CallFunc_I32IND_I32_RetI32; + switch (numArgs) { + case 2: + if (args[0] == ConvertType::ToI32Indirect && + args[1] == ConvertType::ToI32) + { + return (void*)&CallFunc_I32IND_I32_RetI32; + } + break; + case 6: + if (args[0] == ConvertType::ToI32Indirect && + args[1] == ConvertType::ToI32 && + args[2] == ConvertType::ToI32 && + args[3] == ConvertType::ToI32 && + args[4] == ConvertType::ToI32 && + args[5] == ConvertType::ToI32) + { + return (void*)&CallFunc_I32IND_I32_I32_I32_I32_I32_RetI32; + } + break; } } From f936db69d9b5159f11c98d9c9efbaea0767780e0 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 10 Sep 2025 12:44:41 -0700 Subject: [PATCH 12/15] Feedback --- src/coreclr/vm/jitinterface.cpp | 39 ++++++++++++++------------------ src/coreclr/vm/jitinterface.h | 2 +- src/coreclr/vm/nativelibrary.cpp | 4 ++-- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 3c94df3b09cc41..b31cc94dbcd07a 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -10767,14 +10767,24 @@ void CEECodeGenInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN LPVOID targetAddr; MethodDesc* helperMD = NULL; + VMHELPDEF const& helperDef = hlpFuncTable[ftnNum]; + DynamicCorInfoHelpFunc dynamicFtnNum; + #ifdef FEATURE_PORTABLE_ENTRYPOINTS accessType = IAT_VALUE; - targetAddr = (LPVOID)getHelperFtnStatic(ftnNum, &helperMD); + targetAddr = (LPVOID)getHelperFtnStatic(ftnNum); -#else // !FEATURE_PORTABLE_ENTRYPOINTS - VMHELPDEF const& helperDef = hlpFuncTable[ftnNum]; + // If the caller asked for the method handle then we verify the helper is + // an IL based dynamic helper and get the method handle for it. + if (pMethodHandle != NULL + && helperDef.IsDynamicHelper(&dynamicFtnNum) + && HasILBasedDynamicJitHelper(dynamicFtnNum)) + { + helperMD = GetMethodDescForILBasedDynamicJitHelper(dynamicFtnNum); + _ASSERTE(PortableEntryPoint::GetMethodDesc((PCODE)targetAddr) == helperMD); + } - DynamicCorInfoHelpFunc dynamicFtnNum; +#else // !FEATURE_PORTABLE_ENTRYPOINTS PCODE pfnHelper = helperDef.pfnHelper; if (helperDef.IsDynamicHelper(&dynamicFtnNum)) { @@ -10901,7 +10911,7 @@ exit: ; EE_TO_JIT_TRANSITION(); } -PCODE CEECodeGenInfo::getHelperFtnStatic(CorInfoHelpFunc ftnNum, MethodDesc** pMethod) +PCODE CEECodeGenInfo::getHelperFtnStatic(CorInfoHelpFunc ftnNum) { CONTRACTL { @@ -10914,25 +10924,13 @@ PCODE CEECodeGenInfo::getHelperFtnStatic(CorInfoHelpFunc ftnNum, MethodDesc** pM VMHELPDEF const& helperDef = hlpFuncTable[ftnNum]; PCODE pfnHelper; - MethodDesc* helperMD = NULL; DynamicCorInfoHelpFunc dynamicFtnNum; #ifdef FEATURE_PORTABLE_ENTRYPOINTS pfnHelper = VolatileLoad(&hlpFuncEntryPoints[ftnNum]); - if (pfnHelper != (PCODE)NULL) - { - // If the target address is already cached, but the caller asked for the method handle - // then we verify the helper is an IL based dynamic helper and load the method handle for it. - if (pMethod != NULL - && helperDef.IsDynamicHelper(&dynamicFtnNum) - && HasILBasedDynamicJitHelper(dynamicFtnNum)) - { - helperMD = GetMethodDescForILBasedDynamicJitHelper(dynamicFtnNum); - _ASSERTE(PortableEntryPoint::GetMethodDesc(pfnHelper) == helperMD); - } - } - else + if (pfnHelper == (PCODE)NULL) { + MethodDesc* helperMD = NULL; pfnHelper = helperDef.pfnHelper; if (helperDef.IsDynamicHelper(&dynamicFtnNum)) { @@ -10971,9 +10969,6 @@ PCODE CEECodeGenInfo::getHelperFtnStatic(CorInfoHelpFunc ftnNum, MethodDesc** pM } #endif // FEATURE_PORTABLE_ENTRYPOINTS - if (pMethod != NULL) - *pMethod = helperMD; - _ASSERTE(pfnHelper != (PCODE)NULL); return pfnHelper; diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 1af1b2aedc99f3..3067854ac0882f 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -569,7 +569,7 @@ class CEECodeGenInfo : public CEEInfo void getHelperFtn(CorInfoHelpFunc tnNum, /* IN */ CORINFO_CONST_LOOKUP * pNativeEntrypoint, /* OUT */ CORINFO_METHOD_HANDLE * pMethodHandle) override; /* OUT */ - static PCODE getHelperFtnStatic(CorInfoHelpFunc ftnNum, MethodDesc** pMethod /* OUT */ = NULL); + static PCODE getHelperFtnStatic(CorInfoHelpFunc ftnNum); InfoAccessType constructStringLiteral(CORINFO_MODULE_HANDLE scopeHnd, mdToken metaTok, void **ppValue) override; InfoAccessType emptyStringLiteral(void ** ppValue) override; diff --git a/src/coreclr/vm/nativelibrary.cpp b/src/coreclr/vm/nativelibrary.cpp index 534e25db221cc1..a78a018e3e1565 100644 --- a/src/coreclr/vm/nativelibrary.cpp +++ b/src/coreclr/vm/nativelibrary.cpp @@ -14,8 +14,8 @@ extern bool g_hostpolicy_embedded; #define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100 #ifdef TARGET_UNIX -#define PLATFORM_SHARED_LIB_SUFFIX_W PAL_SHLIB_SUFFIX_W -#define PLATFORM_SHARED_LIB_PREFIX_W PAL_SHLIB_PREFIX_W +#define PLATFORM_SHARED_LIB_SUFFIX_W u".dylib" +#define PLATFORM_SHARED_LIB_PREFIX_W u"lib" #else // !TARGET_UNIX // The default for Windows OS is ".DLL". This causes issues with case-sensitive file systems on Windows. // We are using the lowercase version due to historical precedence and how common it is now. From fe9875a3ce9b2ddc6f085c14ce7d9c76f71d4ded Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 10 Sep 2025 14:52:09 -0700 Subject: [PATCH 13/15] Revert testing change. --- src/coreclr/vm/nativelibrary.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/nativelibrary.cpp b/src/coreclr/vm/nativelibrary.cpp index a78a018e3e1565..534e25db221cc1 100644 --- a/src/coreclr/vm/nativelibrary.cpp +++ b/src/coreclr/vm/nativelibrary.cpp @@ -14,8 +14,8 @@ extern bool g_hostpolicy_embedded; #define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100 #ifdef TARGET_UNIX -#define PLATFORM_SHARED_LIB_SUFFIX_W u".dylib" -#define PLATFORM_SHARED_LIB_PREFIX_W u"lib" +#define PLATFORM_SHARED_LIB_SUFFIX_W PAL_SHLIB_SUFFIX_W +#define PLATFORM_SHARED_LIB_PREFIX_W PAL_SHLIB_PREFIX_W #else // !TARGET_UNIX // The default for Windows OS is ".DLL". This causes issues with case-sensitive file systems on Windows. // We are using the lowercase version due to historical precedence and how common it is now. From 89caec8fa0ba13a94809780685542ce210b67317 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Wed, 10 Sep 2025 18:57:07 -0700 Subject: [PATCH 14/15] Update src/coreclr/vm/interpexec.cpp --- src/coreclr/vm/interpexec.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 9e6b8322673e89..fe74fc864e0724 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -2196,9 +2196,8 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr targetMethod = PortableEntryPoint::GetMethodDesc(calliFunctionPointer); goto CALL_INTERP_METHOD; } -#else // FEATURE_PORTABLE_ENTRYPOINTS - InvokeCalliStub(calliFunctionPointer, cookie, stack + callArgsOffset, stack + returnOffset); #endif // FEATURE_PORTABLE_ENTRYPOINTS + InvokeCalliStub(calliFunctionPointer, cookie, stack + callArgsOffset, stack + returnOffset); } break; From 001f0b8517095a5add838c3d2a895bcc74a3ba17 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Wed, 10 Sep 2025 19:05:17 -0700 Subject: [PATCH 15/15] Update src/coreclr/vm/interpexec.cpp --- src/coreclr/vm/interpexec.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index fe74fc864e0724..e9014fcb1a8fa5 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -2191,6 +2191,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { PCODE calliFunctionPointer = LOCAL_VAR(calliFunctionPointerVar, PCODE); #ifdef FEATURE_PORTABLE_ENTRYPOINTS + // WASMTODO: We may end up here with native JIT helper entrypoint without MethodDesc + // that CALL_INTERP_METHOD is not able to handle. This is a potential problem for + // interpreter<->native code stub generator. + // https://github.com/dotnet/runtime/pull/119516#discussion_r2337631271 if (!PortableEntryPoint::HasNativeEntryPoint(calliFunctionPointer)) { targetMethod = PortableEntryPoint::GetMethodDesc(calliFunctionPointer);