diff --git a/docs/design/coreclr/botr/clr-abi.md b/docs/design/coreclr/botr/clr-abi.md index 3f1cf8dd3fa090..16d9a08dd64914 100644 --- a/docs/design/coreclr/botr/clr-abi.md +++ b/docs/design/coreclr/botr/clr-abi.md @@ -714,12 +714,42 @@ The linear stack pointer `$sp` is the first argument to all methods. At a native A frame pointer, if used, points at the bottom of the "fixed" portion of the stack to facilitate use of Wasm addressing modes, which only allow positive offsets. -Structs are generally passed by-reference, unless they happen to exactly contain a single primitive field (or be a struct exactly containing such a struct). The linear stack provides the backing storage for the by-reference structs. +Arguments and return values are processed via the Type Lowering algorithm below. -Structs are generally returned via hidden buffers, whose address is supplied by the caller and passed just after the managed `this`, or after `$sp` argument when `this` is not present. In such cases the return value of the method is the address of the return value. But if the struct can be passed on the Wasm stack it is returned on the Wasm stack. +If a struct is returned via a hidden buffer, the address is supplied by the caller and passed just after the managed `this`, or after `$sp` argument when `this` is not present. In such cases the return value of the method is the address of the return value. But if the struct can be passed on the Wasm stack it is returned on the Wasm stack per the Type Lowering rules. (TBD: ABI for vector types) +### Type Lowering + +Managed types are lowered to WebAssembly value types according to the following rules +(implemented in `WasmLowering.LowerToAbiType` and `WasmLowering.LowerType`): + +| Managed type | Wasm value type | +|---|---| +| `bool`, `char`, `sbyte`, `byte`, `short`, `ushort`, `int`, `uint` | `i32` | +| `long`, `ulong` | `i64` | +| `float` | `f32` | +| `double` | `f64` | +| `nint`, `nuint`, pointer, byref, function pointer | `i32` (pointer-sized) | +| Reference types (class, string, array, szarray, interface) | `i32` (pointer-sized) | +| Value type (struct) — single primitive field, no padding | Unwrap recursively to the field's wasm type | +| Value type (struct) — single field with padding, or multiple fields | Passed by reference (`i32` pointer) | +| Empty struct (zero instance fields) | Elided from the signature entirely | + +**Struct unwrapping** is recursive: a struct containing a single struct field, where the inner struct +has the same size as the outer, is unwrapped until a primitive is reached or the rule no longer applies. +For example, a struct `Wrapper { Inner value; }` where `Inner { int x; }` is unwrapped all the way +to `i32`. + +A struct is **not** unwrapped when: +- It has more than one instance field. +- It has exactly one instance field but the field's size differs from the struct's size (i.e., the + struct has padding due to explicit layout or alignment attributes). + +Structs that cannot be unwrapped are passed by reference. The caller allocates space on the linear +stack and passes a pointer. For return values, the caller provides a hidden return buffer pointer. + ### Prolog The prolog will decrement the stack pointer by the fixed frame size, home any arguments that are stored on the linear stack, and zero initialize slots on the linear stack as appropriate. It will establish a frame pointer if one is needed. diff --git a/docs/design/coreclr/botr/readytorun-format.md b/docs/design/coreclr/botr/readytorun-format.md index 6ce4f1470da6a8..59dead55ab2bf3 100644 --- a/docs/design/coreclr/botr/readytorun-format.md +++ b/docs/design/coreclr/botr/readytorun-format.md @@ -288,6 +288,7 @@ fixup kind, the rest of the signature varies based on the fixup kind. | READYTORUN_FIXUP_Verify_IL_Body | 0x36 | Verify an IL body is defined the same at compile time and runtime. A failed match will cause a hard runtime failure. See[IL Body signatures](il-body-signatures) for details. | READYTORUN_FIXUP_ContinuationLayout | 0x37 | Layout of an async method continuation type, followed by typespec signature | READYTORUN_FIXUP_ResumptionStubEntryPoint | 0x38 | Entry point of an async method resumption stub +| READYTORUN_FIXUP_InjectStringThunks | 0x39 | Inject pregenerated string-to-code thunk mappings. See [InjectStringThunks signatures](#injectstringthunks-signatures) for details. | READYTORUN_FIXUP_ModuleOverride | 0x80 | When or-ed to the fixup ID, the fixup byte in the signature is followed by an encoded uint with assemblyref index, either within the MSIL metadata of the master context module for the signature or within the manifest metadata R2R header table (used in cases inlining brings in references to assemblies not seen in the input MSIL). #### Method Signatures @@ -333,6 +334,21 @@ ECMA 335 does not have a natural encoding for describing an overridden method. T ECMA 335 does not define a format that can represent the exact implementation of a method by itself. This signature holds all of the IL of the method, the EH table, the locals table, and each token (other than type references) in those tables is replaced with an index into a local stream of signatures. Those signatures are simply verbatim copies of the needed metadata to describe MemberRefs, TypeSpecs, MethodSpecs, StandaloneSignatures and strings. All of that is bundled into a large byte array. In addition, a series of TypeSignatures follows which allow the type references to be resolved, as well as a methodreference to the uninstantiated method. Assuming all of this matches with the data that is present at runtime, the fixup is considered to be satisfied. See ReadyToRunStandaloneMetadata.cs for the exact details of the format. +#### InjectStringThunks signatures + +The `READYTORUN_FIXUP_InjectStringThunks` fixup is placed in an eager import section and is processed at R2R module load time. There is at most one such fixup per compilation. It encodes a mapping from UTF-8 strings to pregenerated code thunks embedded in the R2R image. + +The signature following the fixup kind byte is a series of elements: + +| Field | Size | Description +|:------|-----:|:----------- +| LookupString | variable | A null-terminated UTF-8 string (the lookup key) +| ThunkRVA | 4 bytes | An RVA into the module indicating the location of the thunk code. On WebAssembly platforms, this is an I32 function table index instead. + +The series terminates when the null-terminated string is the empty string (a single `0x00` byte). There is no trailing RVA after the terminal empty string. + +At runtime, the entries are merged into a global hash table. Strings already present in the table from previously loaded modules take precedence over new entries. The table can be queried via `LookupPregeneratedThunkByString`. + ### READYTORUN_IMPORT_SECTIONS::AuxiliaryData For slots resolved lazily via `READYTORUN_HELPER_DelayLoad_MethodCall` helper, auxiliary data are @@ -985,6 +1001,96 @@ enum ReadyToRunHelper }; ``` +# Wasm Signature String Encoding + +Every managed method signature is encoded as a compact string that uniquely identifies its +lowered Wasm calling convention. This encoding is used in R2R thunk lookup tables and is +shared across three codebases: + +- **crossgen2** (`WasmLowering.GetSignature`): reference implementation, produces the string + during R2R compilation. +- **WasmAppBuilder** (`SignatureMapper`): MSBuild task that generates interpreter-to-native + thunk tables from reflection metadata. +- **CoreCLR runtime** (`helpers.cpp`, `GetSignatureKey`): runtime signature computation for + calli and portable entrypoint thunks. + +The string format is: + +``` + [] [...] ... [p] +``` + +**Return type** (first character): + +| Encoding | Meaning | +|---|---| +| `v` | void return, or empty struct return (no return buffer) | +| `i` | returns `i32` | +| `l` | returns `i64` | +| `f` | returns `f32` | +| `d` | returns `f64` | +| `S` | struct return via hidden buffer, `N` is the struct size in bytes | + +**This pointer** (if the method has a `this` parameter): + +| Encoding | Meaning | +|---|---| +| `T` | `this` pointer (managed instance methods) | + +**Hidden parameters** (inserted between `this` and explicit parameters, in order): + +1. **Generic context** (`i`): present when the method requires an inst method desc or + method table argument. +2. **Async continuation** (`i`): present for async calls. + +Note: the hidden return buffer pointer is **not** encoded in the signature string. Its +presence is implied by the return type being `S` — when the caller sees a struct return, +it knows a hidden retbuf pointer argument is present in the Wasm parameter list. + +**Explicit parameters** (one token per parameter, in declaration order): + +| Encoding | Meaning | +|---|---| +| `i` | `i32` parameter | +| `l` | `i64` parameter | +| `f` | `f32` parameter | +| `d` | `f64` parameter | +| `S` | struct parameter passed by reference, `` is the struct size in bytes | +| `e` | empty struct parameter — elided from Wasm args but present in the string | + +**Suffix**: + +| Encoding | Meaning | +|---|---| +| `p` | managed call with portable entrypoint (the `&pe` argument is implicit) | +| *(absent)* | unmanaged callers only (reverse P/Invoke) | + +**Prefix** (applied by the caller, not part of the core encoding): + +When storing signature strings in thunk lookup tables, callers prepend a single-character +prefix to distinguish thunk categories: + +| Prefix | Meaning | +|---|---| +| `M` | Calli thunk or interpreter-to-native thunk | +| `I` | Portable entrypoint-to-interpreter thunk | + +**Examples**: + +| Method | Signature string (no prefix) | +|---|---| +| `static void F()` | `vp` | +| `static int F(int x)` | `iip` | +| `void F(int x)` (instance) | `vTip` | +| `static MyStruct F()` where `MyStruct` is 16 bytes | `S16p` | +| `static void F(MyStruct s)` where `MyStruct` is 8 bytes | `vS8p` | +| `static int F(float x, double y)` | `ifdp` | +| `[UnmanagedCallersOnly] static int F(int x)` | `ii` | + +**Slot sizing for structs**: When computing interpreter stack layout, struct parameters +(`S`) consume `max(N / 8, 1)` interpreter stack slots, while all other parameter types +consume exactly 1 slot. + # References [ECMA-335](https://www.ecma-international.org/publications-and-standards/standards/ecma-335) diff --git a/src/coreclr/inc/CrstTypes.def b/src/coreclr/inc/CrstTypes.def index 526bdd6e199037..5a73a52eb37a65 100644 --- a/src/coreclr/inc/CrstTypes.def +++ b/src/coreclr/inc/CrstTypes.def @@ -525,3 +525,7 @@ End Crst CallStubCache AcquiredBefore LoaderHeap End + +Crst PregeneratedStringThunks + AcquiredBefore UnresolvedClassLock +End \ No newline at end of file diff --git a/src/coreclr/inc/corjit.h b/src/coreclr/inc/corjit.h index bf17388fd4bb64..649c38b171ba64 100644 --- a/src/coreclr/inc/corjit.h +++ b/src/coreclr/inc/corjit.h @@ -424,6 +424,12 @@ class ICorJitInfo : public ICorDynamicInfo CORINFO_METHOD_HANDLE methodHandle /* IN */ ) = 0; + // Records the signature of a managed call site for Wasm R2R thunk generation. + // This is a no-op on all targets except ReadyToRun Wasm compilation. + virtual void recordWasmManagedCallSig( + CORINFO_SIG_INFO * callSig /* IN */ + ) = 0; + // A relocation is recorded if we are pre-jitting. // A jump thunk may be inserted if we are jitting virtual void recordRelocation( diff --git a/src/coreclr/inc/crsttypes_generated.h b/src/coreclr/inc/crsttypes_generated.h index c52fcd9eb65e2e..d6720d9ee42ac3 100644 --- a/src/coreclr/inc/crsttypes_generated.h +++ b/src/coreclr/inc/crsttypes_generated.h @@ -88,37 +88,38 @@ enum CrstType CrstPgoData = 70, CrstPinnedByrefValidation = 71, CrstPinnedHeapHandleTable = 72, - CrstProfilerGCRefDataFreeList = 73, - CrstProfilingAPIStatus = 74, - CrstRCWCache = 75, - CrstRCWCleanupList = 76, - CrstReadyToRunEntryPointToMethodDescMap = 77, - CrstReflection = 78, - CrstReJITGlobalRequest = 79, - CrstSigConvert = 80, - CrstSingleUseLock = 81, - CrstStressLog = 82, - CrstStubCache = 83, - CrstStubDispatchCache = 84, - CrstSyncBlockCache = 85, - CrstSyncHashLock = 86, - CrstSystemDomain = 87, - CrstSystemDomainDelayedUnloadList = 88, - CrstThreadIdDispenser = 89, - CrstThreadLocalStorageLock = 90, - CrstThreadStore = 91, - CrstTieredCompilation = 92, - CrstTypeEquivalenceMap = 93, - CrstTypeIDMap = 94, - CrstUMEntryThunkCache = 95, - CrstUMEntryThunkFreeListLock = 96, - CrstUniqueStack = 97, - CrstUnresolvedClassLock = 98, - CrstUnwindInfoTablePendingLock = 99, - CrstUnwindInfoTablePublishLock = 100, - CrstVSDIndirectionCellLock = 101, - CrstWrapperTemplate = 102, - kNumberOfCrstTypes = 103 + CrstPregeneratedStringThunks = 73, + CrstProfilerGCRefDataFreeList = 74, + CrstProfilingAPIStatus = 75, + CrstRCWCache = 76, + CrstRCWCleanupList = 77, + CrstReadyToRunEntryPointToMethodDescMap = 78, + CrstReflection = 79, + CrstReJITGlobalRequest = 80, + CrstSigConvert = 81, + CrstSingleUseLock = 82, + CrstStressLog = 83, + CrstStubCache = 84, + CrstStubDispatchCache = 85, + CrstSyncBlockCache = 86, + CrstSyncHashLock = 87, + CrstSystemDomain = 88, + CrstSystemDomainDelayedUnloadList = 89, + CrstThreadIdDispenser = 90, + CrstThreadLocalStorageLock = 91, + CrstThreadStore = 92, + CrstTieredCompilation = 93, + CrstTypeEquivalenceMap = 94, + CrstTypeIDMap = 95, + CrstUMEntryThunkCache = 96, + CrstUMEntryThunkFreeListLock = 97, + CrstUniqueStack = 98, + CrstUnresolvedClassLock = 99, + CrstUnwindInfoTablePendingLock = 100, + CrstUnwindInfoTablePublishLock = 101, + CrstVSDIndirectionCellLock = 102, + CrstWrapperTemplate = 103, + kNumberOfCrstTypes = 104 }; #endif // __CRST_TYPES_INCLUDED @@ -202,6 +203,7 @@ int g_rgCrstLevelMap[] = 3, // CrstPgoData 0, // CrstPinnedByrefValidation 15, // CrstPinnedHeapHandleTable + 7, // CrstPregeneratedStringThunks 0, // CrstProfilerGCRefDataFreeList 14, // CrstProfilingAPIStatus 3, // CrstRCWCache @@ -310,6 +312,7 @@ LPCSTR g_rgCrstNameMap[] = "CrstPgoData", "CrstPinnedByrefValidation", "CrstPinnedHeapHandleTable", + "CrstPregeneratedStringThunks", "CrstProfilerGCRefDataFreeList", "CrstProfilingAPIStatus", "CrstRCWCache", diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 7a67f9ea0ff3cd..8fc80cda20018c 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -742,6 +742,9 @@ void recordCallSite( CORINFO_SIG_INFO* callSig, CORINFO_METHOD_HANDLE methodHandle) override; +void recordWasmManagedCallSig( + CORINFO_SIG_INFO* callSig) override; + void recordRelocation( void* location, void* locationRW, diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index fe5749d03fdda1..d51dc20a9cca3d 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* e92fbf65-4856-4729-ab9e-f66f7adcecf9 */ - 0xe92fbf65, - 0x4856, - 0x4729, - {0xab, 0x9e, 0xf6, 0x6f, 0x7a, 0xdc, 0xec, 0xf9} +constexpr GUID JITEEVersionIdentifier = { /* bf284efa-a3fb-4420-a62f-6e1a0a1b2cfc */ + 0xbf284efa, + 0xa3fb, + 0x4420, + {0xa6, 0x2f, 0x6e, 0x1a, 0x0a, 0x1b, 0x2c, 0xfc} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index b1998ad855888b..ebcec465ce4e2a 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -20,7 +20,7 @@ // If you update this, ensure you run `git grep MINIMUM_READYTORUN_MAJOR_VERSION` // and handle pending work. #define READYTORUN_MAJOR_VERSION 18 -#define READYTORUN_MINOR_VERSION 0x0005 +#define READYTORUN_MINOR_VERSION 0x0007 #define MINIMUM_READYTORUN_MAJOR_VERSION 18 @@ -56,6 +56,8 @@ // R2R Version 18.3 adds the ExternalTypeMaps, ProxyTypeMaps, TypeMapAssemblyTargets sections // R2R Version 18.4 adds ThrowArgument, ThrowArgumentOutOfRange, ThrowPlatformNotSupported, and ThrowNotImplemented helpers // R2R Version 18.5 adds READYTORUN_FLAG_STRIPPED_IL_BODIES, READYTORUN_FLAG_STRIPPED_INLINING_INFO, and READYTORUN_FLAG_STRIPPED_DEBUG_INFO flags +// R2R Version 18.6 adds READYTORUN_FIXUP_InjectStringThunks for mapping strings to pregenerated code thunks +// R2R Version 18.7 adds READYTORUN_HELPER_R2RToInterpreter struct READYTORUN_CORE_HEADER { @@ -310,6 +312,8 @@ enum ReadyToRunFixupKind READYTORUN_FIXUP_Continuation_Layout = 0x37, /* Layout of an async method continuation type */ READYTORUN_FIXUP_ResumptionStubEntryPoint = 0x38, /* Entry point of an async method resumption stub */ + READYTORUN_FIXUP_InjectStringThunks = 0x39, /* Inject pregenerated string-to-code thunk mappings into the global lookup table */ + READYTORUN_FIXUP_ModuleOverride = 0x80, /* followed by sig-encoded UInt with assemblyref index into either the assemblyref table of the MSIL metadata of the master context module for the signature or */ /* into the extra assemblyref table in the manifest metadata R2R header table (used in cases inlining brings in references to assemblies not seen in the MSIL). */ }; @@ -490,6 +494,7 @@ enum ReadyToRunHelper READYTORUN_HELPER_InitClass = 0x116, READYTORUN_HELPER_InitInstClass = 0x117, + READYTORUN_HELPER_R2RToInterpreter = 0x118, }; #include "readytoruninstructionset.h" diff --git a/src/coreclr/inc/webcildecoder.h b/src/coreclr/inc/webcildecoder.h index f91bf891f2d2da..10333cc7c905d2 100644 --- a/src/coreclr/inc/webcildecoder.h +++ b/src/coreclr/inc/webcildecoder.h @@ -207,6 +207,8 @@ class WebcilDecoder CHECK CheckDirectory(IMAGE_DATA_DIRECTORY *pDir, int forbiddenFlags = 0, IsNullOK ok = NULL_NOT_OK) const; TADDR GetDirectoryData(IMAGE_DATA_DIRECTORY *pDir) const; + +public: SSIZE_T GetTableBaseOffset() const { if (m_pHeader == NULL) diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index d03d03b1007970..57781f560ce0d5 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -178,6 +178,7 @@ DEF_CLR_API(reportFatalError) DEF_CLR_API(getPgoInstrumentationResults) DEF_CLR_API(allocPgoInstrumentationBySchema) DEF_CLR_API(recordCallSite) +DEF_CLR_API(recordWasmManagedCallSig) DEF_CLR_API(recordRelocation) DEF_CLR_API(getRelocTypeHint) DEF_CLR_API(getExpectedTargetArchitecture) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index 80411912d6c9cc..87e541b608f155 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -1728,6 +1728,14 @@ void WrapICorJitInfo::recordCallSite( API_LEAVE(recordCallSite); } +void WrapICorJitInfo::recordWasmManagedCallSig( + CORINFO_SIG_INFO* callSig) +{ + API_ENTER(recordWasmManagedCallSig); + wrapHnd->recordWasmManagedCallSig(callSig); + API_LEAVE(recordWasmManagedCallSig); +} + void WrapICorJitInfo::recordRelocation( void* location, void* locationRW, diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 4df06a6e5d22b0..4f6f6309c90e9d 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2488,8 +2488,31 @@ void CodeGen::genCallInstruction(GenTreeCall* call) params.sigInfo = call->callSig; } #endif // DEBUG + GenTree* target = getCallTarget(call, ¶ms.methHnd); + // Report managed call signatures to the R2R compiler for thunk generation. + if (!call->IsHelperCall() && !call->IsUnmanaged()) + { + CORINFO_SIG_INFO sigInfoLocal; + CORINFO_SIG_INFO* sigInfoCall = call->callSig; + + if (sigInfoCall == nullptr) + { + if ((params.methHnd != NO_METHOD_HANDLE) && + (Compiler::eeGetHelperNum(params.methHnd) == CORINFO_HELP_UNDEF)) + { + m_compiler->eeGetMethodSig(params.methHnd, &sigInfoLocal); + sigInfoCall = &sigInfoLocal; + } + } + + if (sigInfoCall != nullptr) + { + m_compiler->info.compCompHnd->recordWasmManagedCallSig(sigInfoCall); + } + } + ArrayStack typeStack(m_compiler->getAllocator(CMK_Codegen)); if (call->TypeIs(TYP_STRUCT)) diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index f9365400bd7f09..e634b9fdbb56cf 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -9775,7 +9775,7 @@ GenTreeCall* Compiler::gtNewCallNode(gtCallTypes callType, node->gtFlags |= GTF_CALL_POP_ARGS; #endif // UNIX_X86_ABI node->gtCallType = callType; - INDEBUG(node->callSig = nullptr;) + INDEBUG_OR_WASM(node->callSig = nullptr;) node->tailCallInfo = nullptr; node->gtRetClsHnd = nullptr; node->gtCallMoreFlags = GTF_CALL_M_EMPTY; @@ -11369,7 +11369,7 @@ GenTreeCall* Compiler::gtCloneExprCallHelper(GenTreeCall* tree) // we only really need one physical copy of it. Therefore a shallow pointer copy will suffice. // (Note that this still holds even if the tree we are cloning was created by an inlinee compiler, // because the inlinee still uses the inliner's memory allocator anyway.) - INDEBUG(copy->callSig = tree->callSig;) + INDEBUG_OR_WASM(copy->callSig = tree->callSig;) if (tree->IsUnmanaged()) { diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 85b8693ce996a3..112c0365d8e820 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -5155,7 +5155,7 @@ struct GenTreeCall final : public GenTree { CallArgs gtArgs; -#ifdef DEBUG +#if defined(DEBUG) || defined(TARGET_WASM) // Used to register callsites with the EE CORINFO_SIG_INFO* callSig; #endif diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 6ef07513ee4b10..d110a448c78e97 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -1102,10 +1102,14 @@ var_types Compiler::impImportCall(OPCODE opcode, DONE: -#ifdef DEBUG +#if defined(DEBUG) || defined(TARGET_WASM) // In debug we want to be able to register callsites with the EE. assert(call->AsCall()->callSig == nullptr); - call->AsCall()->callSig = new (this, CMK_DebugOnly) CORINFO_SIG_INFO; +#ifdef TARGET_WASM + call->AsCall()->callSig = new (this, CMK_ASTNode) CORINFO_SIG_INFO; +#else + call->AsCall()->callSig = new (this, CMK_DebugOnly) CORINFO_SIG_INFO; +#endif *call->AsCall()->callSig = *sig; #endif diff --git a/src/coreclr/jit/jit.h b/src/coreclr/jit/jit.h index 8b6aaa227ef84a..5c7f32e258598d 100644 --- a/src/coreclr/jit/jit.h +++ b/src/coreclr/jit/jit.h @@ -344,6 +344,12 @@ typedef ptrdiff_t ssize_t; #define DEBUGARG(x) #endif +#if defined(DEBUG) || defined(TARGET_WASM) +#define INDEBUG_OR_WASM(x) x +#else +#define INDEBUG_OR_WASM(x) +#endif + #if defined(DEBUG) || defined(LATE_DISASM) #define INDEBUG_LDISASM_COMMA(x) x, #else diff --git a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h index bf8be4d276088c..27eac374c5893d 100644 --- a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h +++ b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h @@ -12,7 +12,7 @@ struct ReadyToRunHeaderConstants static const uint32_t Signature = 0x00525452; // 'RTR' static const uint32_t CurrentMajorVersion = 18; - static const uint32_t CurrentMinorVersion = 5; + static const uint32_t CurrentMinorVersion = 7; }; struct ReadyToRunHeader diff --git a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs new file mode 100644 index 00000000000000..f3a270d255af18 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs @@ -0,0 +1,62 @@ +// 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 Internal.TypeSystem; + +namespace ILCompiler +{ + public partial class CompilerTypeSystemContext + { + private readonly object _structCacheLock = new object(); + private readonly Dictionary _structsBySize = new Dictionary(); + private volatile TypeDesc _cachedEmptyStruct; + + /// + /// Gets the first empty struct type encountered during lowering, or null if none has been seen. + /// Used by RaiseSignature to produce a roundtrippable type for the 'e' encoding. + /// + public TypeDesc CachedEmptyStruct => _cachedEmptyStruct; + + /// + /// Caches an empty struct type discovered during lowering. Only the first one is retained. + /// + public void CacheEmptyStruct(TypeDesc type) + { + _cachedEmptyStruct ??= type; + } + + /// + /// Caches a struct type by its element size, so RaiseSignature can retrieve a real + /// type of that size. Only the first struct encountered for a given size is retained. + /// + public void CacheStructBySize(TypeDesc type) + { + int size = type.GetElementSize().AsInt; + if (size <= 0) + return; + + lock (_structCacheLock) + { + _structsBySize.TryAdd(size, type); + } + } + + /// + /// Gets a previously cached struct type of the specified byte size. + /// Returns null if no struct of that size has been cached. + /// Used by RaiseSignature to produce a roundtrippable type for the 'S<N>' encoding. + /// + public TypeDesc GetCachedStructOfSize(int size) + { + lock (_structCacheLock) + { + if (_structsBySize.TryGetValue(size, out TypeDesc result)) + return result; + } + + return null; + } + } +} diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs index 6018d69f5dda0e..91e57b3b29cca3 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs @@ -268,6 +268,7 @@ public void EmitReloc(ISymbolNode symbol, RelocType relocType, int delta = 0) switch (relocType) { case RelocType.WASM_TABLE_INDEX_I32: + case RelocType.WASM_TABLE_INDEX_REL_I32: case RelocType.IMAGE_REL_BASED_REL32: case RelocType.IMAGE_REL_BASED_RELPTR32: case RelocType.IMAGE_REL_BASED_ABSOLUTE: diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs index 05f2e1213bf73f..3ed1f41e378e9d 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs @@ -61,6 +61,7 @@ public enum RelocType WASM_TABLE_INDEX_I64 = 0x208, // Wasm: a table index encoded as a 8-byte uint64, e.g. for storing the "address" of a function into linear memory WASM_MEMORY_ADDR_REL_LEB = 0x209, // Wasm: a relative linear memory index encoded as a 5-byte varuint32. Used as the immediate argument of a load or store instruction, // e.g. in R2R scenarios as an offset from $imageBase + WASM_TABLE_INDEX_REL_I32 = 0x20A, // Wasm: a table index encoded as a 4-byte uint32 relative to the tableBase of the R2R image // // Relocation operators related to TLS access @@ -668,6 +669,7 @@ public static unsafe void WriteValue(RelocType relocType, void* location, long v DwarfHelper.WritePaddedSLEB128(new Span((byte*)location, WASM_PADDED_RELOC_SIZE_32), value); return; case RelocType.WASM_TABLE_INDEX_I32: + case RelocType.WASM_TABLE_INDEX_REL_I32: *(uint*)location = checked((uint)value); return; case RelocType.WASM_TABLE_INDEX_I64: @@ -716,6 +718,7 @@ public static int GetSize(RelocType relocType) RelocType.WASM_MEMORY_ADDR_REL_LEB => WASM_PADDED_RELOC_SIZE_32, RelocType.WASM_MEMORY_ADDR_REL_SLEB => WASM_PADDED_RELOC_SIZE_32, RelocType.WASM_TABLE_INDEX_I32 => 4, + RelocType.WASM_TABLE_INDEX_REL_I32 => 4, RelocType.WASM_TABLE_INDEX_I64 => 8, _ => throw new NotSupportedException(), @@ -780,6 +783,7 @@ public static unsafe long ReadValue(RelocType relocType, void* location) case RelocType.WASM_FUNCTION_INDEX_LEB: case RelocType.WASM_TABLE_INDEX_SLEB: case RelocType.WASM_TABLE_INDEX_I32: + case RelocType.WASM_TABLE_INDEX_REL_I32: case RelocType.WASM_TABLE_INDEX_I64: case RelocType.WASM_TYPE_INDEX_LEB: case RelocType.WASM_GLOBAL_INDEX_LEB: diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmTypes.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmTypes.cs index d2a560f9b98c36..30944c04ce70cf 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmTypes.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmTypes.cs @@ -145,6 +145,42 @@ public static string ToTypeListString(this WasmResultType result) } } + public readonly struct WasmSignature : IEquatable, IComparable + { + public WasmFuncType FuncType { get; } + public string SignatureString { get; } + + public WasmSignature(WasmFuncType funcType, string signatureString) + { + FuncType = funcType; + SignatureString = signatureString; + } + + public bool Equals(WasmSignature other) + { + bool result = SignatureString.Equals(other.SignatureString, StringComparison.Ordinal); + Debug.Assert(!result || FuncType.Equals(other.FuncType), + "WasmSignature strings match but FuncTypes differ"); + + return result; + } + + public override bool Equals(object? obj) => obj is WasmSignature other && Equals(other); + + public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(SignatureString); + + public int CompareTo(WasmSignature other) + { + int result = string.Compare(SignatureString, other.SignatureString, StringComparison.Ordinal); + Debug.Assert(result != 0 || FuncType.Equals(other.FuncType), + "WasmSignature strings match but FuncTypes differ"); + return result; + } + + public static bool operator ==(WasmSignature left, WasmSignature right) => left.Equals(right); + public static bool operator !=(WasmSignature left, WasmSignature right) => !left.Equals(right); + } + public struct WasmFuncType : IEquatable, IComparable { private readonly WasmResultType _params; diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs index 813b439c2ecac3..e4c27619991e6f 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs @@ -135,6 +135,8 @@ public enum WasmExprKind RefNull = 0xD0, // Variable length instructions — not directly cast to a byte, instead the prefix byte is set in the upper 8 bits of the enum, and the lower 24 bits are the extended variable length opcode MemoryInit = unchecked((int)0xFC000008), + MemoryCopy = unchecked((int)0xFC00000A), + MemoryFill = unchecked((int)0xFC00000B), TableInit = unchecked((int)0xFC00000C), TableGrow = unchecked((int)0xFC00000F), V128Load = unchecked((int)0xFD00000A), @@ -183,7 +185,7 @@ public static bool IsGlobalVarExpr(this WasmExprKind kind) public static bool IsMemoryExpr(this WasmExprKind kind) { - return kind == WasmExprKind.MemoryInit; + return kind == WasmExprKind.MemoryInit || kind == WasmExprKind.MemoryCopy || kind == WasmExprKind.MemoryFill; } public static bool IsVariableLengthInstruction(this WasmExprKind kind) { @@ -569,8 +571,68 @@ public WasmUnaryExpr(WasmExprKind kind) : base(kind) // base class defaults are sufficient as the base class encodes just the opcode } + // Represents a memory.copy expression. + // Binary encoding: 0xFC prefix + u32(10) sub-opcode + u32(dstMemoryIndex) + u32(srcMemoryIndex) + // Stack operands: (dst: i32, src: i32, len: i32) -> () + class WasmMemoryCopyExpr : WasmExpr + { + public readonly int DstMemoryIndex; + public readonly int SrcMemoryIndex; + + public WasmMemoryCopyExpr(int dstMemoryIndex = 0, int srcMemoryIndex = 0) : base(WasmExprKind.MemoryCopy) + { + Debug.Assert(dstMemoryIndex >= 0); + Debug.Assert(srcMemoryIndex >= 0); + DstMemoryIndex = dstMemoryIndex; + SrcMemoryIndex = srcMemoryIndex; + } + + public override int Encode(Span buffer) + { + int pos = base.Encode(buffer); + pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), (uint)DstMemoryIndex); + pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), (uint)SrcMemoryIndex); + + return pos; + } + + public override int EncodeSize() + { + return base.EncodeSize() + + (int)DwarfHelper.SizeOfULEB128((uint)DstMemoryIndex) + + (int)DwarfHelper.SizeOfULEB128((uint)SrcMemoryIndex); + } + } + + // Represents a memory.fill expression. + // Binary encoding: 0xFC prefix + u32(11) sub-opcode + u32(memoryIndex) + // Stack operands: (dst: i32, val: i32, len: i32) -> () + class WasmMemoryFillExpr : WasmExpr + { + public readonly int MemoryIndex; + + public WasmMemoryFillExpr(int memoryIndex = 0) : base(WasmExprKind.MemoryFill) + { + Debug.Assert(memoryIndex >= 0); + MemoryIndex = memoryIndex; + } + + public override int Encode(Span buffer) + { + int pos = base.Encode(buffer); + pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), (uint)MemoryIndex); + + return pos; + } + + public override int EncodeSize() + { + return base.EncodeSize() + + (int)DwarfHelper.SizeOfULEB128((uint)MemoryIndex); + } + } + // Represents a memory.init expression. - // Binary encoding: 0xFC prefix + u32(8) sub-opcode + u32(dataSegmentIndex) + u32(memoryIndex) class WasmMemoryInitExpr : WasmExpr { public readonly int DataSegmentIndex; @@ -760,6 +822,10 @@ public static WasmExpr ConstRVA(ISymbolNode symbolNode) static class I64 { + public static WasmExpr Const(long value) + { + return new WasmConstExpr(WasmExprKind.I64Const, value); + } public static WasmExpr Load(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.I64Load, 8, new WasmEncodableULong(offset)); public static WasmExpr Store(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.I64Store, 8, new WasmEncodableULong(offset)); } @@ -784,6 +850,16 @@ static class V128 static class Memory { + public static WasmExpr Copy(int dstMemoryIndex = 0, int srcMemoryIndex = 0) + { + return new WasmMemoryCopyExpr(dstMemoryIndex, srcMemoryIndex); + } + + public static WasmExpr Fill(int memoryIndex = 0) + { + return new WasmMemoryFillExpr(memoryIndex); + } + public static WasmExpr Init(int dataSegmentIndex, int memoryIndex = 0) { return new WasmMemoryInitExpr(dataSegmentIndex, memoryIndex); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 7f1bb208f7b563..0d7a2f283fdabc 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -128,7 +128,7 @@ private void WriteSignatureIndexForFunction(MethodSignature managedSignature, Wa { SectionWriter writer = GetOrCreateSection(WasmObjectNodeSection.FunctionSection); - WasmFuncType signature = WasmLowering.GetSignature(managedSignature, flags); + WasmFuncType signature = WasmLowering.GetSignature(managedSignature, flags).FuncType; Utf8String key = signature.GetMangledName(_nodeFactory.NameMangler); if (!_uniqueSignatures.TryGetValue(key, out int signatureIndex)) { @@ -980,12 +980,14 @@ private unsafe void ResolveRelocations(int sectionIndex, MemoryStream sectionStr case RelocType.WASM_TABLE_INDEX_I32: case RelocType.WASM_TABLE_INDEX_I64: case RelocType.WASM_TABLE_INDEX_SLEB: + case RelocType.WASM_TABLE_INDEX_REL_I32: { string symbolName = reloc.SymbolName.ToString(); int index = _uniqueSymbols[symbolName]; // Here, we are effectively writing a table offset relative to the table_base. - // These will need to be fixed up by the runtime after load by adding __image_function_pointer_base - // TODO-WASM: We need to emit these for fixup with an addend at runtime + // These will need to be fixed up by the runtime after load by adding tableBase + // except for WASM_TABLE_INDEX_REL_I32 and WASM_TABLE_INDEX_SLEB which are relative + // to the start of the table. Relocation.WriteValue(reloc.Type, pData, index); break; } diff --git a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs index c2d18e209e65f4..fc4acc9c25ca39 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs @@ -16,7 +16,7 @@ internal struct ReadyToRunHeaderConstants public const uint Signature = 0x00525452; // 'RTR' public const ushort CurrentMajorVersion = 18; - public const ushort CurrentMinorVersion = 5; + public const ushort CurrentMinorVersion = 7; } #if READYTORUN #pragma warning disable 0169 diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index b431b7b1ee800a..a68b32c2b6c7d7 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -193,6 +193,8 @@ public enum ReadyToRunFixupKind ContinuationLayout = 0x37, /* Layout of an async method continuation type */ ResumptionStubEntryPoint = 0x38, /* Entry point of an async method resumption stub */ + InjectStringThunks = 0x39, /* Inject pregenerated string-to-code thunk mappings into the global lookup table */ + ModuleOverride = 0x80, // followed by sig-encoded UInt with assemblyref index into either the assemblyref // table of the MSIL metadata of the master context module for the signature or @@ -369,6 +371,7 @@ public enum ReadyToRunHelper InitClass = 0x116, InitInstClass = 0x117, + R2RToInterpreter = 0x118, // ********************************************************************************************** // diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 6c44f4e6ebfdbc..8bac676350a37e 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -4123,12 +4123,6 @@ private void reportFatalError(CorJitResult result) // CompileMethod is going to fail with this CorJitResult anyway. } -#pragma warning disable CA1822 // Mark members as static - private void recordCallSite(uint instrOffset, CORINFO_SIG_INFO* callSig, CORINFO_METHOD_STRUCT_* methodHandle) -#pragma warning restore CA1822 // Mark members as static - { - } - private ArrayBuilder _codeRelocs; private ArrayBuilder _roDataRelocs; private ArrayBuilder _rwDataRelocs; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index a1eebc3762aaa1..2ec1fc175a8d91 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -194,6 +194,7 @@ static ICorJitInfoCallbacks() s_callbacks.getPgoInstrumentationResults = &_getPgoInstrumentationResults; s_callbacks.allocPgoInstrumentationBySchema = &_allocPgoInstrumentationBySchema; s_callbacks.recordCallSite = &_recordCallSite; + s_callbacks.recordWasmManagedCallSig = &_recordWasmManagedCallSig; s_callbacks.recordRelocation = &_recordRelocation; s_callbacks.getRelocTypeHint = &_getRelocTypeHint; s_callbacks.getExpectedTargetArchitecture = &_getExpectedTargetArchitecture; @@ -376,6 +377,7 @@ static ICorJitInfoCallbacks() public delegate* unmanaged getPgoInstrumentationResults; public delegate* unmanaged allocPgoInstrumentationBySchema; public delegate* unmanaged recordCallSite; + public delegate* unmanaged recordWasmManagedCallSig; public delegate* unmanaged recordRelocation; public delegate* unmanaged getRelocTypeHint; public delegate* unmanaged getExpectedTargetArchitecture; @@ -2950,6 +2952,20 @@ private static void _recordCallSite(IntPtr thisHandle, IntPtr* ppException, uint } } + [UnmanagedCallersOnly] + private static void _recordWasmManagedCallSig(IntPtr thisHandle, IntPtr* ppException, CORINFO_SIG_INFO* callSig) + { + var _this = GetThis(thisHandle); + try + { + _this.recordWasmManagedCallSig(callSig); + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + } + } + [UnmanagedCallersOnly] private static void _recordRelocation(IntPtr thisHandle, IntPtr* ppException, void* location, void* locationRW, void* target, CorInfoReloc fRelocType, int addlDelta) { diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 8bba89b5e829fb..5cae27262ef01f 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -116,14 +116,14 @@ public unsafe struct CORINFO_SIG_INFO public mdToken token; public CorInfoType retType { get { return (CorInfoType)_retType; } set { _retType = (byte)value; } } - private CorInfoCallConv getCallConv() { return (CorInfoCallConv)((callConv & CorInfoCallConv.CORINFO_CALLCONV_MASK)); } + internal CorInfoCallConv getCallConv() { return (CorInfoCallConv)((callConv & CorInfoCallConv.CORINFO_CALLCONV_MASK)); } private bool hasThis() { return ((callConv & CorInfoCallConv.CORINFO_CALLCONV_HASTHIS) != 0); } private bool hasExplicitThis() { return ((callConv & CorInfoCallConv.CORINFO_CALLCONV_EXPLICITTHIS) != 0); } private bool hasImplicitThis() { return ((callConv & (CorInfoCallConv.CORINFO_CALLCONV_HASTHIS | CorInfoCallConv.CORINFO_CALLCONV_EXPLICITTHIS)) == CorInfoCallConv.CORINFO_CALLCONV_HASTHIS); } private uint totalILArgs() { return (uint)(numArgs + (hasImplicitThis() ? 1 : 0)); } private bool isVarArg() { return ((getCallConv() == CorInfoCallConv.CORINFO_CALLCONV_VARARG) || (getCallConv() == CorInfoCallConv.CORINFO_CALLCONV_NATIVEVARARG)); } internal bool hasTypeArg() { return ((callConv & CorInfoCallConv.CORINFO_CALLCONV_PARAMTYPE) != 0); } - private bool isAsyncCall() { return ((callConv & CorInfoCallConv.CORINFO_CALLCONV_ASYNCCALL) != 0); } + internal bool isAsyncCall() { return ((callConv & CorInfoCallConv.CORINFO_CALLCONV_ASYNCCALL) != 0); } }; //---------------------------------------------------------------------------- diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index 8e605002f90174..384ca10f881504 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -347,6 +347,7 @@ FUNCTIONS JITINTERFACE_HRESULT getPgoInstrumentationResults(CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema** pSchema, uint32_t* pCountSchemaItems, uint8_t**pInstrumentationData, ICorJitInfo::PgoSource* pPgoSource, bool* pDynamicPgo) JITINTERFACE_HRESULT allocPgoInstrumentationBySchema(CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema* pSchema, uint32_t countSchemaItems, uint8_t** pInstrumentationData) void recordCallSite(uint32_t instrOffset, CORINFO_SIG_INFO* callSig, CORINFO_METHOD_HANDLE methodHandle) + void recordWasmManagedCallSig(CORINFO_SIG_INFO* callSig) void recordRelocation(void* location, void* locationRW, void* target, CorInfoReloc fRelocType, int32_t addlDelta) CorInfoReloc getRelocTypeHint(void* target) uint32_t getExpectedTargetArchitecture() diff --git a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs index b49fa31d24888c..ab9857664c6116 100644 --- a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs +++ b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Text; using ILCompiler; using ILCompiler.DependencyAnalysis.Wasm; @@ -121,32 +122,129 @@ public static WasmValueType LowerType(TypeDesc type) } } - private static TypeDesc RaiseType(WasmValueType valueType, TypeSystemContext context) + /// + /// Determines whether a type is an empty struct (no instance fields) that should + /// be ignored in the WebAssembly calling convention per the BasicCABI spec. + /// + // WASM-TODO: This currently always returns false because .NET pads empty structs + // to size 1. A proper implementation should check for 0 non-static fields. + // See https://github.com/dotnet/runtime/issues/127361 + public static bool IsEmptyStruct(TypeDesc type) => false; + + /// + /// Maps a WasmValueType to its single-character signature encoding. + /// + private static char WasmValueTypeToSigChar(WasmValueType vt) => vt switch + { + WasmValueType.I32 => 'i', + WasmValueType.I64 => 'l', + WasmValueType.F32 => 'f', + WasmValueType.F64 => 'd', + WasmValueType.V128 => 'V', + _ => throw new NotSupportedException($"Unknown WasmValueType: {vt}") + }; + + private static TypeDesc RaiseSigChar(char c, TypeSystemContext context) => c switch + { + 'i' => context.GetWellKnownType(WellKnownType.Int32), + 'l' => context.GetWellKnownType(WellKnownType.Int64), + 'f' => context.GetWellKnownType(WellKnownType.Single), + 'd' => context.GetWellKnownType(WellKnownType.Double), + 'V' => throw new NotSupportedException("SIMD types are not supported in this version of the compiler"), + _ => throw new InvalidOperationException($"Unknown signature char: {c}") + }; + + private static int ParseStructSize(string sig, ref int pos) { - return valueType switch + Debug.Assert(sig[pos] == 'S'); + pos++; // skip 'S' + int start = pos; + while (pos < sig.Length && char.IsDigit(sig[pos])) { - WasmValueType.I32 => context.GetWellKnownType(WellKnownType.Int32), - WasmValueType.I64 => context.GetWellKnownType(WellKnownType.Int64), - WasmValueType.F32 => context.GetWellKnownType(WellKnownType.Single), - WasmValueType.F64 => context.GetWellKnownType(WellKnownType.Double), - WasmValueType.V128 => throw new NotSupportedException("SIMD types are not supported in this version of the compiler"), - _ => throw new InvalidOperationException("Unknown WasmValueType: " + valueType), - }; + pos++; + } + return int.Parse(sig.AsSpan(start, pos - start)); } - public static MethodSignature RaiseSignature(WasmFuncType funcType, TypeSystemContext context) + public static MethodSignature RaiseSignature(WasmSignature wasmSignature, TypeSystemContext context) { + string sig = wasmSignature.SignatureString; + int pos = 0; + + // Parse return type + TypeDesc returnType; + if (sig[pos] == 'v') + { + returnType = context.GetWellKnownType(WellKnownType.Void); + pos++; + } + else if (sig[pos] == 'S') + { + int structSize = ParseStructSize(sig, ref pos); + returnType = ((CompilerTypeSystemContext)context).GetCachedStructOfSize(structSize); + Debug.Assert(returnType is not null, $"No cached struct of size {structSize} for return type in signature '{sig}'"); + } + else + { + returnType = RaiseSigChar(sig[pos], context); + pos++; + } + + // Parse parameters (everything until 'p' suffix or end of string) List parameters = new List(); - for (int i = 1; i < funcType.Params.Types.Length - 1; i++) + bool hasThis = false; + + while (pos < sig.Length && sig[pos] != 'p') + { + char c = sig[pos]; + if (c == 'T') + { + // 'this' parameter — not added as explicit param, sets hasThis flag + hasThis = true; + pos++; + } + else if (c == 'e') + { + // Empty struct — include the cached empty struct type for roundtrip fidelity + TypeDesc emptyStruct = ((CompilerTypeSystemContext)context).CachedEmptyStruct; + Debug.Assert(emptyStruct is not null, "Encountered 'e' in signature but no empty struct was cached during lowering"); + parameters.Add(emptyStruct); + pos++; + } + else if (c == 'S') + { + int structSize = ParseStructSize(sig, ref pos); + TypeDesc cachedStruct = ((CompilerTypeSystemContext)context).GetCachedStructOfSize(structSize); + Debug.Assert(cachedStruct is not null, $"No cached struct of size {structSize} for parameter in signature '{sig}'"); + parameters.Add(cachedStruct); + } + else + { + parameters.Add(RaiseSigChar(c, context)); + pos++; + } + } + + bool isManaged = pos < sig.Length && sig[pos] == 'p'; + MethodSignatureFlags flags = hasThis ? MethodSignatureFlags.None : MethodSignatureFlags.Static; + if (!isManaged) { - parameters.Add(RaiseType(funcType.Params.Types[i], context)); + flags |= MethodSignatureFlags.UnmanagedCallingConvention; } - TypeDesc returnType = funcType.Returns.Types.Length > 0 ? RaiseType(funcType.Returns.Types[0], context) : context.GetWellKnownType(WellKnownType.Void); - return new MethodSignature(MethodSignatureFlags.Static, 0, returnType, parameters.ToArray()); + + MethodSignature result = new MethodSignature(flags, 0, returnType, parameters.ToArray()); + + WasmSignature roundtripped = GetSignature(result, LoweringFlags.None); + Debug.Assert(roundtripped.Equals(wasmSignature), + $"RaiseSignature roundtrip failed: input='{wasmSignature.SignatureString}', roundtripped='{roundtripped.SignatureString}'"); + + return result; } /// /// Gets the Wasm-level signature for a given MethodDesc. + /// The signature string format is documented in docs/design/coreclr/botr/readytorun-format.md + /// (section "Wasm Signature String Encoding"). /// /// Parameters for managed Wasm calls have the following layout: /// i32 (SP), loweredParam0, ..., loweredParamN, i32 (PE entrypoint) @@ -156,7 +254,7 @@ public static MethodSignature RaiseSignature(WasmFuncType funcType, TypeSystemCo /// /// /// - public static WasmFuncType GetSignature(MethodDesc method) + public static WasmSignature GetSignature(MethodDesc method) { return GetSignature(method.Signature, GetLoweringFlags(method)); } @@ -188,10 +286,18 @@ public enum LoweringFlags IsUnmanagedCallersOnly = 0x4 } - public static WasmFuncType GetSignature(MethodSignature signature, LoweringFlags flags) + public static WasmSignature GetSignature(MethodSignature signature, LoweringFlags flags) { + if (!flags.HasFlag(LoweringFlags.IsUnmanagedCallersOnly) && signature.Flags.HasFlag(MethodSignatureFlags.UnmanagedCallingConvention)) + { + flags = flags | LoweringFlags.IsUnmanagedCallersOnly; + } + TypeDesc returnType = signature.ReturnType; WasmValueType pointerType = (signature.ReturnType.Context.Target.PointerSize == 4) ? WasmValueType.I32 : WasmValueType.I64; + char hiddenParamChar = WasmValueTypeToSigChar(pointerType); + + StringBuilder sigBuilder = new StringBuilder(); // Determine if the return value is via a return buffer // @@ -203,12 +309,30 @@ public static WasmFuncType GetSignature(MethodSignature signature, LoweringFlags if (loweredReturnType == null) { - hasReturnBuffer = true; - returnIsVoid = true; + if (IsEmptyStruct(returnType)) + { + // Empty struct return — treated as void with no return buffer + returnIsVoid = true; + sigBuilder.Append('v'); + } + else + { + hasReturnBuffer = true; + returnIsVoid = true; + int returnSize = returnType.GetElementSize().AsInt; + sigBuilder.Append('S'); + sigBuilder.Append(returnSize); + ((CompilerTypeSystemContext)returnType.Context).CacheStructBySize(returnType); + } } else if (loweredReturnType.IsVoid) { returnIsVoid = true; + sigBuilder.Append('v'); + } + else + { + sigBuilder.Append(WasmValueTypeToSigChar(LowerType(loweredReturnType))); } // Reserve space for potential implicit this, stack pointer parameter, portable entrypoint parameter, @@ -234,11 +358,12 @@ public static WasmFuncType GetSignature(MethodSignature signature, LoweringFlags } else // managed call { - result.Add(pointerType); // Stack pointer parameter + result.Add(pointerType); // Stack pointer parameter (encoded via 'p' suffix, not here) if (hasThis) { result.Add(pointerType); + sigBuilder.Append('T'); } if (hasReturnBuffer) @@ -250,28 +375,56 @@ public static WasmFuncType GetSignature(MethodSignature signature, LoweringFlags if (flags.HasFlag(LoweringFlags.HasGenericContextArg)) { result.Add(pointerType); // generic context + sigBuilder.Append(hiddenParamChar); } if (flags.HasFlag(LoweringFlags.IsAsyncCall)) { result.Add(pointerType); // async continuation + sigBuilder.Append(hiddenParamChar); } for (int i = explicitThis ? 1 : 0; i < signature.Length; i++) { - result.Add(LowerType(signature[i])); + TypeDesc paramType = signature[i]; + TypeDesc loweredParamType = LowerToAbiType(paramType); + + if (loweredParamType == null) + { + if (IsEmptyStruct(paramType)) + { + // Empty struct — not emitted as a WebAssembly argument + sigBuilder.Append('e'); + ((CompilerTypeSystemContext)signature.ReturnType.Context).CacheEmptyStruct(paramType); + continue; + } + + // Struct that cannot be lowered to a single primitive — passed by reference + int paramSize = paramType.GetElementSize().AsInt; + sigBuilder.Append('S'); + sigBuilder.Append(paramSize); + result.Add(pointerType); + ((CompilerTypeSystemContext)paramType.Context).CacheStructBySize(paramType); + } + else + { + WasmValueType paramWasmType = LowerType(paramType); + sigBuilder.Append(WasmValueTypeToSigChar(paramWasmType)); + result.Add(paramWasmType); + } } if (!flags.HasFlag(LoweringFlags.IsUnmanagedCallersOnly)) { - result.Add(pointerType); // PE entrypoint parameter + result.Add(pointerType); // PE entrypoint parameter (encoded via 'p' suffix) + sigBuilder.Append('p'); } WasmResultType ps = new(result.ToArray()); WasmResultType ret = returnIsVoid ? new(Array.Empty()) : new([LowerType(loweredReturnType)]); - return new WasmFuncType(ps, ret); + return new WasmSignature(new WasmFuncType(ps, ret), sigBuilder.ToString()); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 05a5439d22e056..9c12dbe07f7a25 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -37,6 +37,7 @@ + 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 19d4688723936d..78b6b3aa0f7f79 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,7 +41,15 @@ public DelayLoadHelperImport( _useJumpableStub = useJumpableStub; if (factory.Target.Architecture == TargetArchitecture.Wasm32) { - _delayLoadHelper = factory.WasmImportThunkPortableEntrypoint(this); + if (instanceSignature is GenericLookupSignature) + { + // Generic lookups are resolved via eager fixups and don't need import thunks + _delayLoadHelper = null; + } + else + { + _delayLoadHelper = factory.WasmImportThunkPortableEntrypoint(this); + } } else { @@ -74,10 +82,18 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde public override void EncodeData(ref ObjectDataBuilder dataBuilder, NodeFactory factory, bool relocsOnly) { - // This needs to be an empty target pointer since it will be filled in with Module* - // when loaded by CoreCLR - dataBuilder.EmitReloc(_delayLoadHelper, - factory.Target.PointerSize == 4 ? RelocType.IMAGE_REL_BASED_HIGHLOW : RelocType.IMAGE_REL_BASED_DIR64, factory.Target.CodeDelta); + if (_delayLoadHelper is not null) + { + // This needs to be an empty target pointer since it will be filled in with Module* + // when loaded by CoreCLR + dataBuilder.EmitReloc(_delayLoadHelper, + factory.Target.PointerSize == 4 ? RelocType.IMAGE_REL_BASED_HIGHLOW : RelocType.IMAGE_REL_BASED_DIR64, factory.Target.CodeDelta); + } + else + { + // Eager fixups don't need a delay load helper thunk — emit a zero pointer + dataBuilder.EmitNaturalInt(0); + } if (Table.EntrySize == (factory.Target.PointerSize * 2)) { @@ -91,9 +107,17 @@ public override void EncodeData(ref ObjectDataBuilder dataBuilder, NodeFactory f public override IEnumerable GetStaticDependencies(NodeFactory factory) { - return new DependencyListEntry[] + if (_delayLoadHelper is not null) + { + return new DependencyListEntry[] + { + new DependencyListEntry(_delayLoadHelper, "Delay load helper thunk for ready-to-run fixup import"), + new DependencyListEntry(ImportSignature, "Signature for ready-to-run fixup import"), + }; + } + + return new DependencyListEntry[] { - new DependencyListEntry(_delayLoadHelper, "Delay load helper thunk for ready-to-run fixup import"), new DependencyListEntry(ImportSignature, "Signature for ready-to-run fixup import"), }; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InjectStringThunksSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InjectStringThunksSignature.cs new file mode 100644 index 00000000000000..e5076a652ba18e --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InjectStringThunksSignature.cs @@ -0,0 +1,71 @@ +// 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.Text; + +using Internal.ReadyToRunConstants; +using Internal.Text; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis.ReadyToRun +{ + /// + /// Signature node for the READYTORUN_FIXUP_InjectStringThunks fixup. + /// Encodes a series of (null-terminated UTF8 string, 4-byte RVA/table index) pairs, + /// terminated by an empty string (single 0x00 byte with no trailing RVA). + /// + internal class InjectStringThunksSignature : Signature + { + public InjectStringThunksSignature() + { + } + + public override int ClassCode => 1493287651; + + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + ObjectDataSignatureBuilder builder = new ObjectDataSignatureBuilder(factory, relocsOnly); + builder.AddSymbol(this); + builder.EmitByte((byte)ReadyToRunFixupKind.InjectStringThunks); + + if (!relocsOnly) + { + List stubs = factory.GetStringDiscoverableStubs(); + stubs.Sort((a, b) => string.CompareOrdinal(a.LookupString, b.LookupString)); + + foreach (StringDiscoverableAssemblyStubNode stub in stubs) + { + // Emit the null-terminated UTF8 string + byte[] stringBytes = Encoding.UTF8.GetBytes(stub.LookupString); + builder.EmitBytes(stringBytes); + builder.EmitByte(0); // null terminator + + // Emit a 4-byte relocation to the stub code. + // On WASM, this is a table index; on other platforms, an RVA. + RelocType relocType = factory.Target.Architecture == TargetArchitecture.Wasm32 + ? RelocType.WASM_TABLE_INDEX_REL_I32 + : RelocType.IMAGE_REL_BASED_ADDR32NB; + builder.EmitReloc(stub, relocType, delta: factory.Target.CodeDelta); + } + } + + // Terminal empty string (no trailing RVA) + builder.EmitByte(0); + + return builder.ToObjectData(); + } + + public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(nameMangler.CompilationUnitPrefix); + sb.Append("InjectStringThunks"u8); + } + + public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + // There should only be one instance of this signature per compilation + return 0; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/StringDiscoverableAssemblyStubNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/StringDiscoverableAssemblyStubNode.cs new file mode 100644 index 00000000000000..133ac674549d47 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/StringDiscoverableAssemblyStubNode.cs @@ -0,0 +1,40 @@ +// 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.Diagnostics; + +using ILCompiler.DependencyAnalysisFramework; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// An abstract assembly stub node whose instances are discoverable by string key. + /// All instances present in the dependency graph are collected and emitted as a single + /// READYTORUN_FIXUP_InjectStringThunks eager fixup, mapping each LookupString to + /// the code address of the stub in the R2R image. + /// + public abstract class StringDiscoverableAssemblyStubNode : AssemblyStubNode + { + /// + /// The string key used to look up this stub at runtime via LookupPregeneratedThunkByString. + /// Must be non-empty and must not contain embedded null characters. + /// + public abstract string LookupString { get; } + + protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory) + { + DependencyList dependencies = new DependencyList(); + dependencies.Add(factory.InjectStringThunksImport, "StringDiscoverableAssemblyStubNode requires InjectStringThunks fixup"); + + return dependencies; + } + + protected override void OnMarked(NodeFactory factory) + { + Debug.Assert(!string.IsNullOrEmpty(LookupString), "LookupString must be non-empty"); + Debug.Assert(!LookupString.Contains('\0'), "LookupString must not contain embedded null characters"); + factory.RegisterStringDiscoverableStub(this); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs index 87b7db9e1ebeab..b85e3fa2163b60 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs @@ -289,7 +289,7 @@ public bool IsArgPassedByRef(int size) /// Check whether an arg is automatically switched to passing by reference. /// Note that this overload does not handle varargs. This method only works for /// valuetypes - true value types, primitives, enums and TypedReference. - /// The method is only overridden to do something meaningful on X64 and ARM64. + /// The method is only overridden to do something meaningful on X64, ARM64 and WASM. /// /// Type to analyze public virtual bool IsArgPassedByRef(TypeHandle th) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.cs index 5e6e47fe771069..b5f200735f9894 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.cs @@ -19,6 +19,7 @@ public class WasmImportThunk : AssemblyStubNode, INodeWithTypeSignature, ISymbol private readonly TypeSystemContext _context; private readonly Import _helperCell; private readonly WasmTypeNode _typeNode; + private readonly WasmSignature _wasmSignature; private readonly ImportThunkKind _thunkKind; @@ -33,10 +34,11 @@ public class WasmImportThunk : AssemblyStubNode, INodeWithTypeSignature, ISymbol /// Import thunks are used to call a runtime-provided helper which fixes up an indirection cell in a particular /// import section. Optionally they may also contain a relocation for a specific indirection cell to fix up. /// - public WasmImportThunk(NodeFactory factory, WasmTypeNode typeNode, ReadyToRunHelper helperId, ImportSectionNode containingImportSection, bool useVirtualCall, bool useJumpableStub) + public WasmImportThunk(NodeFactory factory, WasmSignature wasmSignature, ReadyToRunHelper helperId, ImportSectionNode containingImportSection, bool useVirtualCall, bool useJumpableStub) { _context = factory.TypeSystemContext; - _typeNode = typeNode; + _wasmSignature = wasmSignature; + _typeNode = factory.WasmTypeNode(wasmSignature); _helperCell = factory.GetReadyToRunHelperCell(helperId); _containingImportSection = containingImportSection; @@ -72,8 +74,7 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde { sb.Append("WasmDelayLoadHelper->"u8); _helperCell.AppendMangledName(nameMangler, sb); - sb.Append($"(ImportSection:{_containingImportSection.Name},Kind:{_thunkKind})"); - _typeNode.AppendMangledName(nameMangler, sb); + sb.Append($"(ImportSection:{_containingImportSection.Name},Kind:{_thunkKind},Sig:{_wasmSignature.SignatureString})"); } protected override string GetName(NodeFactory factory) @@ -85,7 +86,7 @@ protected override string GetName(NodeFactory factory) public override int ClassCode => 948271336; - MethodSignature INodeWithTypeSignature.Signature => WasmLowering.RaiseSignature(_typeNode.Type, _context); + MethodSignature INodeWithTypeSignature.Signature => WasmLowering.RaiseSignature(_wasmSignature, _context); bool INodeWithTypeSignature.IsUnmanagedCallersOnly => false; bool INodeWithTypeSignature.IsAsyncCall => false; @@ -98,7 +99,7 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer if (result != 0) return result; - result = _typeNode.CompareToImpl(otherNode._typeNode, comparer); + result = _wasmSignature.CompareTo(otherNode._wasmSignature); if (result != 0) return result; @@ -122,17 +123,18 @@ protected override void EmitCode(NodeFactory factory, ref Wasm.WasmEmitter instr ISymbolNode helperTypeIndex = factory.WasmTypeNode(_helperTypeParams); - MethodSignature methodSignature = WasmLowering.RaiseSignature(_typeNode.Type, _context); + MethodSignature methodSignature = WasmLowering.RaiseSignature(_wasmSignature, _context); (ArgIterator argit, TransitionBlock transitionBlock) = GCRefMapBuilder.BuildArgIterator(methodSignature, _context); int[] offsets = new int[methodSignature.Length]; - Debug.Assert(offsets.Length == _typeNode.Type.Params.Types.Length - 2); + bool[] isIndirectStructArg = new bool[methodSignature.Length]; int argIndex = 0; int argOffset; while ((argOffset = argit.GetNextOffset()) != TransitionBlock.InvalidOffset) { offsets[argIndex] = argOffset; + isIndirectStructArg[argIndex] = argit.IsArgPassedByRef() && argit.IsValueType(); argIndex++; } @@ -177,32 +179,76 @@ protected override void EmitCode(NodeFactory factory, ref Wasm.WasmEmitter instr // } // In the calling convention, the first arg is the sp arg, and the last is the portable entrypoint arg. Each of those are treated specially - for (int i = 1; i < _typeNode.Type.Params.Types.Length - 1; i++) + // Iterate over the raised MethodSignature params rather than wasm-level types. + // This allows us to: + // - Skip empty struct params (no wasm local exists) + // - Zero-fill indirect struct params instead of copying the byref pointer + bool hasThis = !methodSignature.IsStatic; + int wasmLocalIndex = 1; // local 0 is $sp + + // Store 'this' pointer if present — it occupies a wasm local but is not in the raised MethodSignature params + if (hasThis) { expressions.Add(Local.Get(0)); - expressions.Add(Local.Get(i)); - WasmValueType type = _typeNode.Type.Params.Types[i]; - int currentOffset = offsets[i - 1]; - switch (type) + expressions.Add(Local.Get(wasmLocalIndex)); + expressions.Add(I32.Store((ulong)transitionBlock.ThisOffset)); + wasmLocalIndex++; + } + + for (int i = 0; i < methodSignature.Length; i++) + { + TypeDesc paramType = methodSignature[i]; + + if (WasmLowering.IsEmptyStruct(paramType)) + { + // Empty struct — no wasm local, nothing to store + continue; + } + + int currentOffset = offsets[i]; + + if (isIndirectStructArg[i]) + { + // Indirect struct — zero-fill the transition block slot instead of copying the byref pointer. + int structSize = paramType.GetElementSize().AsInt; + int fillSize = AlignmentHelper.AlignUp(structSize, 8); + + // memory.fill: (dst, val, len) -> () + expressions.Add(Local.Get(0)); + expressions.Add(I32.Const(currentOffset)); + expressions.Add(I32.Add); + expressions.Add(I32.Const(0)); + expressions.Add(I32.Const(fillSize)); + expressions.Add(Memory.Fill()); + wasmLocalIndex++; + } + else { - case WasmValueType.I32: - expressions.Add(I32.Store((ulong)currentOffset)); - break; - case WasmValueType.F32: - expressions.Add(F32.Store((ulong)currentOffset)); - break; - case WasmValueType.I64: - expressions.Add(I64.Store((ulong)currentOffset)); - break; - case WasmValueType.F64: - expressions.Add(F64.Store((ulong)currentOffset)); - break; - case WasmValueType.V128: - expressions.Add(V128.Store((ulong)currentOffset)); - break; - - default: - throw new System.Exception("Unexpected wasm type arg"); + expressions.Add(Local.Get(0)); + expressions.Add(Local.Get(wasmLocalIndex)); + WasmValueType type = _typeNode.Type.Params.Types[wasmLocalIndex]; + switch (type) + { + case WasmValueType.I32: + expressions.Add(I32.Store((ulong)currentOffset)); + break; + case WasmValueType.F32: + expressions.Add(F32.Store((ulong)currentOffset)); + break; + case WasmValueType.I64: + expressions.Add(I64.Store((ulong)currentOffset)); + break; + case WasmValueType.F64: + expressions.Add(F64.Store((ulong)currentOffset)); + break; + case WasmValueType.V128: + expressions.Add(V128.Store((ulong)currentOffset)); + break; + + default: + throw new System.Exception("Unexpected wasm type arg"); + } + wasmLocalIndex++; } } // @@ -245,31 +291,60 @@ protected override void EmitCode(NodeFactory factory, ref Wasm.WasmEmitter instr // local.set (i+1) // } // In the calling convention, the first arg is the sp arg, and the last is the portable entrypoint arg. Each of those are treated specially - for (int i = 1; i < _typeNode.Type.Params.Types.Length - 1; i++) + // Iterate over the raised MethodSignature params to handle indirect/empty structs correctly + wasmLocalIndex = 1; + + // Restore 'this' pointer if present + if (hasThis) { expressions.Add(Local.Get(0)); - WasmValueType type = _typeNode.Type.Params.Types[i]; - int currentOffset = offsets[i - 1]; - switch (type) + expressions.Add(I32.Load((ulong)transitionBlock.ThisOffset)); + wasmLocalIndex++; + } + + for (int i = 0; i < methodSignature.Length; i++) + { + TypeDesc paramType = methodSignature[i]; + + if (WasmLowering.IsEmptyStruct(paramType)) + { + // Empty struct — no wasm local, nothing to restore + continue; + } + + if (isIndirectStructArg[i]) + { + // Indirect struct — pass the original byref pointer from the caller + expressions.Add(Local.Get(wasmLocalIndex)); + wasmLocalIndex++; + } + else { - case WasmValueType.I32: - expressions.Add(I32.Load((ulong)currentOffset)); - break; - case WasmValueType.F32: - expressions.Add(F32.Load((ulong)currentOffset)); - break; - case WasmValueType.I64: - expressions.Add(I64.Load((ulong)currentOffset)); - break; - case WasmValueType.F64: - expressions.Add(F64.Load((ulong)currentOffset)); - break; - case WasmValueType.V128: - expressions.Add(V128.Load((ulong)currentOffset)); - break; - - default: - throw new System.Exception("Unexpected wasm type arg"); + expressions.Add(Local.Get(0)); + WasmValueType type = _typeNode.Type.Params.Types[wasmLocalIndex]; + int currentOffset = offsets[i]; + switch (type) + { + case WasmValueType.I32: + expressions.Add(I32.Load((ulong)currentOffset)); + break; + case WasmValueType.F32: + expressions.Add(F32.Load((ulong)currentOffset)); + break; + case WasmValueType.I64: + expressions.Add(I64.Load((ulong)currentOffset)); + break; + case WasmValueType.F64: + expressions.Add(F64.Load((ulong)currentOffset)); + break; + case WasmValueType.V128: + expressions.Add(V128.Load((ulong)currentOffset)); + break; + + default: + throw new System.Exception("Unexpected wasm type arg"); + } + wasmLocalIndex++; } } // ; Add the portable entrypoint arg 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 3a0faf084a0d38..b11a7115897b6e 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 @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using ILCompiler.DependencyAnalysis.Wasm; using Internal.ReadyToRunConstants; using Internal.Text; using Internal.JitInterface; +using Internal.TypeSystem; using System.Diagnostics; namespace ILCompiler.DependencyAnalysis.ReadyToRun @@ -53,8 +55,16 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer return comparer.Compare(_import, otherNode._import); } - private static readonly CorInfoWasmType[] _genericLookupTypes32Bit = new CorInfoWasmType[] { CorInfoWasmType.CORINFO_WASM_TYPE_I32, CorInfoWasmType.CORINFO_WASM_TYPE_I32, CorInfoWasmType.CORINFO_WASM_TYPE_I32 }; - private static readonly CorInfoWasmType[] _genericLookupTypes64Bit = new CorInfoWasmType[] { CorInfoWasmType.CORINFO_WASM_TYPE_I64, CorInfoWasmType.CORINFO_WASM_TYPE_I64, CorInfoWasmType.CORINFO_WASM_TYPE_I64 }; + private static readonly WasmSignature _genericLookupSignature32Bit = new WasmSignature( + new WasmFuncType( + new WasmResultType(new[] { WasmValueType.I32, WasmValueType.I32 }), + new WasmResultType(new[] { WasmValueType.I32 })), + "iii"); + private static readonly WasmSignature _genericLookupSignature64Bit = new WasmSignature( + new WasmFuncType( + new WasmResultType(new[] { WasmValueType.I64, WasmValueType.I64 }), + new WasmResultType(new[] { WasmValueType.I64 })), + "lll"); public override ObjectData GetData(NodeFactory factory, System.Boolean relocsOnly = false) { @@ -62,19 +72,24 @@ public override ObjectData GetData(NodeFactory factory, System.Boolean relocsOnl ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly); builder.AddSymbol(this); - WasmTypeNode typeNode; + WasmSignature wasmSignature; RelocType tableIndexPointerRelocType = factory.Target.PointerSize == 4 ? RelocType.WASM_TABLE_INDEX_I32 : RelocType.WASM_TABLE_INDEX_I64; if (_import.Signature is GenericLookupSignature) { - typeNode = factory.WasmTypeNode(factory.Target.PointerSize == 4 ? _genericLookupTypes32Bit : _genericLookupTypes64Bit); + wasmSignature = factory.Target.PointerSize == 4 ? _genericLookupSignature32Bit : _genericLookupSignature64Bit; } else { - typeNode = factory.WasmTypeNode(((MethodFixupSignature)(_import.Signature)).Method); + MethodDesc method = ((MethodFixupSignature)(_import.Signature)).Method; + // The import thunk always uses managed calling convention ($sp + PE entrypoint) + // even if the underlying method is UnmanagedCallersOnly, because the thunk is + // called from R2R-generated managed code. + WasmLowering.LoweringFlags flags = WasmLowering.GetLoweringFlags(method) & ~WasmLowering.LoweringFlags.IsUnmanagedCallersOnly; + wasmSignature = WasmLowering.GetSignature(method.Signature, flags); } - builder.EmitReloc(factory.WasmImportThunk(typeNode, HelperId, _import.Table, UseVirtualCall, UseJumpableStub), tableIndexPointerRelocType); + builder.EmitReloc(factory.WasmImportThunk(wasmSignature, HelperId, _import.Table, UseVirtualCall, UseJumpableStub), tableIndexPointerRelocType); builder.EmitReloc(_import, RelocType.IMAGE_REL_BASED_ADDR32NB); if (factory.Target.PointerSize == 8) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.cs new file mode 100644 index 00000000000000..bf6ba702683af0 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.cs @@ -0,0 +1,289 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILCompiler.DependencyAnalysis.Wasm; +using ILCompiler.ObjectWriter; +using ILCompiler.ObjectWriter.WasmInstructions; +using Internal.JitInterface; +using Internal.Text; +using Internal.TypeSystem; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using ILCompiler.DependencyAnalysisFramework; + +namespace ILCompiler.DependencyAnalysis.ReadyToRun +{ + /// + /// A thunk that takes arguments in the interpreter calling convention + /// (pcode, pArgs, pRet, pPortableEntryPointContext) and calls a function + /// compiled via R2R with the appropriate wasm-level calling convention. + /// + public class WasmInterpreterToR2RThunkNode : StringDiscoverableAssemblyStubNode, INodeWithTypeSignature, ISymbolDefinitionNode, ISortableSymbolNode + { + private readonly TypeSystemContext _context; + private readonly WasmSignature _wasmSignature; + private readonly WasmTypeNode _targetTypeNode; + + private const int TerminateR2RStackWalk = 1; + + public override bool StaticDependenciesAreComputed => true; + public override bool IsShareable => false; + public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + + public override string LookupString => "M" + _wasmSignature.SignatureString; + + private static WasmSignature sigForInterpToR2RThunks = new WasmSignature(new WasmFuncType(new WasmResultType(new WasmValueType[]{WasmValueType.I32, WasmValueType.I32, WasmValueType.I32}), new WasmResultType(Array.Empty())), "viii"); + MethodSignature INodeWithTypeSignature.Signature => WasmLowering.RaiseSignature(sigForInterpToR2RThunks, _context); + bool INodeWithTypeSignature.IsUnmanagedCallersOnly => false; + bool INodeWithTypeSignature.IsAsyncCall => false; + bool INodeWithTypeSignature.HasGenericContextArg => false; + + public WasmInterpreterToR2RThunkNode(NodeFactory factory, WasmSignature wasmSignature) + { + _context = factory.TypeSystemContext; + _wasmSignature = wasmSignature; + _targetTypeNode = factory.WasmTypeNode(wasmSignature); + } + + public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append("WasmInterpreterToR2RThunk("u8); + sb.Append(_wasmSignature.SignatureString); + sb.Append(")"u8); + } + + protected override string GetName(NodeFactory factory) + { + Utf8StringBuilder sb = new Utf8StringBuilder(); + AppendMangledName(factory.NameMangler, sb); + return sb.ToString(); + } + + public override int ClassCode => 948271450; + + public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + WasmInterpreterToR2RThunkNode otherNode = (WasmInterpreterToR2RThunkNode)other; + return _wasmSignature.CompareTo(otherNode._wasmSignature); + } + + protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory) + { + DependencyList dependencies = base.ComputeNonRelocationBasedDependencies(factory); + dependencies.Add(_targetTypeNode, "Wasm interpreter-to-R2R thunk requires target type node"); + dependencies.Add(factory.WasmTypeNode(sigForInterpToR2RThunks), "Wasm interpreter-to-R2R thunk requires type for the function entry point"); + return dependencies; + } + + protected override void EmitCode(NodeFactory factory, ref Wasm.WasmEmitter instructionEncoder, bool relocsOnly) + { + Debug.Assert(!instructionEncoder.Is64Bit); + + ISymbolNode targetTypeIndex = _targetTypeNode; + + MethodSignature methodSignature = WasmLowering.RaiseSignature(_wasmSignature, _context); + (ArgIterator argit, TransitionBlock transitionBlock) = GCRefMapBuilder.BuildArgIterator(methodSignature, _context); + + bool hasRetBuffArg = _wasmSignature.SignatureString[0] == 'S'; + bool hasThis = !methodSignature.IsStatic; + + // Gather explicit-arg offsets and indirectness from ArgIterator. + // ArgIterator offsets are relative to the TransitionBlock base; the interpreter + // buffer has no TransitionBlock, so subtract SizeOfTransitionBlock (8) to get + // the byte offset into pArgs. + int sizeOfTransitionBlock = transitionBlock.SizeOfTransitionBlock; + int[] interpOffsets = new int[methodSignature.Length]; + bool[] isIndirectStructArg = new bool[methodSignature.Length]; + + int argIndex = 0; + int argOffset; + while ((argOffset = argit.GetNextOffset()) != TransitionBlock.InvalidOffset) + { + interpOffsets[argIndex] = argOffset - sizeOfTransitionBlock; + isIndirectStructArg[argIndex] = argit.IsArgPassedByRef() && argit.IsValueType(); + argIndex++; + } + + WasmFuncType targetFuncType = _targetTypeNode.Type; + bool hasWasmReturn = targetFuncType.Returns.Types.Length > 0; + + // Wasm locals for this thunk: + // local 0: portableEntryPoint (I32) + // local 1: pArgs (I32) + // local 2: pRet (I32) + // local 3: savedSp (I32) - save/restore SP global + const int LocalPortableEntrypoint = 0; + const int LocalPArgs = 1; + const int LocalPRet = 2; + const int LocalSavedSp = 3; + + const int FrameSize = 16; // 16-byte aligned allocation for framePointer + + List expressions = new List(); + + // Save the current stack pointer global + expressions.Add(Global.Get(WasmObjectWriter.StackPointerGlobalIndex)); + expressions.Add(Local.Set(LocalSavedSp)); + + // Allocate frame space: sp -= FrameSize + expressions.Add(Local.Get(LocalSavedSp)); + expressions.Add(I32.Const(FrameSize)); + expressions.Add(I32.Sub); + expressions.Add(Global.Set(WasmObjectWriter.StackPointerGlobalIndex)); + + // Write TERMINATE_R2R_STACK_WALK (1) into the framePointer at new SP + expressions.Add(Global.Get(WasmObjectWriter.StackPointerGlobalIndex)); + expressions.Add(I32.Const(TerminateR2RStackWalk)); + expressions.Add(I32.Store(0)); + + // Build the arguments for the R2R call_indirect. + // Target R2R wasm params: ($sp, [retbuf], [this], explicit_params..., portableEntrypoint) + // We track targetParamIndex to look up the correct wasm type for each arg. + int targetParamIndex = 0; + + // If there is a wasm return value, push pRet underneath all the call args + // so that after call_indirect the stack is [pRet, return_value] for the store. + if (hasWasmReturn) + { + expressions.Add(Local.Get(LocalPRet)); + } + + // Param 0: $sp — pointer to the framePointer on the shadow stack + expressions.Add(Global.Get(WasmObjectWriter.StackPointerGlobalIndex)); + targetParamIndex++; + + // If the R2R function takes a return buffer, pass pRet directly as the retbuf arg + if (hasRetBuffArg) + { + expressions.Add(Local.Get(LocalPRet)); + targetParamIndex++; + } + + // If the method has a 'this' pointer, load it from pArgs at offset 0 + // (ArgIterator offset for this = OffsetOfArgumentRegisters = SizeOfTransitionBlock) + if (hasThis) + { + int thisInterpOffset = transitionBlock.OffsetOfArgumentRegisters - sizeOfTransitionBlock; + expressions.Add(Local.Get(LocalPArgs)); + expressions.Add(I32.Load((ulong)thisInterpOffset)); + targetParamIndex++; + } + + // Explicit parameters — load each from pArgs at the ArgIterator-derived offset + for (int i = 0; i < methodSignature.Length; i++) + { + TypeDesc paramType = methodSignature[i]; + + if (WasmLowering.IsEmptyStruct(paramType)) + { + continue; + } + + if (isIndirectStructArg[i]) + { + // Byreference struct — pass a pointer into the incoming pArgs buffer + expressions.Add(Local.Get(LocalPArgs)); + expressions.Add(I32.Const(interpOffsets[i])); + expressions.Add(I32.Add); + targetParamIndex++; + } + else + { + WasmValueType wasmType = targetFuncType.Params.Types[targetParamIndex]; + expressions.Add(Local.Get(LocalPArgs)); + switch (wasmType) + { + case WasmValueType.I32: + expressions.Add(I32.Load((ulong)interpOffsets[i])); + break; + case WasmValueType.I64: + expressions.Add(I64.Load((ulong)interpOffsets[i])); + break; + case WasmValueType.F32: + expressions.Add(F32.Load((ulong)interpOffsets[i])); + break; + case WasmValueType.F64: + expressions.Add(F64.Load((ulong)interpOffsets[i])); + break; + default: + throw new Exception("Unexpected wasm type for interpreter-to-R2R arg"); + } + targetParamIndex++; + } + } + + // Last R2R arg: portable entrypoint context + expressions.Add(Local.Get(LocalPortableEntrypoint)); + + // call_indirect with the target R2R function's type signature + expressions.Add(Local.Get(LocalPortableEntrypoint)); + expressions.Add(I32.Load(0)); // load the actual function index from the portable entrypoint + expressions.Add(ControlFlow.CallIndirect(targetTypeIndex, 0)); + + // Handle wasm return value — pRet is already on the stack under the return value + if (hasWasmReturn) + { + Debug.Assert(targetFuncType.Returns.Types.Length == 1, "Expected exactly one wasm return type"); + WasmValueType returnWasmType = targetFuncType.Returns.Types[0]; + + // Stack is [pRet, return_value]. Store consumes [addr, value]. + switch (returnWasmType) + { + case WasmValueType.I32: + expressions.Add(I32.Store(0)); + break; + case WasmValueType.I64: + expressions.Add(I64.Store(0)); + break; + case WasmValueType.F32: + expressions.Add(F32.Store(0)); + break; + case WasmValueType.F64: + expressions.Add(F64.Store(0)); + break; + case WasmValueType.V128: + expressions.Add(V128.Store(0)); + break; + default: + throw new Exception("Unexpected wasm return type for interpreter-to-R2R"); + } + } + + // For struct returns via retbuf: the R2R function has already written the struct + // into pRet. Zero-pad to the appropriate alignment boundary. + if (hasRetBuffArg) + { + TypeDesc returnType = methodSignature.ReturnType; + int structSize = returnType.GetElementSize().AsInt; + int alignment = structSize <= 4 ? 4 : 8; + int padding = AlignmentHelper.AlignUp(structSize, alignment) - structSize; + if (padding > 0) + { + expressions.Add(Local.Get(LocalPRet)); + expressions.Add(I32.Const(structSize)); + expressions.Add(I32.Add); + expressions.Add(I32.Const(0)); + expressions.Add(I32.Const(padding)); + expressions.Add(Memory.Fill()); + } + } + + // Restore the stack pointer global + expressions.Add(Local.Get(LocalSavedSp)); + expressions.Add(Global.Set(WasmObjectWriter.StackPointerGlobalIndex)); + + instructionEncoder.FunctionBody = new WasmFunctionBody(sigForInterpToR2RThunks.FuncType, + new[] { WasmValueType.I32 }, + expressions.ToArray()); + } + + protected override void EmitCode(NodeFactory factory, ref X64.X64Emitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + protected override void EmitCode(NodeFactory factory, ref X86.X86Emitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + protected override void EmitCode(NodeFactory factory, ref ARM.ARMEmitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + protected override void EmitCode(NodeFactory factory, ref ARM64.ARM64Emitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + protected override void EmitCode(NodeFactory factory, ref LoongArch64.LoongArch64Emitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + protected override void EmitCode(NodeFactory factory, ref RiscV64.RiscV64Emitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.cs new file mode 100644 index 00000000000000..a8bf4482c3e26a --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.cs @@ -0,0 +1,355 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILCompiler.DependencyAnalysis.Wasm; +using ILCompiler.ObjectWriter; +using ILCompiler.ObjectWriter.WasmInstructions; +using Internal.JitInterface; +using Internal.Text; +using Internal.TypeSystem; +using Internal.ReadyToRunConstants; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using ILCompiler.DependencyAnalysisFramework; + +namespace ILCompiler.DependencyAnalysis.ReadyToRun +{ + /// + /// A thunk that captures all arguments and dispatches to the interpreter via + /// READYTORUN_HELPER_R2RToInterpreter. This node is string-discoverable so the + /// runtime can find it by WasmSignature string at execution time. + /// + public class WasmR2RToInterpreterThunkNode : StringDiscoverableAssemblyStubNode, INodeWithTypeSignature, ISymbolDefinitionNode, ISortableSymbolNode + { + private readonly TypeSystemContext _context; + private readonly WasmSignature _wasmSignature; + private readonly WasmTypeNode _typeNode; + private readonly Import _helperCell; + + // Helper signature: (I32, I32, I32, I32) -> () + private static readonly CorInfoWasmType[] s_helperTypeParams = new CorInfoWasmType[] + { + CorInfoWasmType.CORINFO_WASM_TYPE_VOID, + CorInfoWasmType.CORINFO_WASM_TYPE_I32, + CorInfoWasmType.CORINFO_WASM_TYPE_I32, + CorInfoWasmType.CORINFO_WASM_TYPE_I32, + CorInfoWasmType.CORINFO_WASM_TYPE_I32, + }; + + public override bool StaticDependenciesAreComputed => true; + public override bool IsShareable => false; + public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + + public override string LookupString => "I" + _wasmSignature.SignatureString; + + MethodSignature INodeWithTypeSignature.Signature => WasmLowering.RaiseSignature(_wasmSignature, _context); + bool INodeWithTypeSignature.IsUnmanagedCallersOnly => false; + bool INodeWithTypeSignature.IsAsyncCall => false; + bool INodeWithTypeSignature.HasGenericContextArg => false; + + public WasmR2RToInterpreterThunkNode(NodeFactory factory, WasmSignature wasmSignature) + { + _context = factory.TypeSystemContext; + _wasmSignature = wasmSignature; + _typeNode = factory.WasmTypeNode(wasmSignature); + _helperCell = factory.GetReadyToRunHelperCell(ReadyToRunHelper.R2RToInterpreter); + } + + public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append("WasmR2RToInterpreterThunk("u8); + sb.Append(_wasmSignature.SignatureString); + sb.Append(")"u8); + } + + protected override string GetName(NodeFactory factory) + { + Utf8StringBuilder sb = new Utf8StringBuilder(); + AppendMangledName(factory.NameMangler, sb); + return sb.ToString(); + } + + public override int ClassCode => 948271449; + + public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + WasmR2RToInterpreterThunkNode otherNode = (WasmR2RToInterpreterThunkNode)other; + return _wasmSignature.CompareTo(otherNode._wasmSignature); + } + + protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory) + { + DependencyList dependencies = base.ComputeNonRelocationBasedDependencies(factory); + dependencies.Add(_typeNode, "Wasm R2R to interpreter thunk requires type node"); + return dependencies; + } + + protected override void EmitCode(NodeFactory factory, ref Wasm.WasmEmitter instructionEncoder, bool relocsOnly) + { + Debug.Assert(!instructionEncoder.Is64Bit); + + ISymbolNode helperTypeIndex = factory.WasmTypeNode(s_helperTypeParams); + + MethodSignature methodSignature = WasmLowering.RaiseSignature(_wasmSignature, _context); + (ArgIterator argit, TransitionBlock transitionBlock) = GCRefMapBuilder.BuildArgIterator(methodSignature, _context); + + bool hasRetBuffArg = _wasmSignature.SignatureString[0] == 'S'; + bool hasThis = !methodSignature.IsStatic; + + int[] offsets = new int[methodSignature.Length]; + bool[] isIndirectStructArg = new bool[methodSignature.Length]; + + int argIndex = 0; + int argOffset; + + while ((argOffset = argit.GetNextOffset()) != TransitionBlock.InvalidOffset) + { + offsets[argIndex] = argOffset; + isIndirectStructArg[argIndex] = argit.IsArgPassedByRef() && argit.IsValueType(); + argIndex++; + } + + argit.Reset(); + + int sizeOfArgumentArray = argit.SizeOfFrameArgumentArray(); + int sizeOfTransitionBlock = transitionBlock.SizeOfTransitionBlock; + + // The arguments area must be 16-byte aligned. The TransitionBlock (8 bytes on Wasm32) + // sits before the arguments, so it is 8-byte aligned but not 16-byte aligned. + // Layout from base: [TransitionBlock (8)] [args...] + int argumentsOffset = AlignmentHelper.AlignUp(sizeOfTransitionBlock, 16); + int transitionBlockOffset = argumentsOffset - sizeOfTransitionBlock; + for (int i = 0; i < offsets.Length; i++) + { + offsets[i] += transitionBlockOffset; + } + int sizeOfStoredLocals = argumentsOffset + AlignmentHelper.AlignUp(sizeOfArgumentArray, 16); + + bool hasWasmReturn = _typeNode.Type.Returns.Types.Length > 0; + WasmValueType returnWasmType = hasWasmReturn ? _typeNode.Type.Returns.Types[0] : default; + + // Allocate a local return buffer. V128 needs 16 bytes and 16-byte alignment; + // other types need at most 8 bytes. localRetBufOffset is already 16-byte aligned + // (argumentsOffset is 16-aligned, and args size is rounded up to 16). + int retBufSize = (hasWasmReturn && returnWasmType == WasmValueType.V128) ? 16 : 8; + int localRetBufOffset = sizeOfStoredLocals; + int totalAlloc = AlignmentHelper.AlignUp(sizeOfStoredLocals + retBufSize, 16); + + List expressions = new List(); + + // Allocate stack space: local.get 0; i32.const totalAlloc; i32.sub; local.set 0 + expressions.Add(Local.Get(0)); + expressions.Add(I32.Const(totalAlloc)); + expressions.Add(I32.Sub); + expressions.Add(Local.Set(0)); + + // Initialize TransitionBlock: + // First 4 bytes (m_ReturnAddress) = 0 + expressions.Add(Local.Get(0)); + expressions.Add(I32.Const(0)); + expressions.Add(I32.Store((ulong)transitionBlockOffset)); + + // Second 4 bytes (m_StackPointer) = original SP (local 0 + totalAlloc) + expressions.Add(Local.Get(0)); + expressions.Add(Local.Get(0)); + expressions.Add(I32.Const(totalAlloc)); + expressions.Add(I32.Add); + expressions.Add(I32.Store((ulong)(transitionBlockOffset + 4))); + + // Store all arguments into the transition block area + int wasmLocalIndex = 1; // local 0 is $sp + + // Handle 'this' pointer — it occupies a wasm local but is not in methodSignature.Length + if (hasThis) + { + int thisOffset = transitionBlock.ThisOffset + transitionBlockOffset; + expressions.Add(Local.Get(0)); + expressions.Add(Local.Get(wasmLocalIndex)); + expressions.Add(I32.Store((ulong)thisOffset)); + wasmLocalIndex++; + } + + // Hidden retbuf pointer occupies a wasm local but is not in methodSignature params + if (hasRetBuffArg) + { + wasmLocalIndex++; + } + + for (int i = 0; i < methodSignature.Length; i++) + { + TypeDesc paramType = methodSignature[i]; + + int currentOffset = offsets[i]; + + if (WasmLowering.IsEmptyStruct(paramType)) + { + expressions.Add(Local.Get(0)); + expressions.Add(I32.Const(0)); + expressions.Add(I32.Store((ulong)currentOffset)); + } + else if (isIndirectStructArg[i]) + { + // Indirect struct — copy the exact contents from the incoming pointer + int structSize = paramType.GetElementSize().AsInt; + + // memory.copy: (dst, src, len) -> () + // dst: base + currentOffset + expressions.Add(Local.Get(0)); + expressions.Add(I32.Const(currentOffset)); + expressions.Add(I32.Add); + // src: the byref pointer passed as the wasm local + expressions.Add(Local.Get(wasmLocalIndex)); + // len: struct size + expressions.Add(I32.Const(structSize)); + expressions.Add(Memory.Copy()); + + // Pad remaining bytes to alignment boundary with zeros + int alignment = structSize <= 4 ? 4 : 8; + int padding = AlignmentHelper.AlignUp(structSize, alignment) - structSize; + if (padding > 0) + { + // memory.fill: (dst, val, len) -> () + expressions.Add(Local.Get(0)); + expressions.Add(I32.Const(currentOffset + structSize)); + expressions.Add(I32.Add); + expressions.Add(I32.Const(0)); + expressions.Add(I32.Const(padding)); + expressions.Add(Memory.Fill()); + } + + wasmLocalIndex++; + } + else + { + expressions.Add(Local.Get(0)); + expressions.Add(Local.Get(wasmLocalIndex)); + WasmValueType type = _typeNode.Type.Params.Types[wasmLocalIndex]; + switch (type) + { + case WasmValueType.I32: + expressions.Add(I32.Store((ulong)currentOffset)); + break; + case WasmValueType.F32: + expressions.Add(F32.Store((ulong)currentOffset)); + break; + case WasmValueType.I64: + expressions.Add(I64.Store((ulong)currentOffset)); + break; + case WasmValueType.F64: + expressions.Add(F64.Store((ulong)currentOffset)); + break; + case WasmValueType.V128: + expressions.Add(V128.Store((ulong)currentOffset)); + break; + default: + throw new Exception("Unexpected wasm type arg"); + } + wasmLocalIndex++; + } + } + + // Zero the local return buffer + if (retBufSize <= 8) + { + expressions.Add(Local.Get(0)); + expressions.Add(I64.Const(0)); + expressions.Add(I64.Store((ulong)localRetBufOffset)); + } + else + { + expressions.Add(Local.Get(0)); + expressions.Add(I32.Const(localRetBufOffset)); + expressions.Add(I32.Add); + expressions.Add(I32.Const(0)); + expressions.Add(I32.Const(retBufSize)); + expressions.Add(Memory.Fill()); + } + + // Prepare helper call arguments: + // arg1: portable entrypoint (last wasm local) + int portableEntrypointLocalIndex = _typeNode.Type.Params.Types.Length - 1; + expressions.Add(Local.Get(portableEntrypointLocalIndex)); + + // arg2: pointer to the collected arguments and transition block (base + transitionBlockOffset) + expressions.Add(Local.Get(0)); + expressions.Add(I32.Const(transitionBlockOffset)); + expressions.Add(I32.Add); + + // arg3: size of arguments (excluding transition block) + expressions.Add(I32.Const(sizeOfArgumentArray)); + + // arg4: return buffer pointer + if (hasRetBuffArg) + { + // The retbuf is a wasm parameter — pass it through directly. + // For managed calls: local 0 = $sp, local 1 = this (if present), then retbuf. + int retBufLocalIndex = 1 + (hasThis ? 1 : 0); + expressions.Add(Local.Get(retBufLocalIndex)); + } + else + { + // Pass pointer to local 8-byte return buffer + expressions.Add(Local.Get(0)); + expressions.Add(I32.Const(localRetBufOffset)); + expressions.Add(I32.Add); + } + + // Extra local to save/restore the stack pointer global across the helper call + int savedSpLocalIndex = _typeNode.Type.Params.Types.Length; + + // Save the current stack pointer global into a local, then set it to local 0 + // (16-byte aligned, <= all buffers allocated in this thunk). + expressions.Add(Global.Get(WasmObjectWriter.StackPointerGlobalIndex)); + expressions.Add(Local.Set(savedSpLocalIndex)); + expressions.Add(Local.Get(0)); + expressions.Add(Global.Set(WasmObjectWriter.StackPointerGlobalIndex)); + + // Load the helper function address and call + expressions.Add(Global.Get(WasmObjectWriter.ImageBaseGlobalIndex)); + expressions.Add(I32.LoadWithRVAOffset(_helperCell)); + expressions.Add(ControlFlow.CallIndirect(helperTypeIndex, 0)); + + // Restore the old stack pointer global + expressions.Add(Local.Get(savedSpLocalIndex)); + expressions.Add(Global.Set(WasmObjectWriter.StackPointerGlobalIndex)); + + // If the function has a wasm return value, load it from the local return buffer + if (hasWasmReturn) + { + Debug.Assert(_typeNode.Type.Returns.Types.Length == 1, "Expected exactly one wasm return type"); + expressions.Add(Local.Get(0)); + switch (returnWasmType) + { + case WasmValueType.I32: + expressions.Add(I32.Load((ulong)localRetBufOffset)); + break; + case WasmValueType.F32: + expressions.Add(F32.Load((ulong)localRetBufOffset)); + break; + case WasmValueType.I64: + expressions.Add(I64.Load((ulong)localRetBufOffset)); + break; + case WasmValueType.F64: + expressions.Add(F64.Load((ulong)localRetBufOffset)); + break; + case WasmValueType.V128: + expressions.Add(V128.Load((ulong)localRetBufOffset)); + break; + default: + throw new Exception("Unexpected wasm return type"); + } + } + + instructionEncoder.FunctionBody = new WasmFunctionBody(_typeNode.Type, new[] { WasmValueType.I32 }, expressions.ToArray()); + } + + protected override void EmitCode(NodeFactory factory, ref X64.X64Emitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + protected override void EmitCode(NodeFactory factory, ref X86.X86Emitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + protected override void EmitCode(NodeFactory factory, ref ARM.ARMEmitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + protected override void EmitCode(NodeFactory factory, ref ARM64.ARM64Emitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + protected override void EmitCode(NodeFactory factory, ref LoongArch64.LoongArch64Emitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + protected override void EmitCode(NodeFactory factory, ref RiscV64.RiscV64Emitter instructionEncoder, bool relocsOnly) { throw new NotSupportedException(); } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs index 027ae0dbcc7140..e3382742633fdf 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -297,7 +297,7 @@ private void CreateNodeCaches() _wasmImportThunks = new NodeCache(key => { - return new WasmImportThunk(this, key.TypeNode, key.Helper, key.ContainingImportSection, key.UseVirtualCall, key.UseJumpableStub); + return new WasmImportThunk(this, key.Signature, key.Helper, key.ContainingImportSection, key.UseVirtualCall, key.UseJumpableStub); }); _wasmImportThunkPortableEntrypoints = new NodeCache(key => @@ -305,6 +305,16 @@ private void CreateNodeCaches() return new WasmImportThunkPortableEntrypoint(this, key.Import); }); + _wasmR2RToInterpreterThunks = new NodeCache(key => + { + return new WasmR2RToInterpreterThunkNode(this, key); + }); + + _wasmInterpreterToR2RThunks = new NodeCache(key => + { + return new WasmInterpreterToR2RThunkNode(this, key); + }); + _importMethods = new NodeCache(CreateMethodEntrypoint); _localMethodCache = new NodeCache(key => @@ -441,6 +451,31 @@ private void CreateNodeCaches() public ImportSectionNode ILBodyPrecodeImports; + private readonly ConcurrentBag _stringDiscoverableStubs = new ConcurrentBag(); + + /// + /// The eager import for the InjectStringThunks fixup. Created lazily when the first + /// StringDiscoverableAssemblyStubNode is registered. Each such stub depends on this import. + /// + public Import InjectStringThunksImport; + + /// + /// Register a StringDiscoverableAssemblyStubNode for inclusion in the InjectStringThunks fixup. + /// Called by StringDiscoverableAssemblyStubNode.OnMarked. + /// + public void RegisterStringDiscoverableStub(StringDiscoverableAssemblyStubNode stub) + { + _stringDiscoverableStubs.Add(stub); + } + + /// + /// Get all registered string-discoverable stubs. Should only be called after marking is complete. + /// + public List GetStringDiscoverableStubs() + { + return new List(_stringDiscoverableStubs); + } + private NodeCache _constructedHelpers; private LazyGenericsSupport.GenericCycleDetector _genericCycleDetector; @@ -730,15 +765,15 @@ public ISymbolDefinitionNode ImportThunk(ReadyToRunHelper helper, ImportSectionN } private struct WasmImportThunkKey : IEquatable { - public readonly WasmTypeNode TypeNode; + public readonly WasmSignature Signature; public readonly ReadyToRunHelper Helper; public readonly ImportSectionNode ContainingImportSection; public readonly bool UseVirtualCall; public readonly bool UseJumpableStub; - public WasmImportThunkKey(WasmTypeNode typeNode, ReadyToRunHelper helper, ImportSectionNode containingImportSection, bool useVirtualCall, bool useJumpableStub) + public WasmImportThunkKey(WasmSignature signature, ReadyToRunHelper helper, ImportSectionNode containingImportSection, bool useVirtualCall, bool useJumpableStub) { - TypeNode = typeNode; + Signature = signature; Helper = helper; ContainingImportSection = containingImportSection; UseVirtualCall = useVirtualCall; @@ -747,7 +782,7 @@ public WasmImportThunkKey(WasmTypeNode typeNode, ReadyToRunHelper helper, Import public bool Equals(WasmImportThunkKey other) { - return TypeNode == other.TypeNode && + return Signature.Equals(other.Signature) && Helper == other.Helper && ContainingImportSection == other.ContainingImportSection && UseVirtualCall == other.UseVirtualCall && @@ -762,7 +797,7 @@ public override bool Equals(object obj) public override int GetHashCode() { return HashCode.Combine(Helper.GetHashCode(), - TypeNode.GetHashCode(), + Signature.GetHashCode(), ContainingImportSection.GetHashCode(), UseVirtualCall.GetHashCode(), UseJumpableStub.GetHashCode()); @@ -771,9 +806,9 @@ public override int GetHashCode() private NodeCache _wasmImportThunks; - public ISymbolDefinitionNode WasmImportThunk(WasmTypeNode typeNode, ReadyToRunHelper helper, ImportSectionNode containingImportSection, bool useVirtualCall, bool useJumpableStub) + public ISymbolDefinitionNode WasmImportThunk(WasmSignature signature, ReadyToRunHelper helper, ImportSectionNode containingImportSection, bool useVirtualCall, bool useJumpableStub) { - WasmImportThunkKey thunkKey = new WasmImportThunkKey(typeNode, helper, containingImportSection, useVirtualCall, useJumpableStub); + WasmImportThunkKey thunkKey = new WasmImportThunkKey(signature, helper, containingImportSection, useVirtualCall, useJumpableStub); return _wasmImportThunks.GetOrAdd(thunkKey); } @@ -810,6 +845,18 @@ public ISymbolDefinitionNode WasmImportThunkPortableEntrypoint(DelayLoadHelperIm return _wasmImportThunkPortableEntrypoints.GetOrAdd(thunkKey); } + private NodeCache _wasmR2RToInterpreterThunks; + public WasmR2RToInterpreterThunkNode WasmR2RToInterpreterThunk(WasmSignature wasmSignature) + { + return _wasmR2RToInterpreterThunks.GetOrAdd(wasmSignature); + } + + private NodeCache _wasmInterpreterToR2RThunks; + public WasmInterpreterToR2RThunkNode WasmInterpreterToR2RThunk(WasmSignature wasmSignature) + { + return _wasmInterpreterToR2RThunks.GetOrAdd(wasmSignature); + } + public void AttachToDependencyGraph(DependencyAnalyzerBase graph, ILProvider ilProvider) { graph.ComputingDependencyPhaseChange += Graph_ComputingDependencyPhaseChange; @@ -943,6 +990,10 @@ public void AttachToDependencyGraph(DependencyAnalyzerBase graph, I ReadyToRunHelper.Module)); graph.AddRoot(ModuleImport, "Module import is required by the R2R format spec"); + // Create the InjectStringThunks import but don't root it. It gets pulled in + // as a dependency of any StringDiscoverableAssemblyStubNode that gets marked. + InjectStringThunksImport = new Import(EagerImports, new InjectStringThunksSignature()); + if ((Target.Architecture != TargetArchitecture.X86) && (Target.Architecture != TargetArchitecture.Wasm32)) { Import personalityRoutineImport = new Import(EagerImports, new ReadyToRunHelperSignature( @@ -1215,11 +1266,16 @@ public WasmTypeNode WasmTypeNode(CorInfoWasmType[] types) return _wasmTypeNodes.GetOrAdd(funcType); } + public WasmTypeNode WasmTypeNode(WasmSignature signature) + { + return _wasmTypeNodes.GetOrAdd(signature.FuncType); + } + // TODO-Wasm: Do not use WasmFuncType directly as the key for better // memory efficiency on lookup public WasmTypeNode WasmTypeNode(MethodDesc method) { - WasmFuncType funcType = WasmLowering.GetSignature(method); + WasmFuncType funcType = WasmLowering.GetSignature(method).FuncType; return _wasmTypeNodes.GetOrAdd(funcType); } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 7c69dee450ee6d..b655ae1f029329 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -70,6 +70,7 @@ + @@ -281,6 +282,8 @@ + + @@ -303,6 +306,8 @@ + + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index a5ea5e2c67573d..311cc912791007 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -29,6 +29,8 @@ using System.Runtime.CompilerServices; using ILCompiler.ReadyToRun.TypeSystem; +using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.DependencyList; + namespace Internal.JitInterface { class InfiniteCompileStress @@ -491,6 +493,12 @@ private void AddPrecodeFixup(ISymbolNode node) _precodeFixups.Add(node); } + private void AddAdditionalDependency(ISymbolNode node, string reason) + { + _additionalDependencies ??= new DependencyList(); + _additionalDependencies.Add(node, reason); + } + private void AddResumptionStubFixup(MethodWithGCInfo compiledStubNode) { AddPrecodeFixup(_compilation.SymbolNodeFactory.ResumptionStubEntryPoint(compiledStubNode)); @@ -852,6 +860,17 @@ public void CompileMethod(MethodWithGCInfo methodCodeNodeNeedingCode, Logger log } } } + + // For managed methods on Wasm, add an interpreter-to-R2R thunk so the + // interpreter can call into this R2R-compiled function. + if (_compilation.NodeFactory.Target.IsWasm && !MethodBeingCompiled.IsUnmanagedCallersOnly) + { + WasmSignature wasmSig = WasmLowering.GetSignature(MethodBeingCompiled); + AddAdditionalDependency( + _compilation.NodeFactory.WasmInterpreterToR2RThunk(wasmSig), + "Interpreter-to-R2R thunk for compiled method"); + } + var compilationResult = CompileMethodInternal(methodCodeNodeNeedingCode, methodIL); codeGotPublished = true; @@ -3515,5 +3534,69 @@ private void getThreadLocalStaticInfo_NativeAOT(CORINFO_THREAD_STATIC_INFO_NATIV // Implemented for NativeAOT only for now. } #pragma warning restore CA1822 // Mark members as static + +#pragma warning disable CA1822 // Mark members as static + private void recordCallSite(uint instrOffset, CORINFO_SIG_INFO* callSig, CORINFO_METHOD_STRUCT_* methodHandle) +#pragma warning restore CA1822 // Mark members as static + { + if ((callSig != null) && _compilation.NodeFactory.Target.IsWasm) + { + var sig = HandleToObject(callSig->methodSignature); + + WasmLowering.LoweringFlags flags = 0; + if (callSig->hasTypeArg()) + { + flags |= WasmLowering.LoweringFlags.HasGenericContextArg; + } + if (callSig->isAsyncCall()) + { + flags |= WasmLowering.LoweringFlags.IsAsyncCall; + } + if (((int)callSig->getCallConv() & 0xF) != 0) + { + flags |= WasmLowering.LoweringFlags.IsUnmanagedCallersOnly; + } + + WasmSignature wasmSig = WasmLowering.GetSignature(sig, flags); + + // Only create R2R-to-interpreter thunks for managed calls. + // Unmanaged calls don't go through the interpreter transition. + if (!flags.HasFlag(WasmLowering.LoweringFlags.IsUnmanagedCallersOnly)) + { + AddAdditionalDependency(_compilation.NodeFactory.WasmR2RToInterpreterThunk(wasmSig), "R2R-to-interpreter thunk for call site"); + } + } + } + + private void recordWasmManagedCallSig(CORINFO_SIG_INFO* callSig) + { + if ((callSig != null) && _compilation.NodeFactory.Target.IsWasm) + { + var sig = HandleToObject(callSig->methodSignature); + + WasmLowering.LoweringFlags flags = 0; + if (callSig->hasTypeArg()) + { + flags |= WasmLowering.LoweringFlags.HasGenericContextArg; + } + if (callSig->isAsyncCall()) + { + flags |= WasmLowering.LoweringFlags.IsAsyncCall; + } + if (((int)callSig->getCallConv() & 0xF) != 0) + { + flags |= WasmLowering.LoweringFlags.IsUnmanagedCallersOnly; + } + + WasmSignature wasmSig = WasmLowering.GetSignature(sig, flags); + + // Only create R2R-to-interpreter thunks for managed calls. + // Unmanaged calls don't go through the interpreter transition. + if (!flags.HasFlag(WasmLowering.LoweringFlags.IsUnmanagedCallersOnly)) + { + AddAdditionalDependency(_compilation.NodeFactory.WasmR2RToInterpreterThunk(wasmSig), "R2R-to-interpreter thunk for call site"); + } + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs index 406effbb5305aa..2b54e3b9b3950c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs @@ -1372,6 +1372,27 @@ private ReadyToRunSignature ParseSignature(ReadyToRunFixupKind fixupType, String builder.Append($" (RESUMPTION_STUB RVA[0x{stubRVA:X}])"); break; + case ReadyToRunFixupKind.InjectStringThunks: + builder.Append(" (INJECT_STRING_THUNKS"); + while (true) + { + int strStart = Offset; + while (_image[Offset] != 0) + SkipBytes(1); + int strLen = Offset - strStart; + SkipBytes(1); // skip null terminator + + if (strLen == 0) + break; + + string lookupString = System.Text.Encoding.UTF8.GetString(_image, strStart, strLen); + uint thunkRVA = BitConverter.ToUInt32(_image, Offset); + SkipBytes(4); + builder.Append($" \"{lookupString}\"->RVA[0x{thunkRVA:X}]"); + } + builder.Append(')'); + break; + case ReadyToRunFixupKind.Check_VirtualFunctionOverride: case ReadyToRunFixupKind.Verify_VirtualFunctionOverride: ReadyToRunVirtualFunctionOverrideFlags flags = (ReadyToRunVirtualFunctionOverrideFlags)ReadUInt(); @@ -2065,6 +2086,9 @@ private void ParseHelper(StringBuilder builder) case ReadyToRunHelper.InitInstClass: builder.Append("INIT_INST_CLASS"); break; + case ReadyToRunHelper.R2RToInterpreter: + builder.Append("R2R_TO_INTERPRETER"); + break; default: throw new BadImageFormatException(helperType.ToString()); diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 141ca0c60720b6..135762578a5308 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -2504,5 +2504,17 @@ private bool notifyMethodInfoUsage(CORINFO_METHOD_STRUCT_* ftn) { return true; } + +#pragma warning disable CA1822 // Mark members as static + private void recordCallSite(uint instrOffset, CORINFO_SIG_INFO* callSig, CORINFO_METHOD_STRUCT_* methodHandle) +#pragma warning restore CA1822 // Mark members as static + { + } + +#pragma warning disable CA1822 // Mark members as static + private void recordWasmManagedCallSig(CORINFO_SIG_INFO* callSig) +#pragma warning restore CA1822 // Mark members as static + { + } } } diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index dec68b1eb53d4f..90ec07a3b446bc 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -185,6 +185,7 @@ struct JitInterfaceCallbacks JITINTERFACE_HRESULT (* getPgoInstrumentationResults)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema** pSchema, uint32_t* pCountSchemaItems, uint8_t** pInstrumentationData, ICorJitInfo::PgoSource* pPgoSource, bool* pDynamicPgo); JITINTERFACE_HRESULT (* allocPgoInstrumentationBySchema)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema* pSchema, uint32_t countSchemaItems, uint8_t** pInstrumentationData); void (* recordCallSite)(void * thisHandle, CorInfoExceptionClass** ppException, uint32_t instrOffset, CORINFO_SIG_INFO* callSig, CORINFO_METHOD_HANDLE methodHandle); + void (* recordWasmManagedCallSig)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_SIG_INFO* callSig); void (* recordRelocation)(void * thisHandle, CorInfoExceptionClass** ppException, void* location, void* locationRW, void* target, CorInfoReloc fRelocType, int32_t addlDelta); CorInfoReloc (* getRelocTypeHint)(void * thisHandle, CorInfoExceptionClass** ppException, void* target); uint32_t (* getExpectedTargetArchitecture)(void * thisHandle, CorInfoExceptionClass** ppException); @@ -1910,6 +1911,14 @@ class JitInterfaceWrapper : public ICorJitInfo if (pException != nullptr) throw pException; } + virtual void recordWasmManagedCallSig( + CORINFO_SIG_INFO* callSig) +{ + CorInfoExceptionClass* pException = nullptr; + _callbacks->recordWasmManagedCallSig(_thisHandle, &pException, callSig); + if (pException != nullptr) throw pException; +} + virtual void recordRelocation( void* location, void* locationRW, diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index e29ba7b5cc97ff..bd82b684e91e11 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -2012,6 +2012,13 @@ void interceptor_ICJI::recordCallSite(uint32_t instrOffset, /* IN * mc->cr->recRecordCallSite(instrOffset, callSig, methodHandle); } +void interceptor_ICJI::recordWasmManagedCallSig(CORINFO_SIG_INFO* callSig /* IN */) +{ + mc->cr->AddCall("recordWasmManagedCallSig"); + original_ICorJitInfo->recordWasmManagedCallSig(callSig); + // No-op in the VM, nothing to record for replay. +} + // A relocation is recorded if we are pre-jitting. // A jump thunk may be inserted if we are jitting void interceptor_ICJI::recordRelocation(void* location, /* IN */ diff --git a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp index 8058b6802159e8..874083bd15b940 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -1429,6 +1429,13 @@ void interceptor_ICJI::recordCallSite( original_ICorJitInfo->recordCallSite(instrOffset, callSig, methodHandle); } +void interceptor_ICJI::recordWasmManagedCallSig( + CORINFO_SIG_INFO* callSig) +{ + mcs->AddCall("recordWasmManagedCallSig"); + original_ICorJitInfo->recordWasmManagedCallSig(callSig); +} + void interceptor_ICJI::recordRelocation( void* location, void* locationRW, diff --git a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp index 852a318f83c225..b870f0f947f2f6 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -1255,6 +1255,12 @@ void interceptor_ICJI::recordCallSite( original_ICorJitInfo->recordCallSite(instrOffset, callSig, methodHandle); } +void interceptor_ICJI::recordWasmManagedCallSig( + CORINFO_SIG_INFO* callSig) +{ + original_ICorJitInfo->recordWasmManagedCallSig(callSig); +} + void interceptor_ICJI::recordRelocation( void* location, void* locationRW, diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index 20372434076341..93262fd2279b01 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1827,6 +1827,12 @@ void MyICJI::recordCallSite(uint32_t instrOffset, /* IN */ jitInstance->mc->cr->repRecordCallSite(instrOffset, callSig, methodHandle); } +void MyICJI::recordWasmManagedCallSig(CORINFO_SIG_INFO* callSig /* IN */) +{ + jitInstance->mc->cr->AddCall("recordWasmManagedCallSig"); + // No-op for SuperPMI replay. Only meaningful for ReadyToRun Wasm compilation. +} + // A relocation is recorded if we are pre-jitting. // A jump thunk may be inserted if we are jitting void MyICJI::recordRelocation(void* location, /* IN */ diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 39feb7f1ac6676..451c83760879a6 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -267,6 +267,14 @@ if(FEATURE_READYTORUN) ) endif(FEATURE_READYTORUN) +list(APPEND VM_SOURCES_DAC_AND_WKS_COMMON + pregeneratedstringthunks.cpp +) +list(APPEND VM_HEADERS_DAC_AND_WKS_COMMON + pregeneratedstringthunks.h + stringthunkhash.h +) + set(VM_SOURCES_DAC ${VM_SOURCES_DAC_AND_WKS_COMMON} conditionalweaktable.cpp # The usage of conditionalweaktable is only in the DAC, but we put the headers in the VM to enable validation. diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index d7325f5e5796e3..9c3374f6a35b82 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -175,6 +175,7 @@ #include "stringarraylist.h" #include "stubhelpers.h" +#include "pregeneratedstringthunks.h" #ifdef TARGET_WASM #include "wasm/helpers.hpp" #endif @@ -680,6 +681,12 @@ void EEStartupHelper() OnStackReplacementManager::StaticInitialize(); MethodTable::InitMethodDataCache(); + InitializePregeneratedStringThunkHash(); + +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + InitializePendingThunkResolutionLock(); +#endif // FEATURE_PORTABLE_ENTRYPOINTS + #ifdef TARGET_WASM InitializeWasmThunkCaches(); #endif // TARGET_WASM diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index ed05479d9a9401..830cc01e966fa1 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -5913,6 +5913,12 @@ PCODE JitILStub(MethodDesc* pStubMD) // We need an entry point that can be called multiple times pCode = pStubMD->GetMultiCallableAddrOfCode(); } + else + { +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + MethodDesc::EnsurePortableEntryPointIsCallableFromR2R(pStubMD->GetPortableEntryPoint()); +#endif // FEATURE_PORTABLE_ENTRYPOINTS + } return pCode; } diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index cb30fa87c3aaec..f34070e9e9dc18 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -46,6 +46,7 @@ #include "sigbuilder.h" #include "openum.h" #include "fieldmarshaler.h" +#include "pregeneratedstringthunks.h" #ifdef HAVE_GCCOVER #include "gccover.h" #endif // HAVE_GCCOVER @@ -62,6 +63,10 @@ #include "tailcallhelp.h" #include "patchpointinfo.h" +#if defined(FEATURE_INTERPRETER) && defined(FEATURE_PORTABLE_ENTRYPOINTS) +void ExecuteInterpretedMethodWithArgs_PortableEntryPoint(PCODE portableEntrypoint, TransitionBlock* block, size_t argsSize, int8_t* retBuff); +#endif + // The Stack Overflow probe takes place in the COOPERATIVE_TRANSITION_BEGIN() macro // @@ -11129,7 +11134,7 @@ PCODE CEECodeGenInfo::getHelperFtnStatic(CorInfoHelpFunc ftnNum) { // CoreLib calls newobj helpers via calli. Give these helpers a MethodDesc // so the interpreter can find the method signature for the call cookie. - portableEntryPoint->Init((void*)pfnHelper, CoreLibBinder::GetMethod(METHOD__RUNTIME_HELPERS__NEWOBJ_HELPER_DUMMY)); + portableEntryPoint->Init_WithNativeCode((void*)pfnHelper, CoreLibBinder::GetMethod(METHOD__RUNTIME_HELPERS__NEWOBJ_HELPER_DUMMY)); } else { @@ -11143,6 +11148,9 @@ PCODE CEECodeGenInfo::getHelperFtnStatic(CorInfoHelpFunc ftnNum) } else { +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + MethodDesc::EnsurePortableEntryPointIsCallableFromR2R(pfnHelper); +#endif // FEATURE_PORTABLE_ENTRYPOINTS VolatileStore(&hlpFuncEntryPoints[ftnNum], pfnHelper); } } @@ -14305,6 +14313,15 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, result = (size_t)GetEEFuncEntryPoint(DelayLoad_Helper_ObjObj); break; + case READYTORUN_HELPER_R2RToInterpreter: +#if defined(FEATURE_INTERPRETER) && defined(FEATURE_PORTABLE_ENTRYPOINTS) + result = (size_t)GetEEFuncEntryPoint(ExecuteInterpretedMethodWithArgs_PortableEntryPoint); + break; +#else + STRESS_LOG1(LF_ZAP, LL_WARNING, "READYTORUN_HELPER_R2RToInterpreter unsupported for this target: %d\n", helperNum); + return FALSE; +#endif + default: STRESS_LOG1(LF_ZAP, LL_WARNING, "Unknown READYTORUN_HELPER %d\n", helperNum); _ASSERTE(!"Unknown READYTORUN_HELPER"); @@ -14752,6 +14769,15 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, } break; } + + case READYTORUN_FIXUP_InjectStringThunks: + { + ReadyToRunInfo * pR2RInfo = currentModule->GetReadyToRunInfo(); + ProcessInjectStringThunksFixup(pR2RInfo, pBlob); + result = 1; + } + break; + default: STRESS_LOG1(LF_ZAP, LL_WARNING, "Unknown ReadyToRunFixupKind %d\n", kind); _ASSERTE(!"Unknown ReadyToRunFixupKind"); @@ -15233,6 +15259,14 @@ void CEEInfo::recordCallSite( UNREACHABLE(); // only called on derived class. } +void CEEInfo::recordWasmManagedCallSig( + CORINFO_SIG_INFO * callSig /* IN */ + ) +{ + LIMITED_METHOD_CONTRACT; + // No-op for the VM. Only meaningful for ReadyToRun Wasm compilation. +} + void CEEInfo::recordRelocation( void * location, /* IN */ void * locationRW, /* IN */ diff --git a/src/coreclr/vm/loaderallocator.cpp b/src/coreclr/vm/loaderallocator.cpp index 7cf751d3260593..4e48ffd14974e4 100644 --- a/src/coreclr/vm/loaderallocator.cpp +++ b/src/coreclr/vm/loaderallocator.cpp @@ -17,6 +17,10 @@ #include "interpexec.h" #endif +#ifdef FEATURE_PORTABLE_ENTRYPOINTS +#include "pregeneratedstringthunks.h" +#endif + //#define ENABLE_LOG_LOADER_ALLOCATOR_CLEANUP 1 #define STUBMANAGER_RANGELIST(stubManager) (stubManager::g_pManager->GetRangeList()) @@ -95,6 +99,10 @@ LoaderAllocator::LoaderAllocator(bool collectible) : m_pUMEntryThunkCache = NULL; #endif // !FEATURE_PORTABLE_ENTRYPOINTS +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + m_registeredForPendingThunkResolution = false; +#endif // FEATURE_PORTABLE_ENTRYPOINTS + m_nLoaderAllocator = InterlockedIncrement64((LONGLONG *)&LoaderAllocator::cLoaderAllocatorsCreated); #ifdef FEATURE_PGO @@ -689,6 +697,10 @@ BOOL LoaderAllocator::Destroy(QCall::LoaderAllocatorHandle pLoaderAllocator) LoaderAllocator::RemoveMemoryToLoaderAllocatorAssociation(pLoaderAllocator); } +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + UnregisterLoaderAllocatorForPendingThunkResolution(pLoaderAllocator); +#endif // FEATURE_PORTABLE_ENTRYPOINTS + // This will probably change for shared code unloading _ASSERTE(pID->GetType() == LAT_Assembly); @@ -2518,4 +2530,21 @@ bool LoaderAllocator::InsertObjectIntoFieldWithLifetimeOfCollectibleLoaderAlloca return result; } +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + +void LoaderAllocator::AddPendingPortableEntryPointThunk(MethodDesc* pMD) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + AddPendingPortableEntryPointThunkUnderLock(this, pMD); +} + +#endif // FEATURE_PORTABLE_ENTRYPOINTS + #endif diff --git a/src/coreclr/vm/loaderallocator.hpp b/src/coreclr/vm/loaderallocator.hpp index 15ad9c726db0ef..2564d233ca0082 100644 --- a/src/coreclr/vm/loaderallocator.hpp +++ b/src/coreclr/vm/loaderallocator.hpp @@ -494,6 +494,15 @@ class LoaderAllocator PTR_AsyncContinuationsManager m_asyncContinuationsManager; +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + // Methods whose PortableEntryPoint was initialized without an R2R-to-interpreter thunk + // because the thunk wasn't yet loaded. When a new R2R module injects string thunks, + // these methods are re-checked and resolved if a thunk is now available. + // Protected by s_pendingThunkResolutionLock (not m_crstLoaderAllocator). + SArray m_pendingPortableEntryPointThunks; + bool m_registeredForPendingThunkResolution; +#endif // FEATURE_PORTABLE_ENTRYPOINTS + #ifndef DACCESS_COMPILE public: @@ -906,6 +915,12 @@ class LoaderAllocator PTR_AsyncContinuationsManager GetAsyncContinuationsManager(); +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + // Add a MethodDesc to the pending list of methods waiting for an R2R-to-interpreter thunk. + // Takes s_pendingThunkResolutionLock internally. + void AddPendingPortableEntryPointThunk(MethodDesc* pMD); +#endif // FEATURE_PORTABLE_ENTRYPOINTS + #ifndef DACCESS_COMPILE public: virtual void RegisterDependentHandleToNativeObjectForCleanup(LADependentHandleToNativeObject *dependentHandle) {}; @@ -914,6 +929,11 @@ class LoaderAllocator #endif friend struct ::cdac_data; +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + friend void AddPendingPortableEntryPointThunkUnderLock(LoaderAllocator*, MethodDesc*); + friend void UnregisterLoaderAllocatorForPendingThunkResolution(LoaderAllocator*); + friend void ResolvePendingPortableEntryPointThunksGlobal(); +#endif // FEATURE_PORTABLE_ENTRYPOINTS }; // class LoaderAllocator template<> diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index ca4bf5c4a87e22..a71ea6efd1a0c2 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -2122,6 +2122,9 @@ PCODE MethodDesc::GetMultiCallableAddrOfCode(CORINFO_ACCESS_FLAGS accessFlags /* PCODE ret = TryGetMultiCallableAddrOfCode(accessFlags); +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + _ASSERTE((ret != (PCODE)NULL) && "PortableEntryPoint logic should always cause the TryGetMultiCallableAddrOfCode to return a value"); +#else if (ret == (PCODE)NULL) { GCX_COOP(); @@ -2129,6 +2132,7 @@ PCODE MethodDesc::GetMultiCallableAddrOfCode(CORINFO_ACCESS_FLAGS accessFlags /* // We have to allocate funcptr stub ret = GetLoaderAllocator()->GetFuncPtrStubs()->GetFuncPtrStub(this); } +#endif return ret; } @@ -2209,6 +2213,12 @@ PCODE MethodDesc::TryGetMultiCallableAddrOfCode(CORINFO_ACCESS_FLAGS accessFlags { entryPoint = (PCODE)PortableEntryPoint::GetActualCode(entryPoint); } + else + { +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + MethodDesc::EnsurePortableEntryPointIsCallableFromR2R(entryPoint); +#endif // FEATURE_PORTABLE_ENTRYPOINTS + } return entryPoint; @@ -2926,6 +2936,37 @@ PCODE MethodDesc::GetPortableEntryPointIfExists() return GetTemporaryEntryPointIfExists(); } +// Prepare a portable entry point to be callable from R2R code. This doesn't necessarily +// fill in the native code slot, but if it is possible to do so it will. +// This must be called before any R2R code may call the target method. +// +// Currently this is implemented by calling this in GetMultiCallableAddrOfCode +// which works because current R2R codegen doesn't actually do direct vtable dispatch +// If/When we fix that, we'll have to figure out the best way to ensure this is called +// for virtual dispatches as well. +void MethodDesc::EnsurePortableEntryPointIsCallableFromR2R(PCODE entryPoint) +{ + WRAPPER_NO_CONTRACT; + + PortableEntryPoint *pep = PortableEntryPoint::ToPortableEntryPoint(entryPoint); + if (pep->HasNativeCode()) + { + return; + } + + MethodDesc* pMD = PortableEntryPoint::GetMethodDesc(entryPoint); + void* pPortableEntryPointToInterpreter = GetPortableEntryPointToInterpreterThunk(pMD); + if (pPortableEntryPointToInterpreter != nullptr) + { + pep->TrySetInterpreterThunk(pPortableEntryPointToInterpreter); + } + else + { + _ASSERTE(!pMD->ContainsGenericVariables()); + pMD->GetLoaderAllocator()->AddPendingPortableEntryPointThunk(pMD); + } +} + void MethodDesc::SetPortableEntrypointInitialStateForMethod(PortableEntryPoint *portableEntry) { CONTRACTL @@ -2935,23 +2976,17 @@ void MethodDesc::SetPortableEntrypointInitialStateForMethod(PortableEntryPoint * MODE_ANY; } CONTRACTL_END; - // WASM-TODO! This only handling the R2R to interpreter case for well known signatures. - // Eventually we will need to handle arbitrary signatures by looking in the loaded list of R2R modules - // as well as recording when we couldn't find something, in case another R2R module might be loaded - // later which has an R2R to interpreter stub for that given signature. - void* pPortableEntryPointToInterpreter = GetPortableEntryPointToInterpreterThunk(this); - - if (pPortableEntryPointToInterpreter != nullptr) + if (!IsDynamicMethod() && portableEntry->HasNativeCodeUnchecked()) { - portableEntry->Init(pPortableEntryPointToInterpreter, this); + void* pPortableEntryPointToInterpreter = GetPortableEntryPointToInterpreterThunk(this); + _ASSERTE(pPortableEntryPointToInterpreter != nullptr); + portableEntry->Init_WithInterpreterThunk(pPortableEntryPointToInterpreter, this); } else { portableEntry->Init(this); } - // If we find actual code, we will remove this flag, but we want to prefer the interpreter entry point - // until then to allow helpers to work for methods that haven't tried to get an entry point yet. - portableEntry->SetPrefersInterpreterEntryPoint(); + return; } void MethodDesc::ResetPortableEntryPoint() @@ -3412,7 +3447,12 @@ void MethodDesc::ResetCodeEntryPointForEnC() LOG((LF_ENC, LL_INFO100000, "MD::RCEPFENC: this:%p - %s::%s\n", this, m_pszDebugClassName, m_pszDebugMethodName)); #ifdef FEATURE_PORTABLE_ENTRYPOINTS + bool oldEntrypointHadNativeCode = GetPortableEntryPointIfExists() != (PCODE)NULL && PortableEntryPoint::ToPortableEntryPoint(GetPortableEntryPoint())->HasNativeCode(); ResetPortableEntryPoint(); + if (oldEntrypointHadNativeCode) + { + MethodDesc::EnsurePortableEntryPointIsCallableFromR2R(GetPortableEntryPoint()); + } #else // !FEATURE_PORTABLE_ENTRYPOINTS LOG((LF_ENC, LL_INFO100000, "MD::RCEPFENC: HasPrecode():%s, HasNativeCodeSlot():%s\n", (HasPrecode() ? "true" : "false"), (HasNativeCodeSlot() ? "true" : "false"))); diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 38bc47b364c01e..dc0c6b3cef2b08 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1629,6 +1629,7 @@ class MethodDesc void ResetPortableEntryPoint(); void SetPortableEntrypointInitialStateForMethod(PortableEntryPoint *portableEntry); + static void EnsurePortableEntryPointIsCallableFromR2R(PCODE entryPoint); #endif // FEATURE_PORTABLE_ENTRYPOINTS //******************************************************************************* @@ -1867,6 +1868,16 @@ class MethodDesc void PrepareForUseAsAFunctionPointer(); +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + bool IsPendingThunkResolution() + { + return (VolatileLoad(&m_bFlags4) & enum_flag4_PendingThunkResolution) != 0; + } + void SetPendingThunkResolution(bool isPending) + { + InterlockedUpdateFlags4(enum_flag4_PendingThunkResolution, isPending ? TRUE : FALSE); + } +#endif private: void PrepareForUseAsADependencyOfANativeImageWorker(); @@ -1899,9 +1910,11 @@ class MethodDesc enum_flag4_RequiresStableEntryPoint = 0x02, enum_flag4_TemporaryEntryPointAssigned = 0x04, enum_flag4_EnCAddedMethod = 0x08, +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + enum_flag4_PendingThunkResolution = 0x10, +#endif }; - void InterlockedSetFlags4(BYTE mask, BYTE newValue); BYTE m_bFlags4; // Used to hold more flags WORD m_wSlotNumber; // The slot number of this MethodDesc in the vtable array. @@ -2971,6 +2984,14 @@ class DynamicMethodDesc : public StoredSigMethodDesc { m_dwExtendedFlags = (m_dwExtendedFlags & ~flags); } + void InterlockedSetFlags(DWORD flags) + { + InterlockedOr((LONG*)&m_dwExtendedFlags, (LONG)flags); + } + void InterlockedClearFlags(DWORD flags) + { + InterlockedAnd((LONG*)&m_dwExtendedFlags, (LONG)~flags); + } ILStubType GetILStubType() const { diff --git a/src/coreclr/vm/precode_portable.cpp b/src/coreclr/vm/precode_portable.cpp index 934b5fb3c1e6ba..0ffdaa130a8591 100644 --- a/src/coreclr/vm/precode_portable.cpp +++ b/src/coreclr/vm/precode_portable.cpp @@ -117,7 +117,7 @@ void PortableEntryPoint::Init(MethodDesc* pMD) _pActualCode = NULL; _pMD = pMD; _pInterpreterData = NULL; - _flags = kNone; + _flags = kPrefersInterpreterEntryPoint; INDEBUG(_canary = CANARY_VALUE); } @@ -132,7 +132,18 @@ void PortableEntryPoint::Init(void* nativeEntryPoint) INDEBUG(_canary = CANARY_VALUE); } -void PortableEntryPoint::Init(void* nativeEntryPoint, MethodDesc* pMD) +void PortableEntryPoint::Init_WithInterpreterThunk(void* nativeEntryPoint, MethodDesc* pMD) +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(pMD != NULL); + _pActualCode = nativeEntryPoint; + _pMD = pMD; + _pInterpreterData = NULL; + _flags = kPrefersInterpreterEntryPoint; + INDEBUG(_canary = CANARY_VALUE); +} + +void PortableEntryPoint::Init_WithNativeCode(void* nativeEntryPoint, MethodDesc* pMD) { LIMITED_METHOD_CONTRACT; _ASSERTE(pMD != NULL); diff --git a/src/coreclr/vm/precode_portable.hpp b/src/coreclr/vm/precode_portable.hpp index a1761950e95cd0..5c60ab71a25463 100644 --- a/src/coreclr/vm/precode_portable.hpp +++ b/src/coreclr/vm/precode_portable.hpp @@ -51,7 +51,8 @@ class PortableEntryPoint final public: void Init(MethodDesc* pMD); void Init(void* nativeEntryPoint); - void Init(void* nativeEntryPoint, MethodDesc* pMD); + void Init_WithInterpreterThunk(void* nativeEntryPoint, MethodDesc* pMD); + void Init_WithNativeCode(void* nativeEntryPoint, MethodDesc* pMD); // Check if the entry point represents a method with the UnmanagedCallersOnly attribute. // If it does, update the entry point to point to the UnmanagedCallersOnly thunk if not @@ -73,6 +74,13 @@ class PortableEntryPoint final return _pActualCode != nullptr; } + // This api can be used on a PortableEntryPoint which has not yet been initted + bool HasNativeCodeUnchecked() const + { + LIMITED_METHOD_CONTRACT; + return _pActualCode != nullptr; + } + bool IsPreparedForNativeCall() const { LIMITED_METHOD_CONTRACT; @@ -101,17 +109,21 @@ class PortableEntryPoint final InterlockedAnd(reinterpret_cast(&_flags), static_cast(~flags)); } public: - void SetPrefersInterpreterEntryPoint() + void ClearPrefersInterpreterEntryPoint() { LIMITED_METHOD_CONTRACT; _ASSERTE(IsValid()); - SetFlagsInterlocked(kPrefersInterpreterEntryPoint); + ClearFlagsInterlocked(kPrefersInterpreterEntryPoint); } - void ClearPrefersInterpreterEntryPoint() + + // Atomically install an interpreter thunk if _pActualCode is still NULL. + // Returns true if the thunk was installed, false if _pActualCode was already set. + bool TrySetInterpreterThunk(void* thunk) { LIMITED_METHOD_CONTRACT; _ASSERTE(IsValid()); - ClearFlagsInterlocked(kPrefersInterpreterEntryPoint); + _ASSERTE(thunk != nullptr); + return InterlockedCompareExchangeT(&_pActualCode, thunk, (void*)nullptr) == nullptr; } friend struct ::cdac_data; diff --git a/src/coreclr/vm/pregeneratedstringthunks.cpp b/src/coreclr/vm/pregeneratedstringthunks.cpp new file mode 100644 index 00000000000000..91d3fee06306ad --- /dev/null +++ b/src/coreclr/vm/pregeneratedstringthunks.cpp @@ -0,0 +1,347 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#include "common.h" +#include "pregeneratedstringthunks.h" + +#ifndef DACCESS_COMPILE + +#ifdef TARGET_WASM + +#include "stringthunkhash.h" +#include "loaderallocator.hpp" +#include "wasm/helpers.hpp" +#include "precode_portable.hpp" + +static StringToThunkHash* s_pPregeneratedStringThunks = nullptr; + +void InitializePregeneratedStringThunkHash() +{ + WRAPPER_NO_CONTRACT; + + StringToThunkHash* newTable = new StringToThunkHash(); + s_pPregeneratedStringThunks = newTable; +} + +PCODE LookupPregeneratedThunkByString(const char* str) +{ + WRAPPER_NO_CONTRACT; + + StringToThunkHash* table = VolatileLoadWithoutBarrier(&s_pPregeneratedStringThunks); + if (table == nullptr) + return NULL; + + void* thunk; + if (table->Lookup(str, &thunk)) + return (PCODE)(size_t)thunk; + + return NULL; +} + +void ProcessInjectStringThunksFixup(ReadyToRunInfo * pR2RInfo, PCCOR_SIGNATURE pBlob) +{ + STANDARD_VM_CONTRACT; + + // Retry loop: if another thread races us to update the global hash, we retry. + while (true) + { + StringToThunkHash* existingTable = VolatileLoadWithoutBarrier(&s_pPregeneratedStringThunks); + _ASSERTE(existingTable != nullptr); + + // First pass: check if there are any new strings not already in the table. + uint32_t newEntryCount = 0; + { + PCCOR_SIGNATURE pCurrent = pBlob; + while (true) + { + const char* str = (const char*)pCurrent; + if (*str == '\0') + break; + + // Advance past the null-terminated string + pCurrent += strlen(str) + 1; + // Skip the 4-byte RVA/table index + pCurrent += 4; + + void* unused; + if (!existingTable->Lookup(str, &unused)) + { + newEntryCount++; + } + } + } + + if (newEntryCount == 0) + return; + +#ifdef TARGET_WASM + WebcilDecoder decoder; + decoder.Init(dac_cast(pR2RInfo->GetImage()->GetBase()), pR2RInfo->GetImage()->GetVirtualSize()); + TADDR rvaBase = decoder.GetTableBaseOffset(); +#else + TADDR rvaBase = dac_cast(pR2RInfo->GetImage()->GetBase()); +#endif + + // Build a new table with all existing entries plus new ones. + StringToThunkHash* newTable = new StringToThunkHash(); + newTable->Reallocate((existingTable->GetCount() + newEntryCount) * StringToThunkHash::s_density_factor_denominator / StringToThunkHash::s_density_factor_numerator + 1); + // Copy existing entries + for (auto iter = existingTable->Begin(); iter != existingTable->End(); ++iter) + { + newTable->Add((*iter).Key(), (*iter).Value()); + } + + // Add new entries (existing entries take precedence) + { + PCCOR_SIGNATURE pCurrent = pBlob; + while (true) + { + const char* str = (const char*)pCurrent; + if (*str == '\0') + break; + + // Advance past the null-terminated string + pCurrent += strlen(str) + 1; + + // Read the 4-byte RVA (or WASM table index) + DWORD rva = GET_UNALIGNED_VAL32(pCurrent); + pCurrent += 4; + + void* unused; + if (!newTable->Lookup(str, &unused)) + { + void* codeAddr = (void*)(rvaBase + (TADDR)rva); + newTable->Add(str, codeAddr); + } + } + } + + // Try to swap in the new table. If CAS fails, another thread updated first - retry. + StringToThunkHash* previous = InterlockedCompareExchangeT(&s_pPregeneratedStringThunks, newTable, existingTable); + if (previous == existingTable) + { + // Success. The old table is intentionally leaked - it may still be in use + // by concurrent readers via VolatileLoadWithoutBarrier. +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + ResolvePendingPortableEntryPointThunksGlobal(); +#endif // FEATURE_PORTABLE_ENTRYPOINTS + return; + } + + // CAS failed, another thread updated the table. Delete our new table and retry. + delete newTable; + } +} + +// ===================================================================== +// Pending portable entrypoint thunk resolution +// ===================================================================== +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + +// s_pendingThunkResolutionLock protects BOTH the global LA list AND the +// per-LoaderAllocator m_pendingPortableEntryPointThunks arrays. This avoids +// any lock-ordering issues and keeps LAs alive during the scan (Destroy +// takes the same lock to unregister). + +static CrstStatic s_pendingThunkResolutionLock; +static SArray s_pendingThunkLoaderAllocators; + +void InitializePendingThunkResolutionLock() +{ + WRAPPER_NO_CONTRACT; + s_pendingThunkResolutionLock.Init(CrstPregeneratedStringThunks, CRST_DEFAULT); +} + +void ClearPendingThunkResolutionUnderLock(DynamicMethodDesc* pMD) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + CrstHolder holder(&s_pendingThunkResolutionLock); + + // Clear the pending thunk resolution flag for this method. + // This is necessary so that once a MethodDesc is no longer in use and gets recycled for a new method, it will not + // mistakenly be treated as needing a resolution. This must be done under the pendingThunkResolutionLock to avoid + // races with the ResolvePendingPortableEntryPointThunksGlobal loop. + pMD->SetPendingThunkResolution(false); +} + +void PortableEntrypointThunkProcessingReady() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // This is called once the EE is ready to process thunks (i.e. after the first R2R module is loaded and ProcessInjectStringThunksFixup can be called). + // At this point we can resolve any pending thunks that were added before we were ready. + ResolvePendingPortableEntryPointThunksGlobal(); +} + +void AddPendingPortableEntryPointThunkUnderLock(LoaderAllocator* pLoaderAllocator, MethodDesc* pMD) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + CrstHolder holder(&s_pendingThunkResolutionLock); + + if (pMD->IsPendingThunkResolution()) + { + // Already pending, nothing to do. + return; + } + + if (!pLoaderAllocator->m_registeredForPendingThunkResolution) + { + s_pendingThunkLoaderAllocators.Append(pLoaderAllocator); + pLoaderAllocator->m_registeredForPendingThunkResolution = true; + } + + pLoaderAllocator->m_pendingPortableEntryPointThunks.Append(pMD); + + pMD->SetPendingThunkResolution(true); +} + +void UnregisterLoaderAllocatorForPendingThunkResolution(LoaderAllocator* pLoaderAllocator) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + CrstHolder holder(&s_pendingThunkResolutionLock); + + if (!pLoaderAllocator->m_registeredForPendingThunkResolution) + return; + + // Remove from the global array by finding and replacing with the last element. + COUNT_T count = s_pendingThunkLoaderAllocators.GetCount(); + for (COUNT_T i = 0; i < count; i++) + { + if (s_pendingThunkLoaderAllocators[i] == pLoaderAllocator) + { + if (i < count - 1) + { + s_pendingThunkLoaderAllocators[i] = s_pendingThunkLoaderAllocators[count - 1]; + } + s_pendingThunkLoaderAllocators.SetCount(count - 1); + break; + } + } + + // Don't mark the loader allocator as unregistered, in case there is some degenerate path + // which attempts to register a thunk after this. +} + +void ResolvePendingPortableEntryPointThunksGlobal() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + CrstHolder holder(&s_pendingThunkResolutionLock); + + COUNT_T laCount = s_pendingThunkLoaderAllocators.GetCount(); + for (COUNT_T laIdx = 0; laIdx < laCount; laIdx++) + { + LoaderAllocator* pLA = s_pendingThunkLoaderAllocators[laIdx]; + SArray& pending = pLA->m_pendingPortableEntryPointThunks; + COUNT_T count = pending.GetCount(); + COUNT_T nullCount = 0; + + for (COUNT_T i = 0; i < count; i++) + { + MethodDesc* pMD = pending[i]; + if (pMD == nullptr) + { + nullCount++; + continue; + } + + if (!pMD->IsPendingThunkResolution()) + { + _ASSERTE(pMD->IsDynamicMethod()); + // This can happen if the method was GC'd and its slot reused for a new method. Clear the entry so we don't repeatedly check it. + pending[i] = nullptr; + nullCount++; + continue; + } + + void* thunk = GetPortableEntryPointToInterpreterThunk(pMD); + if (thunk != nullptr) + { + PCODE portableEntry = pMD->GetPortableEntryPointIfExists(); + if (portableEntry != (PCODE)NULL) + { + PortableEntryPoint* pep = PortableEntryPoint::ToPortableEntryPoint(portableEntry); + pep->TrySetInterpreterThunk(thunk); + } + pending[i] = nullptr; + nullCount++; + + if (pMD->IsPendingThunkResolution()) + { + pMD->SetPendingThunkResolution(false); + } + } + } + + // Compact: move non-null entries to the front, then truncate. + if (nullCount > 0 && nullCount < count) + { + COUNT_T dest = 0; + for (COUNT_T src = 0; src < count; src++) + { + if (pending[src] != nullptr) + { + pending[dest] = pending[src]; + dest++; + } + } + pending.SetCount(dest); + } + else if (nullCount == count) + { + pending.Clear(); + } + } +} + +#endif // FEATURE_PORTABLE_ENTRYPOINTS + +#else // !TARGET_WASM + +void InitializePregeneratedStringThunkHash() +{ + LIMITED_METHOD_CONTRACT; +} + +void ProcessInjectStringThunksFixup(ReadyToRunInfo * pR2RInfo, PCCOR_SIGNATURE pBlob) +{ + LIMITED_METHOD_CONTRACT; +} + +#endif // TARGET_WASM + +#endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/pregeneratedstringthunks.h b/src/coreclr/vm/pregeneratedstringthunks.h new file mode 100644 index 00000000000000..0f7a2d3ee92e8b --- /dev/null +++ b/src/coreclr/vm/pregeneratedstringthunks.h @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +#pragma once + +#include "daccess.h" + +class LoaderAllocator; +class MethodDesc; +class DynamicMethodDesc; + +// Initialize the global pregenerated string thunk hash table. +// Must be called during EE startup before any R2R module loading. +// No-op on non-WASM platforms. +void InitializePregeneratedStringThunkHash(); + +#ifdef TARGET_WASM +// Look up a pregenerated thunk by its string key. +// Returns NULL if the string is not found in the table. +PCODE LookupPregeneratedThunkByString(const char* str); +#endif // TARGET_WASM + +// Process a READYTORUN_FIXUP_InjectStringThunks fixup, adding new entries to the global hash. +// On non-WASM platforms this is a no-op. +// moduleBase is the base address of the R2R image. +// pBlob points to the first byte after the fixup kind byte in the signature. +void ProcessInjectStringThunksFixup(ReadyToRunInfo * pR2RInfo, PCCOR_SIGNATURE pBlob); + +#ifdef FEATURE_PORTABLE_ENTRYPOINTS +// Initialize the lock used for pending thunk resolution tracking. +void InitializePendingThunkResolutionLock(); + +// Add a MethodDesc to its LoaderAllocator's pending list under the global lock. +// Registers the LoaderAllocator if not already registered. +void AddPendingPortableEntryPointThunkUnderLock(LoaderAllocator* pLoaderAllocator, MethodDesc* pMD); + +void ClearPendingThunkResolutionUnderLock(DynamicMethodDesc* pMD); + +// Unregister a LoaderAllocator from the global pending thunk resolution list. +// Called during LoaderAllocator::Destroy. +void UnregisterLoaderAllocatorForPendingThunkResolution(LoaderAllocator* pLoaderAllocator); + +// After new thunks are injected, resolve pending methods across all registered LoaderAllocators. +void ResolvePendingPortableEntryPointThunksGlobal(); +#endif // FEATURE_PORTABLE_ENTRYPOINTS diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index d5e7ee2d2893b9..67836befe68743 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2982,6 +2982,10 @@ EXTERN_C PCODE STDCALL ExternalMethodFixupWorker(TransitionBlock * pTransitionBl } } +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + MethodDesc::EnsurePortableEntryPointIsCallableFromR2R(pCode); +#endif + // Force a GC on every jit if the stress level is high enough GCStress::MaybeTrigger(); if (g_externalMethodFixupTraceActiveCount > 0) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index da8093d38779e7..1e1fb0c0222cae 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -31,6 +31,7 @@ #include "castcache.h" #include "encee.h" #include "finalizerthread.h" +#include "pregeneratedstringthunks.h" extern "C" BOOL QCALLTYPE MdUtf8String_EqualsCaseInsensitive(LPCUTF8 szLhs, LPCUTF8 szRhs, INT32 stringNumBytes) { @@ -1798,6 +1799,10 @@ extern "C" void QCALLTYPE RuntimeMethodHandle_Destroy(MethodDesc * pMethod) DynamicMethodDesc* pDynamicMethodDesc = pMethod->AsDynamicMethodDesc(); { +#if defined(FEATURE_PORTABLE_ENTRYPOINTS) + ClearPendingThunkResolutionUnderLock(pDynamicMethodDesc); +#endif + GCX_COOP(); // Destroy should be called only if the managed part is gone. diff --git a/src/coreclr/vm/stringthunkhash.h b/src/coreclr/vm/stringthunkhash.h new file mode 100644 index 00000000000000..20f46afe8541b5 --- /dev/null +++ b/src/coreclr/vm/stringthunkhash.h @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +#pragma once + +#include "shash.h" +#include "utilcode.h" + +// Hash traits for mapping C strings (const char*) to void pointers. +// Used for string-to-thunk lookup tables, both for WASM thunk caches +// and for ReadyToRun pregenerated string thunks. +class StringThunkSHashTraits : public MapSHashTraits +{ +public: + static BOOL Equals(const char* s1, const char* s2) { return strcmp(s1, s2) == 0; } + static count_t Hash(const char* key) { return HashStringA(key); } +}; + +typedef MapSHash> StringToThunkHash; diff --git a/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp b/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp index 7a87bbde0e76b8..849e33a9cf07b9 100644 --- a/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp +++ b/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp @@ -88,24 +88,146 @@ namespace *((int32_t*)pRet) = (*fptr)(); } + static void CallFunc_S64_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t) = (int32_t (*)(int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0)); + } + + static void CallFunc_S8_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t) = (int32_t (*)(int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0)); + } + + static void CallFunc_S8_S8_S8_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_IND(1), ARG_IND(2)); + } + + static void CallFunc_S8_S8_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_IND(1), ARG_I32(2)); + } + + static void CallFunc_S8_S8_I32_S8_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_IND(1), ARG_I32(2), ARG_IND(3)); + } + + static void CallFunc_S8_S8_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_IND(1), ARG_I32(2), ARG_I32(3)); + } + + static void CallFunc_S8_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t) = (int32_t (*)(int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1)); + } + + static void CallFunc_S8_I32_S8_S8_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_IND(2), ARG_IND(3)); + } + + static void CallFunc_S8_I32_S8_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_IND(2), ARG_I32(3)); + } + + static void CallFunc_S8_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2)); + } + + static void CallFunc_S8_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3)); + } + + static void CallFunc_S8_I32_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4)); + } + + static void CallFunc_S8_I32_I32_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5)); + } + + NOINLINE static void CallFunc_This_I32_RetI32_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) + { + alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; + int32_t (*fptr)(int*, int32_t, int32_t, PCODE) = *(int32_t (**)(int*, int32_t, int32_t, PCODE))(pPortableEntryPoint); + *((int32_t*)pRet) = (*fptr)(&framePointer, ARG_I32(0), ARG_I32(1), pPortableEntryPoint); + } + + NOINLINE static void CallFunc_This_RetI32_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) + { + alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; + int32_t (*fptr)(int*, int32_t, PCODE) = *(int32_t (**)(int*, int32_t, PCODE))(pPortableEntryPoint); + *((int32_t*)pRet) = (*fptr)(&framePointer, ARG_I32(0), pPortableEntryPoint); + } + static void CallFunc_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) { int32_t (*fptr)(int32_t) = (int32_t (*)(int32_t))pcode; *((int32_t*)pRet) = (*fptr)(ARG_I32(0)); } + static void CallFunc_I32_S8_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_IND(1), ARG_I32(2)); + } + + static void CallFunc_I32_S8_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_IND(1), ARG_I32(2), ARG_I32(3), ARG_I32(4)); + } + static void CallFunc_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) { int32_t (*fptr)(int32_t, int32_t) = (int32_t (*)(int32_t, int32_t))pcode; *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1)); } + static void CallFunc_I32_I32_S8_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_I32(3), ARG_I32(4)); + } + + static void CallFunc_I32_I32_S8_I32_I32_S8_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_I32(3), ARG_I32(4), ARG_IND(5)); + } + static void CallFunc_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) { int32_t (*fptr)(int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t))pcode; *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2)); } + static void CallFunc_I32_I32_I32_S8_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_IND(3), ARG_I32(4)); + } + static void CallFunc_I32_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) { int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; @@ -163,12 +285,6 @@ namespace *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I64(3)); } - static void CallFunc_I32_I32_I32_IND_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_IND(3), ARG_I32(4)); - } - NOINLINE static void CallFunc_I32_I32_I32_RetI32_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; @@ -182,18 +298,6 @@ namespace *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I64(2)); } - static void CallFunc_I32_I32_IND_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_I32(3), ARG_I32(4)); - } - - static void CallFunc_I32_I32_IND_I32_I32_IND_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_I32(3), ARG_I32(4), ARG_IND(5)); - } - NOINLINE static void CallFunc_I32_I32_RetI32_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; @@ -231,18 +335,6 @@ namespace *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I64(1), ARG_I64(2), ARG_I32(3)); } - static void CallFunc_I32_IND_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_IND(1), ARG_I32(2)); - } - - static void CallFunc_I32_IND_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_IND(1), ARG_I32(2), ARG_I32(3), ARG_I32(4)); - } - NOINLINE static void CallFunc_I32_RetI32_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; @@ -256,78 +348,6 @@ namespace *((int32_t*)pRet) = (*fptr)(ARG_I64(0), ARG_I32(1), ARG_I64(2), ARG_I32(3)); } - static void CallFunc_IND_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t) = (int32_t (*)(int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0)); - } - - static void CallFunc_IND_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t) = (int32_t (*)(int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1)); - } - - static void CallFunc_IND_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2)); - } - - static void CallFunc_IND_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3)); - } - - static void CallFunc_IND_I32_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4)); - } - - static void CallFunc_IND_I32_I32_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5)); - } - - static void CallFunc_IND_I32_IND_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_IND(2), ARG_I32(3)); - } - - static void CallFunc_IND_I32_IND_IND_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_I32(1), ARG_IND(2), ARG_IND(3)); - } - - static void CallFunc_IND_IND_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_IND(1), ARG_I32(2)); - } - - static void CallFunc_IND_IND_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_IND(1), ARG_I32(2), ARG_I32(3)); - } - - static void CallFunc_IND_IND_I32_IND_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_IND(1), ARG_I32(2), ARG_IND(3)); - } - - static void CallFunc_IND_IND_IND_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet) - { - int32_t (*fptr)(int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t))pcode; - *((int32_t*)pRet) = (*fptr)(ARG_IND(0), ARG_IND(1), ARG_IND(2)); - } - NOINLINE static void CallFunc_Void_RetI32_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; @@ -406,171 +426,178 @@ namespace (*fptr)(); } - NOINLINE static void CallFunc_F64_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; - void (*fptr)(int*, double, int32_t, int32_t, PCODE) = *(void (**)(int*, double, int32_t, int32_t, PCODE))(pPortableEntryPoint); - (*fptr)(&framePointer, ARG_F64(0), ARG_I32(1), ARG_I32(2), pPortableEntryPoint); + void (*fptr)(int32_t) = (void (*)(int32_t))pcode; + (*fptr)(ARG_IND(0)); } - NOINLINE static void CallFunc_F32_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_S8_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; - void (*fptr)(int*, float, int32_t, int32_t, PCODE) = *(void (**)(int*, float, int32_t, int32_t, PCODE))(pPortableEntryPoint); - (*fptr)(&framePointer, ARG_F32(0), ARG_I32(1), ARG_I32(2), pPortableEntryPoint); + void (*fptr)(int32_t, int32_t) = (void (*)(int32_t, int32_t))pcode; + (*fptr)(ARG_IND(0), ARG_IND(1)); } - static void CallFunc_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_S8_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t) = (void (*)(int32_t))pcode; - (*fptr)(ARG_I32(0)); + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_IND(0), ARG_IND(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6)); } - static void CallFunc_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { void (*fptr)(int32_t, int32_t) = (void (*)(int32_t, int32_t))pcode; - (*fptr)(ARG_I32(0), ARG_I32(1)); + (*fptr)(ARG_IND(0), ARG_I32(1)); } - static void CallFunc_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2)); + (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2)); } - static void CallFunc_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { void (*fptr)(int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3)); + (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3)); } - static void CallFunc_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4)); + (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4)); } - static void CallFunc_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5)); + (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5)); } - static void CallFunc_I32_I32_I32_IND_IND_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_IND(3), ARG_IND(4)); + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6)); } - static void CallFunc_I32_I32_I32_IND_IND_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_IND(3), ARG_IND(4), ARG_I32(5)); + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6), ARG_I32(7), ARG_I32(8), ARG_I32(9), ARG_I32(10), ARG_I32(11)); } - NOINLINE static void CallFunc_I32_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) + NOINLINE static void CallFunc_This_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; - void (*fptr)(int*, int32_t, int32_t, int32_t, PCODE) = *(void (**)(int*, int32_t, int32_t, int32_t, PCODE))(pPortableEntryPoint); - (*fptr)(&framePointer, ARG_I32(0), ARG_I32(1), ARG_I32(2), pPortableEntryPoint); + void (*fptr)(int*, int32_t, PCODE) = *(void (**)(int*, int32_t, PCODE))(pPortableEntryPoint); + (*fptr)(&framePointer, ARG_I32(0), pPortableEntryPoint); } - static void CallFunc_I32_I32_IND_IND_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + NOINLINE static void CallFunc_F64_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_IND(3), ARG_I32(4)); + alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; + void (*fptr)(int*, double, int32_t, int32_t, PCODE) = *(void (**)(int*, double, int32_t, int32_t, PCODE))(pPortableEntryPoint); + (*fptr)(&framePointer, ARG_F64(0), ARG_I32(1), ARG_I32(2), pPortableEntryPoint); } - static void CallFunc_I32_I32_IND_IND_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + NOINLINE static void CallFunc_F32_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_IND(3), ARG_I32(4), ARG_I32(5)); + alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; + void (*fptr)(int*, float, int32_t, int32_t, PCODE) = *(void (**)(int*, float, int32_t, int32_t, PCODE))(pPortableEntryPoint); + (*fptr)(&framePointer, ARG_F32(0), ARG_I32(1), ARG_I32(2), pPortableEntryPoint); } - NOINLINE static void CallFunc_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) + static void CallFunc_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; - void (*fptr)(int*, int32_t, int32_t, PCODE) = *(void (**)(int*, int32_t, int32_t, PCODE))(pPortableEntryPoint); - (*fptr)(&framePointer, ARG_I32(0), ARG_I32(1), pPortableEntryPoint); + void (*fptr)(int32_t) = (void (*)(int32_t))pcode; + (*fptr)(ARG_I32(0)); } - static void CallFunc_I32_IND_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_I32_S8_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode; (*fptr)(ARG_I32(0), ARG_IND(1), ARG_I32(2)); } - NOINLINE static void CallFunc_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) + static void CallFunc_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; - void (*fptr)(int*, int32_t, PCODE) = *(void (**)(int*, int32_t, PCODE))(pPortableEntryPoint); - (*fptr)(&framePointer, ARG_I32(0), pPortableEntryPoint); + void (*fptr)(int32_t, int32_t) = (void (*)(int32_t, int32_t))pcode; + (*fptr)(ARG_I32(0), ARG_I32(1)); } - static void CallFunc_I64_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_I32_I32_S8_S8_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int64_t) = (void (*)(int64_t))pcode; - (*fptr)(ARG_I64(0)); + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_IND(3), ARG_I32(4)); } - static void CallFunc_IND_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_I32_I32_S8_S8_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t) = (void (*)(int32_t))pcode; - (*fptr)(ARG_IND(0)); + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_IND(3), ARG_I32(4), ARG_I32(5)); } - static void CallFunc_IND_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t) = (void (*)(int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG_I32(1)); + void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2)); } - static void CallFunc_IND_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_I32_I32_I32_S8_S8_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2)); + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_IND(3), ARG_IND(4)); + } + + static void CallFunc_I32_I32_I32_S8_S8_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + { + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_IND(3), ARG_IND(4), ARG_I32(5)); } - static void CallFunc_IND_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { void (*fptr)(int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3)); + (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3)); } - static void CallFunc_IND_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4)); + (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4)); } - static void CallFunc_IND_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5)); + (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5)); } - static void CallFunc_IND_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + NOINLINE static void CallFunc_I32_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6)); + alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; + void (*fptr)(int*, int32_t, int32_t, int32_t, PCODE) = *(void (**)(int*, int32_t, int32_t, int32_t, PCODE))(pPortableEntryPoint); + (*fptr)(&framePointer, ARG_I32(0), ARG_I32(1), ARG_I32(2), pPortableEntryPoint); } - static void CallFunc_IND_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + NOINLINE static void CallFunc_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6), ARG_I32(7), ARG_I32(8), ARG_I32(9), ARG_I32(10), ARG_I32(11)); + alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; + void (*fptr)(int*, int32_t, int32_t, PCODE) = *(void (**)(int*, int32_t, int32_t, PCODE))(pPortableEntryPoint); + (*fptr)(&framePointer, ARG_I32(0), ARG_I32(1), pPortableEntryPoint); } - static void CallFunc_IND_IND_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + NOINLINE static void CallFunc_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t) = (void (*)(int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG_IND(1)); + alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK; + void (*fptr)(int*, int32_t, PCODE) = *(void (**)(int*, int32_t, PCODE))(pPortableEntryPoint); + (*fptr)(&framePointer, ARG_I32(0), pPortableEntryPoint); } - static void CallFunc_IND_IND_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_I64_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG_IND(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6)); + void (*fptr)(int64_t) = (void (*)(int64_t))pcode; + (*fptr)(ARG_I64(0)); } NOINLINE static void CallFunc_Void_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) @@ -582,95 +609,99 @@ namespace } const StringToWasmSigThunk g_wasmThunks[] = { - { "ddddp", (void*)&CallFunc_F64_F64_F64_RetF64_PE }, - { "dddp", (void*)&CallFunc_F64_F64_RetF64_PE }, - { "ddip", (void*)&CallFunc_F64_I32_RetF64_PE }, - { "ddp", (void*)&CallFunc_F64_RetF64_PE }, - { "di", (void*)&CallFunc_I32_RetF64 }, - { "ffffp", (void*)&CallFunc_F32_F32_F32_RetF32_PE }, - { "fffp", (void*)&CallFunc_F32_F32_RetF32_PE }, - { "ffip", (void*)&CallFunc_F32_I32_RetF32_PE }, - { "ffp", (void*)&CallFunc_F32_RetF32_PE }, - { "i", (void*)&CallFunc_Void_RetI32 }, - { "ii", (void*)&CallFunc_I32_RetI32 }, - { "iii", (void*)&CallFunc_I32_I32_RetI32 }, - { "iiii", (void*)&CallFunc_I32_I32_I32_RetI32 }, - { "iiiii", (void*)&CallFunc_I32_I32_I32_I32_RetI32 }, - { "iiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetI32 }, - { "iiiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetI32 }, - { "iiiiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_I32_RetI32 }, - { "iiiiiiiiiiiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetI32 }, - { "iiiiiiip", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetI32_PE }, - { "iiiiiip", (void*)&CallFunc_I32_I32_I32_I32_I32_RetI32_PE }, - { "iiiiip", (void*)&CallFunc_I32_I32_I32_I32_RetI32_PE }, - { "iiiil", (void*)&CallFunc_I32_I32_I32_I64_RetI32 }, - { "iiiini", (void*)&CallFunc_I32_I32_I32_IND_I32_RetI32 }, - { "iiiip", (void*)&CallFunc_I32_I32_I32_RetI32_PE }, - { "iiil", (void*)&CallFunc_I32_I32_I64_RetI32 }, - { "iiinii", (void*)&CallFunc_I32_I32_IND_I32_I32_RetI32 }, - { "iiiniin", (void*)&CallFunc_I32_I32_IND_I32_I32_IND_RetI32 }, - { "iiip", (void*)&CallFunc_I32_I32_RetI32_PE }, - { "iil", (void*)&CallFunc_I32_I64_RetI32 }, - { "iili", (void*)&CallFunc_I32_I64_I32_RetI32 }, - { "iiliiil", (void*)&CallFunc_I32_I64_I32_I32_I32_I64_RetI32 }, - { "iill", (void*)&CallFunc_I32_I64_I64_RetI32 }, - { "iilli", (void*)&CallFunc_I32_I64_I64_I32_RetI32 }, - { "iini", (void*)&CallFunc_I32_IND_I32_RetI32 }, - { "iiniii", (void*)&CallFunc_I32_IND_I32_I32_I32_RetI32 }, - { "iip", (void*)&CallFunc_I32_RetI32_PE }, - { "ilili", (void*)&CallFunc_I64_I32_I64_I32_RetI32 }, - { "in", (void*)&CallFunc_IND_RetI32 }, - { "ini", (void*)&CallFunc_IND_I32_RetI32 }, - { "inii", (void*)&CallFunc_IND_I32_I32_RetI32 }, - { "iniii", (void*)&CallFunc_IND_I32_I32_I32_RetI32 }, - { "iniiii", (void*)&CallFunc_IND_I32_I32_I32_I32_RetI32 }, - { "iniiiii", (void*)&CallFunc_IND_I32_I32_I32_I32_I32_RetI32 }, - { "inini", (void*)&CallFunc_IND_I32_IND_I32_RetI32 }, - { "ininn", (void*)&CallFunc_IND_I32_IND_IND_RetI32 }, - { "inni", (void*)&CallFunc_IND_IND_I32_RetI32 }, - { "innii", (void*)&CallFunc_IND_IND_I32_I32_RetI32 }, - { "innin", (void*)&CallFunc_IND_IND_I32_IND_RetI32 }, - { "innn", (void*)&CallFunc_IND_IND_IND_RetI32 }, - { "ip", (void*)&CallFunc_Void_RetI32_PE }, - { "l", (void*)&CallFunc_Void_RetI64 }, - { "li", (void*)&CallFunc_I32_RetI64 }, - { "liii", (void*)&CallFunc_I32_I32_I32_RetI64 }, - { "liiil", (void*)&CallFunc_I32_I32_I32_I64_RetI64 }, - { "lili", (void*)&CallFunc_I32_I64_I32_RetI64 }, - { "lillp", (void*)&CallFunc_I32_I64_I64_RetI64_PE }, - { "lilp", (void*)&CallFunc_I32_I64_RetI64_PE }, - { "lip", (void*)&CallFunc_I32_RetI64_PE }, - { "lllp", (void*)&CallFunc_I64_I64_RetI64_PE }, - { "lp", (void*)&CallFunc_Void_RetI64_PE }, - { "v", (void*)&CallFunc_Void_RetVoid }, - { "vdiip", (void*)&CallFunc_F64_I32_I32_RetVoid_PE }, - { "vfiip", (void*)&CallFunc_F32_I32_I32_RetVoid_PE }, - { "vi", (void*)&CallFunc_I32_RetVoid }, - { "vii", (void*)&CallFunc_I32_I32_RetVoid }, - { "viii", (void*)&CallFunc_I32_I32_I32_RetVoid }, - { "viiii", (void*)&CallFunc_I32_I32_I32_I32_RetVoid }, - { "viiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetVoid }, - { "viiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetVoid }, - { "viiinn", (void*)&CallFunc_I32_I32_I32_IND_IND_RetVoid }, - { "viiinni", (void*)&CallFunc_I32_I32_I32_IND_IND_I32_RetVoid }, - { "viiip", (void*)&CallFunc_I32_I32_I32_RetVoid_PE }, - { "viinni", (void*)&CallFunc_I32_I32_IND_IND_I32_RetVoid }, - { "viinnii", (void*)&CallFunc_I32_I32_IND_IND_I32_I32_RetVoid }, - { "viip", (void*)&CallFunc_I32_I32_RetVoid_PE }, - { "vini", (void*)&CallFunc_I32_IND_I32_RetVoid }, - { "vip", (void*)&CallFunc_I32_RetVoid_PE }, - { "vl", (void*)&CallFunc_I64_RetVoid }, - { "vn", (void*)&CallFunc_IND_RetVoid }, - { "vni", (void*)&CallFunc_IND_I32_RetVoid }, - { "vnii", (void*)&CallFunc_IND_I32_I32_RetVoid }, - { "vniii", (void*)&CallFunc_IND_I32_I32_I32_RetVoid }, - { "vniiii", (void*)&CallFunc_IND_I32_I32_I32_I32_RetVoid }, - { "vniiiii", (void*)&CallFunc_IND_I32_I32_I32_I32_I32_RetVoid }, - { "vniiiiii", (void*)&CallFunc_IND_I32_I32_I32_I32_I32_I32_RetVoid }, - { "vniiiiiiiiiii", (void*)&CallFunc_IND_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid }, - { "vnn", (void*)&CallFunc_IND_IND_RetVoid }, - { "vnniiiii", (void*)&CallFunc_IND_IND_I32_I32_I32_I32_I32_RetVoid }, - { "vp", (void*)&CallFunc_Void_RetVoid_PE } + { "Mddddp", (void*)&CallFunc_F64_F64_F64_RetF64_PE }, + { "Mdddp", (void*)&CallFunc_F64_F64_RetF64_PE }, + { "Mddip", (void*)&CallFunc_F64_I32_RetF64_PE }, + { "Mddp", (void*)&CallFunc_F64_RetF64_PE }, + { "Mdi", (void*)&CallFunc_I32_RetF64 }, + { "Mffffp", (void*)&CallFunc_F32_F32_F32_RetF32_PE }, + { "Mfffp", (void*)&CallFunc_F32_F32_RetF32_PE }, + { "Mffip", (void*)&CallFunc_F32_I32_RetF32_PE }, + { "Mffp", (void*)&CallFunc_F32_RetF32_PE }, + { "Mi", (void*)&CallFunc_Void_RetI32 }, + { "MiS64", (void*)&CallFunc_S64_RetI32 }, + { "MiS8", (void*)&CallFunc_S8_RetI32 }, + { "MiS8S8S8", (void*)&CallFunc_S8_S8_S8_RetI32 }, + { "MiS8S8i", (void*)&CallFunc_S8_S8_I32_RetI32 }, + { "MiS8S8iS8", (void*)&CallFunc_S8_S8_I32_S8_RetI32 }, + { "MiS8S8ii", (void*)&CallFunc_S8_S8_I32_I32_RetI32 }, + { "MiS8i", (void*)&CallFunc_S8_I32_RetI32 }, + { "MiS8iS8S8", (void*)&CallFunc_S8_I32_S8_S8_RetI32 }, + { "MiS8iS8i", (void*)&CallFunc_S8_I32_S8_I32_RetI32 }, + { "MiS8ii", (void*)&CallFunc_S8_I32_I32_RetI32 }, + { "MiS8iii", (void*)&CallFunc_S8_I32_I32_I32_RetI32 }, + { "MiS8iiii", (void*)&CallFunc_S8_I32_I32_I32_I32_RetI32 }, + { "MiS8iiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_RetI32 }, + { "MiTip", (void*)&CallFunc_This_I32_RetI32_PE }, + { "MiTp", (void*)&CallFunc_This_RetI32_PE }, + { "Mii", (void*)&CallFunc_I32_RetI32 }, + { "MiiS8i", (void*)&CallFunc_I32_S8_I32_RetI32 }, + { "MiiS8iii", (void*)&CallFunc_I32_S8_I32_I32_I32_RetI32 }, + { "Miii", (void*)&CallFunc_I32_I32_RetI32 }, + { "MiiiS8ii", (void*)&CallFunc_I32_I32_S8_I32_I32_RetI32 }, + { "MiiiS8iiS8", (void*)&CallFunc_I32_I32_S8_I32_I32_S8_RetI32 }, + { "Miiii", (void*)&CallFunc_I32_I32_I32_RetI32 }, + { "MiiiiS8i", (void*)&CallFunc_I32_I32_I32_S8_I32_RetI32 }, + { "Miiiii", (void*)&CallFunc_I32_I32_I32_I32_RetI32 }, + { "Miiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetI32 }, + { "Miiiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetI32 }, + { "Miiiiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_I32_RetI32 }, + { "Miiiiiiiiiiiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetI32 }, + { "Miiiiiiip", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetI32_PE }, + { "Miiiiiip", (void*)&CallFunc_I32_I32_I32_I32_I32_RetI32_PE }, + { "Miiiiip", (void*)&CallFunc_I32_I32_I32_I32_RetI32_PE }, + { "Miiiil", (void*)&CallFunc_I32_I32_I32_I64_RetI32 }, + { "Miiiip", (void*)&CallFunc_I32_I32_I32_RetI32_PE }, + { "Miiil", (void*)&CallFunc_I32_I32_I64_RetI32 }, + { "Miiip", (void*)&CallFunc_I32_I32_RetI32_PE }, + { "Miil", (void*)&CallFunc_I32_I64_RetI32 }, + { "Miili", (void*)&CallFunc_I32_I64_I32_RetI32 }, + { "Miiliiil", (void*)&CallFunc_I32_I64_I32_I32_I32_I64_RetI32 }, + { "Miill", (void*)&CallFunc_I32_I64_I64_RetI32 }, + { "Miilli", (void*)&CallFunc_I32_I64_I64_I32_RetI32 }, + { "Miip", (void*)&CallFunc_I32_RetI32_PE }, + { "Milili", (void*)&CallFunc_I64_I32_I64_I32_RetI32 }, + { "Mip", (void*)&CallFunc_Void_RetI32_PE }, + { "Ml", (void*)&CallFunc_Void_RetI64 }, + { "Mli", (void*)&CallFunc_I32_RetI64 }, + { "Mliii", (void*)&CallFunc_I32_I32_I32_RetI64 }, + { "Mliiil", (void*)&CallFunc_I32_I32_I32_I64_RetI64 }, + { "Mlili", (void*)&CallFunc_I32_I64_I32_RetI64 }, + { "Mlillp", (void*)&CallFunc_I32_I64_I64_RetI64_PE }, + { "Mlilp", (void*)&CallFunc_I32_I64_RetI64_PE }, + { "Mlip", (void*)&CallFunc_I32_RetI64_PE }, + { "Mlllp", (void*)&CallFunc_I64_I64_RetI64_PE }, + { "Mlp", (void*)&CallFunc_Void_RetI64_PE }, + { "Mv", (void*)&CallFunc_Void_RetVoid }, + { "MvS8", (void*)&CallFunc_S8_RetVoid }, + { "MvS8S8", (void*)&CallFunc_S8_S8_RetVoid }, + { "MvS8S8iiiii", (void*)&CallFunc_S8_S8_I32_I32_I32_I32_I32_RetVoid }, + { "MvS8i", (void*)&CallFunc_S8_I32_RetVoid }, + { "MvS8ii", (void*)&CallFunc_S8_I32_I32_RetVoid }, + { "MvS8iii", (void*)&CallFunc_S8_I32_I32_I32_RetVoid }, + { "MvS8iiii", (void*)&CallFunc_S8_I32_I32_I32_I32_RetVoid }, + { "MvS8iiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_RetVoid }, + { "MvS8iiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_RetVoid }, + { "MvS8iiiiiiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid }, + { "MvTp", (void*)&CallFunc_This_RetVoid_PE }, + { "Mvdiip", (void*)&CallFunc_F64_I32_I32_RetVoid_PE }, + { "Mvfiip", (void*)&CallFunc_F32_I32_I32_RetVoid_PE }, + { "Mvi", (void*)&CallFunc_I32_RetVoid }, + { "MviS8i", (void*)&CallFunc_I32_S8_I32_RetVoid }, + { "Mvii", (void*)&CallFunc_I32_I32_RetVoid }, + { "MviiS8S8i", (void*)&CallFunc_I32_I32_S8_S8_I32_RetVoid }, + { "MviiS8S8ii", (void*)&CallFunc_I32_I32_S8_S8_I32_I32_RetVoid }, + { "Mviii", (void*)&CallFunc_I32_I32_I32_RetVoid }, + { "MviiiS8S8", (void*)&CallFunc_I32_I32_I32_S8_S8_RetVoid }, + { "MviiiS8S8i", (void*)&CallFunc_I32_I32_I32_S8_S8_I32_RetVoid }, + { "Mviiii", (void*)&CallFunc_I32_I32_I32_I32_RetVoid }, + { "Mviiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetVoid }, + { "Mviiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetVoid }, + { "Mviiip", (void*)&CallFunc_I32_I32_I32_RetVoid_PE }, + { "Mviip", (void*)&CallFunc_I32_I32_RetVoid_PE }, + { "Mvip", (void*)&CallFunc_I32_RetVoid_PE }, + { "Mvl", (void*)&CallFunc_I64_RetVoid }, + { "Mvp", (void*)&CallFunc_Void_RetVoid_PE } }; const size_t g_wasmThunksCount = sizeof(g_wasmThunks) / sizeof(g_wasmThunks[0]); diff --git a/src/coreclr/vm/wasm/callhelpers-reverse.cpp b/src/coreclr/vm/wasm/callhelpers-reverse.cpp index b61aeb0d7e13fe..38340b5e5b135b 100644 --- a/src/coreclr/vm/wasm/callhelpers-reverse.cpp +++ b/src/coreclr/vm/wasm/callhelpers-reverse.cpp @@ -623,6 +623,19 @@ static void Call_System_Private_CoreLib_System_Exception_InternalPreserveStackTr ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Exception_InternalPreserveStackTrace_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_Exception_InternalPreserveStackTrace_I32_I32_RetVoid); } +static MethodDesc* MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_InvokeArrayContentsConverter_I32_I32_I32_I32_I32_RetVoid = nullptr; +static void Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_InvokeArrayContentsConverter_I32_I32_I32_I32_I32_RetVoid(void * arg0, void * arg1, int32_t arg2, void * arg3, void * 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_StubHelpers_StubHelpers_InvokeArrayContentsConverter_I32_I32_I32_I32_I32_RetVoid) + { + LookupUnmanagedCallersOnlyMethodByName("System.StubHelpers.StubHelpers, System.Private.CoreLib", "InvokeArrayContentsConverter", &MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_InvokeArrayContentsConverter_I32_I32_I32_I32_I32_RetVoid); + } + ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_InvokeArrayContentsConverter_I32_I32_I32_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_InvokeArrayContentsConverter_I32_I32_I32_I32_I32_RetVoid); +} + static MethodDesc* MD_System_Private_CoreLib_System_Runtime_InteropServices_DynamicInterfaceCastableHelpers_IsInterfaceImplemented_I32_I32_I32_I32_I32_RetVoid = nullptr; static void Call_System_Private_CoreLib_System_Runtime_InteropServices_DynamicInterfaceCastableHelpers_IsInterfaceImplemented_I32_I32_I32_I32_I32_RetVoid(void * arg0, void * arg1, int32_t arg2, void * arg3, void * arg4) { @@ -803,45 +816,6 @@ static int32_t Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMa return result; } -static MethodDesc* MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToManaged_I32_I32_I32_I32_I32_RetVoid = nullptr; -static void Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToManaged_I32_I32_I32_I32_I32_RetVoid(void * arg0, void * arg1, void * arg2, int32_t arg3, void * 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_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToManaged_I32_I32_I32_I32_I32_RetVoid) - { - LookupUnmanagedCallersOnlyMethodByName("System.StubHelpers.StubHelpers, System.Private.CoreLib", "NonBlittableStructureArrayConvertToManaged", &MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToManaged_I32_I32_I32_I32_I32_RetVoid); - } - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToManaged_I32_I32_I32_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToManaged_I32_I32_I32_I32_I32_RetVoid); -} - -static MethodDesc* MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToUnmanaged_I32_I32_I32_I32_I32_RetVoid = nullptr; -static void Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToUnmanaged_I32_I32_I32_I32_I32_RetVoid(void * arg0, void * arg1, void * arg2, int32_t arg3, void * 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_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToUnmanaged_I32_I32_I32_I32_I32_RetVoid) - { - LookupUnmanagedCallersOnlyMethodByName("System.StubHelpers.StubHelpers, System.Private.CoreLib", "NonBlittableStructureArrayConvertToUnmanaged", &MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToUnmanaged_I32_I32_I32_I32_I32_RetVoid); - } - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToUnmanaged_I32_I32_I32_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToUnmanaged_I32_I32_I32_I32_I32_RetVoid); -} - -static MethodDesc* MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayFree_I32_I32_I32_I32_I32_RetVoid = nullptr; -static void Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayFree_I32_I32_I32_I32_I32_RetVoid(void * arg0, void * arg1, void * arg2, int32_t arg3, void * 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_StubHelpers_StubHelpers_NonBlittableStructureArrayFree_I32_I32_I32_I32_I32_RetVoid) - { - LookupUnmanagedCallersOnlyMethodByName("System.StubHelpers.StubHelpers, System.Private.CoreLib", "NonBlittableStructureArrayFree", &MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayFree_I32_I32_I32_I32_I32_RetVoid); - } - ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayFree_I32_I32_I32_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayFree_I32_I32_I32_I32_I32_RetVoid); -} - static MethodDesc* MD_System_Private_CoreLib_System_Runtime_Loader_AssemblyLoadContext_OnAssemblyLoad_I32_I32_RetVoid = nullptr; static void Call_System_Private_CoreLib_System_Runtime_Loader_AssemblyLoadContext_OnAssemblyLoad_I32_I32_RetVoid(void * arg0, void * arg1) { @@ -1241,6 +1215,7 @@ const ReverseThunkMapEntry g_ReverseThunks[] = { 513042204, "InitializeDefaultEventSources#1:System.Private.CoreLib:System.Diagnostics.Tracing:EventSource", { &MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventSource_InitializeDefaultEventSources_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventSource_InitializeDefaultEventSources_I32_RetVoid } }, { 266659693, "InitializeForMonitor#4:System.Private.CoreLib:System.Threading:Lock", { &MD_System_Private_CoreLib_System_Threading_Lock_InitializeForMonitor_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_Lock_InitializeForMonitor_I32_I32_I32_I32_RetVoid } }, { 288803216, "InternalPreserveStackTrace#2:System.Private.CoreLib:System:Exception", { &MD_System_Private_CoreLib_System_Exception_InternalPreserveStackTrace_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Exception_InternalPreserveStackTrace_I32_I32_RetVoid } }, + { 2611291109, "InvokeArrayContentsConverter#5:System.Private.CoreLib:System.StubHelpers:StubHelpers", { &MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_InvokeArrayContentsConverter_I32_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_InvokeArrayContentsConverter_I32_I32_I32_I32_I32_RetVoid } }, { 3290644746, "IsInterfaceImplemented#5:System.Private.CoreLib:System.Runtime.InteropServices:DynamicInterfaceCastableHelpers", { &MD_System_Private_CoreLib_System_Runtime_InteropServices_DynamicInterfaceCastableHelpers_IsInterfaceImplemented_I32_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_DynamicInterfaceCastableHelpers_IsInterfaceImplemented_I32_I32_I32_I32_I32_RetVoid } }, { 1577711579, "LayoutTypeConvertToManaged#3:System.Private.CoreLib:System.StubHelpers:StubHelpers", { &MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_LayoutTypeConvertToManaged_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_LayoutTypeConvertToManaged_I32_I32_I32_RetVoid } }, { 2780693056, "LayoutTypeConvertToUnmanaged#3:System.Private.CoreLib:System.StubHelpers:StubHelpers", { &MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_LayoutTypeConvertToUnmanaged_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_LayoutTypeConvertToUnmanaged_I32_I32_I32_RetVoid } }, @@ -1253,9 +1228,6 @@ const ReverseThunkMapEntry g_ReverseThunks[] = { 3515006989, "NewPrecachedExternalTypeMap#1:System.Private.CoreLib:System.Runtime.InteropServices:TypeMapLazyDictionary", { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewPrecachedExternalTypeMap_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewPrecachedExternalTypeMap_I32_RetI32 } }, { 1731038108, "NewPrecachedProxyTypeMap#1:System.Private.CoreLib:System.Runtime.InteropServices:TypeMapLazyDictionary", { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewPrecachedProxyTypeMap_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewPrecachedProxyTypeMap_I32_RetI32 } }, { 3327247096, "NewProxyTypeEntry#2:System.Private.CoreLib:System.Runtime.InteropServices:TypeMapLazyDictionary", { &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 } }, - { 3248038929, "NonBlittableStructureArrayConvertToManaged#5:System.Private.CoreLib:System.StubHelpers:StubHelpers", { &MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToManaged_I32_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToManaged_I32_I32_I32_I32_I32_RetVoid } }, - { 373411722, "NonBlittableStructureArrayConvertToUnmanaged#5:System.Private.CoreLib:System.StubHelpers:StubHelpers", { &MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToUnmanaged_I32_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayConvertToUnmanaged_I32_I32_I32_I32_I32_RetVoid } }, - { 2704509804, "NonBlittableStructureArrayFree#5:System.Private.CoreLib:System.StubHelpers:StubHelpers", { &MD_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayFree_I32_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_StubHelpers_NonBlittableStructureArrayFree_I32_I32_I32_I32_I32_RetVoid } }, { 3837429452, "OnAssemblyLoad#2:System.Private.CoreLib:System.Runtime.Loader:AssemblyLoadContext", { &MD_System_Private_CoreLib_System_Runtime_Loader_AssemblyLoadContext_OnAssemblyLoad_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Runtime_Loader_AssemblyLoadContext_OnAssemblyLoad_I32_I32_RetVoid } }, { 1632250712, "OnAssemblyResolve#4:System.Private.CoreLib:System.Runtime.Loader:AssemblyLoadContext", { &MD_System_Private_CoreLib_System_Runtime_Loader_AssemblyLoadContext_OnAssemblyResolve_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Runtime_Loader_AssemblyLoadContext_OnAssemblyResolve_I32_I32_I32_I32_RetVoid } }, { 3308959471, "OnFirstChanceException#2:System.Private.CoreLib:System:AppContext", { &MD_System_Private_CoreLib_System_AppContext_OnFirstChanceException_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_AppContext_OnFirstChanceException_I32_I32_RetVoid } }, diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 01ccd1aa50da87..c6b62f491bddc5 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -5,7 +5,8 @@ #include #include #include "callhelpers.hpp" -#include "shash.h" +#include "stringthunkhash.h" +#include "pregeneratedstringthunks.h" #include "callingconvention.h" #include "cgensys.h" #include "readytorun.h" @@ -286,20 +287,20 @@ namespace } const StringToWasmSigThunk g_wasmPortableEntryPointThunks[] = { - { "vp", (void*)&CallInterpreter_RetVoid }, - { "vip", (void*)&CallInterpreter_I32_RetVoid }, - { "viip", (void*)&CallInterpreter_I32_I32_RetVoid }, - { "viiip", (void*)&CallInterpreter_I32_I32_I32_RetVoid }, - { "viiiip", (void*)&CallInterpreter_I32_I32_I32_I32_RetVoid }, - { "ip", (void*)&CallInterpreter_RetI32 }, - { "iip", (void*)&CallInterpreter_I32_RetI32 }, - { "iiip", (void*)&CallInterpreter_I32_I32_RetI32 }, - { "iiiip", (void*)&CallInterpreter_I32_I32_I32_RetI32 }, - { "iiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_RetI32 }, - { "iiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_RetI32 }, - { "iiiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_I32_RetI32 }, - { "iiiiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_I32_I32_RetI32 }, - { "iiiiiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_I32_I32_I32_RetI32 }, + { "Ivp", (void*)&CallInterpreter_RetVoid }, + { "Ivip", (void*)&CallInterpreter_I32_RetVoid }, + { "Iviip", (void*)&CallInterpreter_I32_I32_RetVoid }, + { "Iviiip", (void*)&CallInterpreter_I32_I32_I32_RetVoid }, + { "Iviiiip", (void*)&CallInterpreter_I32_I32_I32_I32_RetVoid }, + { "Iip", (void*)&CallInterpreter_RetI32 }, + { "Iiip", (void*)&CallInterpreter_I32_RetI32 }, + { "Iiiip", (void*)&CallInterpreter_I32_I32_RetI32 }, + { "Iiiiip", (void*)&CallInterpreter_I32_I32_I32_RetI32 }, + { "Iiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_RetI32 }, + { "Iiiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_RetI32 }, + { "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 }, }; const size_t g_wasmPortableEntryPointThunksCount = sizeof(g_wasmPortableEntryPointThunks) / sizeof(g_wasmPortableEntryPointThunks[0]); @@ -758,12 +759,72 @@ namespace NotConvertible, ToI32, ToI64, - ToI32Indirect, ToF32, - ToF64 + ToF64, + ToStruct, // S — multi-field struct passed by pointer, structSize holds the size + ToEmpty, // e — empty struct, takes no wasm argument }; - ConvertType ConvertibleTo(CorElementType argType, MetaSig& sig, bool isReturn) + struct ConvertResult + { + ConvertType type; + uint32_t structSize; // only meaningful when type == ToStruct + }; + + // Lowers a TypeHandle to a ConvertResult, unwrapping single-field structs + // per the BasicCABI spec. + ConvertResult LowerTypeHandle(TypeHandle th) + { + uint32_t size = th.GetSize(); + CorElementType elemType = th.GetSignatureCorElementType(); + + if ((elemType != ELEMENT_TYPE_VALUETYPE) && (elemType != ELEMENT_TYPE_TYPEDBYREF)) + { + switch (elemType) + { + case ELEMENT_TYPE_I4: case ELEMENT_TYPE_U4: + case ELEMENT_TYPE_I2: case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_I1: case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_BOOLEAN: case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_I: case ELEMENT_TYPE_U: + case ELEMENT_TYPE_PTR: case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_FNPTR: + case ELEMENT_TYPE_CLASS: case ELEMENT_TYPE_STRING: + case ELEMENT_TYPE_ARRAY: case ELEMENT_TYPE_SZARRAY: + return { ConvertType::ToI32, 0 }; + case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: + return { ConvertType::ToI64, 0 }; + case ELEMENT_TYPE_R4: + return { ConvertType::ToF32, 0 }; + case ELEMENT_TYPE_R8: + return { ConvertType::ToF64, 0 }; + default: + return { ConvertType::NotConvertible, 0 }; + } + } + + MethodTable* pMT = th.AsMethodTable(); + uint32_t numInstanceFields = pMT->GetNumInstanceFields(); + + // WASM-TODO: Empty structs should return ToEmpty once .NET + // stops padding them to size 1. See runtime issue #127361. + + if (numInstanceFields == 1) + { + FieldDesc* pField = pMT->GetApproxFieldDescListRaw(); + TypeHandle fieldType = pField->GetApproxFieldTypeHandleThrowing(); + if (fieldType.GetSize() == size) + { + // Single field, no padding — unwrap recursively + return LowerTypeHandle(fieldType); + } + // One field with padding — treat as multi-field struct + } + + return { ConvertType::ToStruct, size }; + } + + ConvertResult ConvertibleTo(CorElementType argType, MetaSig& sig, bool isReturn) { // See https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md switch (argType) @@ -785,61 +846,71 @@ namespace case ELEMENT_TYPE_U: case ELEMENT_TYPE_FNPTR: case ELEMENT_TYPE_SZARRAY: - return ConvertType::ToI32; + return { ConvertType::ToI32, 0 }; case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: - return ConvertType::ToI64; + return { ConvertType::ToI64, 0 }; case ELEMENT_TYPE_R4: - return ConvertType::ToF32; + return { ConvertType::ToF32, 0 }; case ELEMENT_TYPE_R8: - return ConvertType::ToF64; + return { ConvertType::ToF64, 0 }; case ELEMENT_TYPE_TYPEDBYREF: - // Typed references are passed indirectly in WASM since they are larger than pointer size. - return ConvertType::ToI32Indirect; case ELEMENT_TYPE_VALUETYPE: { - // In WASM, values types that are larger than pointer size or have multiple fields are passed indirectly. - // WASM-TODO: Single fields may not always be passed as i32. Floats and doubles are passed as f32 and f64 respectively. TypeHandle vt = isReturn ? sig.GetRetTypeHandleThrowing() : sig.GetLastTypeHandleThrowing(); - - if (!vt.IsTypeDesc() - && vt.AsMethodTable()->GetNumInstanceFields() >= 2) - { - return ConvertType::ToI32Indirect; - } - - return vt.GetSize() <= sizeof(uint32_t) - ? ConvertType::ToI32 - : ConvertType::ToI32Indirect; + return LowerTypeHandle(vt); } default: - return ConvertType::NotConvertible; + return { ConvertType::NotConvertible, 0 }; } } - char GetTypeCode(ConvertType type) + // Appends the encoding for a ConvertResult to keyBuffer. + // Returns the number of characters that would be written (even if pos >= maxSize). + // Only writes characters while pos < maxSize. + uint32_t AppendTypeCode(ConvertResult cr, char* keyBuffer, uint32_t pos, uint32_t maxSize) { - switch (type) + char c; + switch (cr.type) { - case ConvertType::ToI32: - return 'i'; - case ConvertType::ToI64: - return 'l'; - case ConvertType::ToF32: - return 'f'; - case ConvertType::ToF64: - return 'd'; - case ConvertType::ToI32Indirect: - return 'n'; + case ConvertType::ToI32: c = 'i'; break; + case ConvertType::ToI64: c = 'l'; break; + case ConvertType::ToF32: c = 'f'; break; + case ConvertType::ToF64: c = 'd'; break; + case ConvertType::ToEmpty: c = 'e'; break; + case ConvertType::ToStruct: + { + // Encode as S where N is the struct size in decimal + char sizeBuf[16]; + int len = sprintf_s(sizeBuf, sizeof(sizeBuf), "S%u", cr.structSize); + for (int j = 0; j < len; j++) + { + if (pos + (uint32_t)j < maxSize) + keyBuffer[pos + (uint32_t)j] = sizeBuf[j]; + } + return (uint32_t)len; + } default: PORTABILITY_ASSERT("Unknown type"); - return '?'; + c = '?'; + break; } + + if (pos < maxSize) + keyBuffer[pos] = c; + + return 1; } - bool GetSignatureKey(MetaSig& sig, char* keyBuffer, uint32_t maxSize) + // Computes the signature key string for a MetaSig. + // The format is documented in docs/design/coreclr/botr/readytorun-format.md + // (section "Wasm Signature String Encoding"). + // Returns the total number of characters needed (excluding null terminator). + // Only writes characters while pos < maxSize, so the buffer is never overflowed. + // Callers should check if the return value >= maxSize and retry with a larger buffer. + uint32_t GetSignatureKey(MetaSig& sig, char prefix, char* keyBuffer, uint32_t maxSize) { CONTRACTL { @@ -851,53 +922,56 @@ namespace uint32_t pos = 0; + if (pos < maxSize) + keyBuffer[pos] = prefix; + pos++; + if (sig.IsReturnTypeVoid()) { - keyBuffer[pos++] = 'v'; + if (pos < maxSize) + keyBuffer[pos] = 'v'; + pos++; } else { - keyBuffer[pos++] = GetTypeCode(ConvertibleTo(sig.GetReturnType(), sig, true /* isReturn */)); + ConvertResult cr = ConvertibleTo(sig.GetReturnType(), sig, true /* isReturn */); + if (cr.type == ConvertType::NotConvertible) + return UINT32_MAX; + pos += AppendTypeCode(cr, keyBuffer, pos, maxSize); } if (sig.HasThis()) - keyBuffer[pos++] = 'i'; + { + if (pos < maxSize) + keyBuffer[pos] = 'T'; + pos++; + } for (CorElementType argType = sig.NextArg(); argType != ELEMENT_TYPE_END; argType = sig.NextArg()) { - if (pos >= maxSize) - return false; - - keyBuffer[pos++] = GetTypeCode(ConvertibleTo(argType, sig, false /* isReturn */)); + ConvertResult cr = ConvertibleTo(argType, sig, false /* isReturn */); + if (cr.type == ConvertType::NotConvertible) + return UINT32_MAX; + pos += AppendTypeCode(cr, keyBuffer, pos, maxSize); } // Add the portable entrypoint parameter if (sig.GetCallingConvention() == IMAGE_CEE_CS_CALLCONV_DEFAULT) { - if (pos >= maxSize) - return false; - - keyBuffer[pos++] = 'p'; + if (pos < maxSize) + keyBuffer[pos] = 'p'; + pos++; } - if (pos >= maxSize) - return false; - - keyBuffer[pos] = 0; + if (pos < maxSize) + keyBuffer[pos] = 0; - return true; + return pos; } - class StringThunkSHashTraits : public MapSHashTraits - { - public: - static BOOL Equals(const char* s1, const char* s2) { return strcmp(s1, s2) == 0; } - static count_t Hash(const char* key) { return HashStringA(key); } - }; - - typedef MapSHash> StringToWasmSigThunkHash; + typedef StringToThunkHash StringToWasmSigThunkHash; static StringToWasmSigThunkHash* thunkCache = nullptr; static StringToWasmSigThunkHash* portableEntrypointThunkCache = nullptr; @@ -906,8 +980,14 @@ namespace StringToWasmSigThunkHash* table = thunkCache; _ASSERTE(table != nullptr && "Wasm thunk cache not initialized. Call InitializeWasmThunkCaches() at EEStartup."); void* thunk; - bool success = table->Lookup(key, &thunk); - return success ? (InterpreterCalliCookie)thunk : nullptr; + if (table->Lookup(key, &thunk)) + return (InterpreterCalliCookie)thunk; + + PCODE r2rThunk = LookupPregeneratedThunkByString(key); + if (r2rThunk != NULL) + return (InterpreterCalliCookie)(size_t)r2rThunk; + + return nullptr; } void* LookupPortableEntryPointThunk(const char* key) @@ -915,8 +995,14 @@ namespace StringToWasmSigThunkHash* table = portableEntrypointThunkCache; _ASSERTE(table != nullptr && "Wasm portable entrypoint thunk cache not initialized. Call InitializeWasmThunkCaches() at EEStartup."); void* thunk; - bool success = table->Lookup(key, &thunk); - return success ? thunk : nullptr; + if (table->Lookup(key, &thunk)) + return thunk; + + PCODE r2rThunk = LookupPregeneratedThunkByString(key); + if (r2rThunk != NULL) + return (void*)(size_t)r2rThunk; + + return nullptr; } // This is a simple signature computation routine for signatures currently supported in the wasm environment. @@ -938,10 +1024,21 @@ namespace return NULL; } - uint32_t keyBufferLen = sig.NumFixedArgs() + (sig.HasThis() ? 1 : 0) + 2 + ((callConv == IMAGE_CEE_CS_CALLCONV_DEFAULT) ? 1 : 0); - char* keyBuffer = (char*)alloca(keyBufferLen); - if (!GetSignatureKey(sig, keyBuffer, keyBufferLen)) + char fixedBuffer[64]; + char* keyBuffer = fixedBuffer; + uint32_t keyBufferLen = sizeof(fixedBuffer); + uint32_t needed = GetSignatureKey(sig, 'M', keyBuffer, keyBufferLen); + if (needed == UINT32_MAX) return NULL; + if (needed >= keyBufferLen) + { + keyBufferLen = needed + 1; + keyBuffer = (char*)alloca(keyBufferLen); + sig.Reset(); + needed = GetSignatureKey(sig, 'M', keyBuffer, keyBufferLen); + if (needed == UINT32_MAX || needed >= keyBufferLen) + return NULL; + } InterpreterCalliCookie thunk = LookupThunk(keyBuffer); #ifdef _DEBUG @@ -972,25 +1069,27 @@ namespace default: return NULL; } - uint32_t keyBufferLen = sig.NumFixedArgs() + (sig.HasThis() ? 1 : 0) + 2 + 1; // +1 for the 'p' suffix to indicate portable entry point - char* keyBuffer = (char*)alloca(keyBufferLen); - if (!GetSignatureKey(sig, keyBuffer, keyBufferLen)) + + char fixedBuffer[64]; + char* keyBuffer = fixedBuffer; + uint32_t keyBufferLen = sizeof(fixedBuffer); + uint32_t needed = GetSignatureKey(sig, 'I', keyBuffer, keyBufferLen); + if (needed == UINT32_MAX) return NULL; + if (needed >= keyBufferLen) + { + keyBufferLen = needed + 1; + keyBuffer = (char*)alloca(keyBufferLen); + sig.Reset(); + needed = GetSignatureKey(sig, 'I', keyBuffer, keyBufferLen); + if (needed == UINT32_MAX || needed >= keyBufferLen) + return NULL; + } void* thunk = LookupPortableEntryPointThunk(keyBuffer); #ifdef _DEBUG if (thunk == NULL) { - // WASM-TODO: The R2R compiler will be generating these for all necessary signatures, but the implementation - // will need to be clever here. Notably, R2R files can be dynamically loaded after the initial load, so we can't - // just drop a NULL here if the R2R to interpreter thunk isn't found. Instead, we'll need to keep a list of - // MethodDescs associated with a given signature and as we load R2R files, find the relevant thunks and update the - // PortableEntryPoint fields on them to be the right R2R to intepreter thunk. This list needs to be stored on the - // LoaderAllocator of the associated MethodDesc for proper lifetime management. - - // For debugging purposes, engineers working on R2R for WASM may wish to enable this log to see which signatures - // are missing R2R to interpreter thunks. Once the R2R to interpreter thunk generation and dynamic updating is implemented, - // we'll want to put registration of missing thunks somewhere around here. LOG((LF_STUBS, LL_INFO100000, "WASM R2R to interpreter call missing for key: %s\n", keyBuffer)); } #endif diff --git a/src/coreclr/vm/wasm/helpers.hpp b/src/coreclr/vm/wasm/helpers.hpp index 62b4299708db68..676b6970b40160 100644 --- a/src/coreclr/vm/wasm/helpers.hpp +++ b/src/coreclr/vm/wasm/helpers.hpp @@ -5,3 +5,9 @@ // Forward declaration for explicit initialization void InitializeWasmThunkCaches(); + +class MethodDesc; + +// Look up a pregenerated R2R-to-interpreter thunk for the given MethodDesc. +// Returns NULL if no thunk is available for the method's signature. +void* GetPortableEntryPointToInterpreterThunk(MethodDesc *pMD); diff --git a/src/tasks/WasmAppBuilder/coreclr/InterpToNativeGenerator.cs b/src/tasks/WasmAppBuilder/coreclr/InterpToNativeGenerator.cs index a358f74c4c57a8..0a5fb08b362030 100644 --- a/src/tasks/WasmAppBuilder/coreclr/InterpToNativeGenerator.cs +++ b/src/tasks/WasmAppBuilder/coreclr/InterpToNativeGenerator.cs @@ -43,15 +43,16 @@ public void Generate(IEnumerable cookies, string outputPath) private static string SignatureToArguments(string signature) { - if (signature.Length <= 1) + var tokens = SignatureMapper.ParseSignatureTokens(signature); + if (tokens.Count <= 1) return "void"; - return string.Join(", ", signature.Skip(1).Select(static c => SignatureMapper.CharToNativeType(c))); + return string.Join(", ", tokens.Skip(1).Select(static t => SignatureMapper.TokenToNativeType(t))); } - private static string CallFuncName(IEnumerable args, string result, bool isPortableEntryPointCall) + private static string CallFuncName(IEnumerable args, string result, bool isPortableEntryPointCall) { - var paramTypes = args.Any() ? args.Join("_", (p, i) => SignatureMapper.CharToNameType(p)).ToString() : "Void"; + var paramTypes = args.Any() ? string.Join("_", args.Select(static t => SignatureMapper.TokenToNameType(t))) : "Void"; return $"CallFunc_{paramTypes}_Ret{result}{(isPortableEntryPointCall ? "_PE" : "")}"; } @@ -93,17 +94,19 @@ private static void Emit(StreamWriter w, IEnumerable cookies) string signature = signatureValue; try { - var result = Result(signature); - bool isPortableEntryPointCall = IsPortableEntryPointCall(signature); + var tokens = SignatureMapper.ParseSignatureTokens(signature); + string returnToken = tokens[0]; + var result = Result(returnToken); + bool isPortableEntryPointCall = IsPortableEntryPointCall(tokens); if (isPortableEntryPointCall) { // Portable entrypoints have an extra hidden parameter for the portable entrypoint context, so we need to adjust the signature and result accordingly for the call function generation - signature = signature.Substring(0, signature.Length - 1); + tokens.RemoveAt(tokens.Count - 1); } - var args = Args(signature); - var portabilityAssert = signature[0] == 'n' ? "PORTABILITY_ASSERT(\"Indirect struct return is not yet implemented.\");\n " : ""; + var args = Args(tokens); + var portabilityAssert = returnToken[0] == 'S' ? "PORTABILITY_ASSERT(\"Indirect struct return is not yet implemented.\");\n " : ""; - var portableEntryPointComma = signature.Length > 1 ? ", " : ""; + var portableEntryPointComma = args.Count > 0 ? ", " : ""; var portableEntrypointDeclaration = isPortableEntryPointCall ? portableEntryPointComma + "PCODE" : ""; var portableEntrypointParam = isPortableEntryPointCall ? portableEntryPointComma + "pPortableEntryPoint" : ""; var portableEntrypointStackDeclaration = isPortableEntryPointCall ? "int*, " : ""; @@ -112,10 +115,10 @@ private static void Emit(StreamWriter w, IEnumerable cookies) w.Write( $$""" - {{(isPortableEntryPointCall ? "NOINLINE " : "")}}static void {{CallFuncName(args, SignatureMapper.CharToNameType(signature[0]), isPortableEntryPointCall)}}(PCODE {{(isPortableEntryPointCall ? "pPortableEntryPoint" : "pcode")}}, int8_t* pArgs, int8_t* pRet) + {{(isPortableEntryPointCall ? "NOINLINE " : "")}}static void {{CallFuncName(args, SignatureMapper.TokenToNameType(returnToken), isPortableEntryPointCall)}}(PCODE {{(isPortableEntryPointCall ? "pPortableEntryPoint" : "pcode")}}, int8_t* pArgs, int8_t* pRet) {{{(isPortableEntryPointCall ? "\n alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK;" : "")}} - {{result.nativeType}} (*fptr)({{portableEntrypointStackDeclaration}}{{args.Join(", ", (p, i) => SignatureMapper.CharToNativeType(p))}}{{portableEntrypointDeclaration}}) = {{portableEntrypointPointerRD}}({{result.nativeType}} ({{portableEntrypointPointerRD}}*)({{portableEntrypointStackDeclaration}}{{args.Join(", ", (p, i) => SignatureMapper.CharToNativeType(p))}}{{portableEntrypointDeclaration}})){{(isPortableEntryPointCall ? "(pPortableEntryPoint)" : "pcode")}}; - {{portabilityAssert}}{{(result.isVoid ? "" : "*" + "((" + result.nativeType + "*)pRet) = ")}}(*fptr)({{portableEntrypointStackParam}}{{args.Join(", ", (p, i) => $"{SignatureMapper.CharToArgType(p)}({i})")}}{{portableEntrypointParam}}); + {{result.nativeType}} (*fptr)({{portableEntrypointStackDeclaration}}{{string.Join(", ", args.Select(static t => SignatureMapper.TokenToNativeType(t)))}}{{portableEntrypointDeclaration}}) = {{portableEntrypointPointerRD}}({{result.nativeType}} ({{portableEntrypointPointerRD}}*)({{portableEntrypointStackDeclaration}}{{string.Join(", ", args.Select(static t => SignatureMapper.TokenToNativeType(t)))}}{{portableEntrypointDeclaration}})){{(isPortableEntryPointCall ? "(pPortableEntryPoint)" : "pcode")}}; + {{portabilityAssert}}{{(result.isVoid ? "" : "*" + "((" + result.nativeType + "*)pRet) = ")}}(*fptr)({{portableEntrypointStackParam}}{{string.Join(", ", ArgsWithSlotOffsets(args))}}{{portableEntrypointParam}}); } """); @@ -134,10 +137,11 @@ private static void Emit(StreamWriter w, IEnumerable cookies) {{signatures.Join($",{w.NewLine}", signature => { string initialSignature = signature; - bool isPortableEntryPointCall = IsPortableEntryPointCall(signature); + var tokens = SignatureMapper.ParseSignatureTokens(signature); + bool isPortableEntryPointCall = IsPortableEntryPointCall(tokens); if (isPortableEntryPointCall) - signature = signature.Substring(0, signature.Length - 1); - return $" {{ \"{initialSignature}\", (void*)&{CallFuncName(Args(signature), SignatureMapper.CharToNameType(signature[0]), isPortableEntryPointCall)} }}"; + tokens.RemoveAt(tokens.Count - 1); + return $" {{ \"M{initialSignature}\", (void*)&{CallFuncName(Args(tokens), SignatureMapper.TokenToNameType(tokens[0]), isPortableEntryPointCall)} }}"; } )}} }; @@ -146,22 +150,30 @@ private static void Emit(StreamWriter w, IEnumerable cookies) """); - static IEnumerable Args(string signature) + static List Args(List tokens) { - for (int i = 1; i < signature.Length; ++i) - yield return signature[i]; + return tokens.Count > 1 ? tokens.GetRange(1, tokens.Count - 1) : new List(); } - static (bool isVoid, string nativeType) Result(string signature) - => new(SignatureMapper.IsVoidSignature(signature), SignatureMapper.CharToNativeType(signature[0])); + static List ArgsWithSlotOffsets(List args) + { + var result = new List(); + int slot = 0; + foreach (var token in args) + { + result.Add($"{SignatureMapper.TokenToArgType(token)}({slot})"); + slot += SignatureMapper.TokenToSlotCount(token); + } + + return result; + } + + static (bool isVoid, string nativeType) Result(string returnToken) + => new(returnToken == "v", SignatureMapper.TokenToNativeType(returnToken)); - static bool IsPortableEntryPointCall(string signature) + static bool IsPortableEntryPointCall(List tokens) { -#if NETFRAMEWORK - return signature.EndsWith("p"); -#else - return signature.EndsWith('p'); -#endif + return tokens.Count > 0 && tokens[tokens.Count - 1] == "p"; } } } diff --git a/src/tasks/WasmAppBuilder/coreclr/ManagedToNativeGenerator.cs b/src/tasks/WasmAppBuilder/coreclr/ManagedToNativeGenerator.cs index 8bc1c897bc9da5..368da23206f4a4 100644 --- a/src/tasks/WasmAppBuilder/coreclr/ManagedToNativeGenerator.cs +++ b/src/tasks/WasmAppBuilder/coreclr/ManagedToNativeGenerator.cs @@ -91,15 +91,7 @@ private void ExecuteInternal(LogAdapter log) // The signatures should be in the form of a string where the first character represents the return type and the // following characters represent the argument types. The type characters should match those used by the // SignatureMapper.CharToNativeType method. - string[] pregeneratedInterpreterToNativeSignatures = - { - "ip", - "iip", - "iiip", - "iiiip", - "vip", - "viip", - }; + string[] pregeneratedInterpreterToNativeSignatures = Array.Empty(); // Currently none, but can be added here as needed in the future. IEnumerable cookies = pinvoke.Generate(PInvokeModules, PInvokeOutputPath, ReversePInvokeOutputPath); cookies = cookies.Concat(internalCallCollector.GetSignatures()); diff --git a/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs b/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs index b08d5a213c14da..9d49f7bcfa9c44 100644 --- a/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs +++ b/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs @@ -9,11 +9,27 @@ namespace Microsoft.WebAssembly.Build.Tasks.CoreClr; +// Computes Wasm signature strings from reflection metadata. +// The signature string format is documented in docs/design/coreclr/botr/readytorun-format.md +// (section "Wasm Signature String Encoding"). internal static class SignatureMapper { - internal static char? TypeToChar(Type t, LogAdapter log, out bool isByRefStruct, int depth = 0) + // Hardcoded struct sizes for types that crossgen2 encodes as S. + // The fully general case is handled by crossgen2's type system; these + // cover the small set of multi-field structs that appear in InternalCall + // and PInvoke signatures. + private static readonly Dictionary s_knownStructSizes = new() + { + ["System.Runtime.CompilerServices.QCallModule"] = 8, + ["System.Runtime.CompilerServices.QCallAssembly"] = 8, + ["System.Runtime.CompilerServices.QCallTypeHandle"] = 8, + ["System.GC+GCHeapHardLimitInfo"] = 64, + }; + + internal static char? TypeToChar(Type t, LogAdapter log, out bool isByRefStruct, out int structSize, int depth = 0) { isByRefStruct = false; + structSize = 0; if (depth > 5) { log.Warning("WASM0064", $"Unbounded recursion detected through parameter type '{t.Name}'"); @@ -60,7 +76,7 @@ internal static class SignatureMapper else if (t.IsEnum) { Type underlyingType = t.GetEnumUnderlyingType(); - c = TypeToChar(underlyingType, log, out _, ++depth); + c = TypeToChar(underlyingType, log, out _, out structSize, ++depth); } else if (t.IsPointer) c = 'i'; @@ -72,10 +88,24 @@ internal static class SignatureMapper if (fields.Length == 1) { Type fieldType = fields[0].FieldType; - return TypeToChar(fieldType, log, out isByRefStruct, ++depth); + return TypeToChar(fieldType, log, out isByRefStruct, out structSize, ++depth); + } + else + { + string fullName = t.FullName ?? t.Name; + if (s_knownStructSizes.TryGetValue(fullName, out int size)) + { + structSize = size; + } + else + { + log.Error("WASM0067", + $"SignatureMapper: unknown multi-field struct '{fullName}' (fields: {fields.Length}) — add its size to s_knownStructSizes in SignatureMapper.cs"); + return null; + } + + c = 'S'; } - else if (PInvokeTableGenerator.IsBlittable(t, log)) - c = 'n'; isByRefStruct = true; } @@ -85,77 +115,149 @@ internal static class SignatureMapper return c; } + internal static char? TypeToChar(Type t, LogAdapter log, out bool isByRefStruct, int depth = 0) + => TypeToChar(t, log, out isByRefStruct, out _, depth); + + /// + /// Builds the multi-char token for a type in the signature string. + /// For most types this is a single character; for multi-field structs it is "S<N>". + /// + private static string? TypeToSignatureToken(Type t, LogAdapter log, out bool isByRefStruct) + { + char? c = TypeToChar(t, log, out isByRefStruct, out int structSize); + if (c is null) + return null; + + if (c == 'S' && structSize > 0) + return $"S{structSize}"; + + return c.Value.ToString(); + } + public static string? MethodToSignature(MethodInfo method, LogAdapter log, bool includeThis = false) { - string? result = TypeToChar(method.ReturnType, log, out bool resultIsByRef)?.ToString(); - if (result == null) - { + string? returnToken = TypeToSignatureToken(method.ReturnType, log, out bool resultIsByRef); + if (returnToken is null) return null; - } + + var sb = new StringBuilder(); if (resultIsByRef) { - result = "n"; + // Struct return — encode as S (the return type token already has the size) + sb.Append(returnToken); + } + else + { + sb.Append(returnToken); } if (includeThis && !method.IsStatic) { - result += 'i'; + sb.Append('T'); } foreach (var parameter in method.GetParameters()) { - char? parameterChar = TypeToChar(parameter.ParameterType, log, out _); - if (parameterChar == null) - { + string? paramToken = TypeToSignatureToken(parameter.ParameterType, log, out _); + if (paramToken is null) return null; - } - result += parameterChar; + sb.Append(paramToken); + } + + return sb.ToString(); + } + + /// + /// Parses a signature string into individual tokens. + /// Single-char types produce one-char tokens; S<N> produces a multi-char token like "S8" or "S64". + /// The 'p' suffix is included as its own token. + /// + public static List ParseSignatureTokens(string signature) + { + var tokens = new List(); + int i = 0; + while (i < signature.Length) + { + if (signature[i] == 'S') + { + int start = i; + i++; // skip 'S' + while (i < signature.Length && char.IsDigit(signature[i])) + i++; + tokens.Add(signature.Substring(start, i - start)); + } + else + { + tokens.Add(signature[i].ToString()); + i++; + } } - return result; + return tokens; } - public static string CharToNativeType(char c) => c switch + public static string TokenToNativeType(string token) => token[0] switch { 'v' => "void", 'i' => "int32_t", 'l' => "int64_t", 'f' => "float", 'd' => "double", - 'n' => "int32_t", - _ => throw new InvalidSignatureCharException(c) + 'S' => "int32_t", + 'T' => "int32_t", + 'p' => "PCODE", + _ => throw new InvalidSignatureCharException(token[0]) }; - public static string CharToNameType(char c) => c switch + public static string TokenToNameType(string token) => token[0] switch { 'v' => "Void", 'i' => "I32", 'l' => "I64", 'f' => "F32", 'd' => "F64", - 'n' => "IND", - _ => throw new InvalidSignatureCharException(c) + 'S' => token, // e.g. "S8", "S64" — encodes size in the name + 'T' => "This", + 'p' => "PE", + _ => throw new InvalidSignatureCharException(token[0]) }; - public static string CharToArgType(char c) => c switch + public static string TokenToArgType(string token) => token[0] switch { 'i' => "ARG_I32", 'l' => "ARG_I64", 'f' => "ARG_F32", 'd' => "ARG_F64", - 'n' => "ARG_IND", - _ => throw new InvalidSignatureCharException(c) + 'S' => "ARG_IND", + 'T' => "ARG_I32", + _ => throw new InvalidSignatureCharException(token[0]) }; + /// + /// Returns the number of INTERP_STACK_SLOT_SIZE slots consumed by a token. + /// Struct tokens (S<N>) consume max((size + 7) / 8, 1) slots; all others consume 1. + /// + public static int TokenToSlotCount(string token) + { + if (token[0] != 'S' || token.Length < 2) + return 1; + + int size = int.Parse(token.Substring(1)); + return Math.Max((size + 7) / 8, 1); + } + + // Legacy single-char overloads — still used by consumers that don't encounter S tokens. + public static string CharToNativeType(char c) => TokenToNativeType(c.ToString()); + public static string CharToNameType(char c) => TokenToNameType(c.ToString()); + public static string CharToArgType(char c) => TokenToArgType(c.ToString()); + public static string TypeToNameType(Type t, LogAdapter log) { char? c = TypeToChar(t, log, out _); - if (c == null) - { + if (c is null) throw new InvalidSignatureCharException('?'); - } return CharToNameType(c.Value); }