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
On browser-WASM CoreCLR ReadyToRun, a cross-module call to a closed generic value-type method can remain bound to an interpreter thunk even when the exact specialized R2R body exists in the target assembly.
The concrete reproduction is System.MemoryExtensions.Contains<int>(ReadOnlySpan<int>, int). R2R callers repeatedly enter the interpreter and execute the complete scan there instead of calling the compiled specialization and its existing SIMD SpanHelpers leaf.
This is not merely a missing profile/root. Explicitly rooting Contains<int> successfully emits its R2R body, but the caller still cannot bind to it because the callable import ABI has one more hidden generic-context parameter than the specialized body.
Configuration
The original signal came from browser-WASM CoreCLR microbenchmarks in build 20260923.2, runtime SHA 806095e5, performance SHA 99531dce, V8 15.3.76, Ubuntu 22.04 x64 Viper workers.
Controlled reproduction used the same runtime cohort in a minimal browser app. A supported deterministic MIBC was generated with dotnet-pgo create-mibc-from-method-list and consumed through @(PublishReadyToRunPgoFiles).
Regression?
This is best treated as an incomplete WASM R2R generic-call mechanism rather than a temporal regression from a known-good implementation.
Controlled minimal app, 512 Contains calls per operation:
Case
Interpreter
R2R
False Span, length 512
17.6 ms
170.4 ms
False Array, length 512
17.6 ms
168.9 ms
True Span, length 512
9.0 ms
86.2 ms
The slowdown scales with elements examined: length 0/1 is approximately equal or faster under R2R, while length 8 and 512 diverge sharply. Fixed transition overhead is therefore not the main cost; the full scan runs interpreted.
Rooting MemoryExtensions.Contains<int> does not improve the result. With the rooted MIBC, representative results remained about 6-6.5x slower under R2R, and the compiled SIMD leaf still was not reached.
Analysis
The linker rewrites both the Array and Span benchmarks to the same System.MemoryExtensions.Contains<T> MethodSpec, so all three cases share the hot path.
Without the explicit profile, the CoreLib R2R image contains no MemoryExtensions.Contains<int> body. It does contain the optimized leaf SpanHelpers.NonPackedIndexOfValueType<int,DontNegate<int>>, whose WASM disassembly has the expected SIMD loop.
Live NESM/Chrome CDP debugging of the R2R caller shows:
The first hot call enters the delay-load helper.
The patched subsequent call enters CoreLib WasmR2RToInterpreterThunk(iiiip).
This happens once per lookup: 512 R2R→interpreter transitions per benchmark operation.
Breakpoints on the compiled specialized Contains<int> body and compiled SIMD SpanHelpers leaf do not fire.
A supported root experiment proves this is deeper than missing rooting:
The published CoreLib gains the exact managed R2R body, independently identified by both its name section and R2R MethodDef index.
The exact compiled specialization has WASM type:
(i32, i32, i32, i32) -> i32
The closed generic MethodSpec import and its working interpreter thunk have WASM type:
(i32, i32, i32, i32, i32) -> i32
The extra argument is the hidden generic-instantiation/MethodDesc context expected by the generic callable ABI. The exact value-type specialization does not need this context. Both shapes already contain the normal WASM managed-call plumbing; this is an additional generic-context parameter.
Because a WebAssembly table entry must have the exact expected function type, the four-argument specialized body cannot be installed in the five-argument import slot. No R2R-callable instantiating adapter is emitted or selected, so the runtime retains the correctly typed interpreter thunk despite the R2R body existing.
The likely fix is to emit/select an R2R instantiating adapter that:
Accepts the generic MethodSpec callable ABI, including the hidden context.
Drops or translates the context for an exact specialization that does not require it.
Tail-calls the specialized R2R body using its actual ABI.
Publishes that adapter in the portable entrypoint/import cell instead of the interpreter thunk.
Alternatively, the specialized body could be emitted with the externally callable generic-context ABI, if that is compatible with existing ReadyToRun method identity and sharing rules.
A regression test should:
Explicitly root MemoryExtensions.Contains<int>.
Verify the specialized body and SIMD leaf exist.
Verify the app MethodSpec import patches to the specialized body or an instantiating adapter, not WasmR2RToInterpreterThunk.
Verify the SIMD leaf executes.
Cover Span and linker-rewritten Array forms with true/false and length 0/8/512 cases.
Description
On browser-WASM CoreCLR ReadyToRun, a cross-module call to a closed generic value-type method can remain bound to an interpreter thunk even when the exact specialized R2R body exists in the target assembly.
The concrete reproduction is
System.MemoryExtensions.Contains<int>(ReadOnlySpan<int>, int). R2R callers repeatedly enter the interpreter and execute the complete scan there instead of calling the compiled specialization and its existing SIMDSpanHelpersleaf.This is not merely a missing profile/root. Explicitly rooting
Contains<int>successfully emits its R2R body, but the caller still cannot bind to it because the callable import ABI has one more hidden generic-context parameter than the specialized body.Configuration
The original signal came from browser-WASM CoreCLR microbenchmarks in build
20260923.2, runtime SHA806095e5, performance SHA99531dce, V815.3.76, Ubuntu 22.04 x64 Viper workers.Controlled reproduction used the same runtime cohort in a minimal browser app. A supported deterministic MIBC was generated with
dotnet-pgo create-mibc-from-method-listand consumed through@(PublishReadyToRunPgoFiles).Regression?
This is best treated as an incomplete WASM R2R generic-call mechanism rather than a temporal regression from a known-good implementation.
Data
ADX build
20260923.2, same-build CoreCLR interpreter/R2R:ContainsFalse<Int32>.Span(Size: 512)ContainsFalse<Int32>.Array(Size: 512)ContainsTrue<Int32>.Span(Size: 512)Controlled minimal app, 512
Containscalls per operation:The slowdown scales with elements examined: length 0/1 is approximately equal or faster under R2R, while length 8 and 512 diverge sharply. Fixed transition overhead is therefore not the main cost; the full scan runs interpreted.
Rooting
MemoryExtensions.Contains<int>does not improve the result. With the rooted MIBC, representative results remained about 6-6.5x slower under R2R, and the compiled SIMD leaf still was not reached.Analysis
The linker rewrites both the Array and Span benchmarks to the same
System.MemoryExtensions.Contains<T>MethodSpec, so all three cases share the hot path.Without the explicit profile, the CoreLib R2R image contains no
MemoryExtensions.Contains<int>body. It does contain the optimized leafSpanHelpers.NonPackedIndexOfValueType<int,DontNegate<int>>, whose WASM disassembly has the expected SIMD loop.Live NESM/Chrome CDP debugging of the R2R caller shows:
WasmR2RToInterpreterThunk(iiiip).Contains<int>body and compiled SIMDSpanHelpersleaf do not fire.A supported root experiment proves this is deeper than missing rooting:
System.MemoryExtensions.Contains<int>(ReadOnlySpan<int>, int).The extra argument is the hidden generic-instantiation/
MethodDesccontext expected by the generic callable ABI. The exact value-type specialization does not need this context. Both shapes already contain the normal WASM managed-call plumbing; this is an additional generic-context parameter.Because a WebAssembly table entry must have the exact expected function type, the four-argument specialized body cannot be installed in the five-argument import slot. No R2R-callable instantiating adapter is emitted or selected, so the runtime retains the correctly typed interpreter thunk despite the R2R body existing.
The likely fix is to emit/select an R2R instantiating adapter that:
Alternatively, the specialized body could be emitted with the externally callable generic-context ABI, if that is compatible with existing ReadyToRun method identity and sharing rules.
A regression test should:
MemoryExtensions.Contains<int>.WasmR2RToInterpreterThunk.Related issues/PRs:
Note
This issue was prepared with GitHub Copilot assistance.