Skip to content

Mostly unblock single method mode for compiling virtual methods for WASM with NativeAOT - #132143

Open
jtschuster wants to merge 22 commits into
dev/jtschuster/wasm-data-segment-refactor2from
dev/jtschuster/wasm-jit-naot-changes
Open

Mostly unblock single method mode for compiling virtual methods for WASM with NativeAOT#132143
jtschuster wants to merge 22 commits into
dev/jtschuster/wasm-data-segment-refactor2from
dev/jtschuster/wasm-jit-naot-changes

Conversation

@jtschuster

@jtschuster jtschuster commented Aug 11, 2026

Copy link
Copy Markdown
Member

Adds the non-ObjectWriter changes from #131556 into the NativeAOT Wasm enablement PR stack.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 6 pipeline(s).
10 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

Enables a NativeAOT → Wasm “single method” compilation path (integrating NativeAOT tooling with the Wasm RyuJIT backend) and adds an end-to-end tools test that compiles and executes the existing SwitchTest.TestEntryPoint as a Wasm module.

Changes:

  • Wire up Wasm signature/type symbol plumbing for NativeAOT and route Wasm code into the Wasm code section.
  • Harden/clarify unsupported NativeAOT-Wasm stub paths with explicit PlatformNotSupportedException failures.
  • Add WasmSingleMethodTests plus build/test subset plumbing to build the Wasm JIT where needed for tools tests.

Reviewed changes

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

Show a summary per file
File Description
src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmEmitter.cs Refactors emitter construction; throws explicitly for unsupported NativeAOT Wasm assembly stubs.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs Implements getWasmTypeSymbol for NativeAOT RyuJIT Wasm signature/type symbol support.
src/coreclr/tools/aot/ILCompiler.RyuJit/ILCompiler.RyuJit.csproj Removes duplicate Wasm object-writer/lowering sources now provided via ILCompiler.Compiler.
src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs Disables debug-info emission for Wasm32.
src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs Places Wasm code in WasmCodeSection; adjusts funclet-kind behavior for Wasm.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs Explicitly fails unsupported unboxing stub emission on NativeAOT Wasm.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmTentativeMethodNode.cs Explicitly fails unsupported tentative method stubs on NativeAOT Wasm.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs Explicitly fails unsupported ReadyToRun helper stubs on NativeAOT Wasm.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs Explicitly fails unsupported runtime generic helper/context paths on NativeAOT Wasm.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmJumpStubNode.cs Explicitly fails unsupported jump stubs on NativeAOT Wasm.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs Adds clarifying XML documentation about “ReadyToRun” helper naming/ABI.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs Implements Wasm type-node creation from lowered signatures and CorInfo Wasm signatures.
src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs Adds end-to-end compile + (optional) Node-based validation/execution test for Wasm single-method output.
src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj Builds ilc for tests; wires AppContext/config values for locating artifacts; includes new test file.
src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs Adds minimal Xunit FactAttribute stub for linked JIT test asset compilation.
src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj Links existing JIT Switch.cs into the test assets project.
src/coreclr/jit/lowerwasm.cpp Updates Wasm PEP lowering to support direct calls and avoid assuming a control expression exists.
src/coreclr/jit/lower.cpp Makes virtual vtable-call lowering work on targets without fixed register sets (e.g., Wasm).
src/coreclr/jit/flowgraph.cpp Adjusts shared cctor helper selection for Wasm when compiling NativeAOT vs non-NativeAOT.
src/coreclr/jit/emitwasm.cpp Emits function-index relocation using the call address parameter instead of a placeholder.
src/coreclr/jit/codegenwasm.cpp Improves Wasm epilog termination; supports direct helper addresses; refines PEP/indirect-call stack setup.
src/coreclr/jit/codegenlinear.cpp Ensures Wasm function bodies are properly terminated at end-of-function/funclet boundaries for more block kinds.
eng/Subsets.props Ensures Wasm JIT subset is built for +clr.toolstests+ on supported host/target arch combinations.

Comment thread src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs Outdated
Comment thread src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs Outdated
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 6 pipeline(s).
10 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

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

Suppressed comments (4)

src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs:78

  • The Node script doesn't reliably propagate asynchronous failures/success: exceptions thrown inside the instantiate(...).then(...) callback can become unhandled rejections depending on Node settings, and the script never explicitly exits on success. This can make the test flaky or silently pass/fail depending on Node version/config.
                    WebAssembly.instantiate(bytes, { webcil }).then(({ instance }) => {
                        const result = instance.exports.{{ExportName}}(65000, 0);
                        if (result !== 100) {
                            throw new Error(`Expected 100, got ${result}.`);
                        }

src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs:405

  • The Debug.Assert checks call GetOrCreateSection(...), which can mutate writer state (and potentially the emitted output) in DEBUG builds just to evaluate an assertion. If a section is legitimately absent (e.g., zero entries), these asserts will also create it rather than validating the intended invariant.
            Debug.Assert(GetOrCreateSection<WasmImportSection>(WasmObjectNodeSection.ImportSection, out _).EntryCount == _wasmSymbolManager.GetImportCount());
            Debug.Assert(GetOrCreateSection<WasmFunctionSection>(WasmObjectNodeSection.FunctionSection, out _).EntryCount == MethodCount);
            Debug.Assert(GetOrCreateSection<WasmGlobalSection>(WasmObjectNodeSection.GlobalSection, out _).EntryCount == _wasmSymbolManager.GetDefinitionCount(WasmIndexSpace.Global));

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs:1629

  • This introduces a new public API (NodeFactory.WasmTypeNode(CorInfoWasmType[])). Per repo policy, new public surface needs an api-approved issue linked from the PR, or the API should be kept non-public (e.g., internal with an approved exposure plan) until reviewed.
        public WasmTypeNode WasmTypeNode(CorInfoWasmType[] types)
        {
            WasmFuncType funcType = WasmFuncType.FromCorInfoSignature(types);
            return _wasmTypeNodes.GetOrAdd(funcType);

src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs:101

  • ilcPath is constructed but never validated before invoking it. On configurations where NativeAotWasmTest.IsSupported is true but ilc isn't present at the computed location, this will fail with a less actionable process-start error. Add an explicit existence assertion similar to the JIT path check.
            string ilcPath = Path.Combine(
                coreClrArtifactsDir,
                buildArchitecture,
                "ilc",
                OperatingSystem.IsWindows() ? "ilc.exe" : "ilc");

Comment thread src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSection.cs Outdated
Copilot AI review requested due to automatic review settings August 18, 2026 17:56
@agocke
agocke force-pushed the dev/jtschuster/wasm-jit-naot-changes branch from 075d571 to f739f09 Compare August 18, 2026 17:56
@agocke
agocke force-pushed the dev/jtschuster/wasm-jit-naot-changes branch from f739f09 to 7f4a6ea Compare August 18, 2026 17:59
Comment thread src/coreclr/jit/codegenlinear.cpp Outdated
Comment thread src/coreclr/jit/lowerwasm.cpp Outdated
@pavelsavara pavelsavara added the arch-wasm WebAssembly architecture label Aug 26, 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.

Copilot AI review requested due to automatic review settings August 26, 2026 23:17

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 12 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs:122

  • GetFuncletKinds now returns an empty array unconditionally. If the method actually has EH/funclets (i.e., EHInfo is present), this will silently skip recording funclets in WasmObjectWriter.RecordFunclets and can lead to emitting an incomplete/invalid wasm module rather than failing fast. Until wasm funclet kinds are implemented, this should throw (or at least assert) when EH info exists, and only return empty when there are truly no funclets.
        // TODO-WASM: Appropriately extract funclet kinds from eh clause info
        public FuncletKind[] GetFuncletKinds() => [];

src/coreclr/jit/codegenwasm.cpp:3369

  • genEmitHelperCall conditionally stops pushing the PEP argument when JIT_FLAG_PORTABLE_ENTRY_POINTS is not set, but the hard-coded wasm helper signatures still include the trailing PEP parameter for managed helpers (see HELPER_SIG entries). This makes the signature passed to getWasmTypeSymbol disagree with the actual arguments on the wasm stack, which can miscompile managed helper calls (e.g., throw helpers for range checks) in non-PEP configurations such as NativeAOT wasm single-method compilation. Either keep PEP pushing/signatures consistent, or (preferably) drop the trailing PEP type from the signature when PEP is disabled.
    params.wasmSignature = m_compiler->info.compCompHnd->getWasmTypeSymbol(types, typeCount);

    if (helperIsManaged && m_compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PORTABLE_ENTRY_POINTS))
    {
        // Push PEP onto the stack because we are calling a managed helper that expects it as the last parameter.
        // The helper function address is the address of an indirection cell, so we load from the cell to get the PEP
        // address to push.
        assert(helperFunction.accessType == IAT_PVALUE);
        GetEmitter()->emitAddressConstant(helperFunction.addr);
        GetEmitter()->emitIns_I(INS_I_load, EA_PTRSIZE, 0);
    }

Copilot AI review requested due to automatic review settings August 27, 2026 01:28

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 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs:122

  • GetFuncletKinds now always returns an empty array. This makes WebAssembly object writing silently skip funclet registration even when the method has EH info, which can produce an invalid object/module instead of failing fast for unsupported EH. Since EH/funclets aren’t supported yet for NativeAOT Wasm, this should throw when EH info exists and only return an empty array for the no-EH case.
        // TODO-WASM: Appropriately extract funclet kinds from eh clause info
        public FuncletKind[] GetFuncletKinds() => [];

src/coreclr/jit/codegenwasm.cpp:3367

  • The hard-coded signatures for managed helpers in this method include the portable-entry-point (PEP) argument, but PEP is now only pushed when JIT_FLAG_PORTABLE_ENTRY_POINTS is set. If this flag is off, the emitted call will not match the signature (missing the last argument), producing invalid Wasm. Either the signature needs to vary with the flag, or (simpler/safer) always push PEP for helpers whose signatures include it.
    if (helperIsManaged && m_compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PORTABLE_ENTRY_POINTS))
    {
        // Push PEP onto the stack because we are calling a managed helper that expects it as the last parameter.
        // The helper function address is the address of an indirection cell, so we load from the cell to get the PEP
        // address to push.
        assert(helperFunction.accessType == IAT_PVALUE);
        GetEmitter()->emitAddressConstant(helperFunction.addr);

@jtschuster

Copy link
Copy Markdown
Member Author

Reverted all the JIT changes for which I couldn't create a compilation that required them. After stubbing out some NotImplementedExceptions in ilc locally, I have it compiling VirtualMethod() from the following snippet in single method mode.

class Base
{
    public virtual int VirtualMethod(int value)
    {
        return value;
    }
}

class Program : Base
{
    public override int VirtualMethod(int value)
    {
        switch(value)
        {
            case 0:
                return 42;
            default:
                Base b = this;
                return b.VirtualMethod(value-1);
        }
    }
}

Copilot AI review requested due to automatic review settings August 27, 2026 01:59
@jtschuster jtschuster changed the title Enable wasm compilations in native aot, add single method tests Mostly unblock single method mode for compiling virtual methods for WASM with NativeAOT Aug 27, 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 12 out of 12 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

Previously missed (3) — in code that hasn't changed since the last review.

src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs:85

  • AddAlias only Debug.Asserts against collisions with _entries and then indexes _entries[target]. In Release builds this can (1) silently shadow an existing symbol name (since lookups prefer _entries) and (2) throw a KeyNotFoundException if the target was never registered. It would be more robust to validate alias uniqueness across both dictionaries and validate the target with an actionable exception.
    public void AddAlias(Utf8String alias, Utf8String target)
    {
        Debug.Assert(!_entries.ContainsKey(alias));
        Entry entry = _entries[target];
        _aliases.Add(alias, entry with { Name = alias });
    }

src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs:163

  • The IMAGE_REL_BASED_HIGHLOW relocation handler unconditionally casts the referenced symbol’s defining section to WasmDataSegmentEmitter. If a HIGHLOW reloc ever targets a non-data symbol, this becomes an InvalidCastException rather than a clear failure. Consider pattern-matching the section and throwing a targeted exception when the relocation cannot be resolved as a data-segment address.
                        case RelocType.IMAGE_REL_BASED_HIGHLOW:
                        {
                            WasmDataSegmentEmitter segment = (WasmDataSegmentEmitter)_sections[definedSymbol.SectionIndex];
                            int targetOffsetFromMemoryBase = segment.GetMemoryAddressOfOffset((int)(definedSymbol.Value + addend));
                            Relocation.WriteValue(reloc.Type, pData, targetOffsetFromMemoryBase);
                            break;
                        }

src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmEmitter.cs:48

  • Encode throws PlatformNotSupportedException for NativeAOT, but the message doesn’t identify which stub/node attempted to emit Wasm code. Including the failing node makes this actionable when the compiler hits this path during bring-up.
            throw new PlatformNotSupportedException("NativeAOT WebAssembly assembly stubs are not supported.");

src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs:122

  • GetFuncletKinds now always returns an empty array. If this MethodCodeNode has EH info, WasmObjectWriter.RecordFunclets will treat it as having no funclets and will skip emitting any funclet-related entries, which can lead to incorrect output rather than an explicit failure. Consider throwing when EH is present until funclet kinds are implemented.
        // TODO-WASM: Appropriately extract funclet kinds from eh clause info
        public FuncletKind[] GetFuncletKinds() => [];

Comment thread src/coreclr/jit/codegenwasm.cpp
Comment thread src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs Outdated
Comment thread src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmEmitter.cs Outdated
Copilot AI review requested due to automatic review settings August 27, 2026 19:02

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 14 out of 14 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs:162

  • This new IMAGE_REL_BASED_HIGHLOW handling assumes the referenced symbol lives in a WasmDataSegmentEmitter and truncates (definedSymbol.Value + addend) to int without overflow checking. If the relocation targets a non-data section, this will throw an InvalidCastException; and if the offset doesn’t fit in int, it will silently wrap before the segment bounds assert. Prefer an explicit section type check and a checked cast so failures are actionable.
                        case RelocType.IMAGE_REL_BASED_HIGHLOW:
                        {
                            WasmDataSegmentEmitter segment = (WasmDataSegmentEmitter)_sections[definedSymbol.SectionIndex];
                            int targetOffsetFromMemoryBase = segment.GetMemoryAddressOfOffset((int)(definedSymbol.Value + addend));
                            Relocation.WriteValue(reloc.Type, pData, targetOffsetFromMemoryBase);
                            break;

src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs:122

  • GetFuncletKinds now returns an empty array unconditionally. For methods with EH, this can silently skip funclet emission (see WasmObjectWriter.RecordFunclets) and produce an invalid/incorrect module rather than failing fast. If EH funclets aren’t supported for Wasm yet, it’s safer to throw when EH info is present.
        // TODO-WASM: Appropriately extract funclet kinds from eh clause info
        public FuncletKind[] GetFuncletKinds() => [];

Comment thread src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs

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 24 out of 24 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

Previously missed (3) — in code that hasn't changed since the last review.

src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmByteArrayDataSegment.cs:70

  • IWasmDataSegment’s contract says SetMemoryOffset is a no-op for passive segments and GetMemoryAddressOfOffset should return offsetInSegment for passive segments. This implementation always updates/uses _memoryOffset, so a passive segment can accidentally start returning incorrect addresses if SetMemoryOffset is ever called (now or in future layout code).
        private WasmInstructionGroup GetInitExpr() => _type == WasmDataSegmentType.Active ? new WasmInstructionGroup([I32.Const(_memoryOffset)]) : null;

        public void SetMemoryOffset(int offset) => _memoryOffset = offset;
        public int GetMemoryAddressOfOffset(int offsetInSegment)
        {

src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs:85

  • AddAlias relies on Debug.Assert to prevent alias collisions with existing symbols; in Release builds an alias can silently collide with an existing entry, and GetEntry will then resolve to _entries (ignoring the alias). Since alternate names can come from NodeFactory aliases (potentially user-provided entry points), validate collisions in all builds and throw an actionable exception.
    public void AddAlias(Utf8String alias, Utf8String target)
    {
        Debug.Assert(!_entries.ContainsKey(alias));
        Entry entry = _entries[target];
        _aliases.Add(alias, entry with { Name = alias });
    }

src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSection.cs:61

  • Typo in comment: “assigneed” -> “assigned”.
            // The active segment memory offset expression may change the size of a segment, so the layout must be
            // assigneed before calculating the total size of the data section.
            AssignSegmentLayout();

src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs:122

  • Returning an empty funclet list unconditionally will silently treat EH methods as having no funclets, which can lead to incorrect output instead of an explicit “unsupported” failure. If EH info exists, fail fast until proper funclet classification is implemented.
        // TODO-WASM: Appropriately extract funclet kinds from eh clause info
        public FuncletKind[] GetFuncletKinds() => [];

Comment on lines 61 to 63
FinalizeSectionEntryCounts();
ResolveSectionRelocations();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-NativeAOT-coreclr

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

7 participants