Skip to content

[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

Description

@pavelsavara

Tested on top of #134092

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:

pep=0x60d494 hasNative=1 code=0x21b7 key=MvTiip a=2
reqInst=0 shared=1 codeReqInst=0 codeShared=1 codeSame=1
cls=System.Collections.Generic.Dictionary`2 n=.ctor

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.:

sig=vTiip reqInst=False ... Dictionary`2<System.Guid,System.__Canon>..ctor(int32,IEqualityComparer`1<Guid>)
sig=vTiip reqInst=False ... Dictionary`2<native int,System.__Canon>..ctor(int32,IEqualityComparer`1<native int>)

The fully-shared Dictionary<__Canon,__Canon> form is compiled only for the 0/1/4-arg ctors — not the 2-arg (int, IEqualityComparer) overload:

sig=vTp     ... Dictionary`2<__Canon,__Canon>..ctor()
sig=vTip    ... (various 1-arg forms)
sig=vTiiiip ... ConcurrentDictionary`2<__Canon,__Canon>..ctor(int32,int32,bool,IEqualityComparer`1<__Canon>)

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

  1. 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).
  2. Don't report a non-callable native entry. Ensure PortableEntryPoint::HasNativeEntryPoint / MethodDesc::GetMultiCallableAddrOfCode do not report a callable native entry for a method that lacks a real, signature-matching R2R body, so the interpreter falls back correctly. (Overlaps the EnsurePortableEntryPointIsCallableFromR2R analysis in [wasm][R2R] function signature mismatch on the first (cold) R2R call to a method whose native code is not yet on its portable entry point #130634.)
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

arch-wasmWebAssembly architecturearea-VM-coreclrdisabled-testThe test is disabled in source code against the issue

Type

No type

Projects

  • Status
    No status

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions