Skip to content

Commit 0bc7eda

Browse files
Fix generic lookups and thunks (#129825)
Add support for generic lookups to Wasm R2R code by following the DynamicHelper pattern we use on other platforms. This looks like a quite suitable implementation for Wasm, so I expect that this will remain for the long term. Add support for WebAssembly .S files, as it was difficult to implement the helpers in inline assembly due to clang limitations. Tweak Crossgen2 into generating uses of the DynamicHelper logic. Also add support for precomputed thunks for various runtime helpers which were missing them, and add an assert that will fire even in interpreted runs for missing cases. Fixes (most) of #129622 Fixes #129821 --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 207ffd1 commit 0bc7eda

13 files changed

Lines changed: 789 additions & 34 deletions

File tree

eng/native/configurecompiler.cmake

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,14 @@ if (CLR_CMAKE_HOST_WIN32)
11331133
message(FATAL_ERROR "MC not found")
11341134
endif()
11351135

1136-
elseif (NOT CLR_CMAKE_HOST_BROWSER AND NOT CLR_CMAKE_HOST_WASI)
1136+
elseif (CLR_CMAKE_HOST_BROWSER OR CLR_CMAKE_HOST_WASI)
1137+
# The wasm toolchains (emscripten / wasi-sdk) use clang, which can assemble
1138+
# preprocessed (.S) wasm assembly files directly.
1139+
set (CMAKE_ASM_COMPILER_VERSION "${CMAKE_C_COMPILER_VERSION}")
1140+
1141+
enable_language(ASM)
1142+
1143+
else()
11371144
# This is a workaround for upstream issue: https://gitlab.kitware.com/cmake/cmake/-/issues/22995.
11381145
#
11391146
# In Clang.cmake, the decision to use single or double hyphen for target and gcc-toolchain

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperImport.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,7 @@ public DelayLoadHelperImport(
4141
_useJumpableStub = useJumpableStub;
4242
if (factory.Target.Architecture == TargetArchitecture.Wasm32)
4343
{
44-
if (instanceSignature is GenericLookupSignature)
45-
{
46-
// Generic lookups are resolved via eager fixups and don't need import thunks
47-
_delayLoadHelper = null;
48-
}
49-
else
50-
{
51-
_delayLoadHelper = factory.WasmImportThunkPortableEntrypoint(this);
52-
}
44+
_delayLoadHelper = factory.WasmImportThunkPortableEntrypoint(this);
5345
}
5446
else
5547
{

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunkPortableEntrypoint.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer
5757

5858
private static readonly WasmSignature _genericLookupSignature32Bit = new WasmSignature(
5959
new WasmFuncType(
60-
new WasmResultType(new[] { WasmValueType.I32, WasmValueType.I32 }),
60+
new WasmResultType(new[] { WasmValueType.I32, WasmValueType.I32, WasmValueType.I32 }),
6161
new WasmResultType(new[] { WasmValueType.I32 })),
62-
"iii");
62+
"iip");
6363
private static readonly WasmSignature _genericLookupSignature64Bit = new WasmSignature(
6464
new WasmFuncType(
65-
new WasmResultType(new[] { WasmValueType.I64, WasmValueType.I64 }),
65+
new WasmResultType(new[] { WasmValueType.I64, WasmValueType.I64, WasmValueType.I64 }),
6666
new WasmResultType(new[] { WasmValueType.I64 })),
67-
"lll");
67+
"llp");
6868

6969
public override ObjectData GetData(NodeFactory factory, System.Boolean relocsOnly = false)
7070
{

src/coreclr/vm/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,12 +943,17 @@ elseif(CLR_CMAKE_TARGET_ARCH_RISCV64)
943943
elseif(CLR_CMAKE_TARGET_ARCH_WASM)
944944
set(VM_HEADERS_WKS_ARCH_ASM
945945
${ARCH_SOURCES_DIR}/entrypoints.h
946+
${ARCH_SOURCES_DIR}/asmconstants.h
947+
)
948+
set(VM_SOURCES_WKS_ARCH_ASM
949+
${ARCH_SOURCES_DIR}/dynamichelpers.S
946950
)
947951
set(VM_SOURCES_WKS_ARCH
948952
${RUNTIME_DIR}/${ARCH_SOURCES_DIR}/writebarriers.cpp
949953
${ARCH_SOURCES_DIR}/calldescrworkerwasm.cpp
950954
${ARCH_SOURCES_DIR}/profiler.cpp
951955
${ARCH_SOURCES_DIR}/helpers.cpp
956+
${ARCH_SOURCES_DIR}/dynamichelpers.cpp
952957
exceptionhandling.cpp
953958
gcinfodecoder.cpp
954959
)

src/coreclr/vm/cgensys.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ extern "C" PCODE STDCALL DelayLoad_MethodCall(TransitionBlock* pTransitionBlock,
6868
extern "C" void STDCALL DelayLoad_MethodCall();
6969
#endif
7070

71+
#ifdef TARGET_WASM
72+
extern "C" SIZE_T STDCALL DelayLoad_Helper(TransitionBlock* pTransitionBlock, READYTORUN_IMPORT_THUNK_PORTABLE_ENTRYPOINT* pImportThunkEntry, uint8_t *moduleBase, int32_t rvaOfModuleFixup);
73+
#else
7174
extern "C" void STDCALL DelayLoad_Helper();
75+
#endif
7276
extern "C" void STDCALL DelayLoad_Helper_Obj();
7377
extern "C" void STDCALL DelayLoad_Helper_ObjObj();
7478
#endif

src/coreclr/vm/jitinterface.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11021,6 +11021,9 @@ void CEECodeGenInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN
1102111021
{
1102211022
helperMD = GetMethodDescForILBasedDynamicJitHelper(dynamicFtnNum);
1102311023
_ASSERTE(PortableEntryPoint::GetMethodDesc((PCODE)targetAddr) == helperMD);
11024+
#ifdef FEATURE_READYTORUN
11025+
_ASSERTE(PortableEntryPoint::GetActualCode((PCODE)targetAddr) != NULL);
11026+
#endif
1102411027
}
1102511028

1102611029
#else // !FEATURE_PORTABLE_ENTRYPOINTS

src/coreclr/vm/prestub.cpp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2742,11 +2742,15 @@ EXTERN_C PCODE STDCALL ExternalMethodFixupWorker(TransitionBlock * pTransitionBl
27422742
PTR_READYTORUN_IMPORT_SECTION pImportSection;
27432743
if (sectionIndex != (DWORD)-1)
27442744
{
2745+
// On some platforms (everywhere except wasm) we can get the section index from the callsite,
2746+
// so we don't have to search for it.
27452747
pImportSection = pModule->GetImportSectionFromIndex(sectionIndex);
27462748
_ASSERTE(pImportSection == pModule->GetImportSectionForRVA(rva));
27472749
}
27482750
else
27492751
{
2752+
// On some platforms (currently only wasm) we would need to bloat the R2R binary a bit to store
2753+
// the section index, so we search for it instead.
27502754
pImportSection = pModule->GetImportSectionForRVA(rva);
27512755
}
27522756
_ASSERTE(pImportSection != NULL);
@@ -3362,7 +3366,20 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR
33623366

33633367
RVA rva = pNativeImage->GetDataRva((TADDR)pCell);
33643368

3365-
PTR_READYTORUN_IMPORT_SECTION pImportSection = pModule->GetImportSectionFromIndex(sectionIndex);
3369+
PTR_READYTORUN_IMPORT_SECTION pImportSection;
3370+
if (sectionIndex != (DWORD)-1)
3371+
{
3372+
// On some platforms (everywhere except wasm) we can get the section index from the callsite,
3373+
// so we don't have to search for it.
3374+
pImportSection = pModule->GetImportSectionFromIndex(sectionIndex);
3375+
}
3376+
else
3377+
{
3378+
// On some platforms (currently only wasm) we would need to bloat the R2R binary a bit to store
3379+
// the section index, so we search for it instead.
3380+
pImportSection = pModule->GetImportSectionForRVA(rva);
3381+
}
3382+
33663383
_ASSERTE(pImportSection == pModule->GetImportSectionForRVA(rva));
33673384

33683385
_ASSERTE(pImportSection->EntrySize == sizeof(TADDR));
@@ -3393,6 +3410,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR
33933410

33943411
switch (kind)
33953412
{
3413+
#ifndef TARGET_WASM
33963414
case READYTORUN_FIXUP_NewObject:
33973415
th = ZapSig::DecodeType(pModule, pInfoModule, pBlob);
33983416
th.AsMethodTable()->EnsureInstanceActive();
@@ -3444,7 +3462,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR
34443462
pMD->EnsureActive();
34453463
}
34463464
break;
3447-
3465+
#endif // !TARGET_WASM
34483466
case READYTORUN_FIXUP_ThisObjDictionaryLookup:
34493467
case READYTORUN_FIXUP_TypeDictionaryLookup:
34503468
case READYTORUN_FIXUP_MethodDictionaryLookup:
@@ -3465,6 +3483,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR
34653483
{
34663484
switch (kind)
34673485
{
3486+
#ifndef TARGET_WASM
34683487
case READYTORUN_FIXUP_IsInstanceOf:
34693488
case READYTORUN_FIXUP_ChkCast:
34703489
{
@@ -3554,7 +3573,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR
35543573
}
35553574
}
35563575
break;
3557-
3576+
#endif // !TARGET_WASM
35583577
default:
35593578
UNREACHABLE();
35603579
}
@@ -3578,6 +3597,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR
35783597
{
35793598
switch (kind)
35803599
{
3600+
#ifndef TARGET_WASM
35813601
case READYTORUN_FIXUP_NewObject:
35823602
{
35833603
bool fHasSideEffectsUnused;
@@ -3651,7 +3671,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR
36513671
}
36523672
}
36533673
break;
3654-
3674+
#endif // !TARGET_WASM
36553675
case READYTORUN_FIXUP_ThisObjDictionaryLookup:
36563676
case READYTORUN_FIXUP_TypeDictionaryLookup:
36573677
case READYTORUN_FIXUP_MethodDictionaryLookup:

src/coreclr/vm/readytoruninfo.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2752,9 +2752,16 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
27522752
else
27532753
{
27542754
_ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK);
2755+
// SecondIndir is in bytes, but actual indirections into the table are always pointer aligned.
2756+
// A value of 0 indicates that the second indirection is into the first generic dictionary of
2757+
// the type, which is the most common access pattern for generics. For Dictionary<TKey,TValue>,
2758+
// a SecondIndir of 0, and a LastIndir of 0 would indicate the MethodTable pointer of TKey,
2759+
// and if LastIndir was sizeof(TADDR) it would access the MethodTable pointer of TValue and so on.
27552760
if ((dictLookupData.SecondIndir == 0) && (dictLookupData.LastIndir <= sizeof(TADDR) * 3))
27562761
{
27572762
needsDictLookupData = false;
2763+
// Since LastIndir is in bytes, but actual indirections into the table are always pointer
2764+
// aligned, we can divide by sizeof(TADDR) to compute the possible cases here.
27582765
switch (dictLookupData.LastIndir / sizeof(TADDR))
27592766
{
27602767
case 0:
@@ -2784,6 +2791,7 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
27842791
_ASSERTE(helperAddress == g_pMethodWithSlotAndModule);
27852792
_ASSERTE(pLookup->offsets[0] == offsetof(InstantiatedMethodDesc, m_pPerInstInfo));
27862793
dictLookupData.LastIndir = (UINT32)pLookup->offsets[1];
2794+
_ASSERTE(dictLookupData.SecondIndir == 0); // There are only 2 indirections, so there is no "SecondIndir" value to set, and it should be 0.
27872795
if (pLookup->testForNull && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK)
27882796
{
27892797
helper = (PCODE)DynamicHelper_GenericDictionaryLookup_Method_SizeCheck_TestForNull;
@@ -2797,9 +2805,10 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
27972805
else
27982806
{
27992807
_ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK);
2800-
if ((dictLookupData.SecondIndir == 0) && (dictLookupData.LastIndir <= sizeof(TADDR) * 3))
2808+
if (dictLookupData.LastIndir <= sizeof(TADDR) * 3)
28012809
{
28022810
needsDictLookupData = false;
2811+
// Since LastIndir is in bytes, but actual indirections into the table are always pointer aligned, we can divide by sizeof(TADDR) to compute the possible cases here.
28032812
switch (dictLookupData.LastIndir / sizeof(TADDR))
28042813
{
28052814
case 0:

src/coreclr/vm/readytoruninfo.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,14 @@ struct GenericDictionaryDynamicHelperStubData
479479
GenericHandleArgs *HandleArgs;
480480
};
481481

482+
#ifdef FEATURE_PORTABLE_ENTRYPOINTS
483+
struct GenericDictionaryDynamicHelperStubData_PortableEntryPoint
484+
{
485+
PCODE HelperFunctionTableIndex;
486+
GenericDictionaryDynamicHelperStubData stubData;
487+
};
488+
#endif
489+
482490
class ReadyToRunLoadedImage
483491
{
484492
TADDR m_pImageBase;

src/coreclr/vm/wasm/asmconstants.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,55 @@
66
// Be sure to rebuild clr/src/vm/ceemain.cpp after changing this file, to
77
// ensure that the constants match the expected C/C++ values
88

9+
#ifndef ASMCONSTANTS_C_ASSERT
10+
#define ASMCONSTANTS_C_ASSERT(cond)
11+
#endif
12+
13+
#ifndef ASMCONSTANTS_RUNTIME_ASSERT
14+
#define ASMCONSTANTS_RUNTIME_ASSERT(cond)
15+
#endif
16+
17+
// Some constants are different in _DEBUG builds. This macro factors out ifdefs from below.
18+
#ifdef _DEBUG
19+
#define DBG_FRE(dbg,fre) dbg
20+
#else
21+
#define DBG_FRE(dbg,fre) fre
22+
#endif
23+
924
#define DynamicHelperFrameFlags_ObjectArg 1
1025
#define DynamicHelperFrameFlags_ObjectArg2 2
26+
27+
#define OFFSETOF__MethodTable__m_pPerInstInfo DBG_FRE(0x24, 0x20)
28+
ASMCONSTANTS_C_ASSERT(OFFSETOF__MethodTable__m_pPerInstInfo
29+
== offsetof(MethodTable, m_pPerInstInfo));
30+
31+
#define OFFSETOF__InstantiatedMethodDesc__m_pPerInstInfo DBG_FRE(0x28, 0x14)
32+
ASMCONSTANTS_C_ASSERT(OFFSETOF__InstantiatedMethodDesc__m_pPerInstInfo
33+
== offsetof(InstantiatedMethodDesc, m_pPerInstInfo));
34+
35+
#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__stubData 0x04
36+
ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__stubData
37+
== offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData));
38+
39+
#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SecondIndir 0x4
40+
ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SecondIndir
41+
== offsetof(GenericDictionaryDynamicHelperStubData, SecondIndir) + offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData));
42+
43+
#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__LastIndir 0x8
44+
ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__LastIndir
45+
== offsetof(GenericDictionaryDynamicHelperStubData, LastIndir) + offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData));
46+
47+
#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SizeOffset 0xC
48+
ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SizeOffset
49+
== offsetof(GenericDictionaryDynamicHelperStubData, SizeOffset) + offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData));
50+
51+
#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SlotOffset 0x10
52+
ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__SlotOffset
53+
== offsetof(GenericDictionaryDynamicHelperStubData, SlotOffset) + offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData));
54+
55+
#define OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__HandleArgs 0x14
56+
ASMCONSTANTS_C_ASSERT(OFFSETOF__GenericDictionaryDynamicHelperStubData_PortableEntryPoint__HandleArgs
57+
== offsetof(GenericDictionaryDynamicHelperStubData, HandleArgs) + offsetof(GenericDictionaryDynamicHelperStubData_PortableEntryPoint, stubData));
58+
59+
#undef ASMCONSTANTS_RUNTIME_ASSERT
60+
#undef ASMCONSTANTS_C_ASSERT

0 commit comments

Comments
 (0)