From c8c3fc058b41e80ff0d43270dfd98423b3d0148a Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Tue, 18 Nov 2025 18:08:51 +0100 Subject: [PATCH 1/7] [wasm][coreclr] 2nd part of call helpers generator, reverse calls --- src/coreclr/vm/wasm/callhelpers-reverse.cpp | 217 ++++++- src/coreclr/vm/wasm/helpers.cpp | 7 +- .../WasmAppBuilder/WasmAppBuilder.csproj | 2 +- .../coreclr/PInvokeCollector.cs | 282 +++++++++ .../coreclr/PInvokeTableGenerator.cs | 555 ++++++++++++++++++ .../WasmAppBuilder/coreclr/SignatureMapper.cs | 11 + .../{ => mono}/PInvokeCollector.cs | 0 .../{ => mono}/PInvokeTableGenerator.cs | 0 8 files changed, 1061 insertions(+), 13 deletions(-) create mode 100644 src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs create mode 100644 src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs rename src/tasks/WasmAppBuilder/{ => mono}/PInvokeCollector.cs (100%) rename src/tasks/WasmAppBuilder/{ => mono}/PInvokeTableGenerator.cs (100%) diff --git a/src/coreclr/vm/wasm/callhelpers-reverse.cpp b/src/coreclr/vm/wasm/callhelpers-reverse.cpp index 2be9ba25e1b18e..ab4729811a45d0 100644 --- a/src/coreclr/vm/wasm/callhelpers-reverse.cpp +++ b/src/coreclr/vm/wasm/callhelpers-reverse.cpp @@ -2,20 +2,33 @@ // The .NET Foundation licenses this file to you under the MIT license. // -#include - -// Define reverse thunks here +// +// GENERATED FILE, DON'T EDIT +// Generated by coreclr InterpToNativeGenerator +// -// Entry point for interpreted method execution from unmanaged code -class MethodDesc; +#include // WASM-TODO: The method lookup would ideally be fully qualified assembly and then methodDef token. // The current approach has limitations with overloaded methods. extern "C" void LookupMethodByName(const char* fullQualifiedTypeName, const char* methodName, MethodDesc** ppMD); extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* args, size_t argSize, int8_t* ret); +static MethodDesc* MD_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid = nullptr; +static void Call_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid(void * arg0) +{ + int64_t args[1] = { (int64_t)arg0 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid) + { + LookupMethodByName("System.GC, System.Private.CoreLib", "g__Callback|72_0", &MD_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid); + } + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr); +} + static MethodDesc* MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid = nullptr; -static void Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler() +static void Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid() { // Lazy lookup of MethodDesc for the function export scenario. if (!MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid) @@ -27,11 +40,165 @@ static void Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJo extern "C" void SystemJS_ExecuteBackgroundJobCallback() { - Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler(); + Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid(); +} + +static MethodDesc* MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid = nullptr; +static void Call_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid(void * arg0, void * arg1, void * arg2, int32_t arg3, int64_t arg4) +{ + int64_t args[5] = { (int64_t)arg0, (int64_t)arg1, (int64_t)arg2, (int64_t)arg3, (int64_t)arg4 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid) + { + LookupMethodByName("System.GC, System.Private.CoreLib", "ConfigCallback", &MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid); + } + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid, (int8_t*)args, sizeof(args), nullptr); +} + +static MethodDesc* MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid = nullptr; +static void Call_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid(void * arg0, void * arg1) +{ + int64_t args[2] = { (int64_t)arg0, (int64_t)arg1 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid) + { + LookupMethodByName("System.Globalization.CalendarData, System.Private.CoreLib", "EnumCalendarInfoCallback", &MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid); + } + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr); +} + +static MethodDesc* MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32 = nullptr; +static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32(void * arg0) +{ + int64_t args[1] = { (int64_t)arg0 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32) + { + LookupMethodByName("Internal.Runtime.InteropServices.ComActivator, System.Private.CoreLib", "GetClassFactoryForTypeInternal", &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32); + } + + int32_t result; + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + return result; +} + +static MethodDesc* MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 = nullptr; +static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32(void * arg0, void * arg1, void * arg2, void * arg3, void * arg4, void * arg5) +{ + int64_t args[6] = { (int64_t)arg0, (int64_t)arg1, (int64_t)arg2, (int64_t)arg3, (int64_t)arg4, (int64_t)arg5 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32) + { + LookupMethodByName("Internal.Runtime.InteropServices.ComponentActivator, System.Private.CoreLib", "GetFunctionPointer", &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32); + } + + int32_t result; + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + return result; +} + +static MethodDesc* MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32 = nullptr; +static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32(void * arg0, void * arg1, void * arg2) +{ + int64_t args[3] = { (int64_t)arg0, (int64_t)arg1, (int64_t)arg2 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32) + { + LookupMethodByName("Internal.Runtime.InteropServices.ComponentActivator, System.Private.CoreLib", "LoadAssembly", &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32); + } + + int32_t result; + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + return result; +} + +static MethodDesc* MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 = nullptr; +static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32(void * arg0, void * arg1, void * arg2, void * arg3, void * arg4, void * arg5) +{ + int64_t args[6] = { (int64_t)arg0, (int64_t)arg1, (int64_t)arg2, (int64_t)arg3, (int64_t)arg4, (int64_t)arg5 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32) + { + LookupMethodByName("Internal.Runtime.InteropServices.ComponentActivator, System.Private.CoreLib", "LoadAssemblyAndGetFunctionPointer", &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32); + } + + int32_t result; + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + return result; +} + +static MethodDesc* MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32 = nullptr; +static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32(void * arg0, void * arg1, void * arg2, void * arg3, void * arg4, void * arg5) +{ + int64_t args[6] = { (int64_t)arg0, (int64_t)arg1, (int64_t)arg2, (int64_t)arg3, (int64_t)arg4, (int64_t)arg5 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32) + { + LookupMethodByName("Internal.Runtime.InteropServices.ComponentActivator, System.Private.CoreLib", "LoadAssemblyBytes", &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32); + } + + int32_t result; + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + return result; +} + +static MethodDesc* MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32 = nullptr; +static int32_t Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32(void * arg0, void * arg1) +{ + int64_t args[2] = { (int64_t)arg0, (int64_t)arg1 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32) + { + LookupMethodByName("System.Runtime.InteropServices.TypeMapLazyDictionary, System.Private.CoreLib", "NewExternalTypeEntry", &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32); + } + + int32_t result; + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + return result; +} + +static MethodDesc* MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32 = nullptr; +static int32_t Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32(void * arg0, void * arg1) +{ + int64_t args[2] = { (int64_t)arg0, (int64_t)arg1 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32) + { + LookupMethodByName("System.Runtime.InteropServices.TypeMapLazyDictionary, System.Private.CoreLib", "NewProxyTypeEntry", &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32); + } + + int32_t result; + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + return result; +} + +static MethodDesc* MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32 = nullptr; +static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32(void * arg0) +{ + int64_t args[1] = { (int64_t)arg0 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32) + { + LookupMethodByName("Internal.Runtime.InteropServices.ComActivator, System.Private.CoreLib", "RegisterClassForTypeInternal", &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32); + } + + int32_t result; + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + return result; } static MethodDesc* MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid = nullptr; -static void Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler() +static void Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid() { // Lazy lookup of MethodDesc for the function export scenario. if (!MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid) @@ -43,13 +210,41 @@ static void Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler extern "C" void SystemJS_ExecuteTimerCallback() { - Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler(); + Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid(); +} + +static MethodDesc* MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32 = nullptr; +static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32(void * arg0) +{ + int64_t args[1] = { (int64_t)arg0 }; + + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32) + { + LookupMethodByName("Internal.Runtime.InteropServices.ComActivator, System.Private.CoreLib", "UnregisterClassForTypeInternal", &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32); + } + + int32_t result; + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + return result; } extern const ReverseThunkMapEntry g_ReverseThunks[] = { - { 100678287, { &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler } }, - { 100678363, { &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler } }, + { 100664517, { &MD_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid } }, + { 100678287, { &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid } }, + { 100664508, { &MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid } }, + { 100674589, { &MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid } }, + { 100704729, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32 } }, + { 100704719, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 } }, + { 100704716, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32 } }, + { 100704715, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 } }, + { 100704718, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32 } }, + { 100693346, { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32 } }, + { 100693347, { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32 } }, + { 100704730, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32 } }, + { 100678363, { &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid } }, + { 100704731, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32 } } }; const size_t g_ReverseThunksCount = sizeof(g_ReverseThunks) / sizeof(g_ReverseThunks[0]); diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 742e3a947da208..fe0184985c7cd1 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -618,7 +618,12 @@ namespace if (!GetSignatureKey(sig, keyBuffer, keyBufferLen)) return NULL; - return LookupThunk(keyBuffer); + void* thunk = LookupThunk(keyBuffer); +#ifdef _DEBUG + if (thunk == NULL) + printf("WASM calli missing for key: %s\n", keyBuffer); +#endif + return thunk; } // TODO: This hashing function should be replaced. diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj index 3fc33e370b1558..7ce3aef29217af 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj @@ -73,7 +73,7 @@ diff --git a/src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs b/src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs new file mode 100644 index 00000000000000..48332b744394ab --- /dev/null +++ b/src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs @@ -0,0 +1,282 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System; +using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.Build.Tasks; +using WasmAppBuilder; +using JoinedString; + +#pragma warning disable CA1067 +#pragma warning disable CS0649 +internal sealed class PInvoke : IEquatable +#pragma warning restore CA1067 +{ + public PInvoke(string entryPoint, string module, MethodInfo method, bool wasmLinkage) + { + EntryPoint = entryPoint; + Module = module; + Method = method; + WasmLinkage = wasmLinkage; + } + + public string EntryPoint; + public string Module; + public MethodInfo Method; + public bool Skip; + public bool WasmLinkage; + + public bool Equals(PInvoke? other) + => other != null && + string.Equals(EntryPoint, other.EntryPoint, StringComparison.Ordinal) && + string.Equals(Module, other.Module, StringComparison.Ordinal) && + string.Equals(Method.ToString(), other.Method.ToString(), StringComparison.Ordinal); + + public override string ToString() => $"{{ EntryPoint: {EntryPoint}, Module: {Module}, Method: {Method}, Skip: {Skip} }}"; +} +#pragma warning restore CS0649 + +internal sealed class PInvokeComparer : IEqualityComparer +{ + public bool Equals(PInvoke? x, PInvoke? y) + { + if (x == null && y == null) + return true; + if (x == null || y == null) + return false; + + return x.Equals(y); + } + + public int GetHashCode(PInvoke pinvoke) + => $"{pinvoke.EntryPoint}{pinvoke.Module}{pinvoke.Method}".GetHashCode(); +} + + +internal sealed class PInvokeCollector { + private readonly Dictionary _assemblyDisableRuntimeMarshallingAttributeCache = new(); + private LogAdapter Log { get; init; } + + public PInvokeCollector(LogAdapter log) + { + Log = log; + } + + public void CollectPInvokes(List pinvokes, List callbacks, HashSet signatures, Type type) + { + foreach (var method in type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)) + { + try + { + CollectPInvokesForMethod(method); + if (DoesMethodHaveCallbacks(method, Log)) + callbacks.Add(new PInvokeCallback(method)); + } + catch (Exception ex) when (ex is not LogAsErrorException) + { + Log.Warning("WASM0001", $"Could not get pinvoke, or callbacks for method '{type.FullName}::{method.Name}' because '{ex}'"); + } + } + + if (HasAttribute(type, "System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute")) + { + var method = type.GetMethod("Invoke"); + + if (method != null) + { + string? signature = SignatureMapper.MethodToSignature(method!, Log); + if (signature == null) + throw new NotSupportedException($"Unsupported parameter type in method '{type.FullName}.{method.Name}'"); + + if (signatures.Add(signature)) + Log.LogMessage(MessageImportance.Low, $"Adding pinvoke signature {signature} for method '{type.FullName}.{method.Name}'"); + } + } + + void CollectPInvokesForMethod(MethodInfo method) + { + if ((method.Attributes & MethodAttributes.PinvokeImpl) != 0) + { + var dllimport = method.CustomAttributes.First(attr => attr.AttributeType.Name == "DllImportAttribute"); + var wasmLinkage = method.CustomAttributes.Any(attr => attr.AttributeType.Name == "WasmImportLinkageAttribute"); + var module = (string)dllimport.ConstructorArguments[0].Value!; + var entrypoint = (string)dllimport.NamedArguments.First(arg => arg.MemberName == "EntryPoint").TypedValue.Value!; + pinvokes.Add(new PInvoke(entrypoint, module, method, wasmLinkage)); + + string? signature = SignatureMapper.MethodToSignature(method, Log); + if (signature == null) + { + throw new NotSupportedException($"Unsupported parameter type in method '{type.FullName}.{method.Name}'"); + } + + if (signatures.Add(signature)) + Log.LogMessage(MessageImportance.Low, $"Adding pinvoke signature {signature} for method '{type.FullName}.{method.Name}'"); + } + } + + bool DoesMethodHaveCallbacks(MethodInfo method, LogAdapter log) + { + if (!MethodHasCallbackAttributes(method)) + return false; + + if (TryIsMethodGetParametersUnsupported(method, out string? reason)) + { + Log.Warning("WASM0001", $"Skipping callback '{method.DeclaringType!.FullName}::{method.Name}' because '{reason}'."); + return false; + } + + if (method.DeclaringType != null && HasAssemblyDisableRuntimeMarshallingAttribute(method.DeclaringType.Assembly)) + return true; + + // No DisableRuntimeMarshalling attribute, so check if the params/ret-type are + // blittable + bool isVoid = method.ReturnType.FullName == "System.Void"; + if (!isVoid && !IsBlittable(method.ReturnType, log)) + Error($"The return type '{method.ReturnType.FullName}' of pinvoke callback method '{method}' needs to be blittable."); + + foreach (var p in method.GetParameters()) + { + if (!IsBlittable(p.ParameterType, log)) + Error("Parameter types of pinvoke callback method '" + method + "' needs to be blittable."); + } + + return true; + } + + static bool MethodHasCallbackAttributes(MethodInfo method) + { + foreach (CustomAttributeData cattr in CustomAttributeData.GetCustomAttributes(method)) + { + try + { + if (cattr.AttributeType.FullName == "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute" || + cattr.AttributeType.Name == "MonoPInvokeCallbackAttribute") + { + return true; + } + } + catch + { + // Assembly not found, ignore + } + } + + return false; + } + } + + public static bool IsBlittable(Type type, LogAdapter log) => PInvokeTableGenerator.IsBlittable(type, log); + + private static void Error(string msg) => throw new LogAsErrorException(msg); + + internal static bool HasAttribute(MemberInfo element, params string[] attributeNames) => PInvokeTableGenerator.HasAttribute(element, attributeNames); + + private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotNullWhen(true)] out string? reason) + { + try + { + method.GetParameters(); + } + catch (NotSupportedException nse) + { + reason = nse.Message; + return true; + } + catch + { + // not concerned with other exceptions + } + + reason = null; + return false; + } + + private bool HasAssemblyDisableRuntimeMarshallingAttribute(Assembly assembly) + { + if (!_assemblyDisableRuntimeMarshallingAttributeCache.TryGetValue(assembly, out var value)) + { + _assemblyDisableRuntimeMarshallingAttributeCache[assembly] = value = assembly + .GetCustomAttributesData() + .Any(d => d.AttributeType.Name == "DisableRuntimeMarshallingAttribute"); + } + + return value; + } +} + +internal sealed class PInvokeCallbackComparer : IComparer +{ + public int Compare(PInvokeCallback? x, PInvokeCallback? y) + { + int compare = string.Compare(x!.Key, y!.Key, StringComparison.Ordinal); + return compare != 0 ? compare : (int)(x.Token - y.Token); + } +} + +#pragma warning disable CS0649 +internal sealed class PInvokeCallback +{ + public PInvokeCallback(MethodInfo method) + { + Method = method; + var t = method.DeclaringType!; + TypeName = t.Name!; + TypeFullName = t.FullName!; + AssemblyName = t.Module!.Assembly!.GetName()!.Name!; + Namespace = t.Namespace; + MethodName = method.Name!; + ReturnType = method.ReturnType!; + IsVoid = ReturnType.Name == "Void"; + Token = (uint)method.MetadataToken; + + // FIXME: this is a hack, we need to encode this better and allow reflection in the interp case + // but either way it needs to match the key generated in get_native_to_interp since the key is + // used to look up the interp entry function. It must be unique for each callback runtime errors + // can occur since it is used to look up the index in the wasm_native_to_interp_ftndescs and + // the signature of the interp entry function must match the native signature + // + // the key also needs to survive being encoded in C literals, if in doubt + // add something like "\U0001F412" to the key on both the managed and unmanaged side + Key = $"{MethodName}#{Method.GetParameters().Length}:{AssemblyName}:{Namespace}:{TypeName}"; + + IsExport = false; + foreach (var attr in method.CustomAttributes) + { + if (attr.AttributeType.Name == "UnmanagedCallersOnlyAttribute") + { + foreach (var arg in attr.NamedArguments) + { + if (arg.MemberName == "EntryPoint") + { + EntryPoint = arg.TypedValue.Value!.ToString(); + IsExport = true; + return; + } + } + } + } + } + + public string EntryName => $"{AssemblyName}_{Namespace}_{TypeName}_{MethodName}"; + + public ParameterInfo[] Parameters => Method.GetParameters(); + public string? EntryPoint { get; } + public MethodInfo Method { get; } + public string? EntrySymbol { get; set; } + public string AssemblyName { get; } + public string TypeName { get; } + public string TypeFullName { get; } + public string? Namespace { get;} + public string MethodName { get; } + public Type ReturnType { get;} + public bool IsExport { get; } + public bool IsVoid { get; } + public uint Token { get; } + public string Key { get; } +} +#pragma warning restore CS0649 diff --git a/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs new file mode 100644 index 00000000000000..a1c87371e14f59 --- /dev/null +++ b/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs @@ -0,0 +1,555 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Reflection; +using System.Runtime.InteropServices; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using WasmAppBuilder; +using JoinedString; + +internal sealed class PInvokeTableGenerator +{ + private readonly Dictionary _assemblyDisableRuntimeMarshallingAttributeCache = new(); + + private LogAdapter Log { get; set; } + private readonly Func _fixupSymbolName; + private readonly HashSet signatures = new(); + private readonly List pinvokes = new(); + private readonly List callbacks = new(); + private readonly PInvokeCollector _pinvokeCollector; + private readonly bool _isLibraryMode; + + public PInvokeTableGenerator(Func fixupSymbolName, LogAdapter log, bool isLibraryMode = false) + { + Log = log; + _fixupSymbolName = fixupSymbolName; + _pinvokeCollector = new(log); + _isLibraryMode = isLibraryMode; + } + + public void ScanAssembly(Assembly asm) + { + foreach (Type type in asm.GetTypes()) + _pinvokeCollector.CollectPInvokes(pinvokes, callbacks, signatures, type); + } + + public IEnumerable Generate(string[] pinvokeModules, string outputPath) + { + var modules = new SortedDictionary(StringComparer.Ordinal); + foreach (var module in pinvokeModules) + modules[module] = module; + + using TempFileName tmpFileName = new(); + using (var w = new JoinedStringStreamWriter(tmpFileName.Path, false)) + { + // WASM-TODO: + // EmitPInvokeTable(w, modules, pinvokes); + EmitNativeToInterp(w, callbacks); + } + + if (Utils.CopyIfDifferent(tmpFileName.Path, outputPath, useHash: false)) + Log.LogMessage(MessageImportance.Low, $"Generating pinvoke table to '{outputPath}'."); + else + Log.LogMessage(MessageImportance.Low, $"PInvoke table in {outputPath} is unchanged."); + + return signatures; + } + + private void EmitPInvokeTable(StreamWriter w, SortedDictionary modules, List pinvokes) + { + + foreach (var pinvoke in pinvokes) + { + if (modules.ContainsKey(pinvoke.Module)) + continue; + // Handle special modules, and add them to the list of modules + // otherwise, skip them and throw an exception at runtime if they + // are called. + if (pinvoke.WasmLinkage) + { + // WasmLinkage means we needs to import the module + modules.Add(pinvoke.Module, pinvoke.Module); + Log.LogMessage(MessageImportance.Low, $"Adding module {pinvoke.Module} for WasmImportLinkage"); + } + else if (pinvoke.Module == "*" || pinvoke.Module == "__Internal") + { + // Special case for __Internal and * modules to indicate static linking wihtout specifying the module + modules.Add(pinvoke.Module, pinvoke.Module); + Log.LogMessage(MessageImportance.Low, $"Adding module {pinvoke.Module} for static linking"); + } + } + + w.WriteLine( + $""" + // GENERATED FILE, DO NOT MODIFY (PInvokeTableGenerator.cs) + #include + #include + #include + #include + #include + #include "runtime.h" + #include "pinvoke.h" + """); + + var pinvokesGroupedByEntryPoint = pinvokes + .Where(l => modules.ContainsKey(l.Module)) + .OrderBy(l => l.EntryPoint, StringComparer.Ordinal) + .GroupBy(CEntryPoint, StringComparer.Ordinal); + var comparer = new PInvokeComparer(); + foreach (IGrouping group in pinvokesGroupedByEntryPoint) + { + var candidates = group.Distinct(comparer).ToArray(); + PInvoke first = candidates[0]; + if (ShouldTreatAsVariadic(candidates)) + { + string imports = string.Join(Environment.NewLine, + candidates.Select( + p => $" {p.Method} (in [{p.Method.DeclaringType?.Assembly.GetName().Name}] {p.Method.DeclaringType})")); + Log.Warning("WASM0001", $"Found a native function ({first.EntryPoint}) with varargs in {first.Module}." + + " Calling such functions is not supported, and will fail at runtime." + + $" Managed DllImports: {Environment.NewLine}{imports}"); + + foreach (var c in candidates) + c.Skip = true; + + continue; + } + + var decls = new HashSet(); + foreach (var candidate in candidates) + { + var decl = GenPInvokeDecl(candidate); + if (decl is null || decls.Contains(decl)) + continue; + + w.WriteLine(decl); + decls.Add(decl); + } + } + + var moduleImports = new Dictionary>(); + foreach (var module in modules.Keys) + { + static string ListRefs(IGrouping l) => + string.Join(", ", l.Select(c => c.Method.DeclaringType!.Module!.Assembly!.GetName()!.Name!).Distinct().OrderBy(n => n)); + + var imports = pinvokes + .Where(l => l.Module == module && !l.Skip) + .OrderBy(l => l.EntryPoint, StringComparer.Ordinal) + .GroupBy(d => d.EntryPoint, StringComparer.Ordinal) + .Select(l => $"{{\"{EscapeLiteral(l.Key)}\", {CEntryPoint(l.First())}}}, // {ListRefs(l)}{w.NewLine} ") + .ToList(); + + moduleImports[module] = imports; + w.Write( + $$""" + + static PinvokeImport {{_fixupSymbolName(module)}}_imports [] = { + {{string.Join("", imports)}}{NULL, NULL} + }; + + """); + } + + w.Write( + $$""" + + static PinvokeTable pinvoke_tables[] = { + {{modules.Keys.Join($",{w.NewLine} ", m => $"{{\"{EscapeLiteral(m)}\", {_fixupSymbolName(m)}_imports, {moduleImports[m].Count}}}")}} + }; + + """); + + static bool ShouldTreatAsVariadic(PInvoke[] candidates) + { + if (candidates.Length < 2) + return false; + + PInvoke first = candidates[0]; + if (TryIsMethodGetParametersUnsupported(first.Method, out _)) + return false; + + int firstNumArgs = first.Method.GetParameters().Length; + return candidates + .Skip(1) + .Any(c => !TryIsMethodGetParametersUnsupported(c.Method, out _) && + c.Method.GetParameters().Length != firstNumArgs); + } + } + + private string CEntryPoint(PInvoke pinvoke) + { + if (pinvoke.WasmLinkage) + { + // We mangle the name to avoid collisions with symbols in other modules + string namespaceName = pinvoke.Method.DeclaringType?.Namespace ?? string.Empty; + return _fixupSymbolName($"{namespaceName}#{pinvoke.Module}#{pinvoke.EntryPoint}"); + } + return _fixupSymbolName(pinvoke.EntryPoint); + } + + private static string MapType(Type t) => t.Name switch + { + "Void" => "void", + nameof(Double) => "double", + nameof(Single) => "float", + nameof(Int64) => "int64_t", + nameof(UInt64) => "uint64_t", + nameof(Int32) => "int32_t", + nameof(UInt32) => "uint32_t", + nameof(Int16) => "int32_t", + nameof(UInt16) => "uint32_t", + nameof(Char) => "int32_t", + nameof(Boolean) => "int32_t", + nameof(SByte) => "int32_t", + nameof(Byte) => "uint32_t", + nameof(IntPtr) => "void *", + nameof(UIntPtr) => "void *", + _ => PickCTypeNameForUnknownType(t) + }; + + private static string PickCTypeNameForUnknownType(Type t) + { + // Pass objects by-reference (their address by-value) + if (!t.IsValueType) + return "void *"; + // Pass pointers and function pointers by-value + else if (t.IsPointer || IsFunctionPointer(t)) + return "void *"; + else if (t.IsPrimitive) + throw new NotImplementedException("No native type mapping for type " + t); + + // https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md#function-signatures + // Any struct or union that recursively (including through nested structs, unions, and arrays) + // contains just a single scalar value and is not specified to have greater than natural alignment. + // FIXME: Handle the scenario where there are fields of struct types that contain no members + var fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (fields.Length == 1) + return MapType(fields[0].FieldType); + else + return "void *"; + } + + // FIXME: System.Reflection.MetadataLoadContext can't decode function pointer types + // https://github.com/dotnet/runtime/issues/43791 + private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotNullWhen(true)] out string? reason) + { + try + { + method.GetParameters(); + } + catch (NotSupportedException nse) + { + reason = nse.Message; + return true; + } + catch + { + // not concerned with other exceptions + } + + reason = null; + return false; + } + + private string? GenPInvokeDecl(PInvoke pinvoke) + { + var method = pinvoke.Method; + + if (TryIsMethodGetParametersUnsupported(pinvoke.Method, out string? reason)) + { + // Don't use method.ToString() or any of it's parameters, or return type + // because at least one of those are unsupported, and will throw + Log.Warning("WASM0001", $"Skipping pinvoke '{pinvoke.Method.DeclaringType!.FullName}::{pinvoke.Method.Name}' because '{reason}'."); + + pinvoke.Skip = true; + return null; + } + + var realReturnType = method.ReturnType; + var realParameterTypes = method.GetParameters().Select(p => MapType(p.ParameterType)).ToList(); + + SignatureMapper.TypeToChar(realReturnType, Log, out bool resultIsByRef); + if (resultIsByRef) { + realReturnType = typeof(void); + realParameterTypes.Insert(0, "void *"); + } + + return + $$""" + {{(pinvoke.WasmLinkage ? $"__attribute__((import_module(\"{EscapeLiteral(pinvoke.Module)}\"),import_name(\"{EscapeLiteral(pinvoke.EntryPoint)}\")))" : "")}} + {{(pinvoke.WasmLinkage ? "extern " : "")}}{{MapType(realReturnType)}} {{CEntryPoint(pinvoke)}} ({{string.Join(", ", realParameterTypes)}}); + """; + } + + private static string? EscapeLiteral(string? input) + { + if (input == null) + return null; + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < input.Length; i++) + { + char c = input[i]; + + sb.Append(c switch + { + '\\' => "\\\\", + '\"' => "\\\"", + '\n' => "\\n", + '\r' => "\\r", + '\t' => "\\t", + // take special care with surrogate pairs to avoid + // potential decoding issues in generated C literals + _ when char.IsHighSurrogate(c) && i + 1 < input.Length && char.IsLowSurrogate(input[i + 1]) + => $"\\U{char.ConvertToUtf32(c, input[++i]):X8}", + _ when char.IsControl(c) || c > 127 + => $"\\u{(int)c:X4}", + _ => c.ToString() + }); + } + + return sb.ToString(); + } + + private void EmitNativeToInterp(StreamWriter w, List callbacks) + { + // Generate native->interp entry functions + // These are called by native code, so they need to obtain + // the interp entry function/arg from a global array + // They also need to have a signature matching what the + // native code expects, which is the native signature + // of the delegate invoke in the [MonoPInvokeCallback] + // or [UnmanagedCallersOnly] attribute. + // Only blittable parameter/return types are supposed. + w.Write( + $$""" + // Licensed to the .NET Foundation under one or more agreements. + // The .NET Foundation licenses this file to you under the MIT license. + // + + // + // GENERATED FILE, DON'T EDIT + // Generated by coreclr InterpToNativeGenerator + // + + #include + + // WASM-TODO: The method lookup would ideally be fully qualified assembly and then methodDef token. + // The current approach has limitations with overloaded methods. + extern "C" void LookupMethodByName(const char* fullQualifiedTypeName, const char* methodName, MethodDesc** ppMD); + extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* args, size_t argSize, int8_t* ret); + + """); + + var callbackNames = new HashSet(); + var keys = new HashSet(); + int cb_index = 0; + callbacks = callbacks.OrderBy(c => c, new PInvokeCallbackComparer()).ToList(); + foreach (var cb in callbacks) + { + cb.EntrySymbol = FixedSymbolName(cb, Log); + + if (callbackNames.Contains(cb.EntrySymbol)) + { + Error($"Two callbacks with the same symbol '{cb.EntrySymbol}' are not supported."); + } + callbackNames.Add(cb.EntrySymbol); + if (keys.Contains(cb.Key)) + { + Error($"Two callbacks with the same Name and number of arguments '{cb.Key}' are not supported."); + } + keys.Add(cb.Key); + + // The signature of the interp entry function + // This is a gsharedvt_in signature + var entryArgs = new List(); + if (!cb.IsVoid) + { + entryArgs.Add("(int*)&result"); + } + entryArgs.AddRange(cb.Parameters.Select((_, i) => $"(int*)&arg{i}")); + entryArgs.Add($"(int*)wasm_native_to_interp_ftndescs [{cb_index}].arg"); + + var argsArgs = cb.Parameters.Length > 0 ? "(int8_t*)args, sizeof(args)" : "nullptr, 0"; + var argsDeclaration = cb.Parameters.Length > 0 + ? $"\n int64_t args[{cb.Parameters.Length}] = {{ {cb.Parameters.Join(", ", (info, i) => $"(int64_t)arg{i}")} }};\n" + : string.Empty; + var parametersDeclaration = cb.Parameters.Join(", ", (info, i) => $"{MapType(info.ParameterType)} arg{i}"); + var exportFunction = cb.IsExport ? + $$""" + + + extern "C" void {{cb.EntryPoint}}({{parametersDeclaration}}) + { + Call_{{cb.EntrySymbol}}({{cb.Parameters.Join(", ", (info, i) => $"arg{i}")}}); + } + """ : string.Empty; + w.Write( + $$""" + + static MethodDesc* MD_{{cb.EntrySymbol}} = nullptr; + static {{ + MapType(cb.ReturnType)}} Call_{{cb.EntrySymbol}}({{parametersDeclaration}}) + {{{argsDeclaration}} + // Lazy lookup of MethodDesc for the function export scenario. + if (!MD_{{cb.EntrySymbol}}) + { + LookupMethodByName("{{cb.TypeFullName}}, {{cb.AssemblyName}}", "{{cb.MethodName}}", &MD_{{cb.EntrySymbol}}); + }{{ + (!cb.IsVoid ? $"{w.NewLine}{w.NewLine} {MapType(cb.ReturnType)} result;" : "")}} + ExecuteInterpretedMethodFromUnmanaged(MD_{{cb.EntrySymbol}}, {{argsArgs}}, {{(cb.IsVoid ? "nullptr" : "(int8_t*)&result")}});{{ + (!cb.IsVoid ? $"{w.NewLine} return result;" : "")}} + }{{exportFunction}} + + """); + cb_index++; + } + + w.Write( + $$""" + + extern const ReverseThunkMapEntry g_ReverseThunks[] = + { + {{callbacks.Join($",{w.NewLine}", cb => ThunkMapEntryLine(cb, Log))}} + }; + + const size_t g_ReverseThunksCount = sizeof(g_ReverseThunks) / sizeof(g_ReverseThunks[0]); + + """); + } + + private string FixedSymbolName(PInvokeCallback cb, LogAdapter Log) + { + var paramTypes = cb.Parameters.Length > 0 ? cb.Parameters.Join("_", (info, i) => SignatureMapper.TypeToNameType(info.ParameterType, Log)).ToString() : "Void"; + var sig = $"{paramTypes}_Ret{SignatureMapper.TypeToNameType(cb.ReturnType, Log)}"; + + return _fixupSymbolName($"{cb.EntryName}_{sig}"); + } + + + private string ThunkMapEntryLine(PInvokeCallback cb, LogAdapter Log) + { + var fsName = FixedSymbolName(cb, Log); + + return $" {{ {cb.Token}, {{ &MD_{fsName}, (void*)&Call_{cb.EntrySymbol} }} }}"; + } + + private bool HasAssemblyDisableRuntimeMarshallingAttribute(Assembly assembly) + { + if (!_assemblyDisableRuntimeMarshallingAttributeCache.TryGetValue(assembly, out var value)) + { + _assemblyDisableRuntimeMarshallingAttributeCache[assembly] = value = assembly + .GetCustomAttributesData() + .Any(d => d.AttributeType.Name == "DisableRuntimeMarshallingAttribute"); + } + + return value; + } + + private static readonly Dictionary _blittableCache = new(); + + public static bool IsFunctionPointer(Type type) + { + object? bIsFunctionPointer = type.GetType().GetProperty("IsFunctionPointer")?.GetValue(type); + return (bIsFunctionPointer is bool b) && b; + } + + public static bool IsBlittable(Type type, LogAdapter log) + { + // We maintain a cache of results in order to only produce log messages the first time + // we analyze a given type. Otherwise, each (successful) use of a user-defined type + // in a callback or pinvoke would generate duplicate messages. + lock (_blittableCache) + if (_blittableCache.TryGetValue(type, out bool blittable)) + return blittable; + + bool result = IsBlittableUncached(type, log); + lock (_blittableCache) + _blittableCache[type] = result; + return result; + + static bool IsBlittableUncached(Type type, LogAdapter log) + { + if (type.IsPrimitive || type.IsByRef || type.IsPointer || type.IsEnum) + return true; + + if (IsFunctionPointer(type)) + return true; + + // HACK: SkiaSharp has pinvokes that rely on this + if (HasAttribute(type, "System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute")) + return true; + + if (type.Name == "__NonBlittableTypeForAutomatedTests__") + return false; + + if (!type.IsValueType) + { + log.InfoHigh("WASM0060", "Type {0} is not blittable: Not a ValueType", type); + return false; + } + + var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + if (!type.IsLayoutSequential && (fields.Length > 1)) + { + log.InfoHigh("WASM0061", "Type {0} is not blittable: LayoutKind is not Sequential", type); + return false; + } + + foreach (var ft in fields) + { + if (!IsBlittable(ft.FieldType, log)) + { + log.InfoHigh("WASM0062", "Type {0} is not blittable: Field {1} is not blittable", type, ft.Name); + return false; + } + // HACK: Skip literals since they're complicated + // Ideally we would block initonly fields too since the callee could mutate them, but + // we rely on being able to pass types like System.Guid which are readonly + if (ft.IsLiteral) + { + log.InfoHigh("WASM0063", "Type {0} is not blittable: Field {1} is literal", type, ft.Name); + return false; + } + } + + return true; + } + } + + public static bool HasAttribute(MemberInfo element, params string[] attributeNames) + { + foreach (CustomAttributeData cattr in CustomAttributeData.GetCustomAttributes(element)) + { + try + { + for (int i = 0; i < attributeNames.Length; ++i) + { + if (cattr.AttributeType.FullName == attributeNames[i] || + cattr.AttributeType.Name == attributeNames[i]) + { + return true; + } + } + } + catch + { + // Assembly not found, ignore + } + } + return false; + } + + private static void Error(string msg) => throw new LogAsErrorException(msg); +} diff --git a/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs b/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs index 21b47c309f1429..8fe8e13d742344 100644 --- a/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs +++ b/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs @@ -143,6 +143,17 @@ internal static class SignatureMapper _ => throw new InvalidSignatureCharException(c) }; + public static string TypeToNameType(Type t, LogAdapter log) + { + char? c = TypeToChar(t, log, out _); + if (c == null) + { + throw new InvalidSignatureCharException('?'); + } + + return CharToNameType(c.Value); + } + public static bool IsVoidSignature(string signature) => signature[0] == 'v'; } diff --git a/src/tasks/WasmAppBuilder/PInvokeCollector.cs b/src/tasks/WasmAppBuilder/mono/PInvokeCollector.cs similarity index 100% rename from src/tasks/WasmAppBuilder/PInvokeCollector.cs rename to src/tasks/WasmAppBuilder/mono/PInvokeCollector.cs diff --git a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/mono/PInvokeTableGenerator.cs similarity index 100% rename from src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs rename to src/tasks/WasmAppBuilder/mono/PInvokeTableGenerator.cs From a3f20dcacb838815ee691429bc7072aa310a55f6 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Tue, 18 Nov 2025 18:14:16 +0100 Subject: [PATCH 2/7] Do not target NETFramework anymore --- src/tasks/WasmAppBuilder/WasmAppBuilder.csproj | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj index 7ce3aef29217af..7ceadcce3f621b 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj @@ -1,10 +1,9 @@ - $(NetCoreAppToolCurrent);$(NetFrameworkToolCurrent) + $(NetCoreAppToolCurrent) enable $(NoWarn),CA1050 - $(NoWarn),CS8604,CS8602 $(NoWarn),CA1850 false true @@ -58,7 +57,7 @@ - + From 89556d8443da853109ff76e2f2bb75655632b549 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Wed, 26 Nov 2025 22:21:40 +0100 Subject: [PATCH 3/7] Update keys calculation Add fallback key --- .../vm/wasm/callhelpers-interp-to-managed.cpp | 1 - src/coreclr/vm/wasm/callhelpers-reverse.cpp | 28 ++++---- src/coreclr/vm/wasm/callhelpers.hpp | 1 + src/coreclr/vm/wasm/helpers.cpp | 70 +++++++++++++++---- .../coreclr/PInvokeCollector.cs | 2 + .../coreclr/PInvokeTableGenerator.cs | 14 +++- .../WasmAppBuilder/coreclr/SignatureMapper.cs | 2 +- 7 files changed, 87 insertions(+), 31 deletions(-) diff --git a/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp b/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp index 1e1a0ababe7505..91cd17b5a2de1a 100644 --- a/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp +++ b/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp @@ -638,7 +638,6 @@ const StringToWasmSigThunk g_wasmThunks[] = { { "viiinni", (void*)&CallFunc_I32_I32_I32_IND_IND_I32_RetVoid }, { "viin", (void*)&CallFunc_I32_I32_IND_RetVoid }, { "viinni", (void*)&CallFunc_I32_I32_IND_IND_I32_RetVoid }, - { "viin", (void*)&CallFunc_I32_I32_IND_RetVoid }, { "viinnii", (void*)&CallFunc_I32_I32_IND_IND_I32_I32_RetVoid }, { "vin", (void*)&CallFunc_I32_IND_RetVoid }, { "vini", (void*)&CallFunc_I32_IND_I32_RetVoid }, diff --git a/src/coreclr/vm/wasm/callhelpers-reverse.cpp b/src/coreclr/vm/wasm/callhelpers-reverse.cpp index ab4729811a45d0..c129bf3f8755c1 100644 --- a/src/coreclr/vm/wasm/callhelpers-reverse.cpp +++ b/src/coreclr/vm/wasm/callhelpers-reverse.cpp @@ -231,20 +231,20 @@ static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComA extern const ReverseThunkMapEntry g_ReverseThunks[] = { - { 100664517, { &MD_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid } }, - { 100678287, { &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid } }, - { 100664508, { &MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid } }, - { 100674589, { &MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid } }, - { 100704729, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32 } }, - { 100704719, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 } }, - { 100704716, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32 } }, - { 100704715, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 } }, - { 100704718, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32 } }, - { 100693346, { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32 } }, - { 100693347, { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32 } }, - { 100704730, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32 } }, - { 100678363, { &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid } }, - { 100704731, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32 } } + { 2638833076, 3863938719, { &MD_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid } } /* alternate key source: g__Callback|72_0#1:System.Private.CoreLib:System:GC */, + { 2638830566, 1336557534, { &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid } } /* alternate key source: BackgroundJobHandler#0:System.Private.CoreLib:System.Threading:ThreadPool */, + { 2638833085, 3378852959, { &MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid } } /* alternate key source: ConfigCallback#5:System.Private.CoreLib:System:GC */, + { 2638826840, 1196551088, { &MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid } } /* alternate key source: EnumCalendarInfoCallback#2:System.Private.CoreLib:System.Globalization:CalendarData */, + { 2638856344, 2613312799, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32 } } /* alternate key source: GetClassFactoryForTypeInternal#1:System.Private.CoreLib:Internal.Runtime.InteropServices:ComActivator */, + { 2638856354, 993231473, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 } } /* alternate key source: GetFunctionPointer#6:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */, + { 2638856357, 3422156547, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32 } } /* alternate key source: LoadAssembly#3:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */, + { 2638856358, 542185314, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 } } /* alternate key source: LoadAssemblyAndGetFunctionPointer#6:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */, + { 2638856355, 3765950975, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32 } } /* alternate key source: LoadAssemblyBytes#6:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */, + { 2638812175, 343912841, { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32 } } /* alternate key source: NewExternalTypeEntry#2:System.Private.CoreLib:System.Runtime.InteropServices:TypeMapLazyDictionary */, + { 2638812174, 3327247096, { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32 } } /* alternate key source: NewProxyTypeEntry#2:System.Private.CoreLib:System.Runtime.InteropServices:TypeMapLazyDictionary */, + { 2638856343, 4239234100, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32 } } /* alternate key source: RegisterClassForTypeInternal#1:System.Private.CoreLib:Internal.Runtime.InteropServices:ComActivator */, + { 2638830490, 167179540, { &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid } } /* alternate key source: TimerHandler#0:System.Private.CoreLib:System.Threading:TimerQueue */, + { 2638856342, 2150642223, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32 } } /* alternate key source: UnregisterClassForTypeInternal#1:System.Private.CoreLib:Internal.Runtime.InteropServices:ComActivator */ }; const size_t g_ReverseThunksCount = sizeof(g_ReverseThunks) / sizeof(g_ReverseThunks[0]); diff --git a/src/coreclr/vm/wasm/callhelpers.hpp b/src/coreclr/vm/wasm/callhelpers.hpp index 66cff3660a197d..ed016d770ffe2c 100644 --- a/src/coreclr/vm/wasm/callhelpers.hpp +++ b/src/coreclr/vm/wasm/callhelpers.hpp @@ -23,6 +23,7 @@ struct ReverseThunkMapValue struct ReverseThunkMapEntry { ULONG key; + ULONG fallbackKey; ReverseThunkMapValue value; }; diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index fe0184985c7cd1..d88eaf7db64f04 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -626,7 +626,25 @@ namespace return thunk; } - // TODO: This hashing function should be replaced. + ULONG CreateFallbackKey(MethodDesc* pMD) + { + _ASSERTE(pMD != nullptr); + + // the fallback key is in the form $"{MethodName}#{Method.GetParameters().Length}:{AssemblyName}:{Namespace}:{TypeName}"; + LPCUTF8 pszNamespace = nullptr; + LPCUTF8 pszName = pMD->GetMethodTable()->GetFullyQualifiedNameInfo(&pszNamespace); + MetaSig sig(pMD); + SString strFullName; + strFullName.Printf("%s#%d:%s:%s:%s", + pMD->GetName(), + sig.NumFixedArgs(), + pMD->GetAssembly()->GetSimpleName(), + pszNamespace != nullptr ? pszNamespace : "", + pszName); + + return strFullName.Hash(); + } + ULONG CreateKey(MethodDesc* pMD) { _ASSERTE(pMD != nullptr); @@ -645,30 +663,54 @@ namespace typedef MapSHash HashToReverseThunkHash; HashToReverseThunkHash* reverseThunkCache = nullptr; + HashToReverseThunkHash* reverseThunkFallbackCache = nullptr; + + HashToReverseThunkHash* CreateReverseThunkHashTable(bool fallback) + { + HashToReverseThunkHash* newTable = new HashToReverseThunkHash(); + newTable->Reallocate(g_ReverseThunksCount * HashToReverseThunkHash::s_density_factor_denominator / HashToReverseThunkHash::s_density_factor_numerator + 1); + for (size_t i = 0; i < g_ReverseThunksCount; i++) + { + newTable->Add(fallback ? g_ReverseThunks[i].fallbackKey : g_ReverseThunks[i].key, &g_ReverseThunks[i].value); + } + + HashToReverseThunkHash **ppCache = fallback ? &reverseThunkFallbackCache : &reverseThunkCache; + if (InterlockedCompareExchangeT(ppCache, newTable, nullptr) != nullptr) + { + // Another thread won the race, discard ours + delete newTable; + } + return *ppCache; + } const ReverseThunkMapValue* LookupThunk(MethodDesc* pMD) { HashToReverseThunkHash* table = VolatileLoad(&reverseThunkCache); if (table == nullptr) { - HashToReverseThunkHash* newTable = new HashToReverseThunkHash(); - newTable->Reallocate(g_ReverseThunksCount * HashToReverseThunkHash::s_density_factor_denominator / HashToReverseThunkHash::s_density_factor_numerator + 1); - for (size_t i = 0; i < g_ReverseThunksCount; i++) - { - newTable->Add(g_ReverseThunks[i].key, &g_ReverseThunks[i].value); - } - - if (InterlockedCompareExchangeT(&reverseThunkCache, newTable, nullptr) != nullptr) - { - // Another thread won the race, discard ours - delete newTable; - } - table = reverseThunkCache; + table = CreateReverseThunkHashTable(false /* fallback */); } ULONG key = CreateKey(pMD); + // Try primary key, it is based on Assembly fully qualified name and method token const ReverseThunkMapValue* thunk; + if (table->Lookup(key, &thunk)) + { + return thunk; + } + + // Try fallback key, that is based on method properties and assembly name + // The fallback is used when the assembly is trimmed and the token and assembly fully qualified name + // may change. + table = VolatileLoad(&reverseThunkFallbackCache); + if (table == nullptr) + { + table = CreateReverseThunkHashTable(true /* fallback */); + } + + key = CreateFallbackKey(pMD); + bool success = table->Lookup(key, &thunk); return success ? thunk : nullptr; } diff --git a/src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs b/src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs index 48332b744394ab..a16b489a78e6ff 100644 --- a/src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs +++ b/src/tasks/WasmAppBuilder/coreclr/PInvokeCollector.cs @@ -228,6 +228,7 @@ public PInvokeCallback(MethodInfo method) TypeName = t.Name!; TypeFullName = t.FullName!; AssemblyName = t.Module!.Assembly!.GetName()!.Name!; + AssemblyFQName = t.Module!.Assembly!.GetName()!.FullName!; Namespace = t.Namespace; MethodName = method.Name!; ReturnType = method.ReturnType!; @@ -269,6 +270,7 @@ public PInvokeCallback(MethodInfo method) public MethodInfo Method { get; } public string? EntrySymbol { get; set; } public string AssemblyName { get; } + public string AssemblyFQName { get; } public string TypeName { get; } public string TypeFullName { get; } public string? Namespace { get;} diff --git a/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs index a1c87371e14f59..a31f4db6a7adba 100644 --- a/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs @@ -321,6 +321,18 @@ _ when char.IsControl(c) || c > 127 return sb.ToString(); } + // this is eqivalent to `ULONG HashString(LPCWSTR szStr)` in CoreCLR runtime + private static uint HashString(string str) + { + uint hash = 5381; + foreach (char c in str) + { + hash = ((hash << 5) + hash) ^ (uint)c; + } + + return hash; + } + private void EmitNativeToInterp(StreamWriter w, List callbacks) { // Generate native->interp entry functions @@ -441,7 +453,7 @@ private string ThunkMapEntryLine(PInvokeCallback cb, LogAdapter Log) { var fsName = FixedSymbolName(cb, Log); - return $" {{ {cb.Token}, {{ &MD_{fsName}, (void*)&Call_{cb.EntrySymbol} }} }}"; + return $" {{ {cb.Token ^ HashString(cb.AssemblyFQName)}, {HashString(cb.Key)}, {{ &MD_{fsName}, (void*)&Call_{cb.EntrySymbol} }} }} /* alternate key source: {cb.Key} */"; } private bool HasAssemblyDisableRuntimeMarshallingAttribute(Assembly assembly) diff --git a/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs b/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs index 8fe8e13d742344..a5a5bcb2d4993f 100644 --- a/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs +++ b/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; From 270a50695005ec9725cd9924a58989c74ec27ff6 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Wed, 26 Nov 2025 23:06:44 +0100 Subject: [PATCH 4/7] Feedback Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs index a31f4db6a7adba..7f92182a3b4d98 100644 --- a/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs @@ -81,7 +81,7 @@ private void EmitPInvokeTable(StreamWriter w, SortedDictionary m } else if (pinvoke.Module == "*" || pinvoke.Module == "__Internal") { - // Special case for __Internal and * modules to indicate static linking wihtout specifying the module + // Special case for __Internal and * modules to indicate static linking without specifying the module modules.Add(pinvoke.Module, pinvoke.Module); Log.LogMessage(MessageImportance.Low, $"Adding module {pinvoke.Module} for static linking"); } From f4d47bffe90dd052800ad830824b08cc1057ff7a Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Thu, 27 Nov 2025 08:45:51 +0100 Subject: [PATCH 5/7] Feedback Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com> --- src/tasks/WasmAppBuilder/WasmAppBuilder.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj index 7ceadcce3f621b..4f4a32c3fb42a8 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj @@ -4,7 +4,7 @@ enable $(NoWarn),CA1050 - $(NoWarn),CA1850 + $(NoWarn),CA1850 false true true From c1a754f449a0889daa1a02418afb883ffcc57b39 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Thu, 27 Nov 2025 08:45:36 +0100 Subject: [PATCH 6/7] Feedback --- src/tasks/WasmAppBuilder/WasmAppBuilder.csproj | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj index 4f4a32c3fb42a8..af91f960b89293 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj @@ -45,15 +45,10 @@ - - - - - - From 2fdf1e706fe19c0cc0092d2448f5c7726e4b64e1 Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Thu, 27 Nov 2025 08:46:26 +0100 Subject: [PATCH 7/7] Update after merge --- src/coreclr/vm/wasm/callhelpers-reverse.cpp | 28 +++++++++---------- .../coreclr/PInvokeTableGenerator.cs | 4 +-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/coreclr/vm/wasm/callhelpers-reverse.cpp b/src/coreclr/vm/wasm/callhelpers-reverse.cpp index e53f9607540273..2b0dd80e737923 100644 --- a/src/coreclr/vm/wasm/callhelpers-reverse.cpp +++ b/src/coreclr/vm/wasm/callhelpers-reverse.cpp @@ -24,7 +24,7 @@ static void Call_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g_ { LookupMethodByName("System.GC, System.Private.CoreLib", "g__Callback|72_0", &MD_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid); } - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid); } static MethodDesc* MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid = nullptr; @@ -35,7 +35,7 @@ static void Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJo { LookupMethodByName("System.Threading.ThreadPool, System.Private.CoreLib", "BackgroundJobHandler", &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid); } - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, nullptr, 0, nullptr, (PCODE)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, nullptr, 0, nullptr, (PCODE)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid); } extern "C" void SystemJS_ExecuteBackgroundJobCallback() @@ -53,7 +53,7 @@ static void Call_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32 { LookupMethodByName("System.GC, System.Private.CoreLib", "ConfigCallback", &MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid); } - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid, (int8_t*)args, sizeof(args), nullptr); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid); } static MethodDesc* MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid = nullptr; @@ -66,7 +66,7 @@ static void Call_System_Private_CoreLib_System_Globalization_CalendarData_EnumCa { LookupMethodByName("System.Globalization.CalendarData, System.Private.CoreLib", "EnumCalendarInfoCallback", &MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid); } - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid); } static MethodDesc* MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32 = nullptr; @@ -81,7 +81,7 @@ static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComA } int32_t result; - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result, (PCODE)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32); return result; } @@ -97,7 +97,7 @@ static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_Comp } int32_t result; - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result, (PCODE)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32); return result; } @@ -113,7 +113,7 @@ static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_Comp } int32_t result; - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result, (PCODE)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32); return result; } @@ -129,7 +129,7 @@ static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_Comp } int32_t result; - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result, (PCODE)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32); return result; } @@ -145,7 +145,7 @@ static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_Comp } int32_t result; - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result, (PCODE)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32); return result; } @@ -161,7 +161,7 @@ static int32_t Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMa } int32_t result; - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result, (PCODE)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32); return result; } @@ -177,7 +177,7 @@ static int32_t Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMa } int32_t result; - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result, (PCODE)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32); return result; } @@ -193,7 +193,7 @@ static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComA } int32_t result; - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result, (PCODE)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32); return result; } @@ -205,7 +205,7 @@ static void Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler { LookupMethodByName("System.Threading.TimerQueue, System.Private.CoreLib", "TimerHandler", &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid); } - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, nullptr, 0, nullptr, (PCODE)&Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, nullptr, 0, nullptr, (PCODE)&Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid); } extern "C" void SystemJS_ExecuteTimerCallback() @@ -225,7 +225,7 @@ static int32_t Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComA } int32_t result; - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result); + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32, (int8_t*)args, sizeof(args), (int8_t*)&result, (PCODE)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32); return result; } diff --git a/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs index 7f92182a3b4d98..2e9e92ac51cf73 100644 --- a/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs @@ -359,7 +359,7 @@ private void EmitNativeToInterp(StreamWriter w, List callbacks) // WASM-TODO: The method lookup would ideally be fully qualified assembly and then methodDef token. // The current approach has limitations with overloaded methods. extern "C" void LookupMethodByName(const char* fullQualifiedTypeName, const char* methodName, MethodDesc** ppMD); - extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* args, size_t argSize, int8_t* ret); + extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* args, size_t argSize, int8_t* ret, PCODE callerIp); """); @@ -419,7 +419,7 @@ private void EmitNativeToInterp(StreamWriter w, List callbacks) LookupMethodByName("{{cb.TypeFullName}}, {{cb.AssemblyName}}", "{{cb.MethodName}}", &MD_{{cb.EntrySymbol}}); }{{ (!cb.IsVoid ? $"{w.NewLine}{w.NewLine} {MapType(cb.ReturnType)} result;" : "")}} - ExecuteInterpretedMethodFromUnmanaged(MD_{{cb.EntrySymbol}}, {{argsArgs}}, {{(cb.IsVoid ? "nullptr" : "(int8_t*)&result")}});{{ + ExecuteInterpretedMethodFromUnmanaged(MD_{{cb.EntrySymbol}}, {{argsArgs}}, {{(cb.IsVoid ? "nullptr" : "(int8_t*)&result")}}, (PCODE)&Call_{{cb.EntrySymbol}});{{ (!cb.IsVoid ? $"{w.NewLine} return result;" : "")}} }{{exportFunction}}