You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[wasm][R2R] Interpreter→R2R call to a shared-generic method with no R2R body traps with "null function or function signature mismatch" at startup #134200
On browser-wasm CoreCLR with ReadyToRun (full/eager R2R), the runtime aborts at startup (before Main) with:
RuntimeError: null function or function signature mismatch
The trap is an interpreter→R2R call_indirect type mismatch when interpreted code calls a shared-generic method whose R2R body was not emitted by crossgen2. The method's portable entry point (PEP) reports native code, and the interpreter takes the interp→R2R path, but the target function's wasm type does not match the call_indirect type the interp→R2R thunk uses.
Trap stack (named frames, built with WasmNativeStrip=false)
at WasmInterpreterToR2RThunk(vTiip) wasm-function[3857] <- TRAPS (call_indirect to the target)
at <interpreted> wasm-function[1410]/[8698]
at WasmR2RToInterpreterThunk(vTiip) wasm-function[3661]
at WasmDelayLoadHelper ... DelayLoad_MethodCall(Sig:vTiip) wasm-function[3500]
at S_P_CoreLib_System_AppContext__Setup wasm-function[70]
AppContext.Setup (R2R) → an interpreted method → an interpreter→R2R call that traps.
Failing method
Instrumenting InvokeManagedMethod (src/coreclr/vm/wasm/helpers.cpp) right before InvokeCalliStub at the trapping call:
The target is the canonical System.Collections.Generic.Dictionary<__Canon,__Canon>.ctor(int capacity, IEqualityComparer<TKey> comparer):
reqInst=0 — the method needs no generic-context / inst arg (context comes from this). The signature key is MvTiip = void, this, two args, PEP — no genctx.
shared=1, codeSame=1 — it is the canonical shared method and the resolved code maps back to the same MethodDesc.
hasNative=1 — the PEP reports a native entry, so the interpreter takes InvokeManagedMethod (there is no interpreter code for it), but the call_indirect(vTiip) to code=0x21b7 traps.
This rules out a generic-context / exact-vs-canonical signature disagreement — every managed encoder (crossgen method-side thunk, runtime GetSignatureKey) computes vTiip with no genctx.
crossgen2 evidence: the method has no R2R body
Instrumenting the interp→R2R thunk emission in CorInfoImpl.ReadyToRun.cs and re-running the whole-CoreLib crossgen shows the 2-arg (int, IEqualityComparer) Dictionary ctor is emitted only for value-type-keyed instantiations (all reqInst=False, sig=vTiip), e.g.:
So at runtime Dictionary<__Canon,__Canon>.ctor(int, IEqualityComparer) has no R2R body, yet its PEP reports native code and the interp→R2R thunk (vTiip) call_indirects to a target whose wasm type doesn't match → trap.
Relationship to existing issues
This appears to be the same failure family as #130634 (function signature mismatch on a cold R2R call to a method whose native code isn't wired onto its PEP), but observed from the interpreter→R2R direction and for a method with no R2R body at all (a crossgen2 compilation gap), rather than a cold-ordering race on a method that does have R2R code. Likely also related: #129622, #133307, #134144, #133466. Tracking: #130524.
Repro
Build/run a browser-wasm CoreCLR console app with PublishReadyToRun=true on a configuration with full/eager R2R coverage. It aborts at startup, before Main, in AppContext.Setup's call chain — the app's own code is irrelevant; it is a startup-path trap. (Investigated on a branch that enables eager/full R2R for browser-wasm CoreCLR; the fuller R2R coverage is what surfaces the interp→R2R transition into Dictionary.ctor.)
Fix proposals
Robust interp→R2R dispatch (primary). When a method's R2R body is absent, or its PEP is not a matching-signature native function, dispatch it through the interpreter (or an IL adapter) instead of a blind call_indirect that traps. [wasm][R2R] Fix closed static delegate struct returns #134108 added exactly this "target-readiness check + IL-adapter fallback" for closed static delegates; extend the same machinery to the direct interp→R2R path (InvokeManagedMethod / GetCookieForManagedMethod in src/coreclr/vm/wasm/helpers.cpp; the CALL_INTERP_METHOD gate in src/coreclr/vm/interpexec.cpp already does a HasNativeEntryPoint check for the CALLI path).
Close the crossgen2 compilation gap. Have crossgen2 emit the fully-shared Dictionary<__Canon,__Canon>.ctor(int, IEqualityComparer) (and, more generally, startup-reachable shared-generic methods) so that a real vTiip body exists.
Note
This issue was investigated and drafted with GitHub Copilot (AI-generated) and reviewed by the author.
Tested on top of #134092
On browser-wasm CoreCLR with ReadyToRun (full/eager R2R), the runtime aborts at startup (before
Main) with:The trap is an interpreter→R2R
call_indirecttype mismatch when interpreted code calls a shared-generic method whose R2R body was not emitted by crossgen2. The method's portable entry point (PEP) reports native code, and the interpreter takes the interp→R2R path, but the target function's wasm type does not match thecall_indirecttype the interp→R2R thunk uses.Trap stack (named frames, built with
WasmNativeStrip=false)AppContext.Setup(R2R) → an interpreted method → an interpreter→R2R call that traps.Failing method
Instrumenting
InvokeManagedMethod(src/coreclr/vm/wasm/helpers.cpp) right beforeInvokeCalliStubat the trapping call:The target is the canonical
System.Collections.Generic.Dictionary<__Canon,__Canon>.ctor(int capacity, IEqualityComparer<TKey> comparer):reqInst=0— the method needs no generic-context / inst arg (context comes fromthis). The signature key isMvTiip=void,this, two args, PEP — no genctx.shared=1,codeSame=1— it is the canonical shared method and the resolved code maps back to the sameMethodDesc.hasNative=1— the PEP reports a native entry, so the interpreter takesInvokeManagedMethod(there is no interpreter code for it), but thecall_indirect(vTiip)tocode=0x21b7traps.This rules out a generic-context / exact-vs-canonical signature disagreement — every managed encoder (crossgen method-side thunk, runtime
GetSignatureKey) computesvTiipwith no genctx.crossgen2 evidence: the method has no R2R body
Instrumenting the interp→R2R thunk emission in
CorInfoImpl.ReadyToRun.csand re-running the whole-CoreLib crossgen shows the 2-arg(int, IEqualityComparer)Dictionary ctor is emitted only for value-type-keyed instantiations (allreqInst=False,sig=vTiip), e.g.:The fully-shared
Dictionary<__Canon,__Canon>form is compiled only for the 0/1/4-arg ctors — not the 2-arg(int, IEqualityComparer)overload:So at runtime
Dictionary<__Canon,__Canon>.ctor(int, IEqualityComparer)has no R2R body, yet its PEP reports native code and the interp→R2R thunk (vTiip)call_indirects to a target whose wasm type doesn't match → trap.Relationship to existing issues
This appears to be the same failure family as #130634 (
function signature mismatchon a cold R2R call to a method whose native code isn't wired onto its PEP), but observed from the interpreter→R2R direction and for a method with no R2R body at all (a crossgen2 compilation gap), rather than a cold-ordering race on a method that does have R2R code. Likely also related: #129622, #133307, #134144, #133466. Tracking: #130524.Repro
Build/run a browser-wasm CoreCLR console app with
PublishReadyToRun=trueon a configuration with full/eager R2R coverage. It aborts at startup, beforeMain, inAppContext.Setup's call chain — the app's own code is irrelevant; it is a startup-path trap. (Investigated on a branch that enables eager/full R2R for browser-wasm CoreCLR; the fuller R2R coverage is what surfaces the interp→R2R transition intoDictionary.ctor.)Fix proposals
call_indirectthat traps. [wasm][R2R] Fix closed static delegate struct returns #134108 added exactly this "target-readiness check + IL-adapter fallback" for closed static delegates; extend the same machinery to the direct interp→R2R path (InvokeManagedMethod/GetCookieForManagedMethodinsrc/coreclr/vm/wasm/helpers.cpp; theCALL_INTERP_METHODgate insrc/coreclr/vm/interpexec.cppalready does aHasNativeEntryPointcheck for the CALLI path).PortableEntryPoint::HasNativeEntryPoint/MethodDesc::GetMultiCallableAddrOfCodedo not report a callable native entry for a method that lacks a real, signature-matching R2R body, so the interpreter falls back correctly. (Overlaps theEnsurePortableEntryPointIsCallableFromR2Ranalysis in [wasm][R2R]function signature mismatchon the first (cold) R2R call to a method whose native code is not yet on its portable entry point #130634.)Dictionary<__Canon,__Canon>.ctor(int, IEqualityComparer)(and, more generally, startup-reachable shared-generic methods) so that a realvTiipbody exists.Note
This issue was investigated and drafted with GitHub Copilot (AI-generated) and reviewed by the author.