Uh oh!
There was an error while loading. Please reload this page.
[wasm] Generate the native-entry-point-to-interpreter thunk table - #132926
[wasm] Generate the native-entry-point-to-interpreter thunk table#132926pavelsavara wants to merge 17 commits into
Conversation
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
|
Azure Pipelines: Successfully started running 4 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Uh oh!
There was an error while loading. Please reload this page.
| "Interpreter-to-R2R thunk for compiled method"); | ||
| AddAdditionalDependency( | ||
| _compilation.NodeFactory.WasmR2RToInterpreterThunk(wasmSig), | ||
| "R2R-to-interpreter thunk for compiled method signature"); |
There was a problem hiding this comment.
Why do we need R2R-to-interpreter thunk here?
There was a problem hiding this comment.
recordCallSite roots the I thunk only for signatures R2R code calls directly. It misses an interpreted method reached solely through a materialized native entry point — delegate/ldftn, a vtable slot used as a code address, or GetMultiCallableAddrOfCode. The interesting case is generics: Foo<int> is compiled, Foo<__Canon> runs interpreted, both lower to the same wasm signature S; if Foo<__Canon> is only reached via a delegate/ftn/vtable there's no R2R call site of S, so its thunk is never rooted. The thunk here isn't for the compiled method (it has native code) — it's keyed by signature, for a same-shaped interpreted method, and compiling M is the signal that S is live in this image so it self-contains the thunk without relinking.
Caveat: this is a superset (every compiled signature), since the exact "address-taken interpreted method" set isn't cheaply available at compile time — happy to tighten the trigger if you'd prefer.
Catches the checked-in table up with main: dotnet#132274 removed the only managed caller of compressBound(), so the P/Invoke is no longer in the shipping System.IO.Compression, and ZipArchive now reaches the native RNG directly.
The 'I' thunks that let R2R code call an interpreted method were hand-written in vm/wasm/helpers.cpp, so every new call shape needed a hand-authored thunk. Emit them from the WasmAppBuilder generator instead: 17 hand-written entries become 69 generated plus 1, and helpers.cpp loses ~330 lines. Fix two parameter transpositions that the struct-returning shapes hit as soon as the generator started emitting them. crossgen2 lays the wasm parameters out as (callersStackPointer, [this], [retBuf], args..., portableEntrypoint), reading the buffer back from retBufLocalIndex = 1 + (hasThis ? 1 : 0): - Returning the struct by value makes clang insert its own sret pointer at parameter 0, ahead of callersStackPointer. These thunks are now void with an explicit int8_t* retBuf. - retBuf was placed first unconditionally; for an instance method it follows 'this'. Neither is detectable at run time. The stack pointer, the return buffer, 'this' and every by-reference argument are all i32, so a transposed order still passes call_indirect type checking and instead writes the return value over the caller's frame pointer, surfacing later as an unrelated NullReferenceException or an out-of-bounds trap. The generated table is browser-only; wasi keeps the hand-written thunk. The one remaining hand-written entry is IS16l2ip, whose 'l2' argument (a 16-byte value passed across two i64 parameters) maps one signature token to several C parameters, which the generator cannot express yet.
SignatureMapper mixes two things: reflection over scanned assemblies, which needs a LogAdapter and so drags in Microsoft.Build, and a pure mapping from signature tokens to native types, which needs nothing. Make it partial and move the pure half out, so a test can compile it directly instead of pulling MSBuild into a compiler test assembly. No behaviour change: the generator emits a byte-identical portable entrypoint table and an identical interp-to-managed table afterwards.
The R2R-to-interpreter thunks are written by the WasmAppBuilder generator but called by code crossgen2 emits, and nothing at run time can detect a disagreement, so test that the two agree. GeneratedThunkMatchesLoweredWasmSignature lowers a managed signature with crossgen2 and requires the generator to produce the same wasm parameter arity and types for the resulting key. That catches a missing hidden return buffer, which is what returning the struct by value produces, but it cannot catch two same-typed parameters being swapped: 'this' and retBuf are both i32, so a transposition leaves the type sequence identical. ThunkParametersFollowCrossgen2Order covers the positions separately, which is the only way that case is visible. Both were checked by reintroducing each bug: the transposition fails 3 cases in the ordering theory and none elsewhere, and dropping the return buffer fails 10.
The generated portable entrypoint thunks wrote every argument through (int64_t), which converts a float or double to its integer value instead of storing its bits: 1.5 arrived as 1. The interpreter reads those slots back as ARG_F32/ARG_F64, so every floating point argument crossing an R2R to interpreter call was corrupt. This was a regression for Iidp and Ildp, whose hand-written thunks used a typed 'double args[1]' and stored the value correctly, and was wrong from the start for the float and double shapes the generator discovered on its own. The unit tests cannot see this: they compare parameter types and positions, not the stores. The runtime test added alongside covers it.
The struct-returning shapes added to the pregenerated cookie list feed both generators, and only the R2R-to-interpreter half was corrected: the interpreter-to-R2R thunks still called through a pointer declared as returning the struct by value, so the compiler inserted its own sret pointer at parameter 0, ahead of the stack pointer, while the R2R callee expects (callersStackPointer, [this], retBuf, args..., portableEntrypoint). Every parameter involved is an i32, so the mismatch passed call_indirect type checking and corrupted memory instead. It showed up as an out-of-bounds access during EventSource start-up, far from the call, and it broke tests that have nothing to do with struct returns: WasmR2RStructAlignment passes on main, passes with the P/Invoke table regenerated, and failed once the thunk table was generated. Native callees keep the by-value form, which is what their own C ABI gives them.
Methods marked BypassReadyToRun are skipped by crossgen2 and run interpreted while the rest of the assembly is compiled, so a single test assembly can put a thunk on a call in either direction. Cover the shapes the thunk table carries: struct returns of 8, 12 and 16 bytes from both instance and static methods, struct arguments, mixed float, double and long scalars, void, and an interpreted method calling back into compiled code. Every case checks a value rather than only that the call returned. Nothing here traps when it goes wrong: the stack pointer, the return buffer, 'this' and every by-reference argument are i32, so a thunk with its parameters in the wrong order still passes call_indirect type checking and quietly returns bad data. The callees are NoInlining so that an inlined callee cannot skip the transition and leave the test passing without exercising anything. This covers two bugs the unit tests structurally cannot reach, both found by running it: float and double arguments stored through an integer cast, and the interpreter-to-R2R struct return convention.
'l2' is a 16-byte value (Int128, UInt128, Decimal128) passed by value across two i64 wasm parameters. SignatureMapper rejected the token outright, so IS16l2ip had to stay hand-written: one signature token maps to several C parameters, which the generator could not express. Expand a multi-slot token into one parameter per slot in both directions, as arg<n>Lo and arg<n>Hi, stored into consecutive transition block slots and read back through consecutive ARG_I64 accessors. TokenToNativeType and TokenToArgType still reject an unexpanded multi-slot token, so one cannot quietly collapse into a single parameter -- the shape every parameter bug in this area has taken. 'V2' and 'V4' now fail with a specific message instead: these thunks have no portable spelling for a v128 and nothing generates one today. The generated CallInterpreter_L2_I32_RetS16 is identical to the hand-written thunk it replaces, which was itself verified against the wasm crossgen2 emits. This empties the hand-written table, so it is removed. Browser is unaffected; every thunk it uses is generated. wasi has no generated table yet, so it now has no portable entrypoint thunks at all and a call needing one reports a missing key. wasi had 17 before this series and needs its own generated table, which requires a wasi testhost to scan.
The wasi portable entrypoint table was left behind when the generator took over the browser one: wasi had 17 hand-written thunks on main, then 1, then none once the multi-slot shape removed the last of them. Generate wasi's table too, so it has the same 70 entries as browser, and drop the browser-only guards on the CMake source entry, the extern declarations and the cache population. The other wasi tables (interp-to-managed, pinvoke, reverse) are regenerated at the same time; they were stale against the current scan set. This restores wasi to the state it had before this series and no further. It does not make R2R work there: an R2R image is a wasm module that has to be instantiated at run time against the runtime's memory and indirect function table, which only the JavaScript host does (libCorerun.js, host/assets.ts). wasi has no equivalent, so its table stays latent until that exists. Generated but not compiled locally: 'build.cmd -os wasi -subset clr' fails on a Windows host because the cross-components build passes clang flags to cl.exe (D8021: invalid numeric argument '/Werror'), which predates this change. The table was produced from a managed-only 'clr.corelib+libs' build whose testhost matches browser's exactly -- 181 assemblies, no difference in either direction. CI's wasi leg is the first thing that will compile the file.
Every other platform block in configureplatform.cmake keys on CLR_CMAKE_HOST_OS. The wasi one keyed on CLR_CMAKE_TARGET_OS, so a wasi cross-components build - which compiles host tools with MSVC on Windows - still got CLR_CMAKE_HOST_UNIX=1 and CLR_CMAKE_HOST_ARCH=wasm. That handed cl.exe the clang flags from configurecompiler.cmake, failing with D8021 on /Werror.
…signature A same-shaped method that runs interpreted must be enterable from R2R via a function pointer, delegate, virtual slot, or GetMultiCallableAddrOfCode. That thunk was previously rooted only when an R2R call site happened to share the signature; root it per compiled method so the crossgen2 fallback covers shapes beyond the generated table.
…e path Adds R2R<->interpreter cases for float/double returns in both directions, the 'S1'/'S2' single-slot struct encodings the hand-written table lacked, and an interpreted method reached through a delegate (GetMultiCallableAddrOfCode), which needs the R2R-to-interpreter thunk independent of any direct call site.
The (byte)A / (short)B / (short)(A+C) expected values are constant expressions that overflow a checked constant conversion; the runtime test build compiles constants checked, so wrap them to match the methods' unchecked truncation.
The portable-entry-point thunk is the native entry point of an interpreted method, used by any native caller reaching it through a materialized code address - a delegate/ldftn, a vtable slot, or GetMultiCallableAddrOfCode - not only R2R code, and so it is required even with no R2R present. Rename the crossgen2 node WasmR2RToInterpreterThunkNode -> WasmNativeToInterpreterThunkNode (and NodeFactory accessor) plus the surrounding comments/diagnostics/tests. The runtime lookup key (LookupString 'I'+signature) is unchanged. The R2R format helper READYTORUN_HELPER_R2RToInterpreter and the WasmInterpreterToR2RThunkNode direction (which does target R2R code) keep their names.
…iveCode The portable entry point must be made callable for any native caller reaching an interpreted method through a materialized address - a delegate/ldftn, a vtable slot, or GetMultiCallableAddrOfCode - not only R2R code, so name the API for what it guarantees. Pure rename across the declaration, definition, all call sites, and comments; no behavior change.
The thunk is the value stored in PortableEntryPoint._pActualCode - what Init_WithInterpreterThunk(void* nativeEntryPoint) calls the native entry point - so name it after what it is, distinct from the PortableEntryPoint data structure that holds it. Bare 'native' was ambiguous next to the C-ABI reverse thunk; '-to-interpreter' distinguishes it from the UnmanagedCallersOnly native entry point. Renames WasmNativeToInterpreterThunkNode -> WasmNativeEntryPointToInterpreterThunkNode (and NodeFactory accessor) plus comments/strings/tests. Runtime lookup key (LookupString 'I'+signature) is unchanged. Existing runtime names using PortableEntryPoint (GetPortableEntryPointToInterpreterThunk, the struct) are kept.
ea41ce7 to
19b9572Compare|
Azure Pipelines: Successfully started running 4 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
🟡 Changes recommended
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR replaces hand-written wasm “native entrypoint → interpreter” thunks with generator-emitted tables, wires the generated thunk table into the CoreCLR wasm VM for both browser and wasi, and updates crossgen2 rooting so signature-shaped interpreted methods reached via materialized code pointers are covered. It also adds new ReadyToRun+wasm test coverage and includes a couple of Windows-host wasi build/config fixes.
Changes:
- Generate and consume
g_wasmGeneratedPortableEntryPointThunks(browser + wasi) instead of maintaining a hand-written table inhelpers.cpp. - Update crossgen2 ReadyToRun compilation to root native-entry-point-to-interpreter thunks by signature, and rename the corresponding node/type.
- Add wasm interpreter transition tests and extend WasmArgumentLayout unit tests to validate thunk lowering and parameter ordering.
File summaries
| File | Description |
|---|---|
| src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.csproj | New wasm-only R2R/interpreter transition test project configuration. |
| src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.cs | Runtime test exercising both directions across scalar/struct/fp shapes and delegate entrypoint materialization. |
| src/tasks/WasmAppBuilder/WasmAppBuilder.csproj | Adds generator output path for portable-entrypoint thunk table emission. |
| src/tasks/WasmAppBuilder/coreclr/SignatureMapper.Tokens.cs | Splits MSBuild-free token/type mapping for reuse in tests. |
| src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs | Makes SignatureMapper partial and moves token helpers out to MSBuild-free file. |
| src/tasks/WasmAppBuilder/coreclr/PortableEntryPointThunkSignature.cs | Centralizes thunk parameter ordering logic for generator + tests. |
| src/tasks/WasmAppBuilder/coreclr/ManagedToNativeGenerator.cs | Adds pregenerated signatures and emits portable-entrypoint thunk tables when configured. |
| src/tasks/WasmAppBuilder/coreclr/InterpToNativeGenerator.cs | Implements emission of g_wasmGeneratedPortableEntryPointThunks and expands signature handling. |
| src/native/libs/build-native.cmd | Treats wasi as cross-target on Windows native build script. |
| src/coreclr/build-runtime.cmd | Treats wasi as cross-target on Windows CoreCLR build script. |
| eng/native/configureplatform.cmake | Fixes host-wasi detection to avoid mixing target/host flags. |
| src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp | Regenerated wasm wasi pinvoke entry tables and counts. |
| src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp | Regenerated interpreter→managed thunk helpers and table entries for wasi. |
| src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp | Regenerated wasm browser pinvoke entry tables and counts. |
| src/coreclr/vm/wasm/browser/callhelpers-interp-to-managed.cpp | Regenerated interpreter→managed thunk helpers and table entries for browser. |
| src/coreclr/vm/wasm/helpers.hpp | Updates comment to reflect “native-entry-point-to-interpreter” terminology. |
| src/coreclr/vm/wasm/helpers.cpp | Removes hand-written portable-entrypoint thunks and wires generated table + diagnostics. |
| src/coreclr/vm/wasm/callhelpers.hpp | Declares generated portable-entrypoint thunk table symbols. |
| src/coreclr/vm/CMakeLists.txt | Ensures callhelpers-portable-entrypoints.cpp is built into the shipped static lib for wasm. |
| src/coreclr/vm/prestub.cpp | Updates comments and uses EnsurePortableEntryPointIsCallableFromNativeCode. |
| src/coreclr/vm/precode_portable.cpp | Updates wasm comment terminology for portable entrypoint prestub behavior. |
| src/coreclr/vm/methodtable.cpp | Ensures portable entrypoints are callable from native code for cctor invocation. |
| src/coreclr/vm/method.hpp | Renames EnsurePortableEntryPointIsCallableFromR2R to ...FromNativeCode. |
| src/coreclr/vm/method.cpp | Renames implementation and updates comments describing native-call scenarios. |
| src/coreclr/vm/loaderallocator.hpp | Updates comments around pending thunk resolution list. |
| src/coreclr/vm/jitinterface.cpp | Ensures helper entrypoints are callable from native code under portable entrypoints. |
| src/coreclr/vm/dllimport.cpp | Ensures IL stubs’ portable entrypoints are callable from native code. |
| src/coreclr/vm/comutilnative.cpp | Ensures finalizer portable entrypoints are callable from native code. |
| src/coreclr/vm/callhelpers.cpp | Ensures default ctor portable entrypoint is callable from native code. |
| src/coreclr/vm/assembly.cpp | Ensures managed entrypoint portable entrypoint is callable from native code. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs | Roots both thunk directions by signature during wasm compilation and updates call-site thunk creation. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj | Renames node file include to WasmNativeEntryPointToInterpreterThunkNode.cs. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs | Renames node cache and factory entrypoint for native-entry-point-to-interpreter thunks. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmNativeEntryPointToInterpreterThunkNode.cs | Renames and documents the thunk node; updates mangled name and dependency text. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.cs | Updates comment reference to renamed native-entry-point thunk node. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.cs | Adds unit tests validating generated thunk parameter types and ordering. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj | Links MSBuild-free generator pieces into the ReadyToRun test project. |
| docs/design/coreclr/botr/clr-abi.md | Updates documentation to reflect renamed runtime API and thunk role. |
Review details
- Files reviewed: 40/40 changed files
- Comments generated: 5
- Review effort level: Lite
| if (!isStructReturn) | ||
| w.WriteLine(isVoid ? " void * result = NULL;" : $" {retType} result = 0;"); | ||
| string retBuffArg = isStructReturn ? "retBuf" : "(int8_t*)&result"; | ||
| w.WriteLine($" ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, {(slot > 0 ? "sizeof(transitionBlock.args)" : "0")}, {retBuffArg});"); | ||
| w.WriteLine(isVoid ? " return;" : " return result;"); |
| <ItemGroup> | ||
| <Compile Include="$(MSBuildProjectName).cs" /> | ||
| </ItemGroup> |
| stores.Add(t switch | ||
| { | ||
| _ when IsStructToken(t) => $" memcpy(&transitionBlock.args[{slot}], arg{i}, {SignatureMapper.GetStructSize(t)});", | ||
| "f" => $" *(float*)&transitionBlock.args[{slot}] = arg{i};", | ||
| "d" => $" *(double*)&transitionBlock.args[{slot}] = arg{i};", | ||
| _ => $" transitionBlock.args[{slot}] = (int64_t)arg{i};", | ||
| }); |
| var m2n = new InterpToNativeGenerator(log); | ||
| m2n.Generate(cookies, InterpToNativeOutputPath); | ||
| if (!string.IsNullOrEmpty(PortableEntryPointOutputPath)) | ||
| m2n.GeneratePortableEntryPoints(cookies, PortableEntryPointOutputPath); | ||
| void* thunk = LookupPortableEntryPointThunk(keyBuffer); | ||
| #ifdef _DEBUG | ||
| if (thunk == NULL) | ||
| { | ||
| LOG((LF_STUBS, LL_INFO100000, "WASM R2R to interpreter call missing for key: %s\n", keyBuffer)); | ||
| // Printed rather than only asserted: the caller's assert compiles out in release, where these | ||
| // gaps surface, and cannot carry the key. A miss leaves the entry point's table index 0 and | ||
| // traps later as "null function", far from here. | ||
| printf("WASM: no native-entry-point-to-interpreter thunk for signature key '%s'. Add it to pregeneratedInterpreterToNativeSignatures in ManagedToNativeGenerator and regenerate.\n", keyBuffer); | ||
| } |
jkotas
left a comment
There was a problem hiding this comment.
None of this should be needed.
I would like to understand why the system is not working as expected: #132965 (comment)
Summary
The native entry point of an interpreted method on wasm is a thunk (the
'I'thunks): the wasm funcref stored inPortableEntryPoint._pActualCode, which under the wasm managed calling convention captures the arguments and dispatches into the interpreter. These were hand-written C++ invm/wasm/helpers.cpp, so every call shape needed a hand-authored thunk — some missing, some corrupting memory. The WasmAppBuilder generator now emits that table for both browser and wasi:17hand-written entries become70generated, none left by hand, andhelpers.cpploses ~330 lines.The thunk is needed by any caller reaching an interpreted method through a materialized code pointer — a
calli/delegate/ldftn, a vtable slot,GetMultiCallableAddrOfCode, or an R2R call — including the interpreter itself, so it is required even with no R2R present. It is the managed transition; the C-ABI reverse (UnmanagedCallersOnly) thunk is a separate mechanism.The parameter convention
crossgen2 lays out the wasm parameters of both thunk directions as
reading the return buffer back from
retBufLocalIndex = 1 + (hasThis ? 1 : 0)(WasmNativeEntryPointToInterpreterThunkNode.EmitCode). Two consequences the generator honours:voidwith an explicitint8_t* retBuf. Returning the struct by value instead makes clang insert its own sret pointer at parameter 0, ahead ofcallersStackPointer. This applies in both directions.retBuffollowsthison an instance method rather than coming first.Neither is detectable at run time: the stack pointer, the return buffer,
thisand every by-reference argument are alli32, so a transposed order still passescall_indirecttype checking and the corruption surfaces far from its cause. Floating-point arguments are stored as their own bits rather than through an(int64_t)cast, which would convert the value.Changes
InterpToNativeGenerator— emitsg_wasmGeneratedPortableEntryPointThunks; struct returns take an explicitretBufin both directions;float/doublearguments are stored as themselves; a multi-slotl2value (Int128/UInt128/Decimal128, carried across twoi64parameters) expands into one parameter per slot.PortableEntryPointThunkSignature(new) — owns the parameter ordering; the emitter builds its declarations from it and the tests assert against it, so a test cannot pass while the emitted file disagrees.SignatureMapper— split into apartialclass so the pure token half links into the test project without dragging MSBuild into a compiler test assembly; the emitted tables are byte-identical across the split.CorInfoImpl.ReadyToRun) — roots the native-entry-point-to-interpreter thunk for every compiled method's signature, so a same-shaped interpreted method reached through a code pointer is covered even when no R2R call site shares the signature.helpers.cpp/CMakeLists.txt/callhelpers.hpp— no hand-written thunks remain; the generated table is wired unconditionally for both browser and wasi.'I'node isWasmNativeEntryPointToInterpreterThunkNodeand the VM API isEnsurePortableEntryPointIsCallableFromNativeCode, naming these for the native entry point they produce rather than for R2R, which is only one of their callers. The runtime lookup key ('I'+signature) is unchanged, and thePortableEntryPointdata structure and itsGetPortableEntryPointToInterpreterThunklookup keep their names.Build fixes (separable)
Two fixes let a Windows-host wasi build configure and compile; they are independent of the thunk work:
build-runtime.cmdandbuild-native.cmddid not treatwasias a cross-target, so they rancopy_version_files.cmd(which copies only*.h/*.rc) instead of the.ps1that also produces_version.c; CMake configure then failed withCannot find source file.configureplatform.cmakekeyedCLR_CMAKE_HOST_WASIoffCLR_CMAKE_TARGET_OSwhile every other platform block keys offCLR_CMAKE_HOST_OS, so a wasi cross-components build handedcl.execlang flags (D8021: invalid numeric argument '/Werror').Both are invisible on a Linux host, where the wrong flags land on clang and are accepted.
Validation
Runtime test (
src/tests/readytorun/wasm/WasmInterpreterTransitions) —[BypassReadyToRun]makes crossgen2 skip selected methods so one assembly exercises both directions across struct returns of 8/12/16 bytes (instance and static), 1- and 2-byte structs, struct arguments, mixedfloat/double/long,float/doublereturns, void, a delegate to an interpreted method, and an interpreted callback into compiled code. Every case asserts a value; callees areNoInliningso an inlined callee cannot skip the transition and pass vacuously.Unit tests — added to
WasmArgumentLayoutTests:GeneratedThunkMatchesLoweredWasmSignature— crossgen2 lowers a managed signature and the generator must produce the same wasm parameter arity and types for the resulting key.ThunkParametersFollowCrossgen2Order— pins parameter positions, separate from the above because a transposition of twoi32parameters leaves the type sequence identical.GenericContextArgumentFollowsTheReturnBuffer.Known gaps
libCorerun.js,host/assets.ts); wasi has no equivalent, so its table compiles and links but nothing reaches it yet.V2/V4(Vector256/Vector512) fail with a specific message naming what is missing, and a bareV(Vector128) falls to the generic invalid-token error. Nothing in the cookie list or in interop needs one today.WasmFuncTypewhen the parameters are alli32; the runtime test covers that gap.Why this is needed for pure interp
See #132965 alternative
On wasm a native function pointer is a typed index into the function table. Even a pure interpreter must (A) convert a managed method (delegate / ldftn) into such a native pointer, and (B) have that pointer be callable — from the interpreter and from native/host code — which requires a real, per-signature wasm function (the thunk) that marshals the typed args and re-enters the interpreter.
callhelpers.cpp:581:RuntimeHelpers.CallDefaultConstructordoes acalli ctorCode. In pure interp that helper runs interpreted, and itscallicalls through_pActualCode= the thunk.comutilnative.cpp:797:RunFinalizersinvokes the finalizer via its function pointer.methodtable.cpp:3580:CallClassConstructorinvokes the cctor via its function pointer.Dictionary.Addmaterializing an interpreted comparer's entry point.Note
This description was generated with GitHub Copilot.