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

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

Metadata

Metadata

Assignees

Type

No type

Projects

Status
No status

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions