[mono][wasm] Box gsharedvt Nullable<T> via a wrapper-free runtime helper - #132153
Conversation
|
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. |
There was a problem hiding this comment.
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_BOXinmethod-to-ir.ctriggerGSHAREDVT_FAILUREfor gsharedvt variable-klass boxing undergsharedvt_min(mirrorsMONO_CEE_UNBOX_ANY). - Add a new loader/classloader regression test project and test case for boxing
Nullable<T>via non-genericIEnumerator.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. |
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
There was a problem hiding this comment.
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++;
|
/azp run runtime-wasm |
|
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.
a411ea7 to
68bd702
Compare
There was a problem hiding this comment.
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/testsharness as-written: Loader/classloader tests in this area use theTestEntryPoint()+ return-100 convention (see e.g.src/tests/Loader/classloader/generics/GenericMethods/method001.cs:33-52). With avoidtest method and only xUnit assertions, the regression may compile but never run on the intended lanes. Consider switching to aTestEntryPoint()that returns 100/101, and include at least oneNullable<T>whereTis a struct defined in this test assembly (plus anullelement) to cover the interpreted-assembly value-type case andHasValue=falseboxing.
[Fact]
public static void BoxNullableThroughNonGenericEnumerator()
{
RoundTrip(new List<Int128?> { 1, 2, 3 });
RoundTrip(new List<UInt128?> { 1, 2, 3 });
|
/azp run runtime-wasm |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
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. |
There was a problem hiding this comment.
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_helpermanually promotes a vreg to a local and takes its address. This duplicates the existingEMIT_NEW_VARLOADA_VREGhelper (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);
|
/azp run runtime-wasm |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/ba-g unrelated failures |
… 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.
What
Under minimal gsharedvt (llvmonly / WASM), boxing a gsharedvt
Nullable<T>resolves a per-TNullable<T>.Boxout-sig wrapper (MONO_RGCTX_INFO_NULLABLE_CLASS_BOX). When corelib is AOT'd but the consuming assembly runs interpreted (theWasmTestOnChrome-MONO-STconfiguration), that per-Twrapper is absent and the boxing call traps withfunction signature mismatch— e.g. boxing aNullable<T>element through the non-genericIEnumerator.Current(new Queue(new List<Int128?> { ... })).Fix
Emit a call to a new non-generic
mono_helper_box_nullableicall — a thin wrapper over the existingmono_nullable_box, the same helper the interpreter uses (MINT_BOX_NULLABLE_PTR) — passing the value by address and the concreteNullable<T>class from the rgctx. No per-Tbox wrapper is needed, so:Only the
llvm_onlynullable-box paths inmini_emit_boxare 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 themono_helper_box_nullable(vbuf, klass)JIT icall.src/mono/mono/mini/method-to-ir.c: newmini_emit_nullable_box_helper; bothllvm_onlyNULLABLE_CLASS_BOXsites now route through it.src/tests/Loader/classloader/generics/regressions/131537/: regression test that boxesNullable<T>throughQueue(ICollection)._AOT_InternalForceInterpretAssembliesreproduces 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:
Nullable<UserStruct>.box(mirroringunbox.any) — that cleared the trap but un-AOT'd whole methods, and CI surfaced anull functionregression when an AOT'd caller reached a now-unemitted method (observed in theStream.CopyToAsync/BrowserHttpasync teardown on theMONO-STIntrinsics 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.CoreLibis in the AOT'd set while the app assembly is interpreted): boxingInt128?/UInt128?/Half?/decimal?/Guid?/DateTime?/DateTimeOffset?/TimeSpan?throughnew Queue(List<T?>)round-trips cleanly — nofunction signature mismatch, nonull function. Full library-test coverage (Number_AsCollectionElement_RoundTriponWasmTestOnChrome-MONO) runs in CI.Code size
Runtime binary: negligible (one small icall + a table entry). AOT'd app: roughly neutral — the per-
Tobject(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.