From 23282f956710792d6a1fdfac891e41cea1fc2deb Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 24 Jun 2026 14:38:55 -0700 Subject: [PATCH 1/7] Changes to fix corinfo helper calls, and to make the failure case more debuggable --- src/coreclr/vm/jitinterface.cpp | 3 ++ src/coreclr/vm/wasm/helpers.cpp | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index d73d250b409c2e..1761082a14c2fa 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -10869,6 +10869,9 @@ void CEECodeGenInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN { helperMD = GetMethodDescForILBasedDynamicJitHelper(dynamicFtnNum); _ASSERTE(PortableEntryPoint::GetMethodDesc((PCODE)targetAddr) == helperMD); +#ifdef FEATURE_READYTORUN + _ASSERTE(PortableEntryPoint::GetActualCode((PCODE)targetAddr) != NULL); +#endif } #else // !FEATURE_PORTABLE_ENTRYPOINTS diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 09825c48bc174f..40086ca0762299 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -140,6 +140,40 @@ namespace ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result); return (int32_t)result; } + FCDECL1(int32_t, CallInterpreter_D64_RetI32, double); + WASM_CALLABLE_FUNC_2(int32_t, CallInterpreter_D64_RetI32, double arg0, PCODE portableEntrypoint) + { + struct + { + TransitionBlock block; + double args[1]; + } transitionBlock; + transitionBlock.block.m_ReturnAddress = 0; + transitionBlock.block.m_StackPointer = callersStackPointer; + transitionBlock.args[0] = arg0; + static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block"); + + void * result = NULL; + ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result); + return (int32_t)result; + } + FCDECL1(int64_t, CallInterpreter_D64_RetI64, double); + WASM_CALLABLE_FUNC_2(int64_t, CallInterpreter_D64_RetI64, double arg0, PCODE portableEntrypoint) + { + struct + { + TransitionBlock block; + double args[1]; + } transitionBlock; + transitionBlock.block.m_ReturnAddress = 0; + transitionBlock.block.m_StackPointer = callersStackPointer; + transitionBlock.args[0] = arg0; + static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block"); + + int64_t result = NULL; + ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result); + return result; + } FCDECL2(int32_t, CallInterpreter_I32_I32_RetI32, int32_t, int32_t); WASM_CALLABLE_FUNC_3(int32_t, CallInterpreter_I32_I32_RetI32, int32_t arg0, int32_t arg1, PCODE portableEntrypoint) { @@ -158,6 +192,24 @@ namespace ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result); return (int32_t)result; } + FCDECL2(int32_t, CallInterpreter_I32_S8_RetI32, int32_t, int8_t*); + WASM_CALLABLE_FUNC_3(int32_t, CallInterpreter_I32_S8_RetI32, int32_t arg0, int8_t* arg1, PCODE portableEntrypoint) + { + struct + { + TransitionBlock block; + int64_t args[2]; + } transitionBlock; + transitionBlock.block.m_ReturnAddress = 0; + transitionBlock.block.m_StackPointer = callersStackPointer; + transitionBlock.args[0] = (int64_t)arg0; + memcpy(&transitionBlock.args[1], arg1, 8); + static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block"); + + void * result = NULL; + ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result); + return (int32_t)result; + } FCDECL3(int32_t, CallInterpreter_I32_I32_I32_RetI32, int32_t, int32_t, int32_t); WASM_CALLABLE_FUNC_4(int32_t, CallInterpreter_I32_I32_I32_RetI32, int32_t arg0, int32_t arg1, int32_t arg2, PCODE portableEntrypoint) { @@ -304,6 +356,9 @@ const StringToWasmSigThunk g_wasmPortableEntryPointThunks[] = { { "Iiiiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_I32_RetI32 }, { "Iiiiiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_I32_I32_RetI32 }, { "Iiiiiiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_I32_I32_I32_RetI32 }, + { "Iidp", (void*)&CallInterpreter_D64_RetI32 }, + { "Ildp", (void*)&CallInterpreter_D64_RetI64 }, + { "IiiS8p", (void*)&CallInterpreter_I32_S8_RetI32 } }; const size_t g_wasmPortableEntryPointThunksCount = sizeof(g_wasmPortableEntryPointThunks) / sizeof(g_wasmPortableEntryPointThunks[0]); From 18d8ed4a22461a1ad6b795c58dd92a0602c0ef88 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 24 Jun 2026 14:40:57 -0700 Subject: [PATCH 2/7] Basic support for Generic Lookups --- eng/native/configurecompiler.cmake | 9 +- .../ReadyToRun/DelayLoadHelperImport.cs | 10 +- .../WasmImportThunkPortableEntrypoint.cs | 8 +- src/coreclr/vm/CMakeLists.txt | 4 + src/coreclr/vm/cgensys.h | 4 + src/coreclr/vm/prestub.cpp | 21 +- src/coreclr/vm/readytoruninfo.h | 8 + src/coreclr/vm/wasm/asmconstants.h | 50 +++++ src/coreclr/vm/wasm/dynamichelpers.S | 103 ++++++++++ src/coreclr/vm/wasm/helpers.cpp | 182 +++++++++++++++++- 10 files changed, 379 insertions(+), 20 deletions(-) create mode 100644 src/coreclr/vm/wasm/dynamichelpers.S diff --git a/eng/native/configurecompiler.cmake b/eng/native/configurecompiler.cmake index ee9d2b574c543d..0969caf1fb98e8 100644 --- a/eng/native/configurecompiler.cmake +++ b/eng/native/configurecompiler.cmake @@ -1133,7 +1133,14 @@ if (CLR_CMAKE_HOST_WIN32) message(FATAL_ERROR "MC not found") endif() -elseif (NOT CLR_CMAKE_HOST_BROWSER AND NOT CLR_CMAKE_HOST_WASI) +elseif (CLR_CMAKE_HOST_BROWSER OR CLR_CMAKE_HOST_WASI) + # The wasm toolchains (emscripten / wasi-sdk) use clang, which can assemble + # preprocessed (.S) wasm assembly files directly. + set (CMAKE_ASM_COMPILER_VERSION "${CMAKE_C_COMPILER_VERSION}") + + enable_language(ASM) + +else() # This is a workaround for upstream issue: https://gitlab.kitware.com/cmake/cmake/-/issues/22995. # # In Clang.cmake, the decision to use single or double hyphen for target and gcc-toolchain diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperImport.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperImport.cs index 668af18398026c..0785747f40e9b7 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperImport.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperImport.cs @@ -41,15 +41,7 @@ public DelayLoadHelperImport( _useJumpableStub = useJumpableStub; if (factory.Target.Architecture == TargetArchitecture.Wasm32) { - if (instanceSignature is GenericLookupSignature) - { - // Generic lookups are resolved via eager fixups and don't need import thunks - _delayLoadHelper = null; - } - else - { - _delayLoadHelper = factory.WasmImportThunkPortableEntrypoint(this); - } + _delayLoadHelper = factory.WasmImportThunkPortableEntrypoint(this); } else { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunkPortableEntrypoint.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunkPortableEntrypoint.cs index b11a7115897b6e..22971bdf245370 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunkPortableEntrypoint.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunkPortableEntrypoint.cs @@ -57,14 +57,14 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer private static readonly WasmSignature _genericLookupSignature32Bit = new WasmSignature( new WasmFuncType( - new WasmResultType(new[] { WasmValueType.I32, WasmValueType.I32 }), + new WasmResultType(new[] { WasmValueType.I32, WasmValueType.I32, WasmValueType.I32 }), new WasmResultType(new[] { WasmValueType.I32 })), - "iii"); + "iip"); private static readonly WasmSignature _genericLookupSignature64Bit = new WasmSignature( new WasmFuncType( - new WasmResultType(new[] { WasmValueType.I64, WasmValueType.I64 }), + new WasmResultType(new[] { WasmValueType.I64, WasmValueType.I64, WasmValueType.I64 }), new WasmResultType(new[] { WasmValueType.I64 })), - "lll"); + "llp"); public override ObjectData GetData(NodeFactory factory, System.Boolean relocsOnly = false) { diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index bd14df4d4364f1..8ff1480d10ae96 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -943,6 +943,10 @@ elseif(CLR_CMAKE_TARGET_ARCH_RISCV64) elseif(CLR_CMAKE_TARGET_ARCH_WASM) set(VM_HEADERS_WKS_ARCH_ASM ${ARCH_SOURCES_DIR}/entrypoints.h + ${ARCH_SOURCES_DIR}/asmconstants.h + ) + set(VM_SOURCES_WKS_ARCH_ASM + ${ARCH_SOURCES_DIR}/dynamichelpers.S ) set(VM_SOURCES_WKS_ARCH ${RUNTIME_DIR}/${ARCH_SOURCES_DIR}/writebarriers.cpp diff --git a/src/coreclr/vm/cgensys.h b/src/coreclr/vm/cgensys.h index 92636fc0cdc2ec..3f32aca5cdcecb 100644 --- a/src/coreclr/vm/cgensys.h +++ b/src/coreclr/vm/cgensys.h @@ -68,7 +68,11 @@ extern "C" PCODE STDCALL DelayLoad_MethodCall(TransitionBlock* pTransitionBlock, extern "C" void STDCALL DelayLoad_MethodCall(); #endif +#ifdef TARGET_WASM +extern "C" SIZE_T STDCALL DelayLoad_Helper(TransitionBlock* pTransitionBlock, READYTORUN_IMPORT_THUNK_PORTABLE_ENTRYPOINT* pImportThunkEntry, uint8_t *moduleBase, int32_t rvaOfModuleFixup); +#else extern "C" void STDCALL DelayLoad_Helper(); +#endif extern "C" void STDCALL DelayLoad_Helper_Obj(); extern "C" void STDCALL DelayLoad_Helper_ObjObj(); #endif diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 17cff51ae1a062..e2d162d4c8bf92 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -3361,7 +3361,17 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR RVA rva = pNativeImage->GetDataRva((TADDR)pCell); - PTR_READYTORUN_IMPORT_SECTION pImportSection = pModule->GetImportSectionFromIndex(sectionIndex); + PTR_READYTORUN_IMPORT_SECTION pImportSection; + if (sectionIndex != (DWORD)-1) + { + pImportSection = pModule->GetImportSectionFromIndex(sectionIndex); + _ASSERTE(pImportSection == pModule->GetImportSectionForRVA(rva)); + } + else + { + pImportSection = pModule->GetImportSectionForRVA(rva); + } + _ASSERTE(pImportSection == pModule->GetImportSectionForRVA(rva)); _ASSERTE(pImportSection->EntrySize == sizeof(TADDR)); @@ -3392,6 +3402,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR switch (kind) { +#ifndef TARGET_WASM case READYTORUN_FIXUP_NewObject: th = ZapSig::DecodeType(pModule, pInfoModule, pBlob); th.AsMethodTable()->EnsureInstanceActive(); @@ -3443,7 +3454,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR pMD->EnsureActive(); } break; - +#endif // !TARGET_WASM case READYTORUN_FIXUP_ThisObjDictionaryLookup: case READYTORUN_FIXUP_TypeDictionaryLookup: case READYTORUN_FIXUP_MethodDictionaryLookup: @@ -3464,6 +3475,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR { switch (kind) { +#ifndef TARGET_WASM case READYTORUN_FIXUP_IsInstanceOf: case READYTORUN_FIXUP_ChkCast: { @@ -3553,7 +3565,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR } } break; - +#endif // !TARGET_WASM default: UNREACHABLE(); } @@ -3577,6 +3589,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR { switch (kind) { +#ifndef TARGET_WASM case READYTORUN_FIXUP_NewObject: { bool fHasSideEffectsUnused; @@ -3650,7 +3663,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR } } break; - +#endif // !TARGET_WASM case READYTORUN_FIXUP_ThisObjDictionaryLookup: case READYTORUN_FIXUP_TypeDictionaryLookup: case READYTORUN_FIXUP_MethodDictionaryLookup: diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index 8127e05f6385fd..a5ab695970b2d6 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -479,6 +479,14 @@ struct GenericDictionaryDynamicHelperStubData GenericHandleArgs *HandleArgs; }; +#ifdef FEATURE_PORTABLE_ENTRYPOINTS +struct GenericDictionaryDynamicHelperStubData_PortableEntryPoint +{ + PCODE HelperFunctionTableIndex; + GenericDictionaryDynamicHelperStubData stubData; +}; +#endif + class ReadyToRunLoadedImage { TADDR m_pImageBase; diff --git a/src/coreclr/vm/wasm/asmconstants.h b/src/coreclr/vm/wasm/asmconstants.h index c43f599357ea26..f1520b4eac4b4d 100644 --- a/src/coreclr/vm/wasm/asmconstants.h +++ b/src/coreclr/vm/wasm/asmconstants.h @@ -6,5 +6,55 @@ // Be sure to rebuild clr/src/vm/ceemain.cpp after changing this file, to // ensure that the constants match the expected C/C++ values +#ifndef ASMCONSTANTS_C_ASSERT +#define ASMCONSTANTS_C_ASSERT(cond) +#endif + +#ifndef ASMCONSTANTS_RUNTIME_ASSERT +#define ASMCONSTANTS_RUNTIME_ASSERT(cond) +#endif + +// Some constants are different in _DEBUG builds. This macro factors out ifdefs from below. +#ifdef _DEBUG +#define DBG_FRE(dbg,fre) dbg +#else +#define DBG_FRE(dbg,fre) fre +#endif + #define DynamicHelperFrameFlags_ObjectArg 1 #define DynamicHelperFrameFlags_ObjectArg2 2 + +#define OFFSETOF__MethodTable__m_pPerInstInfo DBG_FRE(0x24, 0x20) +ASMCONSTANTS_C_ASSERT(OFFSETOF__MethodTable__m_pPerInstInfo + == offsetof(MethodTable, m_pPerInstInfo)); + +#define OFFSETOF__InstantiatedMethodDesc__m_pPerInstInfo DBG_FRE(0x28, 0x14) +ASMCONSTANTS_C_ASSERT(OFFSETOF__InstantiatedMethodDesc__m_pPerInstInfo + == offsetof(InstantiatedMethodDesc, m_pPerInstInfo)); + +#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__stubData 0x04 +ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__stubData + == offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData)); + +#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SecondIndir 0x4 +ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SecondIndir + == offsetof(GenericDictionaryDynamicHelperStubData, SecondIndir) + offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData)); + +#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__LastIndir 0x8 +ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__LastIndir + == offsetof(GenericDictionaryDynamicHelperStubData, LastIndir) + offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData)); + +#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SizeOffset 0xC +ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SizeOffset + == offsetof(GenericDictionaryDynamicHelperStubData, SizeOffset) + offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData)); + +#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SlotOffset 0x10 +ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SlotOffset + == offsetof(GenericDictionaryDynamicHelperStubData, SlotOffset) + offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData)); + +#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__HandleArgs 0x14 +ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__HandleArgs + == offsetof(GenericDictionaryDynamicHelperStubData, HandleArgs) + offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData)); + +#undef ASMCONSTANTS_RUNTIME_ASSERT +#undef ASMCONSTANTS_C_ASSERT diff --git a/src/coreclr/vm/wasm/dynamichelpers.S b/src/coreclr/vm/wasm/dynamichelpers.S new file mode 100644 index 00000000000000..d6a3c2ed72c203 --- /dev/null +++ b/src/coreclr/vm/wasm/dynamichelpers.S @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "asmconstants.h" + +.size g_pClassWithSlotAndModule, 4 +.size g_pMethodWithSlotAndModule, 4 + + +.functype DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull +.globl DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull +.type DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull,@function +DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull: + .functype DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull (i32, i32, i32) -> (i32) + .local i32 + block + block + local.get 1 // Load the GenericContext + i32.load OFFSETOF__MethodTable__m_pPerInstInfo + local.get 2 // Load the GenericClassDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SecondIndir + i32.add + i32.load 0 // Load the SecondDir indirection + local.tee 3 // Store the loaded indirection into local 3, but keep it on the stack as well + local.get 2 // Load the GenericClassDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SizeOffset + i32.add + i32.load 0 // Load the size of the actual GenericDictionary on the Class + local.get 2 // Load the GenericClassDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SlotOffset + i32.lt_s + br_if 0 // Branch to the logic to call the helper function if the size of the actual GenericDictionary on the Class is less than the SlotOffset + local.get 3 // Load the SecondDir indirection + local.get 2 // Load the GenericClassDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__LastIndir + i32.add + i32.load 0 // Load the last indirection + local.tee 3 // Store the loaded indirection into local 3, but keep it on the stack as well + br_if 1 // Branch to return if the result is not null + end_block + local.get 0 // Load the stack pointer + local.get 1 // Load the GenericContext + local.get 2 // Load the GenericClassDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__HandleArgs + i32.const 0 + i32.load g_pClassWithSlotAndModule + local.tee 2 + local.get 2 + i32.load 0 + call_indirect __indirect_function_table, (i32, i32, i32, i32) -> (i32) + local.set 3 + end_block + local.get 3 // Return the result. + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull +.globl DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull +.type DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull,@function +DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull: + .functype DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull (i32, i32, i32) -> (i32) + .local i32 + block + block + local.get 1 // Load the GenericContext + i32.load OFFSETOF__InstantiatedMethodDesc__m_pPerInstInfo + local.tee 3 // Store the loaded GenericDictionary into local 3, but keep it on the stack as well + local.get 2 // Load the GenericMethodDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SizeOffset + i32.add + i32.load 0 // Load the size of the actual GenericDictionary on the Method + local.get 2 // Load the GenericMethodDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SlotOffset + i32.lt_s + br_if 0 // Branch to the logic to call the helper function if the size of the actual GenericDictionary on the Method is less than the SlotOffset + local.get 3 // Load the GenericDictionary + local.get 2 // Load the GenericMethodDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__LastIndir + i32.add + i32.load 0 // Load the last indirection + local.tee 3 // Store the loaded indirection into local 3, but keep it on the stack as well + br_if 1 // Branch to return if the result is not null + end_block + local.get 0 // Load the stack pointer + local.get 1 // Load the GenericContext + local.get 2 // Load the GenericMethodDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__HandleArgs + i32.const 0 + i32.load g_pMethodWithSlotAndModule + local.tee 2 + local.get 2 + i32.load 0 + call_indirect __indirect_function_table, (i32, i32, i32, i32) -> (i32) + local.set 3 + end_block + local.get 3 // Return the result. + return + end_function diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 40086ca0762299..9cd5db7231a9d3 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -423,10 +423,29 @@ extern "C" __attribute__((naked)) PCODE STDCALL DelayLoad_MethodCall(TransitionB "global.set __stack_pointer\n" "return" :: "i" (DelayLoad_MethodCallImpl)); } +extern "C" SIZE_T STDCALL DynamicHelperWorker(TransitionBlock * pTransitionBlock, TADDR * pCell, DWORD sectionIndex, Module * pModule, INT frameFlags); -extern "C" void STDCALL DelayLoad_Helper() +extern "C" SIZE_T STDCALL DelayLoad_HelperImpl(TransitionBlock* pTransitionBlock, READYTORUN_IMPORT_THUNK_PORTABLE_ENTRYPOINT* pImportThunkEntry, uint8_t *moduleBase, int32_t rvaOfModuleFixup, INT frameFlags) { - PORTABILITY_ASSERT("DelayLoad_Helper is not implemented on wasm"); + Module** ppModule = (Module**)(moduleBase + rvaOfModuleFixup); + return DynamicHelperWorker(pTransitionBlock, (TADDR*)(moduleBase + pImportThunkEntry->RelocOffset), (DWORD)-1, *ppModule, frameFlags); +} + +extern "C" __attribute__((naked)) SIZE_T STDCALL DelayLoad_Helper(TransitionBlock* pTransitionBlock, READYTORUN_IMPORT_THUNK_PORTABLE_ENTRYPOINT* pImportThunkEntry, uint8_t *moduleBase, int32_t rvaOfModuleFixup) +{ + asm ("local.get 0\n" /* Capture pTransitionBlock onto the stack for calling DelayLoad_MethodCallImpl function. This also happens to be the callersFramePointer */ + "local.get 0\n" /* Capture callersFramePointer onto the stack for setting the __stack_pointer */ + "global.get __stack_pointer\n" /* Get current value of stack global */ + "local.set 0\n" /* Overwrite local 0 with the previous __stack_pointer value so it can be restored after the call */ + "global.set __stack_pointer\n" /* Set stack global to the initial value of callersFramePointer, which is the current stack pointer for the interpreter call */ + "local.get 1\n" /* Load pImportThunkEntry argument onto the stack for calling DelayLoad_MethodCallImpl function*/ + "local.get 2\n" /* Load moduleBase argument onto the stack for calling DelayLoad_MethodCallImpl function*/ + "local.get 3\n" /* Load rvaOfModuleFixup argument onto the stack for calling DelayLoad_MethodCallImpl function*/ + "i32.const 0\n" /* Load frameFlags argument onto the stack for calling DelayLoad_MethodCallImpl function. For this variant we want 0 as the flag */ + "call %0\n" /* Call the actual implementation function */ + "local.get 0\n" /* Reload the saved previous __stack_pointer value for restoration into the stack global */ + "global.set __stack_pointer\n" + "return" :: "i" (DelayLoad_HelperImpl)); } extern "C" void STDCALL DelayLoad_Helper_Obj() @@ -439,6 +458,165 @@ extern "C" void STDCALL DelayLoad_Helper_ObjObj() PORTABILITY_ASSERT("DelayLoad_Helper_ObjObj is not implemented on wasm"); } +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull(); +#include "readytoruninfo.h" +#include "jitinterface.h" +#include "loaderallocator.hpp" + + +extern "C" PCODE g_pMethodWithSlotAndModule; +extern "C" PCODE g_pClassWithSlotAndModule; + +PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, CORINFO_RUNTIME_LOOKUP * pLookup, DWORD dictionaryIndexAndSlot, Module * pModule) +{ + STANDARD_VM_CONTRACT; + + AllocMemTracker amTracker; + + PCODE helperAddress = GetDictionaryLookupHelper(pLookup->helper); + + WORD slotOffset = (WORD)(dictionaryIndexAndSlot & 0xFFFF) * sizeof(Dictionary*); + + // It's available only via the run-time helper function + PCODE helper = (PCODE)NULL; + if (pLookup->indirections == CORINFO_USEHELPER) + { + ThrowHR(COR_E_NOTSUPPORTED); + /* + GenericHandleArgs * pArgs = (GenericHandleArgs *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericHandleArgs)))); + pArgs->dictionaryIndexAndSlot = dictionaryIndexAndSlot; + pArgs->signature = pLookup->signature; + pArgs->module = (CORINFO_MODULE_HANDLE)pModule; + PCODE result = EmitDynamicHelperWithArg(pAllocator, &amTracker, (TADDR)pArgs, helperAddress); + amTracker.SuppressRelease(); + return result;*/ + } + else + { + PCODE result; + GenericDictionaryDynamicHelperStubData dictLookupData = {0}; + dictLookupData.SizeOffset = (UINT32)pLookup->sizeOffset; + dictLookupData.SlotOffset = slotOffset; + bool needsDictLookupData = false; + + if (pLookup->indirections == 3) + { + // Class! + _ASSERTE(helperAddress == g_pClassWithSlotAndModule); + _ASSERTE(pLookup->offsets[0] == offsetof(MethodTable, m_pPerInstInfo)); + dictLookupData.SecondIndir = (UINT32)pLookup->offsets[1]; + dictLookupData.LastIndir = (UINT32)pLookup->offsets[2]; +// if (pLookup->testForNull && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull; + needsDictLookupData = true; + } +/* else if (pLookup->testForNull) + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_TestForNull; + needsDictLookupData = true; + } + else + { + _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK); + if ((dictLookupData.SecondIndir == 0) && (dictLookupData.LastIndir <= sizeof(TADDR) * 3)) + { + needsDictLookupData = false; + switch (dictLookupData.LastIndir / sizeof(TADDR)) + { + case 0: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_0; + break; + case 1: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_1; + break; + case 2: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_2; + break; + case 3: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_3; + break; + } + } + else + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class; + needsDictLookupData = true; + } + }*/ + } + else if (pLookup->indirections == 2) + { + // Method! + _ASSERTE(helperAddress == g_pMethodWithSlotAndModule); + _ASSERTE(pLookup->offsets[0] == offsetof(InstantiatedMethodDesc, m_pPerInstInfo)); + dictLookupData.LastIndir = (UINT32)pLookup->offsets[1]; +// if (pLookup->testForNull && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull; + needsDictLookupData = true; + } +/* else if (pLookup->testForNull) + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_TestForNull; + needsDictLookupData = true; + } + else + { + _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK); + if ((dictLookupData.SecondIndir == 0) && (dictLookupData.LastIndir <= sizeof(TADDR) * 3)) + { + needsDictLookupData = false; + switch (dictLookupData.LastIndir / sizeof(TADDR)) + { + case 0: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_0; + break; + case 1: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_1; + break; + case 2: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_2; + break; + case 3: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_3; + break; + } + } + else + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method; + needsDictLookupData = true; + } + }*/ + } + + if (needsDictLookupData) + { + GenericHandleArgs * pArgs = (GenericHandleArgs *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericHandleArgs)))); + pArgs->dictionaryIndexAndSlot = dictionaryIndexAndSlot; + pArgs->signature = pLookup->signature; + pArgs->module = (CORINFO_MODULE_HANDLE)pModule; + + dictLookupData.HandleArgs = pArgs; + + GenericDictionaryDynamicHelperStubData_PortableEntryPoint *pDictLookupData = (GenericDictionaryDynamicHelperStubData_PortableEntryPoint *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint)))); + + pDictLookupData->stubData = dictLookupData; + pDictLookupData->HelperFunctionTableIndex = helper; + result = (PCODE)pDictLookupData; + } + else + { + result = helper; + } + + amTracker.SuppressRelease(); + return result; + } +} + extern "C" void STDCALL PInvokeImportThunk() { PORTABILITY_ASSERT("PInvokeImportThunk is not implemented on wasm"); From c30152c53833ace4671338e705aecbcc8b80a34f Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 24 Jun 2026 15:00:22 -0700 Subject: [PATCH 3/7] Add handling for the more optimized generic lookups --- src/coreclr/vm/wasm/dynamichelpers.S | 226 +++++++++++++++++++++++++++ src/coreclr/vm/wasm/helpers.cpp | 30 +++- 2 files changed, 249 insertions(+), 7 deletions(-) diff --git a/src/coreclr/vm/wasm/dynamichelpers.S b/src/coreclr/vm/wasm/dynamichelpers.S index d6a3c2ed72c203..694c97fbbf6d7c 100644 --- a/src/coreclr/vm/wasm/dynamichelpers.S +++ b/src/coreclr/vm/wasm/dynamichelpers.S @@ -101,3 +101,229 @@ DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull: local.get 3 // Return the result. return end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Class_TestForNull (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Class_TestForNull,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Class_TestForNull +.globl DynamicHelper_GenericDictionaryLookup_Class_TestForNull +.type DynamicHelper_GenericDictionaryLookup_Class_TestForNull,@function +DynamicHelper_GenericDictionaryLookup_Class_TestForNull: + .functype DynamicHelper_GenericDictionaryLookup_Class_TestForNull (i32, i32, i32) -> (i32) + .local i32 + block + local.get 1 // Load the GenericContext + i32.load OFFSETOF__MethodTable__m_pPerInstInfo + local.get 2 // Load the GenericClassDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SecondIndir + i32.add + i32.load 0 // Load the SecondDir indirection + local.get 2 // Load the GenericClassDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__LastIndir + i32.add + i32.load 0 // Load the last indirection + local.tee 3 // Store the loaded indirection into local 3, but keep it on the stack as well + br_if 0 // Branch to return if the result is not null + local.get 0 // Load the stack pointer + local.get 1 // Load the GenericContext + local.get 2 // Load the GenericClassDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__HandleArgs + i32.const 0 + i32.load g_pClassWithSlotAndModule + local.tee 2 + local.get 2 + i32.load 0 + call_indirect __indirect_function_table, (i32, i32, i32, i32) -> (i32) + local.set 3 + end_block + local.get 3 // Return the result. + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Class (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Class,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Class +.globl DynamicHelper_GenericDictionaryLookup_Class +.type DynamicHelper_GenericDictionaryLookup_Class,@function +DynamicHelper_GenericDictionaryLookup_Class: + .functype DynamicHelper_GenericDictionaryLookup_Class (i32, i32, i32) -> (i32) + local.get 1 // Load the GenericContext + i32.load OFFSETOF__MethodTable__m_pPerInstInfo + local.get 2 // Load the GenericClassDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SecondIndir + i32.add + i32.load 0 // Load the SecondDir indirection + local.get 2 // Load the GenericClassDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__LastIndir + i32.add + i32.load 0 // Load the last indirection + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Class_0_0 (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Class_0_0,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Class_0_0 +.globl DynamicHelper_GenericDictionaryLookup_Class_0_0 +.type DynamicHelper_GenericDictionaryLookup_Class_0_0,@function +DynamicHelper_GenericDictionaryLookup_Class_0_0: + .functype DynamicHelper_GenericDictionaryLookup_Class_0_0 (i32, i32, i32) -> (i32) + local.get 1 // Load the GenericContext + i32.load OFFSETOF__MethodTable__m_pPerInstInfo + i32.load 0 // Load the SecondDir indirection (offset 0) + i32.load 0 // Load the last indirection (offset 0) + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Class_0_1 (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Class_0_1,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Class_0_1 +.globl DynamicHelper_GenericDictionaryLookup_Class_0_1 +.type DynamicHelper_GenericDictionaryLookup_Class_0_1,@function +DynamicHelper_GenericDictionaryLookup_Class_0_1: + .functype DynamicHelper_GenericDictionaryLookup_Class_0_1 (i32, i32, i32) -> (i32) + local.get 1 // Load the GenericContext + i32.load OFFSETOF__MethodTable__m_pPerInstInfo + i32.load 0 // Load the SecondDir indirection (offset 0) + i32.load 0x4 // Load the last indirection (offset sizeof(TADDR) * 1) + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Class_0_2 (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Class_0_2,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Class_0_2 +.globl DynamicHelper_GenericDictionaryLookup_Class_0_2 +.type DynamicHelper_GenericDictionaryLookup_Class_0_2,@function +DynamicHelper_GenericDictionaryLookup_Class_0_2: + .functype DynamicHelper_GenericDictionaryLookup_Class_0_2 (i32, i32, i32) -> (i32) + local.get 1 // Load the GenericContext + i32.load OFFSETOF__MethodTable__m_pPerInstInfo + i32.load 0 // Load the SecondDir indirection (offset 0) + i32.load 0x8 // Load the last indirection (offset sizeof(TADDR) * 2) + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Class_0_3 (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Class_0_3,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Class_0_3 +.globl DynamicHelper_GenericDictionaryLookup_Class_0_3 +.type DynamicHelper_GenericDictionaryLookup_Class_0_3,@function +DynamicHelper_GenericDictionaryLookup_Class_0_3: + .functype DynamicHelper_GenericDictionaryLookup_Class_0_3 (i32, i32, i32) -> (i32) + local.get 1 // Load the GenericContext + i32.load OFFSETOF__MethodTable__m_pPerInstInfo + i32.load 0 // Load the SecondDir indirection (offset 0) + i32.load 0xC // Load the last indirection (offset sizeof(TADDR) * 3) + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Method_TestForNull (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Method_TestForNull,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Method_TestForNull +.globl DynamicHelper_GenericDictionaryLookup_Method_TestForNull +.type DynamicHelper_GenericDictionaryLookup_Method_TestForNull,@function +DynamicHelper_GenericDictionaryLookup_Method_TestForNull: + .functype DynamicHelper_GenericDictionaryLookup_Method_TestForNull (i32, i32, i32) -> (i32) + .local i32 + block + local.get 1 // Load the GenericContext + i32.load OFFSETOF__InstantiatedMethodDesc__m_pPerInstInfo + local.get 2 // Load the GenericMethodDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__LastIndir + i32.add + i32.load 0 // Load the last indirection + local.tee 3 // Store the loaded indirection into local 3, but keep it on the stack as well + br_if 0 // Branch to return if the result is not null + local.get 0 // Load the stack pointer + local.get 1 // Load the GenericContext + local.get 2 // Load the GenericMethodDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__HandleArgs + i32.const 0 + i32.load g_pMethodWithSlotAndModule + local.tee 2 + local.get 2 + i32.load 0 + call_indirect __indirect_function_table, (i32, i32, i32, i32) -> (i32) + local.set 3 + end_block + local.get 3 // Return the result. + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Method (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Method,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Method +.globl DynamicHelper_GenericDictionaryLookup_Method +.type DynamicHelper_GenericDictionaryLookup_Method,@function +DynamicHelper_GenericDictionaryLookup_Method: + .functype DynamicHelper_GenericDictionaryLookup_Method (i32, i32, i32) -> (i32) + local.get 1 // Load the GenericContext + i32.load OFFSETOF__InstantiatedMethodDesc__m_pPerInstInfo + local.get 2 // Load the GenericMethodDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__LastIndir + i32.add + i32.load 0 // Load the last indirection + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Method_0 (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Method_0,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Method_0 +.globl DynamicHelper_GenericDictionaryLookup_Method_0 +.type DynamicHelper_GenericDictionaryLookup_Method_0,@function +DynamicHelper_GenericDictionaryLookup_Method_0: + .functype DynamicHelper_GenericDictionaryLookup_Method_0 (i32, i32, i32) -> (i32) + local.get 1 // Load the GenericContext + i32.load OFFSETOF__InstantiatedMethodDesc__m_pPerInstInfo + i32.load 0 // Load the last indirection (offset 0) + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Method_1 (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Method_1,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Method_1 +.globl DynamicHelper_GenericDictionaryLookup_Method_1 +.type DynamicHelper_GenericDictionaryLookup_Method_1,@function +DynamicHelper_GenericDictionaryLookup_Method_1: + .functype DynamicHelper_GenericDictionaryLookup_Method_1 (i32, i32, i32) -> (i32) + local.get 1 // Load the GenericContext + i32.load OFFSETOF__InstantiatedMethodDesc__m_pPerInstInfo + i32.load 0x4 // Load the last indirection (offset sizeof(TADDR) * 1) + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Method_2 (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Method_2,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Method_2 +.globl DynamicHelper_GenericDictionaryLookup_Method_2 +.type DynamicHelper_GenericDictionaryLookup_Method_2,@function +DynamicHelper_GenericDictionaryLookup_Method_2: + .functype DynamicHelper_GenericDictionaryLookup_Method_2 (i32, i32, i32) -> (i32) + local.get 1 // Load the GenericContext + i32.load OFFSETOF__InstantiatedMethodDesc__m_pPerInstInfo + i32.load 0x8 // Load the last indirection (offset sizeof(TADDR) * 2) + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Method_3 (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Method_3,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Method_3 +.globl DynamicHelper_GenericDictionaryLookup_Method_3 +.type DynamicHelper_GenericDictionaryLookup_Method_3,@function +DynamicHelper_GenericDictionaryLookup_Method_3: + .functype DynamicHelper_GenericDictionaryLookup_Method_3 (i32, i32, i32) -> (i32) + local.get 1 // Load the GenericContext + i32.load OFFSETOF__InstantiatedMethodDesc__m_pPerInstInfo + i32.load 0xC // Load the last indirection (offset sizeof(TADDR) * 3) + return + end_function diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 9cd5db7231a9d3..0e27ca2fa1c737 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -459,7 +459,19 @@ extern "C" void STDCALL DelayLoad_Helper_ObjObj() } extern "C" void DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_TestForNull(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_0(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_1(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_2(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_3(); extern "C" void DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_TestForNull(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_0(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_1(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_2(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_3(); #include "readytoruninfo.h" #include "jitinterface.h" #include "loaderallocator.hpp" @@ -507,12 +519,12 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, _ASSERTE(pLookup->offsets[0] == offsetof(MethodTable, m_pPerInstInfo)); dictLookupData.SecondIndir = (UINT32)pLookup->offsets[1]; dictLookupData.LastIndir = (UINT32)pLookup->offsets[2]; -// if (pLookup->testForNull && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) + if (pLookup->testForNull && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) { helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull; needsDictLookupData = true; } -/* else if (pLookup->testForNull) + else if (pLookup->testForNull) { helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_TestForNull; needsDictLookupData = true; @@ -544,7 +556,7 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class; needsDictLookupData = true; } - }*/ + } } else if (pLookup->indirections == 2) { @@ -552,12 +564,12 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, _ASSERTE(helperAddress == g_pMethodWithSlotAndModule); _ASSERTE(pLookup->offsets[0] == offsetof(InstantiatedMethodDesc, m_pPerInstInfo)); dictLookupData.LastIndir = (UINT32)pLookup->offsets[1]; -// if (pLookup->testForNull && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) + if (pLookup->testForNull && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) { helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull; needsDictLookupData = true; } -/* else if (pLookup->testForNull) + else if (pLookup->testForNull) { helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_TestForNull; needsDictLookupData = true; @@ -589,7 +601,7 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method; needsDictLookupData = true; } - }*/ + } } if (needsDictLookupData) @@ -609,7 +621,11 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, } else { - result = helper; + // The simple helpers do not need any stub data, but the portable entrypoint calling convention + // requires the result to point at a location whose first word is the helper's function table index. + PCODE *pHelperEntryPoint = (PCODE *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(PCODE)))); + *pHelperEntryPoint = helper; + result = (PCODE)pHelperEntryPoint; } amTracker.SuppressRelease(); From f52b69cec88b70e74d8bb90533b952038e1f2e21 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 24 Jun 2026 15:08:34 -0700 Subject: [PATCH 4/7] Implement fallback ultra-slow path --- src/coreclr/vm/wasm/dynamichelpers.S | 42 ++++++++++++++++++++++++++++ src/coreclr/vm/wasm/helpers.cpp | 29 ++++++++++++++++--- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/wasm/dynamichelpers.S b/src/coreclr/vm/wasm/dynamichelpers.S index 694c97fbbf6d7c..dd340691fee6c9 100644 --- a/src/coreclr/vm/wasm/dynamichelpers.S +++ b/src/coreclr/vm/wasm/dynamichelpers.S @@ -327,3 +327,45 @@ DynamicHelper_GenericDictionaryLookup_Method_3: i32.load 0xC // Load the last indirection (offset sizeof(TADDR) * 3) return end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Class_UseHelper (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Class_UseHelper,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Class_UseHelper +.globl DynamicHelper_GenericDictionaryLookup_Class_UseHelper +.type DynamicHelper_GenericDictionaryLookup_Class_UseHelper,@function +DynamicHelper_GenericDictionaryLookup_Class_UseHelper: + .functype DynamicHelper_GenericDictionaryLookup_Class_UseHelper (i32, i32, i32) -> (i32) + local.get 0 // Load the stack pointer + local.get 1 // Load the GenericContext + local.get 2 // Load the GenericDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__HandleArgs + i32.const 0 + i32.load g_pClassWithSlotAndModule + local.tee 2 + local.get 2 + i32.load 0 + call_indirect __indirect_function_table, (i32, i32, i32, i32) -> (i32) + return + end_function + + +.functype DynamicHelper_GenericDictionaryLookup_Method_UseHelper (i32, i32, i32) -> (i32) +.section .text.DynamicHelper_GenericDictionaryLookup_Method_UseHelper,"",@ +.hidden DynamicHelper_GenericDictionaryLookup_Method_UseHelper +.globl DynamicHelper_GenericDictionaryLookup_Method_UseHelper +.type DynamicHelper_GenericDictionaryLookup_Method_UseHelper,@function +DynamicHelper_GenericDictionaryLookup_Method_UseHelper: + .functype DynamicHelper_GenericDictionaryLookup_Method_UseHelper (i32, i32, i32) -> (i32) + local.get 0 // Load the stack pointer + local.get 1 // Load the GenericContext + local.get 2 // Load the GenericDictionaryDynamicHelperStubData parameter + i32.load OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__HandleArgs + i32.const 0 + i32.load g_pMethodWithSlotAndModule + local.tee 2 + local.get 2 + i32.load 0 + call_indirect __indirect_function_table, (i32, i32, i32, i32) -> (i32) + return + end_function diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 0e27ca2fa1c737..26742a1ceaa174 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -472,6 +472,8 @@ extern "C" void DynamicHelper_GenericDictionaryLookup_Method_0(); extern "C" void DynamicHelper_GenericDictionaryLookup_Method_1(); extern "C" void DynamicHelper_GenericDictionaryLookup_Method_2(); extern "C" void DynamicHelper_GenericDictionaryLookup_Method_3(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_UseHelper(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_UseHelper(); #include "readytoruninfo.h" #include "jitinterface.h" #include "loaderallocator.hpp" @@ -494,15 +496,34 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, PCODE helper = (PCODE)NULL; if (pLookup->indirections == CORINFO_USEHELPER) { - ThrowHR(COR_E_NOTSUPPORTED); - /* GenericHandleArgs * pArgs = (GenericHandleArgs *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericHandleArgs)))); pArgs->dictionaryIndexAndSlot = dictionaryIndexAndSlot; pArgs->signature = pLookup->signature; pArgs->module = (CORINFO_MODULE_HANDLE)pModule; - PCODE result = EmitDynamicHelperWithArg(pAllocator, &amTracker, (TADDR)pArgs, helperAddress); + + PCODE helperFunc; + if (helperAddress == g_pClassWithSlotAndModule) + { + helperFunc = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_UseHelper; + } + else + { + _ASSERTE(helperAddress == g_pMethodWithSlotAndModule); + helperFunc = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_UseHelper; + } + + // The UseHelper stubs only ever read the HandleArgs field of the stub data, but the rest of the + // structure is zero-initialized for consistency with the other stubs. + GenericDictionaryDynamicHelperStubData dictLookupData = {0}; + dictLookupData.HandleArgs = pArgs; + + GenericDictionaryDynamicHelperStubData_PortableEntryPoint *pDictLookupData = (GenericDictionaryDynamicHelperStubData_PortableEntryPoint *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint)))); + pDictLookupData->stubData = dictLookupData; + pDictLookupData->HelperFunctionTableIndex = helperFunc; + + PCODE result = (PCODE)pDictLookupData; amTracker.SuppressRelease(); - return result;*/ + return result; } else { From 4a5eefd2bff5669c463f882a23a65fa98d07fc2e Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 24 Jun 2026 15:29:20 -0700 Subject: [PATCH 5/7] Refactor dynamichelpers code into a new file. --- src/coreclr/vm/CMakeLists.txt | 1 + src/coreclr/vm/wasm/dynamichelpers.cpp | 237 +++++++++++++++++++++++++ src/coreclr/vm/wasm/helpers.cpp | 230 ------------------------ 3 files changed, 238 insertions(+), 230 deletions(-) create mode 100644 src/coreclr/vm/wasm/dynamichelpers.cpp diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 8ff1480d10ae96..993787c8339d85 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -953,6 +953,7 @@ elseif(CLR_CMAKE_TARGET_ARCH_WASM) ${ARCH_SOURCES_DIR}/calldescrworkerwasm.cpp ${ARCH_SOURCES_DIR}/profiler.cpp ${ARCH_SOURCES_DIR}/helpers.cpp + ${ARCH_SOURCES_DIR}/dynamichelpers.cpp exceptionhandling.cpp gcinfodecoder.cpp ) diff --git a/src/coreclr/vm/wasm/dynamichelpers.cpp b/src/coreclr/vm/wasm/dynamichelpers.cpp new file mode 100644 index 00000000000000..8e606cd781e64d --- /dev/null +++ b/src/coreclr/vm/wasm/dynamichelpers.cpp @@ -0,0 +1,237 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#include "callingconvention.h" +#include "cgensys.h" +#include "readytorun.h" +#include "readytoruninfo.h" +#include "jitinterface.h" +#include "loaderallocator.hpp" + +extern "C" SIZE_T STDCALL DynamicHelperWorker(TransitionBlock * pTransitionBlock, TADDR * pCell, DWORD sectionIndex, Module * pModule, INT frameFlags); + +extern "C" SIZE_T STDCALL DelayLoad_HelperImpl(TransitionBlock* pTransitionBlock, READYTORUN_IMPORT_THUNK_PORTABLE_ENTRYPOINT* pImportThunkEntry, uint8_t *moduleBase, int32_t rvaOfModuleFixup, INT frameFlags) +{ + Module** ppModule = (Module**)(moduleBase + rvaOfModuleFixup); + return DynamicHelperWorker(pTransitionBlock, (TADDR*)(moduleBase + pImportThunkEntry->RelocOffset), (DWORD)-1, *ppModule, frameFlags); +} + +extern "C" __attribute__((naked)) SIZE_T STDCALL DelayLoad_Helper(TransitionBlock* pTransitionBlock, READYTORUN_IMPORT_THUNK_PORTABLE_ENTRYPOINT* pImportThunkEntry, uint8_t *moduleBase, int32_t rvaOfModuleFixup) +{ + asm ("local.get 0\n" /* Capture pTransitionBlock onto the stack for calling DelayLoad_MethodCallImpl function. This also happens to be the callersFramePointer */ + "local.get 0\n" /* Capture callersFramePointer onto the stack for setting the __stack_pointer */ + "global.get __stack_pointer\n" /* Get current value of stack global */ + "local.set 0\n" /* Overwrite local 0 with the previous __stack_pointer value so it can be restored after the call */ + "global.set __stack_pointer\n" /* Set stack global to the initial value of callersFramePointer, which is the current stack pointer for the interpreter call */ + "local.get 1\n" /* Load pImportThunkEntry argument onto the stack for calling DelayLoad_MethodCallImpl function*/ + "local.get 2\n" /* Load moduleBase argument onto the stack for calling DelayLoad_MethodCallImpl function*/ + "local.get 3\n" /* Load rvaOfModuleFixup argument onto the stack for calling DelayLoad_MethodCallImpl function*/ + "i32.const 0\n" /* Load frameFlags argument onto the stack for calling DelayLoad_MethodCallImpl function. For this variant we want 0 as the flag */ + "call %0\n" /* Call the actual implementation function */ + "local.get 0\n" /* Reload the saved previous __stack_pointer value for restoration into the stack global */ + "global.set __stack_pointer\n" + "return" :: "i" (DelayLoad_HelperImpl)); +} + +extern "C" void STDCALL DelayLoad_Helper_Obj() +{ + PORTABILITY_ASSERT("DelayLoad_Helper_Obj is not implemented on wasm"); +} + +extern "C" void STDCALL DelayLoad_Helper_ObjObj() +{ + PORTABILITY_ASSERT("DelayLoad_Helper_ObjObj is not implemented on wasm"); +} + +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_TestForNull(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_0(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_1(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_2(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_3(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_TestForNull(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_0(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_1(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_2(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_3(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Class_UseHelper(); +extern "C" void DynamicHelper_GenericDictionaryLookup_Method_UseHelper(); + +extern "C" PCODE g_pMethodWithSlotAndModule; +extern "C" PCODE g_pClassWithSlotAndModule; + +PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, CORINFO_RUNTIME_LOOKUP * pLookup, DWORD dictionaryIndexAndSlot, Module * pModule) +{ + STANDARD_VM_CONTRACT; + + AllocMemTracker amTracker; + + PCODE helperAddress = GetDictionaryLookupHelper(pLookup->helper); + + WORD slotOffset = (WORD)(dictionaryIndexAndSlot & 0xFFFF) * sizeof(Dictionary*); + + // It's available only via the run-time helper function + PCODE helper = (PCODE)NULL; + if (pLookup->indirections == CORINFO_USEHELPER) + { + GenericHandleArgs * pArgs = (GenericHandleArgs *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericHandleArgs)))); + pArgs->dictionaryIndexAndSlot = dictionaryIndexAndSlot; + pArgs->signature = pLookup->signature; + pArgs->module = (CORINFO_MODULE_HANDLE)pModule; + + PCODE helperFunc; + if (helperAddress == g_pClassWithSlotAndModule) + { + helperFunc = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_UseHelper; + } + else + { + _ASSERTE(helperAddress == g_pMethodWithSlotAndModule); + helperFunc = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_UseHelper; + } + + // The UseHelper stubs only ever read the HandleArgs field of the stub data, but the rest of the + // structure is zero-initialized for consistency with the other stubs. + GenericDictionaryDynamicHelperStubData dictLookupData = {0}; + dictLookupData.HandleArgs = pArgs; + + GenericDictionaryDynamicHelperStubData_PortableEntryPoint *pDictLookupData = (GenericDictionaryDynamicHelperStubData_PortableEntryPoint *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint)))); + pDictLookupData->stubData = dictLookupData; + pDictLookupData->HelperFunctionTableIndex = helperFunc; + + PCODE result = (PCODE)pDictLookupData; + amTracker.SuppressRelease(); + return result; + } + else + { + PCODE result; + GenericDictionaryDynamicHelperStubData dictLookupData = {0}; + dictLookupData.SizeOffset = (UINT32)pLookup->sizeOffset; + dictLookupData.SlotOffset = slotOffset; + bool needsDictLookupData = false; + + if (pLookup->indirections == 3) + { + // Class! + _ASSERTE(helperAddress == g_pClassWithSlotAndModule); + _ASSERTE(pLookup->offsets[0] == offsetof(MethodTable, m_pPerInstInfo)); + dictLookupData.SecondIndir = (UINT32)pLookup->offsets[1]; + dictLookupData.LastIndir = (UINT32)pLookup->offsets[2]; + if (pLookup->testForNull && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull; + needsDictLookupData = true; + } + else if (pLookup->testForNull) + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_TestForNull; + needsDictLookupData = true; + } + else + { + _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK); + if ((dictLookupData.SecondIndir == 0) && (dictLookupData.LastIndir <= sizeof(TADDR) * 3)) + { + needsDictLookupData = false; + switch (dictLookupData.LastIndir / sizeof(TADDR)) + { + case 0: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_0; + break; + case 1: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_1; + break; + case 2: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_2; + break; + case 3: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_3; + break; + } + } + else + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class; + needsDictLookupData = true; + } + } + } + else if (pLookup->indirections == 2) + { + // Method! + _ASSERTE(helperAddress == g_pMethodWithSlotAndModule); + _ASSERTE(pLookup->offsets[0] == offsetof(InstantiatedMethodDesc, m_pPerInstInfo)); + dictLookupData.LastIndir = (UINT32)pLookup->offsets[1]; + if (pLookup->testForNull && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull; + needsDictLookupData = true; + } + else if (pLookup->testForNull) + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_TestForNull; + needsDictLookupData = true; + } + else + { + _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK); + if ((dictLookupData.SecondIndir == 0) && (dictLookupData.LastIndir <= sizeof(TADDR) * 3)) + { + needsDictLookupData = false; + switch (dictLookupData.LastIndir / sizeof(TADDR)) + { + case 0: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_0; + break; + case 1: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_1; + break; + case 2: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_2; + break; + case 3: + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_3; + break; + } + } + else + { + helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method; + needsDictLookupData = true; + } + } + } + + if (needsDictLookupData) + { + GenericHandleArgs * pArgs = (GenericHandleArgs *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericHandleArgs)))); + pArgs->dictionaryIndexAndSlot = dictionaryIndexAndSlot; + pArgs->signature = pLookup->signature; + pArgs->module = (CORINFO_MODULE_HANDLE)pModule; + + dictLookupData.HandleArgs = pArgs; + + GenericDictionaryDynamicHelperStubData_PortableEntryPoint *pDictLookupData = (GenericDictionaryDynamicHelperStubData_PortableEntryPoint *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint)))); + + pDictLookupData->stubData = dictLookupData; + pDictLookupData->HelperFunctionTableIndex = helper; + result = (PCODE)pDictLookupData; + } + else + { + // The simple helpers do not need any stub data, but the portable entrypoint calling convention + // requires the result to point at a location whose first word is the helper's function table index. + PCODE *pHelperEntryPoint = (PCODE *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(PCODE)))); + *pHelperEntryPoint = helper; + result = (PCODE)pHelperEntryPoint; + } + + amTracker.SuppressRelease(); + return result; + } +} diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 26742a1ceaa174..90e3720d224dba 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -423,236 +423,6 @@ extern "C" __attribute__((naked)) PCODE STDCALL DelayLoad_MethodCall(TransitionB "global.set __stack_pointer\n" "return" :: "i" (DelayLoad_MethodCallImpl)); } -extern "C" SIZE_T STDCALL DynamicHelperWorker(TransitionBlock * pTransitionBlock, TADDR * pCell, DWORD sectionIndex, Module * pModule, INT frameFlags); - -extern "C" SIZE_T STDCALL DelayLoad_HelperImpl(TransitionBlock* pTransitionBlock, READYTORUN_IMPORT_THUNK_PORTABLE_ENTRYPOINT* pImportThunkEntry, uint8_t *moduleBase, int32_t rvaOfModuleFixup, INT frameFlags) -{ - Module** ppModule = (Module**)(moduleBase + rvaOfModuleFixup); - return DynamicHelperWorker(pTransitionBlock, (TADDR*)(moduleBase + pImportThunkEntry->RelocOffset), (DWORD)-1, *ppModule, frameFlags); -} - -extern "C" __attribute__((naked)) SIZE_T STDCALL DelayLoad_Helper(TransitionBlock* pTransitionBlock, READYTORUN_IMPORT_THUNK_PORTABLE_ENTRYPOINT* pImportThunkEntry, uint8_t *moduleBase, int32_t rvaOfModuleFixup) -{ - asm ("local.get 0\n" /* Capture pTransitionBlock onto the stack for calling DelayLoad_MethodCallImpl function. This also happens to be the callersFramePointer */ - "local.get 0\n" /* Capture callersFramePointer onto the stack for setting the __stack_pointer */ - "global.get __stack_pointer\n" /* Get current value of stack global */ - "local.set 0\n" /* Overwrite local 0 with the previous __stack_pointer value so it can be restored after the call */ - "global.set __stack_pointer\n" /* Set stack global to the initial value of callersFramePointer, which is the current stack pointer for the interpreter call */ - "local.get 1\n" /* Load pImportThunkEntry argument onto the stack for calling DelayLoad_MethodCallImpl function*/ - "local.get 2\n" /* Load moduleBase argument onto the stack for calling DelayLoad_MethodCallImpl function*/ - "local.get 3\n" /* Load rvaOfModuleFixup argument onto the stack for calling DelayLoad_MethodCallImpl function*/ - "i32.const 0\n" /* Load frameFlags argument onto the stack for calling DelayLoad_MethodCallImpl function. For this variant we want 0 as the flag */ - "call %0\n" /* Call the actual implementation function */ - "local.get 0\n" /* Reload the saved previous __stack_pointer value for restoration into the stack global */ - "global.set __stack_pointer\n" - "return" :: "i" (DelayLoad_HelperImpl)); -} - -extern "C" void STDCALL DelayLoad_Helper_Obj() -{ - PORTABILITY_ASSERT("DelayLoad_Helper_Obj is not implemented on wasm"); -} - -extern "C" void STDCALL DelayLoad_Helper_ObjObj() -{ - PORTABILITY_ASSERT("DelayLoad_Helper_ObjObj is not implemented on wasm"); -} - -extern "C" void DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Class_TestForNull(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Class(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_0(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_1(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_2(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Class_0_3(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Method_TestForNull(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Method(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Method_0(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Method_1(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Method_2(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Method_3(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Class_UseHelper(); -extern "C" void DynamicHelper_GenericDictionaryLookup_Method_UseHelper(); -#include "readytoruninfo.h" -#include "jitinterface.h" -#include "loaderallocator.hpp" - - -extern "C" PCODE g_pMethodWithSlotAndModule; -extern "C" PCODE g_pClassWithSlotAndModule; - -PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, CORINFO_RUNTIME_LOOKUP * pLookup, DWORD dictionaryIndexAndSlot, Module * pModule) -{ - STANDARD_VM_CONTRACT; - - AllocMemTracker amTracker; - - PCODE helperAddress = GetDictionaryLookupHelper(pLookup->helper); - - WORD slotOffset = (WORD)(dictionaryIndexAndSlot & 0xFFFF) * sizeof(Dictionary*); - - // It's available only via the run-time helper function - PCODE helper = (PCODE)NULL; - if (pLookup->indirections == CORINFO_USEHELPER) - { - GenericHandleArgs * pArgs = (GenericHandleArgs *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericHandleArgs)))); - pArgs->dictionaryIndexAndSlot = dictionaryIndexAndSlot; - pArgs->signature = pLookup->signature; - pArgs->module = (CORINFO_MODULE_HANDLE)pModule; - - PCODE helperFunc; - if (helperAddress == g_pClassWithSlotAndModule) - { - helperFunc = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_UseHelper; - } - else - { - _ASSERTE(helperAddress == g_pMethodWithSlotAndModule); - helperFunc = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_UseHelper; - } - - // The UseHelper stubs only ever read the HandleArgs field of the stub data, but the rest of the - // structure is zero-initialized for consistency with the other stubs. - GenericDictionaryDynamicHelperStubData dictLookupData = {0}; - dictLookupData.HandleArgs = pArgs; - - GenericDictionaryDynamicHelperStubData_PortableEntryPoint *pDictLookupData = (GenericDictionaryDynamicHelperStubData_PortableEntryPoint *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint)))); - pDictLookupData->stubData = dictLookupData; - pDictLookupData->HelperFunctionTableIndex = helperFunc; - - PCODE result = (PCODE)pDictLookupData; - amTracker.SuppressRelease(); - return result; - } - else - { - PCODE result; - GenericDictionaryDynamicHelperStubData dictLookupData = {0}; - dictLookupData.SizeOffset = (UINT32)pLookup->sizeOffset; - dictLookupData.SlotOffset = slotOffset; - bool needsDictLookupData = false; - - if (pLookup->indirections == 3) - { - // Class! - _ASSERTE(helperAddress == g_pClassWithSlotAndModule); - _ASSERTE(pLookup->offsets[0] == offsetof(MethodTable, m_pPerInstInfo)); - dictLookupData.SecondIndir = (UINT32)pLookup->offsets[1]; - dictLookupData.LastIndir = (UINT32)pLookup->offsets[2]; - if (pLookup->testForNull && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) - { - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_SizeCheck_TestForNull; - needsDictLookupData = true; - } - else if (pLookup->testForNull) - { - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_TestForNull; - needsDictLookupData = true; - } - else - { - _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK); - if ((dictLookupData.SecondIndir == 0) && (dictLookupData.LastIndir <= sizeof(TADDR) * 3)) - { - needsDictLookupData = false; - switch (dictLookupData.LastIndir / sizeof(TADDR)) - { - case 0: - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_0; - break; - case 1: - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_1; - break; - case 2: - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_2; - break; - case 3: - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class_0_3; - break; - } - } - else - { - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Class; - needsDictLookupData = true; - } - } - } - else if (pLookup->indirections == 2) - { - // Method! - _ASSERTE(helperAddress == g_pMethodWithSlotAndModule); - _ASSERTE(pLookup->offsets[0] == offsetof(InstantiatedMethodDesc, m_pPerInstInfo)); - dictLookupData.LastIndir = (UINT32)pLookup->offsets[1]; - if (pLookup->testForNull && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) - { - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull; - needsDictLookupData = true; - } - else if (pLookup->testForNull) - { - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_TestForNull; - needsDictLookupData = true; - } - else - { - _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK); - if ((dictLookupData.SecondIndir == 0) && (dictLookupData.LastIndir <= sizeof(TADDR) * 3)) - { - needsDictLookupData = false; - switch (dictLookupData.LastIndir / sizeof(TADDR)) - { - case 0: - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_0; - break; - case 1: - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_1; - break; - case 2: - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_2; - break; - case 3: - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_3; - break; - } - } - else - { - helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method; - needsDictLookupData = true; - } - } - } - - if (needsDictLookupData) - { - GenericHandleArgs * pArgs = (GenericHandleArgs *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericHandleArgs)))); - pArgs->dictionaryIndexAndSlot = dictionaryIndexAndSlot; - pArgs->signature = pLookup->signature; - pArgs->module = (CORINFO_MODULE_HANDLE)pModule; - - dictLookupData.HandleArgs = pArgs; - - GenericDictionaryDynamicHelperStubData_PortableEntryPoint *pDictLookupData = (GenericDictionaryDynamicHelperStubData_PortableEntryPoint *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint)))); - - pDictLookupData->stubData = dictLookupData; - pDictLookupData->HelperFunctionTableIndex = helper; - result = (PCODE)pDictLookupData; - } - else - { - // The simple helpers do not need any stub data, but the portable entrypoint calling convention - // requires the result to point at a location whose first word is the helper's function table index. - PCODE *pHelperEntryPoint = (PCODE *)amTracker.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(PCODE)))); - *pHelperEntryPoint = helper; - result = (PCODE)pHelperEntryPoint; - } - - amTracker.SuppressRelease(); - return result; - } -} extern "C" void STDCALL PInvokeImportThunk() { From bea4cedfeb2e8439568116682cb448b33592d322 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 24 Jun 2026 15:53:52 -0700 Subject: [PATCH 6/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/coreclr/vm/wasm/dynamichelpers.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/wasm/dynamichelpers.cpp b/src/coreclr/vm/wasm/dynamichelpers.cpp index 8e606cd781e64d..6e91656990954c 100644 --- a/src/coreclr/vm/wasm/dynamichelpers.cpp +++ b/src/coreclr/vm/wasm/dynamichelpers.cpp @@ -19,14 +19,14 @@ extern "C" SIZE_T STDCALL DelayLoad_HelperImpl(TransitionBlock* pTransitionBlock extern "C" __attribute__((naked)) SIZE_T STDCALL DelayLoad_Helper(TransitionBlock* pTransitionBlock, READYTORUN_IMPORT_THUNK_PORTABLE_ENTRYPOINT* pImportThunkEntry, uint8_t *moduleBase, int32_t rvaOfModuleFixup) { - asm ("local.get 0\n" /* Capture pTransitionBlock onto the stack for calling DelayLoad_MethodCallImpl function. This also happens to be the callersFramePointer */ + asm ("local.get 0\n" /* Capture pTransitionBlock onto the stack for calling DelayLoad_HelperImpl function. This also happens to be the callersFramePointer */ "local.get 0\n" /* Capture callersFramePointer onto the stack for setting the __stack_pointer */ "global.get __stack_pointer\n" /* Get current value of stack global */ "local.set 0\n" /* Overwrite local 0 with the previous __stack_pointer value so it can be restored after the call */ "global.set __stack_pointer\n" /* Set stack global to the initial value of callersFramePointer, which is the current stack pointer for the interpreter call */ - "local.get 1\n" /* Load pImportThunkEntry argument onto the stack for calling DelayLoad_MethodCallImpl function*/ - "local.get 2\n" /* Load moduleBase argument onto the stack for calling DelayLoad_MethodCallImpl function*/ - "local.get 3\n" /* Load rvaOfModuleFixup argument onto the stack for calling DelayLoad_MethodCallImpl function*/ + "local.get 1\n" /* Load pImportThunkEntry argument onto the stack for calling DelayLoad_HelperImpl function*/ + "local.get 2\n" /* Load moduleBase argument onto the stack for calling DelayLoad_HelperImpl function*/ + "local.get 3\n" /* Load rvaOfModuleFixup argument onto the stack for calling DelayLoad_HelperImpl function*/ "i32.const 0\n" /* Load frameFlags argument onto the stack for calling DelayLoad_MethodCallImpl function. For this variant we want 0 as the flag */ "call %0\n" /* Call the actual implementation function */ "local.get 0\n" /* Reload the saved previous __stack_pointer value for restoration into the stack global */ From 9a00f21c53575b655db8cc69367f0fce240ac04f Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 24 Jun 2026 16:01:52 -0700 Subject: [PATCH 7/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/coreclr/vm/wasm/helpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 90e3720d224dba..8abf0c736818f2 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -170,7 +170,7 @@ namespace transitionBlock.args[0] = arg0; static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block"); - int64_t result = NULL; + int64_t result = 0; ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result); return result; }