dotnet / dotnet/runtime

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

Open
#130,585 1 comment 0 reactions 1 assignee Claimed by @lewing View on GitHub
arch-wasm area-ReadyToRun
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### 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: \
-r:\*.dll \
--singlemethodtypename:"Xunit.RunnerReporterUtility, xunit.runner.utility.netcoreapp10" \
--singlemethodname:"GetAvailableRunnerReporters" \
\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**:

```c
// 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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.