Skip to content

[wasm] R2R crossgen fails on cross-bubble virtual calls: can't tokenize the declaring type embedded by CORINFO_HELP_VIRTUAL_FUNC_PTR #130585

Closed

Description

@AndyAyersMS

Description

When crossgen'ing an assembly for the browser/wasm target, compilation of a method that makes an
unresolvable virtual call to a method whose declaring type lives in another version bubble (e.g. a
System.Private.CoreLib type) fails in the ReadyToRun front-end with:

System.NotImplementedException: [S.P.CoreLib]System.Reflection.MemberInfo
   at ILCompiler.DependencyAnalysis.ReadyToRun.ModuleTokenResolver.GetModuleTokenForType(...)   ModuleTokenResolver.cs:92
   at ILCompiler.DependencyAnalysis.ReadyToRun.SignatureContext.GetTargetModule(...)
   at ILCompiler.DependencyAnalysis.ReadyToRun.TypeFixupSignature.GetData(...)
   at ILCompiler.DependencyAnalysis.ReadyToRun.ImportSectionNode.MaterializeSignature(...)

This is wasm-target-specific — the identical crossgen of the same assembly for x64 succeeds.

Repro

Found while crossgen'ing xunit.runner.utility.netcoreapp10.dll from a CoreCLR wasm test Core_Root.
Minimal single-method repro:

crossgen2 --targetarch:wasm --obj-format:wasm --targetos:browser \
  --jitpath:<clrjit_universal_wasm_x64.dll> \
  -r:<Core_Root>\*.dll \
  --singlemethodtypename:"Xunit.RunnerReporterUtility, xunit.runner.utility.netcoreapp10" \
  --singlemethodname:"GetAvailableRunnerReporters" \
  <Core_Root>\xunit.runner.utility.netcoreapp10.dll

GetAvailableRunnerReporters contains callvirt System.Reflection.MemberInfo::get_Name() on a
System.Type value that the JIT cannot devirtualize (the declaring method is not in the version bubble).

Root cause

The wasm JIT lowers the unresolved virtual call through the generic
CORINFO_HELP_VIRTUAL_FUNC_PTR helper, which embeds the declaring type's handle:

// src/coreclr/jit/importer.cpp (~2732)
// Wasm R2R cannot use the CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR fast path because it
// relies on DelayLoad_Helper_Obj dynamic-helper thunks, which are not implemented on wasm.
// Fall through to the runtime CORINFO_HELP_VIRTUAL_FUNC_PTR helper instead.
#if defined(FEATURE_READYTORUN) && !defined(TARGET_WASM)
    else if (IsAot()) { ... CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR ... }   // method-keyed fast path
#endif
    if (call == nullptr) {
        GenTree* exactTypeDesc   = impParentClassTokenToHandle(pResolvedToken);   // <-- embeds MemberInfo
        GenTree* exactMethodDesc = impTokenToHandle(pResolvedToken);
        call = gtNewVirtualFunctionLookupHelperCallNode(CORINFO_HELP_VIRTUAL_FUNC_PTR, TYP_I_IMPL,
                                                        thisPtr, exactMethodDesc, exactTypeDesc);
    }

On non-wasm R2R, IsAot() takes the CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR fast path, which is
keyed on the method (an entry-point fixup) and never needs the declaring type handle. Wasm is
explicitly excluded from that path (!defined(TARGET_WASM), because the required DelayLoad_Helper_Obj
thunks aren't implemented on wasm), so it falls through and embeds exactTypeDesc =
impParentClassTokenToHandle(...) = the declaring type, System.Reflection.MemberInfo.

That handle becomes a TypeHandle TypeFixupSignature. When crossgen2 materializes the import section,
ModuleTokenResolver.GetModuleTokenForType(MemberInfo) can't reverse-map MemberInfo to a module token
— the consuming assembly (xunit.runner.utility) has no TypeRef for MemberInfo (it references it
only transitively via the System.Type → get_Name MemberRef), and it isn't in the version bubble —
so it throws NotImplementedException.

JIT dump of the call site:

CALL help CORINFO_HELP_VIRTUAL_FUNC_PTR
  this:     LCL_VAR ref V15
  arg1:     IND(CNS_INT(h) 0x4204D8 class)   ← MemberInfo type handle (the fixup that fails)
  meth hnd: IND(CNS_INT(h) 0x4204E0 token)

Why x64 is unaffected

x64 R2R uses the method-keyed fast path (or virtual stub dispatch); the fixup references the (tokenizable)
get_Name MemberRef, never the declaring type MemberInfo, so there is nothing to fail on.

Impact

Any wasm R2R compilation of a method that makes a non-devirtualizable virtual/interface call to a method
whose declaring type is cross-version-bubble and not otherwise TypeRef'd by the consuming assembly will
fail crossgen. Calls to System.Object/MemberInfo/etc. virtual members on framework types are common,
so this is likely to be hit broadly once more wasm R2R assemblies are compiled.

Related

Same underlying gap (missing wasm DelayLoad_Helper_Obj / VirtualFunctionPointer R2R support) as the
runtime traps previously seen for cross-module virtual dispatch on wasm (e.g. the Runtime_45250 /
address_d cases). Here it surfaces at crossgen time as a token-resolution failure rather than a runtime
trap.

Note

This issue was drafted with the assistance of GitHub Copilot.

Activity

  1. dotnet-policy-service commented on Jul 15, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
    See info in area-owners.md if you want to be subscribed.

  2. removed
    untriagedNew issue has not been triaged by the area owner
    on Jul 16, 2026
  3. added this to the 11.0.0 milestone on Jul 16, 2026
  4. modified the milestones: 11.0.0, 12.0.0 on Aug 3, 2026
  5. lewing commented on Sep 20, 2026

    @lewing
    Member

    Thanks for the detailed report and root-cause analysis. The crossgen2 failure described here was fixed by #132528, which changes the ReadyToRun fixup to reference the declaring method when the declaring type cannot be represented by a module token.

    The temporary fallback workaround was tracked separately in #132412 and has been superseded by that fix.

    Closing this issue as already fixed.

    Note

    This response was generated with assistance from GitHub Copilot.

  6. locked and limited conversation to collaborators on Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

  • Status
    No status

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions