Skip to content

[mono][wasm] Box gsharedvt Nullable<T> via a wrapper-free runtime helper - #132153

Merged
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:mising_gsharedvt_out_sig2
Aug 14, 2026
Merged

[mono][wasm] Box gsharedvt Nullable<T> via a wrapper-free runtime helper#132153
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:mising_gsharedvt_out_sig2

Conversation

@pavelsavara

@pavelsavara pavelsavara commented Aug 11, 2026

Copy link
Copy Markdown
Member

What

Under minimal gsharedvt (llvmonly / WASM), boxing a gsharedvt Nullable<T> resolves a per-T Nullable<T>.Box out-sig wrapper (MONO_RGCTX_INFO_NULLABLE_CLASS_BOX). When corelib is AOT'd but the consuming assembly runs interpreted (the WasmTestOnChrome-MONO-ST configuration), that per-T wrapper is absent and the boxing call traps with function signature mismatch — e.g. boxing a Nullable<T> element through the non-generic IEnumerator.Current (new Queue(new List<Int128?> { ... })).

Fix

Emit a call to a new non-generic mono_helper_box_nullable icall — a thin wrapper over the existing mono_nullable_box, the same helper the interpreter uses (MINT_BOX_NULLABLE_PTR) — passing the value by address and the concrete Nullable<T> class from the rgctx. No per-T box wrapper is needed, so:

  • the trap cannot occur, for corelib and value types defined in user assemblies, and
  • the containing method stays fully AOT-compiled (nothing is dropped from the AOT image).

Only the llvm_only nullable-box paths in mini_emit_box are changed; other backends are untouched.

Change

  • src/mono/mono/mini/jit-icalls.c / jit-icalls.h, src/mono/mono/metadata/jit-icall-reg.h, src/mono/mono/mini/mini-runtime.c: add and register the mono_helper_box_nullable(vbuf, klass) JIT icall.
  • src/mono/mono/mini/method-to-ir.c: new mini_emit_nullable_box_helper; both llvm_only NULLABLE_CLASS_BOX sites now route through it.
  • src/tests/Loader/classloader/generics/regressions/131537/: regression test that boxes Nullable<T> through Queue(ICollection). _AOT_InternalForceInterpretAssemblies reproduces the AOT-corelib + interpreted-assembly configuration on wasm-AOT lanes; it passes trivially elsewhere.

Fixes #131537.

Background

This supersedes two earlier approaches explored on this issue:

  • The fixed-list rooting in [wasm][mono][AOT] Emit Nullable<T> gsharedvt box/unbox wrappers for corelib value types #131946 — only covered a hardcoded set of corelib value types, not Nullable<UserStruct>.
  • @BrzVlad's suggestion to fail gsharedvt for box (mirroring unbox.any) — that cleared the trap but un-AOT'd whole methods, and CI surfaced a null function regression when an AOT'd caller reached a now-unemitted method (observed in the Stream.CopyToAsync / BrowserHttp async teardown on the MONO-ST Intrinsics lane).

Routing the nullable box through the wrapper-free icall keeps every method AOT-compiled, so it fixes the trap for all value types without that regression.

Testing

Validated on Release Mono WASM AOT (AOT'd corelib + force-interpreted app — confirmed System.Private.CoreLib is in the AOT'd set while the app assembly is interpreted): boxing Int128? / UInt128? / Half? / decimal? / Guid? / DateTime? / DateTimeOffset? / TimeSpan? through new Queue(List<T?>) round-trips cleanly — no function signature mismatch, no null function. Full library-test coverage (Number_AsCollectionElement_RoundTrip on WasmTestOnChrome-MONO) runs in CI.

Code size

Runtime binary: negligible (one small icall + a table entry). AOT'd app: roughly neutral — the per-T object(Nullable<T>) box out-sig wrappers are no longer needed for the gsharedvt path, offsetting the small per-call-site change, and it is much lighter than the rooting approach.

Note

This pull request was prepared with GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a Mono WASM minimal-gsharedvt/AOT failure mode where boxing a gsharedvt variable valuetype (e.g., Nullable<T>) can require per-T wrappers that aren’t available when corelib is AOT’d but the consuming assembly is interpreted, leading to WebAssembly “function signature mismatch” traps. The change mirrors existing unbox.any behavior by bailing out of gsharedvt sharing for the problematic box case, and adds a regression test intended to reproduce the AOT-corelib + interpreted-app configuration.

Changes:

  • Make MONO_CEE_BOX in method-to-ir.c trigger GSHAREDVT_FAILURE for gsharedvt variable-klass boxing under gsharedvt_min (mirrors MONO_CEE_UNBOX_ANY).
  • Add a new loader/classloader regression test project and test case for boxing Nullable<T> via non-generic IEnumerator.Current.
  • Configure the regression test project to force interpretation on wasm-AOT lanes via _AOT_InternalForceInterpretAssemblies.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/mono/mono/mini/method-to-ir.c Adds minimal-gsharedvt guard for box on gsharedvt variable klass to force per-instantiation resolution.
src/tests/Loader/classloader/generics/regressions/131537/test131537.csproj New regression test project with wasm-AOT interpreter-forcing configuration.
src/tests/Loader/classloader/generics/regressions/131537/test131537.cs New regression test that boxes Nullable<T> elements through Queue(ICollection) / non-generic enumeration.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/tests/Loader/classloader/generics/regressions/131537/test131537.cs:48

  • The test checks per-element equality inside the foreach loop, but it never asserts that the loop actually iterated over all expected elements. If enumeration stopped early, this could still pass. Add a final assertion on the number of items iterated.
        int i = 0;
        foreach (object boxed in queue)
        {
            Assert.Equal(source[i], (T)boxed);
            i++;

@pavelsavara

Copy link
Copy Markdown
Member Author

/azp run runtime-wasm

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Under minimal gsharedvt (llvmonly/WASM), boxing a gsharedvt Nullable<T> resolved a per-T Nullable<T>.Box out-sig wrapper via MONO_RGCTX_INFO_NULLABLE_CLASS_BOX. When corelib is AOT'd but the consuming assembly runs interpreted, that per-T wrapper is absent and the boxing call traps with 'function signature mismatch' (e.g. boxing a Nullable<T> element through the non-generic IEnumerator.Current).

Emit a call to a new non-generic mono_helper_box_nullable icall (a thin wrapper over the existing mono_nullable_box, the same helper the interpreter uses) passing the value by address and the concrete class from the rgctx. No per-T box wrapper is needed, so the trap cannot occur and the containing method stays fully AOT-compiled. Only the llvm_only nullable box paths are changed; other backends are untouched.

Adds a regression test under Loader/classloader/generics/regressions/131537 that boxes Nullable<T> through Queue(ICollection). Fixes dotnet#131537.
Copilot AI review requested due to automatic review settings August 13, 2026 12:09
@pavelsavara
pavelsavara force-pushed the mising_gsharedvt_out_sig2 branch from a411ea7 to 68bd702 Compare August 13, 2026 12:09
@pavelsavara pavelsavara changed the title [mono][wasm] Fail gsharedvt for box of a variable valuetype under minimal gsharedvt [mono][wasm] Box gsharedvt Nullable&lt;T&gt; via a wrapper-free runtime helper Aug 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/tests/Loader/classloader/generics/regressions/131537/test131537.cs:33

  • This test is unlikely to execute under the src/tests harness as-written: Loader/classloader tests in this area use the TestEntryPoint() + return-100 convention (see e.g. src/tests/Loader/classloader/generics/GenericMethods/method001.cs:33-52). With a void test method and only xUnit assertions, the regression may compile but never run on the intended lanes. Consider switching to a TestEntryPoint() that returns 100/101, and include at least one Nullable<T> where T is a struct defined in this test assembly (plus a null element) to cover the interpreted-assembly value-type case and HasValue=false boxing.
    [Fact]
    public static void BoxNullableThroughNonGenericEnumerator()
    {
        RoundTrip(new List<Int128?> { 1, 2, 3 });
        RoundTrip(new List<UInt128?> { 1, 2, 3 });

@pavelsavara

Copy link
Copy Markdown
Member Author

/azp run runtime-wasm

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@pavelsavara pavelsavara changed the title [mono][wasm] Box gsharedvt Nullable&lt;T&gt; via a wrapper-free runtime helper [mono][wasm] Box gsharedvt Nullable<T> via a wrapper-free runtime helper Aug 13, 2026
@pavelsavara
pavelsavara requested a review from BrzVlad August 13, 2026 16:44
@pavelsavara pavelsavara added this to the 11.0.0 milestone Aug 13, 2026
@pavelsavara
pavelsavara marked this pull request as ready for review August 13, 2026 16:44
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI review requested due to automatic review settings August 13, 2026 17:01
BrzVlad

This comment was marked as duplicate.

@BrzVlad BrzVlad left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/mono/mono/mini/method-to-ir.c:3419

  • mini_emit_nullable_box_helper manually promotes a vreg to a local and takes its address. This duplicates the existing EMIT_NEW_VARLOADA_VREG helper (already used elsewhere in method-to-ir.c) and makes the code harder to maintain.
	MonoInst *iargs [2], *addr, *var;

	var = get_vreg_to_inst (cfg, val->dreg);
	if (!var)
		var = mono_compile_create_var_for_vreg (cfg, m_class_get_byval_arg (klass), OP_LOCAL, val->dreg);

@pavelsavara

Copy link
Copy Markdown
Member Author

/azp run runtime-wasm

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@pavelsavara

Copy link
Copy Markdown
Member Author

/ba-g unrelated failures

@pavelsavara
pavelsavara merged commit be0ab63 into dotnet:main Aug 14, 2026
137 of 142 checks passed
@pavelsavara
pavelsavara deleted the mising_gsharedvt_out_sig2 branch August 14, 2026 08:28
pavelsavara added a commit to pavelsavara/runtime that referenced this pull request Aug 14, 2026
… catches regression

Drop this commit to restore green. With the mono fix reverted, the AOT+Chrome
lane should show test131537 failing with 'function signature mismatch', proving
the lane exercises the dotnet#131537 repro. Test files are intentionally kept.
@dotnet-milestone-bot dotnet-milestone-bot Bot modified the milestones: 11.0.0, 12.0-preview1 Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-Codegen-AOT-mono

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[wasm][mono][AOT] Number_AsCollectionElement_RoundTrip crashes with unction signature mismatch boxing Nullable<Int128>

3 participants