Skip to content

[mono][wasm] Fix delegate over static abstract interface method under gsharedvt - #131880

Merged
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:pngdecoder
Aug 6, 2026
Merged

[mono][wasm] Fix delegate over static abstract interface method under gsharedvt#131880
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:pngdecoder

Conversation

@pavelsavara

Copy link
Copy Markdown
Member

Fixes #130545

Problem

Under Mono WebAssembly AOT (llvmonly), reaching a static field initialized from a static-abstract interface method through a value-type generic instantiation (gsharedvt) traps with:

MONO_WASM: function signature mismatch

The original report is the SixLabors.ImageSharp PixelOperations<TPixel> shape, where PixelOperations<TPixel>..cctor builds a Lazy<> from the static-abstract method group TPixel.CreatePixelOperations. It is AOT-only — the same code runs fine interpreted, and disabling RunAOTCompilation avoids it.

Root cause

In mono_method_to_ir, the CEE_LDFTN + delegate-newobj peephole that normally routes delegate creation through the optimized llvmonly path (mini_llvmonly_init_delegate) is guarded by !gshared_static_virtual. For a static-abstract method resolved via gsharedvt, that guard skips the fast path and falls back to a generic newobj Func<T>::.ctor call.

Because mono_method_needs_static_rgctx_invoke(Func<T>::.ctor) returns true, the call_indirect in the containing cctor is emitted with an extra generic-sharing argument (a 4-parameter signature), but the real target is the 3-parameter runtime delegate constructor (ves_icall_mono_delegate_ctor). A 4-argument call to a 3-parameter function is a hard wasm signature mismatch, so the cctor traps. This is the only case that uses the generic newobj delegate-ctor fallback under llvmonly, which is why only this shape reproduces.

Fix

Handle gshared_static_virtual in the llvmonly delegate-ctor path: resolve the target via MONO_RGCTX_INFO_VIRT_METHOD, set del->method, and create the delegate directly through mini_llvmonly_init_delegate (which derives method_ptr/invoke_impl correctly), instead of the generic newobj constructor call. The change is gated on cfg->llvm_only.

Test

Adds a positive regression [Fact] under Loader/classloader/StaticVirtualMethods/Regression that mirrors the PixelOperations<TPixel> shape and reaches it through MakeGenericMethod to stay on the shared gsharedvt path.

Verified against the Mono wasm AOT browser sample:

Runtime Result
Without the fix function signature mismatch, WASM EXIT 1
With the fix correct decode result, WASM EXIT 0

The reproduction is AOT-heuristic sensitive (documented in the test): reducing the surrounding types further lets the JIT fully specialize the generic method and masks the bug.

Note

This PR description was drafted with GitHub Copilot.

… gsharedvt

For a static abstract interface method resolved through gsharedvt, ldftn + delegate creation fell back to a generic newobj ctor-call. Under llvmonly this called the 3-parameter runtime delegate constructor with an extra generic-sharing argument, producing a wasm 'function signature mismatch' trap in the containing cctor. Create the delegate directly via mini_llvmonly_init_delegate instead. Fixes dotnet#130545
Copilot AI review requested due to automatic review settings August 5, 2026 15:10
@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 adjusts Mono’s ldftn + delegate construction peephole under llvmonly to correctly handle delegates created over static abstract interface methods resolved via gsharedvt (runtime rgctx lookup), and adds a regression test covering the reported shape.

Changes:

  • Add an llvmonly-only delegate construction fast-path for gshared_static_virtual that resolves the implementation via MONO_RGCTX_INFO_VIRT_METHOD, sets del->method, and initializes the delegate via mini_llvmonly_init_delegate.
  • Add a new loader regression test project and test that mirrors the static-abstract + Lazy<> delegate creation pattern and reaches the generic instantiation via reflection.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/mono/mono/mini/method-to-ir.c Adds an llvmonly gshared-static-virtual delegate creation path to avoid the generic newobj fallback.
src/tests/Loader/classloader/StaticVirtualMethods/Regression/GitHub_130545.cs Adds a regression test reproducing the delegate-over-static-abstract-through-gsharedvt shape.
src/tests/Loader/classloader/StaticVirtualMethods/Regression/GitHub_130545.csproj Adds a minimal test project to build the new regression test.

Comment thread src/mono/mono/mini/method-to-ir.c
@pavelsavara pavelsavara added area-Codegen-AOT-mono arch-wasm WebAssembly architecture and removed area-VM-meta-mono labels Aug 5, 2026
@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.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

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 (2)

src/tests/Loader/classloader/StaticVirtualMethods/Regression/GitHub_130545.cs:62

  • Prefer Unsafe.BitCast for same-size type punning instead of Unsafe.As. This avoids byref reinterpretation and matches the repo guidance for this pattern while preserving the intended "packed" shape.
        public uint PackedValue
        {
            get => Unsafe.As<Rgba32, uint>(ref this);
            set => Unsafe.As<Rgba32, uint>(ref this) = value;
        }

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

  • The new llvmonly gshared-static-virtual delegate fast path skips the UnmanagedCallersOnly guard that the existing ldftn+delegate optimization applies. If this IL shape ever occurs for an [UnmanagedCallersOnly] method, this path would now create a delegate instead of failing consistently.
			if (gshared_static_virtual && cfg->llvm_only && (sp > stack_start) && (next_ip + 4 < end) && ip_in_bb (cfg, cfg->cbb, next_ip) && (next_ip [0] == CEE_NEWOBJ)) {
				MonoMethod *ctor_method = mini_get_method (cfg, method, read32 (next_ip + 1), NULL, generic_context);
				if (ctor_method && (m_class_get_parent (ctor_method->klass) == mono_defaults.multicastdelegate_class)) {

@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).

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][AOT] Rgba32 decode hangs or traps unless generic pixel operation is directly rooted

3 participants