From f42f47ab6c07bf5012b018fe39db299947c7edda Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:30:43 -0700 Subject: [PATCH 01/21] Remove GetMemoryAddressOfOffset() from IWasmDataSegment This method isn't used, so it doesn't make sense to leave it. Implementations are trivial and can be re-added when actually needed. --- .../Common/Compiler/ObjectWriter/Wasm/IWasmDataSegment.cs | 6 ------ .../Compiler/ObjectWriter/Wasm/WasmByteArrayDataSegment.cs | 6 ------ .../Compiler/ObjectWriter/Wasm/WasmDataSegmentEmitter.cs | 6 ------ .../Compiler/ObjectWriter/Wasm/WebcilPayloadDataSegment.cs | 6 ------ 4 files changed, 24 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/IWasmDataSegment.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/IWasmDataSegment.cs index 4ade8c4e813f53..9a453fd6c52216 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/IWasmDataSegment.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/IWasmDataSegment.cs @@ -52,12 +52,6 @@ internal interface IWasmDataSegment : IWasmEmittable /// segment is aligned properly. /// void SetTrailingPadding(int trailingBytesCount); - - /// - /// Gets the offset of the segment in linear memory when loaded. - /// For passive segments, returns - /// - int GetMemoryAddressOfOffset(int offsetInSegment); } internal interface IWasmActiveDataSegment : IWasmDataSegment diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmByteArrayDataSegment.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmByteArrayDataSegment.cs index 1160b47f364013..b597c064b0fcc4 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmByteArrayDataSegment.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmByteArrayDataSegment.cs @@ -54,12 +54,6 @@ public int EmitToStream(Stream outputFileStream) return headerSize + _contents.Length + _paddingBytesCount; } - public int GetMemoryAddressOfOffset(int offsetInSegment) - { - Debug.Assert(offsetInSegment >= 0 && offsetInSegment <= _contents.Length); - return offsetInSegment; - } - public void SetTrailingPadding(int trailingBytesCount) { Debug.Assert(trailingBytesCount >= 0); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSegmentEmitter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSegmentEmitter.cs index 01b1371426bf0d..44026532e8ea46 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSegmentEmitter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSegmentEmitter.cs @@ -74,11 +74,5 @@ private WasmInstructionGroup GetMemoryOffsetInitExpr() { return new WasmInstructionGroup([I32.PaddedConst(_memoryOffset)]); } - - public int GetMemoryAddressOfOffset(int offsetInSegment) - { - Debug.Assert(offsetInSegment >= 0 && offsetInSegment <= ContentReadStream.Length); - return _memoryOffset + offsetInSegment; - } } } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilPayloadDataSegment.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilPayloadDataSegment.cs index 50abf531c68e98..04c941c9f3af19 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilPayloadDataSegment.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilPayloadDataSegment.cs @@ -103,11 +103,5 @@ public void SetTrailingPadding(int trailingBytesCount) Debug.Assert(trailingBytesCount >= 0); _paddingBytesCount = trailingBytesCount; } - - public int GetMemoryAddressOfOffset(int offsetInSegment) - { - Debug.Assert(offsetInSegment >= 0 && offsetInSegment <= RawContentSize); - return offsetInSegment; - } } } From 0d759c41d4e52a3bf4fd1f72909303c64ef24d79 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:08:14 -0700 Subject: [PATCH 02/21] Revert "Remove GetMemoryAddressOfOffset() from IWasmDataSegment" This reverts commit d3523737fb691ae94324961fda12126d7bfabf08. --- .../Common/Compiler/ObjectWriter/Wasm/IWasmDataSegment.cs | 6 ++++++ .../Compiler/ObjectWriter/Wasm/WasmByteArrayDataSegment.cs | 6 ++++++ .../Compiler/ObjectWriter/Wasm/WasmDataSegmentEmitter.cs | 6 ++++++ .../Compiler/ObjectWriter/Wasm/WebcilPayloadDataSegment.cs | 6 ++++++ 4 files changed, 24 insertions(+) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/IWasmDataSegment.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/IWasmDataSegment.cs index 9a453fd6c52216..4ade8c4e813f53 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/IWasmDataSegment.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/IWasmDataSegment.cs @@ -52,6 +52,12 @@ internal interface IWasmDataSegment : IWasmEmittable /// segment is aligned properly. /// void SetTrailingPadding(int trailingBytesCount); + + /// + /// Gets the offset of the segment in linear memory when loaded. + /// For passive segments, returns + /// + int GetMemoryAddressOfOffset(int offsetInSegment); } internal interface IWasmActiveDataSegment : IWasmDataSegment diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmByteArrayDataSegment.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmByteArrayDataSegment.cs index b597c064b0fcc4..1160b47f364013 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmByteArrayDataSegment.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmByteArrayDataSegment.cs @@ -54,6 +54,12 @@ public int EmitToStream(Stream outputFileStream) return headerSize + _contents.Length + _paddingBytesCount; } + public int GetMemoryAddressOfOffset(int offsetInSegment) + { + Debug.Assert(offsetInSegment >= 0 && offsetInSegment <= _contents.Length); + return offsetInSegment; + } + public void SetTrailingPadding(int trailingBytesCount) { Debug.Assert(trailingBytesCount >= 0); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSegmentEmitter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSegmentEmitter.cs index 44026532e8ea46..01b1371426bf0d 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSegmentEmitter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSegmentEmitter.cs @@ -74,5 +74,11 @@ private WasmInstructionGroup GetMemoryOffsetInitExpr() { return new WasmInstructionGroup([I32.PaddedConst(_memoryOffset)]); } + + public int GetMemoryAddressOfOffset(int offsetInSegment) + { + Debug.Assert(offsetInSegment >= 0 && offsetInSegment <= ContentReadStream.Length); + return _memoryOffset + offsetInSegment; + } } } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilPayloadDataSegment.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilPayloadDataSegment.cs index 04c941c9f3af19..50abf531c68e98 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilPayloadDataSegment.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilPayloadDataSegment.cs @@ -103,5 +103,11 @@ public void SetTrailingPadding(int trailingBytesCount) Debug.Assert(trailingBytesCount >= 0); _paddingBytesCount = trailingBytesCount; } + + public int GetMemoryAddressOfOffset(int offsetInSegment) + { + Debug.Assert(offsetInSegment >= 0 && offsetInSegment <= RawContentSize); + return offsetInSegment; + } } } From 2c18ba68e4c33f32e0c55ed0700da84d0f4dfd27 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:26:15 -0700 Subject: [PATCH 03/21] Merge work from 131556 --- eng/Subsets.props | 6 + src/coreclr/jit/codegenlinear.cpp | 13 +- src/coreclr/jit/codegenwasm.cpp | 33 +-- src/coreclr/jit/emitwasm.cpp | 2 +- src/coreclr/jit/flowgraph.cpp | 5 +- src/coreclr/jit/lower.cpp | 16 +- src/coreclr/jit/lowerwasm.cpp | 34 ++- .../ILCompiler.Compiler.Tests.Assets.csproj | 4 + .../XunitStubs.cs | 9 + .../ILCompiler.Compiler.Tests.csproj | 20 ++ .../WasmSingleMethodTests.cs | 195 ++++++++++++++++++ .../ReadyToRunGenericHelperNode.cs | 4 + .../WasmReadyToRunGenericHelperNode.cs | 9 +- .../Target_Wasm/WasmReadyToRunHelperNode.cs | 2 +- .../Target_Wasm/WasmUnboxingStubNode.cs | 2 +- .../DependencyAnalysis/MethodCodeNode.cs | 7 +- .../Compiler/RyuJitCompilation.cs | 5 +- .../JitInterface/CorInfoImpl.RyuJit.cs | 6 +- 18 files changed, 332 insertions(+), 40 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs diff --git a/eng/Subsets.props b/eng/Subsets.props index 0674e93cf4ded7..9d4fc37e0a0fa2 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -339,6 +339,10 @@ $(ClrRuntimeBuildSubsets);ClrWasmJitSubset=true + + $(ClrRuntimeBuildSubsets);ClrWasmJitSubset=true + + $(ClrRuntimeBuildSubsets);ClrPalTestsSubset=true @@ -396,6 +400,8 @@ + <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' != 'wasm' and $(_subset.Contains('+clr.toolstests+'))" Include="ClrAllJitsSubset=true" /> + <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and ('$(TargetArchitecture)' == 'x64' or '$(TargetArchitecture)' == 'arm64') and $(_subset.Contains('+clr.toolstests+')) and ('$(BuildArchitecture)' == 'x64' or '$(BuildArchitecture)' == 'arm64')" Include="ClrWasmJitSubset=true" /> <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' != 'wasm' and ($(_subset.Contains('+clr.tools+')) or $(_subset.Contains('+clr.nativecorelib+')) or $(_subset.Contains('+clr.crossarchtools+')))" Include="ClrJitSubset=true" /> <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' == 'wasm' and ($(_subset.Contains('+clr.tools+')) or $(_subset.Contains('+clr.nativecorelib+')) or $(_subset.Contains('+clr.crossarchtools+')))" Include="ClrWasmJitSubset=true" /> diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index f866893b560bf0..dcdc7fa13b9e5e 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -870,6 +870,12 @@ void CodeGen::genEmitEndBlock(BasicBlock* block) break; case BBJ_SWITCH: +#if defined(TARGET_WASM) + if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) + { + genEmitFunctionEnd(); + } +#endif break; case BBJ_ALWAYS: @@ -920,7 +926,6 @@ void CodeGen::genEmitEndBlock(BasicBlock* block) genEmitFunctionEnd(); } #endif // defined(TARGET_WASM) - break; case BBJ_COND: @@ -931,6 +936,12 @@ void CodeGen::genEmitEndBlock(BasicBlock* block) SetLoopAlignBackEdge(block, block->GetFalseTarget()); #endif // FEATURE_LOOP_ALIGN +#if defined(TARGET_WASM) + if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) + { + genEmitFunctionEnd(); + } +#endif break; default: diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 1483fe3882680c..ac47cfd7964c7a 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -398,17 +398,17 @@ void CodeGen::genFnEpilog(BasicBlock* block) { if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) { - instGen(INS_end); + genEmitFunctionEnd(/* emitTerminalUnreachable */ false); } return; } // TODO-WASM: shadow stack maintenance - // TODO-WASM: we need to handle the end-of-function case if we reach the end of a codegen for a function - // and do NOT have an epilog. In those cases we currently will not emit an end instruction. + // Close the root function before the first funclet starts. Other returns + // within the root function leave the remaining root blocks reachable. if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) { - instGen(INS_end); + genEmitFunctionEnd(/* emitTerminalUnreachable */ false); } else { @@ -3287,7 +3287,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) if (target != nullptr) { // Codegen should have already evaluated our target node (last) and pushed it onto the stack, - // ready for call_indirect. Consume it. + // ready for call_indirect. Consume it. genConsumeReg(target); params.callType = EC_INDIR_R; @@ -3298,16 +3298,22 @@ void CodeGen::genCallInstruction(GenTreeCall* call) // Generate a direct call to a non-virtual user defined or helper method assert(call->IsHelperCall() || (call->gtCallType == CT_USER_FUNC)); - assert(call->gtEntryPoint.addr == NULL); - if (call->IsHelperCall()) { assert(!call->IsFastTailCall()); - CorInfoHelpFunc helperNum = m_compiler->eeGetHelperNum(params.methHnd); - noway_assert(helperNum != CORINFO_HELP_UNDEF); - CORINFO_CONST_LOOKUP helperLookup = m_compiler->compGetHelperFtn(helperNum); - assert(helperLookup.accessType == IAT_VALUE); - params.addr = helperLookup.addr; + + if (call->gtDirectCallAddress != nullptr) + { + params.addr = call->gtDirectCallAddress; + } + else + { + CorInfoHelpFunc helperNum = m_compiler->eeGetHelperNum(params.methHnd); + noway_assert(helperNum != CORINFO_HELP_UNDEF); + CORINFO_CONST_LOOKUP helperLookup = m_compiler->compGetHelperFtn(helperNum); + assert(helperLookup.accessType == IAT_VALUE); + params.addr = helperLookup.addr; + } } else { @@ -3350,9 +3356,8 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, } else { - params.addr = nullptr; assert(helperFunction.accessType == IAT_PVALUE); - + params.addr = nullptr; params.callType = EC_INDIR_R; } diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index 3b6c8244023a5b..e8eb2e047730a7 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -300,7 +300,7 @@ void emitter::emitIns_Call(const EmitCallParams& params) { case EC_FUNC_TOKEN: ins = params.isJump ? INS_return_call : INS_call; - id = emitNewInstrSC(EA_HANDLE_CNS_RELOC, 0 /* FIXME-WASM: function index reloc */); + id = emitNewInstrSC(EA_HANDLE_CNS_RELOC, (cnsval_ssize_t)params.addr); id->idIns(ins); id->idInsFmt(IF_FUNCIDX); break; diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index c94494d517d39d..5b781136190bb7 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -866,7 +866,10 @@ GenTreeCall* Compiler::fgGetSharedCCtor(CORINFO_CLASS_HANDLE cls) { #if defined(TARGET_WASM) // Wasm does not support dynamically created helpers - return fgGetStaticsCCtorHelper(cls, CORINFO_HELP_INITCLASS); + if (!IsNativeAot()) + { + return fgGetStaticsCCtorHelper(cls, CORINFO_HELP_INITCLASS); + } #endif #ifdef FEATURE_READYTORUN diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index d425a72e33f760..9d05d2a6ba9132 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -7380,21 +7380,27 @@ GenTree* Lowering::LowerVirtualVtableCall(GenTreeCall* call) { noway_assert(call->gtCallType == CT_USER_FUNC); - GenTree* thisArgNode; + CallArg* thisArg; if (call->IsTailCallViaJitHelper()) { assert(call->gtArgs.CountArgs() > 0); - thisArgNode = call->gtArgs.GetArgByIndex(0)->GetNode(); + thisArg = call->gtArgs.GetArgByIndex(0); } else { assert(call->gtArgs.HasThisPointer()); - thisArgNode = call->gtArgs.GetThisArg()->GetNode(); + thisArg = call->gtArgs.GetThisArg(); } + GenTree* thisArgNode = thisArg->GetNode(); // get a reference to the thisPtr being passed +#if HAS_FIXED_REGISTER_SET assert(thisArgNode->OperIs(GT_PUTARG_REG)); GenTree* thisPtr = thisArgNode->AsUnOp()->gtGetOp1(); +#else + // On platforms without fixed registers (e.g., WASM), PUTARG nodes are not inserted. + GenTree* thisPtr = thisArgNode; +#endif // If what we are passing as the thisptr is not already a local, make a new local to place it in // because we will be creating expressions based on it. @@ -7411,7 +7417,11 @@ GenTree* Lowering::LowerVirtualVtableCall(GenTreeCall* call) vtableCallTemp = m_compiler->lvaGrabTemp(true DEBUGARG("virtual vtable call")); } +#if HAS_FIXED_REGISTER_SET LIR::Use thisPtrUse(BlockRange(), &thisArgNode->AsUnOp()->gtOp1, thisArgNode); +#else + LIR::Use thisPtrUse(BlockRange(), &thisArg->NodeRef(), call); +#endif ReplaceWithLclVar(thisPtrUse, vtableCallTemp); lclNum = vtableCallTemp; diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index ab06ed972328bc..dfefc2360908da 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -66,22 +66,28 @@ void Lowering::LowerPEPCall(GenTreeCall* call) JITDUMP("Begin lowering PEP call\n"); DISPTREERANGE(BlockRange(), call); - // PEP call must always have a control expression - assert(call->gtControlExpr != nullptr); - LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); + GenTree* callTargetForArg; + if (call->gtControlExpr != nullptr) + { + LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); - JITDUMP("Creating new local variable for PEP"); - unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); - GenTreeLclVar* callTargetLclForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); + JITDUMP("Creating new local variable for PEP"); + unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); + callTargetForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); + } + else + { + assert(call->gtDirectCallAddress != nullptr); + callTargetForArg = AddrGen(call->gtDirectCallAddress); + } DISPTREE(call); JITDUMP("Add new arg to call arg list corresponding to PEP target"); - NewCallArg pepTargetArg = - NewCallArg::Primitive(callTargetLclForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); - CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); + NewCallArg pepTargetArg = NewCallArg::Primitive(callTargetForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); + CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); pepArg->SetEarlyNode(nullptr); - pepArg->SetLateNode(callTargetLclForArg); + pepArg->SetLateNode(callTargetForArg); call->gtArgs.PushLateBack(pepArg); // Set up ABI information for this arg; PEP's should be passed as the last param to a wasm function @@ -90,12 +96,18 @@ void Lowering::LowerPEPCall(GenTreeCall* call) pepArg->AbiInfo = ABIPassingInformation::FromSegmentByValue(m_compiler, ABIPassingSegment::InRegister(pepReg, 0, TARGET_POINTER_SIZE)); - BlockRange().InsertBefore(call, callTargetLclForArg); + BlockRange().InsertBefore(call, callTargetForArg); // Lower the new PEP arg now that the call abi info is updated and lcl var is inserted LowerArg(call, pepArg); DISPTREE(call); + if (call->gtControlExpr == nullptr) + { + JITDUMP("Finished lowering direct PEP call\n"); + return; + } + JITDUMP("Rewrite PEP call's control expression to indirect through the new local variable\n"); // Rewrite the call's control expression to have an additional load from the PEP local diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj index 2e0b217160f958..2bd02d648b64e7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj @@ -15,4 +15,8 @@ + + + + diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs new file mode 100644 index 00000000000000..4d4f9ef801ffa6 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xunit +{ + internal sealed class FactAttribute : System.Attribute + { + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj index 69e9d87637f92d..ef24d8566e4d3b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj @@ -26,6 +26,9 @@ + + Configuration=$(CoreCLRConfiguration) + false @@ -43,5 +46,22 @@ + + + + + <_NativeAotWasmTestSupported Condition="('$(BuildArchitecture)' == 'x64' or '$(BuildArchitecture)' == 'arm64') and ('$(TargetArchitecture)' == 'x64' or '$(TargetArchitecture)' == 'arm64')">true + + + + + $(_NativeAotWasmTestSupported) + + + $(BuildArchitecture) + + + $(CoreCLRArtifactsPath) + diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs new file mode 100644 index 00000000000000..0eb70da110a9e8 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs @@ -0,0 +1,195 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; + +using Microsoft.DotNet.XUnitExtensions; + +using Xunit; + +namespace ILCompiler.Compiler.Tests +{ + public class WasmSingleMethodTests + { + private const string ExportName = "ILCompiler_Compiler_Tests_Assets_SwitchTest__TestEntryPoint"; + private static readonly byte[] WasmHeader = [0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00]; + + public static bool IsWasmCompilationSupported => + string.Equals( + AppContext.GetData("NativeAotWasmTest.IsSupported") as string, + "true", + StringComparison.OrdinalIgnoreCase); + + public static bool IsWasmExecutionSupported => + IsWasmCompilationSupported && + RunProcess( + "node", + ["-e", "process.exit(typeof WebAssembly.Tag === 'function' ? 0 : 1)"], + throwOnError: false).ExitCode == 0; + + [ConditionalFact(nameof(IsWasmCompilationSupported))] + public void NativeAotWasmSingleMethodCompiles() + { + string outputPath = CompileSwitchTest(); + try + { + byte[] output = File.ReadAllBytes(outputPath); + Assert.True(output.Length >= WasmHeader.Length); + Assert.Equal(WasmHeader, output.AsSpan(0, WasmHeader.Length).ToArray()); + } + finally + { + File.Delete(outputPath); + } + } + + [ConditionalFact(nameof(IsWasmExecutionSupported))] + public void NativeAotWasmSingleMethodExecutes() + { + string outputPath = CompileSwitchTest(); + string scriptPath = Path.ChangeExtension(outputPath, ".js"); + try + { + File.WriteAllText(scriptPath, + $$""" + const fs = require("fs"); + const bytes = fs.readFileSync({{ToJavaScriptString(outputPath)}}); + if (!WebAssembly.validate(bytes)) { + throw new Error("NativeAOT produced an invalid WebAssembly module."); + } + const webcil = { + stackPointer: new WebAssembly.Global({ value: "i32", mutable: true }, 65000), + imageBase: new WebAssembly.Global({ value: "i32", mutable: false }, 0), + tableBase: new WebAssembly.Global({ value: "i32", mutable: false }, 0), + asyncContinuation: new WebAssembly.Global({ value: "i32", mutable: true }, 0), + table: new WebAssembly.Table({ initial: 4096, element: "anyfunc" }), + rtlRestoreContextTag: new WebAssembly.Tag({ parameters: [] }), + memory: new WebAssembly.Memory({ initial: 16 }), + }; + WebAssembly.instantiate(bytes, { webcil }).then(({ instance }) => { + const result = instance.exports.{{ExportName}}(65000, 0); + if (result !== 100) { + throw new Error(`Expected 100, got ${result}.`); + } + }); + """); + + ProcessResult result = RunProcess("node", [scriptPath], throwOnError: false); + Assert.True(result.ExitCode == 0, result.Output); + } + finally + { + File.Delete(scriptPath); + File.Delete(outputPath); + } + } + + private static string CompileSwitchTest() + { + string coreClrArtifactsDir = Assert.IsType(AppContext.GetData("NativeAotWasmTest.CoreCLRArtifactsDir")); + string buildArchitecture = Assert.IsType(AppContext.GetData("NativeAotWasmTest.BuildArchitecture")); + string ilcPath = Path.Combine( + coreClrArtifactsDir, + buildArchitecture, + "ilc", + OperatingSystem.IsWindows() ? "ilc.exe" : "ilc"); + string jitFileName = OperatingSystem.IsWindows() + ? $"clrjit_universal_wasm_{buildArchitecture}.dll" + : OperatingSystem.IsMacOS() + ? $"libclrjit_universal_wasm_{buildArchitecture}.dylib" + : $"libclrjit_universal_wasm_{buildArchitecture}.so"; + string jitPath = Path.Combine(coreClrArtifactsDir, jitFileName); + if (!File.Exists(jitPath)) + { + jitPath = Path.Combine(coreClrArtifactsDir, buildArchitecture, jitFileName); + } + + Assert.True(File.Exists(jitPath), $"WASM JIT not found at '{jitPath}'."); + + string outputPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.wasm"); + try + { + RunProcess( + ilcPath, + [ + "--singlemethodtypename", "SwitchTest, ILCompiler.Compiler.Tests.Assets", + "--singlemethodname", "TestEntryPoint", + Path.Combine(AppContext.BaseDirectory, "ILCompiler.Compiler.Tests.Assets.dll"), + $"-r:{Path.Combine(AppContext.BaseDirectory, "Test.CoreLib.dll")}", + "--systemmodule:Test.CoreLib", + $"-o:{outputPath}", + "--targetarch:wasm", + "--targetos:browser", + $"--jitpath:{jitPath}", + "--stacktracedata:none", + "--reflectiondata:none", + ], + throwOnError: true); + + return outputPath; + } + catch + { + File.Delete(outputPath); + throw; + } + } + + private static ProcessResult RunProcess(string fileName, IEnumerable arguments, bool throwOnError) + { + var startInfo = new ProcessStartInfo(fileName) + { + RedirectStandardError = true, + RedirectStandardOutput = true, + UseShellExecute = false, + }; + foreach (string argument in arguments) + { + startInfo.ArgumentList.Add(argument); + } + + try + { + using Process process = Process.Start(startInfo) ?? + throw new InvalidOperationException($"Failed to start '{fileName}'."); + Task standardOutput = process.StandardOutput.ReadToEndAsync(); + Task standardError = process.StandardError.ReadToEndAsync(); + process.WaitForExit(); + + var result = new ProcessResult( + process.ExitCode, + standardOutput.GetAwaiter().GetResult() + standardError.GetAwaiter().GetResult()); + if (throwOnError && result.ExitCode != 0) + { + throw new InvalidOperationException(result.Output); + } + + return result; + } + catch (Exception ex) when (!throwOnError && ex is Win32Exception or InvalidOperationException) + { + return new ProcessResult(-1, ex.ToString()); + } + } + + private static string ToJavaScriptString(string value) => + '"' + value.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("\"", "\\\"", StringComparison.Ordinal) + '"'; + + private readonly struct ProcessResult + { + public ProcessResult(int exitCode, string output) + { + ExitCode = exitCode; + Output = output; + } + + public int ExitCode { get; } + public string Output { get; } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index ec6fa5d049cd3f..a8e07d0f528fca 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -13,6 +13,10 @@ namespace ILCompiler.DependencyAnalysis { + /// + /// Represents a NativeAOT runtime generic dictionary lookup helper. + /// "ReadyToRun" refers to the JIT helper ABI used to request the lookup, not to the ReadyToRun compiler. + /// public abstract partial class ReadyToRunGenericHelperNode : AssemblyStubNode, INodeWithRuntimeDeterminedDependencies { private readonly ReadyToRunHelperId _id; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs index c5c13718eb873c..dfed6e0e5c3eb9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs @@ -11,12 +11,14 @@ public partial class ReadyToRunGenericHelperNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException( + "NativeAOT WebAssembly does not support runtime generic dictionary lookup helpers."); } protected virtual void EmitLoadGenericContext(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException( + "NativeAOT WebAssembly runtime generic dictionary context loading is not supported."); } } @@ -24,7 +26,8 @@ public partial class ReadyToRunGenericLookupFromTypeNode { protected override void EmitLoadGenericContext(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException( + "NativeAOT WebAssembly runtime generic dictionary context loading from a type is not supported."); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs index 310f7e22c3153d..09702fc27ce327 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs @@ -11,7 +11,7 @@ public partial class ReadyToRunHelperNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException("NativeAOT WebAssembly ReadyToRun helpers are not supported."); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs index 0ed23ac753b3c3..253053cd051ea0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs @@ -11,7 +11,7 @@ public partial class UnboxingStubNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException("NativeAOT WebAssembly unboxing stubs are not supported."); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs index f8418a8d67befc..1d5c713c5596d8 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs @@ -50,7 +50,10 @@ public void SetCode(ObjectData data) public override ObjectNodeSection GetSection(NodeFactory factory) { return factory.Target.IsWindows ? - ObjectNodeSection.ManagedCodeWindowsContentSection : ObjectNodeSection.ManagedCodeUnixContentSection; + ObjectNodeSection.ManagedCodeWindowsContentSection : + factory.Target.IsWasm ? + ObjectNodeSection.WasmCodeSection : + ObjectNodeSection.ManagedCodeUnixContentSection; } public override bool StaticDependenciesAreComputed => _methodCode != null; @@ -119,7 +122,7 @@ public ISymbolNode GetUnboxingThunkTarget(NodeFactory factory) public MethodExceptionHandlingInfoNode EHInfo => _ehInfo; // TODO-WASM: Appropriately extract funclet kinds from eh clause info - public FuncletKind[] GetFuncletKinds() => throw new NotImplementedException(); + public FuncletKind[] GetFuncletKinds() => []; public ISymbolNode GetAssociatedDataNode(NodeFactory factory) { diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs index 5c635bf69e3b4c..a1b006eba54336 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs @@ -110,8 +110,11 @@ protected override void CompileInternal(string outputFile, ObjectDumper dumper) if ((_compilationOptions & RyuJitCompilationOptions.UseDwarf5) != 0) options |= ObjectWritingOptions.UseDwarf5; - if (_debugInformationProvider is not NullDebugInformationProvider) + if (_debugInformationProvider is not NullDebugInformationProvider && + NodeFactory.Target.Architecture != TargetArchitecture.Wasm32) + { options |= ObjectWritingOptions.GenerateDebugInfo; + } if ((_compilationOptions & RyuJitCompilationOptions.ControlFlowGuardAnnotations) != 0) options |= ObjectWritingOptions.ControlFlowGuard; diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 23d0fd509dc85c..1b788a6098c652 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -15,6 +15,7 @@ using ILCompiler; using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysis.Wasm; using System.Runtime.CompilerServices; #if SUPPORT_JIT @@ -2504,7 +2505,10 @@ private void getThreadLocalStaticInfo_NativeAOT(CORINFO_THREAD_STATIC_INFO_NATIV private CORINFO_WASM_TYPE_SYMBOL_STRUCT_* getWasmTypeSymbol(CorInfoWasmType* types, nuint typesSize) { - throw new NotImplementedException(); + CorInfoWasmType[] typeArray = new ReadOnlySpan(types, (int)typesSize).ToArray(); + + WasmTypeNode typeNode = _compilation.NodeFactory.WasmTypeNode(typeArray); + return (CORINFO_WASM_TYPE_SYMBOL_STRUCT_*)ObjectToHandle(typeNode); } #pragma warning disable CA1822 // Mark members as static From 433bff695e07b7820d8461663d3e9e86d2704058 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 11 Aug 2026 08:13:22 -0700 Subject: [PATCH 04/21] Undo ObjectWriter changes --- .../ObjectWriter/Wasm/WasmDataSection.cs | 2 +- .../ObjectWriter/Wasm/WasmSections.cs | 24 ++++-------------- .../ObjectWriter/Wasm/WebcilSection.cs | 18 ++++++------- .../ObjectWriter/WasmGlobalImports.cs | 2 +- .../Compiler/ObjectWriter/WasmNative.cs | 25 ++++++------------- 5 files changed, 22 insertions(+), 49 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSection.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSection.cs index a291551f0d0a2a..e32f6fcbe8918d 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSection.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSection.cs @@ -74,7 +74,7 @@ public int EncodeSize() return HeaderSize + ContentSize; } - public int EmitToStream(Stream outputFileStream) + public override int Emit(Stream outputFileStream) { AssignSegmentLayout(); int size = 0; diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs index 4f2d31a766d191..4d6e26a4e5d6ec 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using System.Diagnostics; @@ -9,31 +8,18 @@ namespace ILCompiler.ObjectWriter { internal sealed class WasmSections { - private readonly List _sections = new(); + private readonly List _sections = new(); private readonly Dictionary _sectionNameToIndex = new(); public int Count => _sections.Count; - public IReadOnlyList Sections => _sections; + public IReadOnlyList Sections => _sections; - public SectionDataEmitter this[int sectionIndex] => _sections[sectionIndex]; + public WasmSection this[int sectionIndex] => _sections[sectionIndex]; - public SectionDataEmitter this[string sectionName] => _sections[_sectionNameToIndex[sectionName]]; + public WasmSection this[string sectionName] => _sections[_sectionNameToIndex[sectionName]]; - public TSection GetSection(int sectionIndex) - where TSection : SectionDataEmitter - { - SectionDataEmitter section = _sections[sectionIndex]; - return (TSection)section; - } - - public TSection GetSection(string sectionName) - where TSection : SectionDataEmitter - { - return GetSection(_sectionNameToIndex[sectionName]); - } - - public void Add(string sectionName, int sectionIndex, SectionDataEmitter section) + public void Add(string sectionName, int sectionIndex, WasmSection section) { Debug.Assert(_sections.Count == sectionIndex); _sections.Add(section); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs index d7be2c692be653..4f0a0c92a3cba9 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs @@ -11,18 +11,16 @@ namespace ILCompiler.ObjectWriter { - /// - /// A WebCIL section is a subsection of the "webcilPayload" data segment in the WebAssembly module. - /// - internal class WebcilSection : SectionDataEmitter + internal class WebcilSection : WasmSection { + public readonly int Index; public WebcilSectionHeader Header; public int Alignment { get; private set; } = WebCilObjectWriter.WebcilSectionAlignment; - public uint Padding => Header.SizeOfRawData - (uint)ContentReadStream.Length; + public uint Padding => Header.SizeOfRawData - (uint)_stream.Length; - public WebcilSection(Utf8String name, WebcilSectionHeader header, Stream stream, int sectionIndex) - : base(stream, name, sectionIndex) + public WebcilSection(Utf8String name, WebcilSectionHeader header, Stream stream, int index) + : base(WasmSectionType.Data, stream, name) { Header = header; } @@ -35,17 +33,17 @@ public void UpdateAlignment(int alignment) public override int EncodeSize() { - return (int)ContentReadStream.Length; + return (int)_stream.Length; } - public override int EmitToStream(Stream outputFileStream) + public override int Emit(Stream outputFileStream) { // Emit the raw contents of this Webcil section followed by any required padding. ContentReadStream.Position = 0; ContentReadStream.CopyTo(outputFileStream); WasmDataSegmentEncoding.EmitPadding(outputFileStream, (int)Padding); - return (int)ContentReadStream.Length + (int)Padding; + return (int)_stream.Length + (int)Padding; } } } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmGlobalImports.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmGlobalImports.cs index a678b633dc8e09..a90047bb7e0425 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmGlobalImports.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmGlobalImports.cs @@ -7,7 +7,7 @@ namespace ILCompiler.ObjectWriter /// Indices of the Wasm globals imported from the webcil host module into every R2R Wasm module. /// /// - /// Must stay in sync with WebCilObjectWriter.CreateDefaultGlobalImports(), _globalSymbolNameToGlobalIndex, + /// Must stay in sync with WasmObjectWriter.CreateDefaultGlobalImports(), _globalSymbolNameToGlobalIndex, /// and the host loader (libCorerun.js). The JIT references these via relocatable well-known-global /// handles (CORINFO_WASM_WELLKNOWN_GLOBALS), not the indices below. /// diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index 9bc77de3a9f201..d6be7e5bd04511 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -72,21 +72,10 @@ public enum WasmExternalKind : byte Count = 0x05 // Not actually part of the spec; used for counting kinds } - /// - /// WebAssembly export descriptor kinds per the specification. - /// - internal enum WasmExportKind : byte - { - Function = 0x00, - Table = 0x01, - Memory = 0x02, - Global = 0x03, - } - public class WasmGlobalImportType : WasmImportType { - private readonly WasmValueType _valueType; - private readonly WasmMutabilityType _mutability; + WasmValueType _valueType; + WasmMutabilityType _mutability; public WasmGlobalImportType(WasmValueType valueType, WasmMutabilityType mutability) : base (WasmExternalKind.Global) { @@ -115,7 +104,7 @@ public WasmTableImportType() : base (WasmExternalKind.Table) public override int Encode(Span buffer) { int pos = 0; - buffer[pos++] = (byte)0x70; // element type: funcref + buffer[pos++] = (byte)0x70; // element type: funcref buffer[pos++] = (byte)0; // table limits: flags (0 = min-only, 1 = min+max) pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), 1); // Requires 1 table entry return pos; @@ -131,12 +120,12 @@ public enum WasmLimitType : byte HasMin = 0x00, HasMinAndMax = 0x01 } - + public class WasmMemoryImportType : WasmImportType { - private readonly WasmLimitType _limitType; - private readonly uint _min; - private readonly uint? _max; + WasmLimitType _limitType; + uint _min; + uint? _max; public WasmMemoryImportType(WasmLimitType limitType, uint min, uint? max = null) : base(WasmExternalKind.Memory) { From dacf3bf15a2e3bca9bb4e8c781b7067d7df2b141 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 11 Aug 2026 09:20:07 -0700 Subject: [PATCH 05/21] Undo changes to ObjectWriter --- .../ObjectWriter/Wasm/WasmDataSection.cs | 2 +- .../ObjectWriter/Wasm/WasmSections.cs | 30 +++++++++++++++---- .../ObjectWriter/Wasm/WebcilSection.cs | 20 +++++++------ .../ObjectWriter/WasmGlobalImports.cs | 2 +- .../Compiler/ObjectWriter/WasmNative.cs | 25 +++++++++++----- .../ObjectWriter/WasmSymbolManager.cs | 15 ++++++++-- 6 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSection.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSection.cs index e32f6fcbe8918d..a291551f0d0a2a 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSection.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmDataSection.cs @@ -74,7 +74,7 @@ public int EncodeSize() return HeaderSize + ContentSize; } - public override int Emit(Stream outputFileStream) + public int EmitToStream(Stream outputFileStream) { AssignSegmentLayout(); int size = 0; diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs index 4d6e26a4e5d6ec..ad5266e2220e07 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Diagnostics; @@ -8,18 +9,37 @@ namespace ILCompiler.ObjectWriter { internal sealed class WasmSections { - private readonly List _sections = new(); + private readonly List _sections = new(); private readonly Dictionary _sectionNameToIndex = new(); public int Count => _sections.Count; - public IReadOnlyList Sections => _sections; + public IReadOnlyList Sections => _sections; - public WasmSection this[int sectionIndex] => _sections[sectionIndex]; + public SectionDataEmitter this[int sectionIndex] => _sections[sectionIndex]; - public WasmSection this[string sectionName] => _sections[_sectionNameToIndex[sectionName]]; + public SectionDataEmitter this[string sectionName] => _sections[_sectionNameToIndex[sectionName]]; - public void Add(string sectionName, int sectionIndex, WasmSection section) + public TSection GetSection(int sectionIndex) + where TSection : SectionDataEmitter + { + SectionDataEmitter section = _sections[sectionIndex]; + if (section is TSection typedSection) + { + return typedSection; + } + + throw new InvalidOperationException( + $"Section at index {sectionIndex} is {section.GetType().Name}, not {typeof(TSection).Name}."); + } + + public TSection GetSection(string sectionName) + where TSection : SectionDataEmitter + { + return GetSection(_sectionNameToIndex[sectionName]); + } + + public void Add(string sectionName, int sectionIndex, SectionDataEmitter section) { Debug.Assert(_sections.Count == sectionIndex); _sections.Add(section); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs index 4f0a0c92a3cba9..9ab02d4cf70ae2 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs @@ -11,16 +11,18 @@ namespace ILCompiler.ObjectWriter { - internal class WebcilSection : WasmSection + /// + /// A WebCIL section is a subsection of the "webcilPayload" data segment in the WebAssembly module. + /// + internal class WebcilSection : SectionDataEmitter { - public readonly int Index; public WebcilSectionHeader Header; public int Alignment { get; private set; } = WebCilObjectWriter.WebcilSectionAlignment; - public uint Padding => Header.SizeOfRawData - (uint)_stream.Length; + public uint Padding => Header.SizeOfRawData - (uint)ContentReadStream.Length; - public WebcilSection(Utf8String name, WebcilSectionHeader header, Stream stream, int index) - : base(WasmSectionType.Data, stream, name) + public WebcilSection(Utf8String name, WebcilSectionHeader header, Stream stream, int sectionIndex) + : base(stream, name, sectionIndex) { Header = header; } @@ -31,19 +33,19 @@ public void UpdateAlignment(int alignment) Alignment = Math.Max(Alignment, alignment); } - public override int EncodeSize() + public override int EncodedSize() { - return (int)_stream.Length; + return (int)ContentReadStream.Length; } - public override int Emit(Stream outputFileStream) + public override int EmitToStream(Stream outputFileStream) { // Emit the raw contents of this Webcil section followed by any required padding. ContentReadStream.Position = 0; ContentReadStream.CopyTo(outputFileStream); WasmDataSegmentEncoding.EmitPadding(outputFileStream, (int)Padding); - return (int)_stream.Length + (int)Padding; + return (int)ContentReadStream.Length + (int)Padding; } } } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmGlobalImports.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmGlobalImports.cs index a90047bb7e0425..a678b633dc8e09 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmGlobalImports.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmGlobalImports.cs @@ -7,7 +7,7 @@ namespace ILCompiler.ObjectWriter /// Indices of the Wasm globals imported from the webcil host module into every R2R Wasm module. /// /// - /// Must stay in sync with WasmObjectWriter.CreateDefaultGlobalImports(), _globalSymbolNameToGlobalIndex, + /// Must stay in sync with WebCilObjectWriter.CreateDefaultGlobalImports(), _globalSymbolNameToGlobalIndex, /// and the host loader (libCorerun.js). The JIT references these via relocatable well-known-global /// handles (CORINFO_WASM_WELLKNOWN_GLOBALS), not the indices below. /// diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index d6be7e5bd04511..9bc77de3a9f201 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -72,10 +72,21 @@ public enum WasmExternalKind : byte Count = 0x05 // Not actually part of the spec; used for counting kinds } + /// + /// WebAssembly export descriptor kinds per the specification. + /// + internal enum WasmExportKind : byte + { + Function = 0x00, + Table = 0x01, + Memory = 0x02, + Global = 0x03, + } + public class WasmGlobalImportType : WasmImportType { - WasmValueType _valueType; - WasmMutabilityType _mutability; + private readonly WasmValueType _valueType; + private readonly WasmMutabilityType _mutability; public WasmGlobalImportType(WasmValueType valueType, WasmMutabilityType mutability) : base (WasmExternalKind.Global) { @@ -104,7 +115,7 @@ public WasmTableImportType() : base (WasmExternalKind.Table) public override int Encode(Span buffer) { int pos = 0; - buffer[pos++] = (byte)0x70; // element type: funcref + buffer[pos++] = (byte)0x70; // element type: funcref buffer[pos++] = (byte)0; // table limits: flags (0 = min-only, 1 = min+max) pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), 1); // Requires 1 table entry return pos; @@ -120,12 +131,12 @@ public enum WasmLimitType : byte HasMin = 0x00, HasMinAndMax = 0x01 } - + public class WasmMemoryImportType : WasmImportType { - WasmLimitType _limitType; - uint _min; - uint? _max; + private readonly WasmLimitType _limitType; + private readonly uint _min; + private readonly uint? _max; public WasmMemoryImportType(WasmLimitType limitType, uint min, uint? max = null) : base(WasmExternalKind.Memory) { diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs index 1fe51a1d5eb0f7..b53d4ad06dd570 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs @@ -56,6 +56,7 @@ public T this[WasmIndexSpace indexSpace] } private readonly Dictionary _entries = new(); + private readonly Dictionary _aliases = new(); private IndexSpaceArray _importCounts = new IndexSpaceArray(); private IndexSpaceArray _definitionCounts = new IndexSpaceArray(); private IndexSpaceArray _importsFrozen = new IndexSpaceArray(); @@ -76,14 +77,21 @@ public void AddDefinition(Utf8String name, WasmIndexSpace indexSpace) _definitionCounts[indexSpace]++; } + public void AddAlias(Utf8String alias, Utf8String target) + { + Entry entry = _entries[target]; + _aliases.Add(alias, entry with { Name = alias }); + } + public WasmSymbol GetSymbol(Utf8String name) { - return ResolveAndFreeze(_entries[name]); + return ResolveAndFreeze(GetEntry(name)); } public bool TryGetSymbol(Utf8String name, out WasmSymbol symbol) { - if (!_entries.TryGetValue(name, out Entry entry)) + if (!_entries.TryGetValue(name, out Entry entry) && + !_aliases.TryGetValue(name, out entry)) { symbol = default; return false; @@ -93,6 +101,9 @@ public bool TryGetSymbol(Utf8String name, out WasmSymbol symbol) return true; } + private Entry GetEntry(Utf8String name) => + _entries.TryGetValue(name, out Entry entry) ? entry : _aliases[name]; + public int GetImportCount() => _importCounts.Values.Sum(); public int GetDefinitionCount(WasmIndexSpace indexSpace) => From 30ed4cf0e9eedd09b1fe90ca9e6c7cd2616a565a Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 17 Aug 2026 14:33:17 -0700 Subject: [PATCH 06/21] Fix build --- .../Compiler/ObjectWriter/WasmInstructions.cs | 20 ++++++++++--------- .../Compiler/ObjectWriter/WasmNative.cs | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs index afc549c7171399..d8d03bb2e01aa8 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs @@ -204,7 +204,7 @@ public static bool IsVariableLengthInstruction(this WasmExprKind kind) // Represents a group of Wasm instructions (expressions) which // form a complete expression ending with the 'end' opcode. - public class WasmInstructionGroup : IWasmEncodable + internal sealed class WasmInstructionGroup : IWasmEncodable { private readonly WasmExpr[] _wasmExprs; public WasmInstructionGroup(WasmExpr[] wasmExprs) @@ -472,8 +472,8 @@ public override int Encode(Span buffer) internal sealed class WasmIndirectCallInstruction : WasmExpr { - private ISymbolNode _type; - private uint _tableIndex; + private readonly ISymbolNode _type; + private readonly uint _tableIndex; public WasmIndirectCallInstruction(WasmExprKind kind, ISymbolNode type, uint tableIndex) : base(kind) { @@ -752,17 +752,17 @@ internal enum WasmAbsHeapType : byte internal sealed class WasmRefNullExpr : WasmExpr { - private WasmAbsHeapType absheaptype; + private readonly WasmAbsHeapType _absoluteHeapType; public WasmRefNullExpr(WasmAbsHeapType heapType) : base(WasmExprKind.RefNull) { - absheaptype = heapType; + _absoluteHeapType = heapType; } public override int Encode(Span buffer) { int pos = base.Encode(buffer); - buffer[pos++] = (byte)absheaptype; + buffer[pos++] = (byte)_absoluteHeapType; return pos; } public override int EncodeSize() @@ -782,15 +782,17 @@ internal enum WasmBlockType : byte } internal sealed class WasmBlockStartExpr : WasmExpr { - private WasmBlockType BlockType; + private readonly WasmBlockType _blockType; + public WasmBlockStartExpr(WasmExprKind kind, WasmBlockType blockType) : base(kind) { - BlockType = blockType; + _blockType = blockType; } + public override int Encode(Span buffer) { int pos = base.Encode(buffer); - buffer[pos++] = (byte)BlockType; + buffer[pos++] = (byte)_blockType; return pos; } public override int EncodeSize() diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index 9bc77de3a9f201..401d1b74d97646 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -222,7 +222,7 @@ public WasmImport(string module, string name, WasmImportType import, int? index public int EncodeRelocations(Span buffer) => Import.EncodeRelocations(buffer); } - public class WasmGlobal : IWasmEncodable + internal sealed class WasmGlobal : IWasmEncodable { public readonly int Index; public readonly string Name; @@ -230,7 +230,7 @@ public class WasmGlobal : IWasmEncodable private readonly WasmMutabilityType _mutability; private readonly WasmInstructionGroup _initExpr; - public WasmGlobal(int index, string name, WasmValueType valueType, WasmMutabilityType mutability, WasmInstructionGroup initExpr) + internal WasmGlobal(int index, string name, WasmValueType valueType, WasmMutabilityType mutability, WasmInstructionGroup initExpr) { Index = index; Name = name; From 226a24d22ad09524e6c4c1760546421056cfbec2 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 17 Aug 2026 14:33:28 -0700 Subject: [PATCH 07/21] Get SingleMethodTests working --- .../ObjectWriter/WasmRelocatableObjectWriter.cs | 11 +++++++++++ .../WasmSingleMethodTests.cs | 10 +++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs index 864b3254183ee2..8a7c3c45ca079f 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs @@ -186,6 +186,9 @@ void WriteRelocFromDataSpan(SymbolicRelocation reloc, byte* pData, long sectionS } } + // TODO: This is a temporary workaround for the fact that we don't yet emit a COMDAT section (or any reloc / linking sections) + private protected override bool UsesSubsectionsViaSymbols => true; + private protected override SectionDataEmitter CreateDataSection( ObjectNodeSection section, int sectionIndex, @@ -245,6 +248,14 @@ private protected override void WriteImports() private protected override void WriteExports() { + // TODO-WASM: Handle exports better (e.g., only export public methods, etc.) + IEnumerable functionSymbols = _wasmSymbolManager.GetDefinitions( + WasmIndexSpace.Function, + Comparer.Create(static (x, y) => x.Name.CompareTo(y.Name))); + foreach (WasmSymbol symbol in functionSymbols) + { + WriteFunctionExport(symbol.Name.ToString(), symbol.Index); + } } private protected override void WriteElements() diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs index 0eb70da110a9e8..2deb78a2d63390 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs @@ -63,13 +63,13 @@ public void NativeAotWasmSingleMethodExecutes() throw new Error("NativeAOT produced an invalid WebAssembly module."); } const webcil = { - stackPointer: new WebAssembly.Global({ value: "i32", mutable: true }, 65000), - imageBase: new WebAssembly.Global({ value: "i32", mutable: false }, 0), - tableBase: new WebAssembly.Global({ value: "i32", mutable: false }, 0), - asyncContinuation: new WebAssembly.Global({ value: "i32", mutable: true }, 0), + __stack_pointer: new WebAssembly.Global({ value: "i32", mutable: true }, 65000), + __memory_base: new WebAssembly.Global({ value: "i32", mutable: false }, 0), + __table_base: new WebAssembly.Global({ value: "i32", mutable: false }, 0), + __async_continuation: new WebAssembly.Global({ value: "i32", mutable: true }, 0), table: new WebAssembly.Table({ initial: 4096, element: "anyfunc" }), rtlRestoreContextTag: new WebAssembly.Tag({ parameters: [] }), - memory: new WebAssembly.Memory({ initial: 16 }), + memory: new WebAssembly.Memory({ initial: 32 }), }; WebAssembly.instantiate(bytes, { webcil }).then(({ instance }) => { const result = instance.exports.{{ExportName}}(65000, 0); From 2373503ae2ae360a681fe0a29185b8c92791dbd5 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:15:50 -0700 Subject: [PATCH 08/21] Rename EncodedSize back to EncodeSize --- .../tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs index 9ab02d4cf70ae2..d7be2c692be653 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WebcilSection.cs @@ -33,7 +33,7 @@ public void UpdateAlignment(int alignment) Alignment = Math.Max(Alignment, alignment); } - public override int EncodedSize() + public override int EncodeSize() { return (int)ContentReadStream.Length; } From 7dccd099675f468ba99ce325a55c9d65d1181c54 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:13:17 -0700 Subject: [PATCH 09/21] Revert files that don't really need to be changed --- .../Common/Compiler/ObjectWriter/Wasm/WasmSections.cs | 8 +------- .../Common/Compiler/ObjectWriter/WasmObjectWriter.cs | 11 +++++++---- .../Target_Wasm/WasmReadyToRunGenericHelperNode.cs | 9 +++------ .../Target_Wasm/WasmReadyToRunHelperNode.cs | 2 +- .../Target_Wasm/WasmUnboxingStubNode.cs | 2 +- .../ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs | 5 +---- 6 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs index ad5266e2220e07..4f2d31a766d191 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/Wasm/WasmSections.cs @@ -24,13 +24,7 @@ public TSection GetSection(int sectionIndex) where TSection : SectionDataEmitter { SectionDataEmitter section = _sections[sectionIndex]; - if (section is TSection typedSection) - { - return typedSection; - } - - throw new InvalidOperationException( - $"Section at index {sectionIndex} is {section.GetType().Name}, not {typeof(TSection).Name}."); + return (TSection)section; } public TSection GetSection(string sectionName) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index e4b080690cd342..af2fcf8296d2e6 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -17,7 +17,7 @@ namespace ILCompiler.ObjectWriter { /// - /// Base class for WebAssembly object file format writers. + /// Base class for WebAssembly object writers. /// internal abstract partial class WasmObjectWriter : ObjectWriter { @@ -382,9 +382,12 @@ private protected void FinalizeSectionEntryCounts() _sections.GetSection(ObjectNodeSection.WasmCodeSection.Name) .SetEntryCount(MethodCount); - Debug.Assert(_sections.GetSection(WasmObjectNodeSection.FunctionSection.Name).EntryCount == MethodCount); - Debug.Assert(_sections.GetSection(WasmObjectNodeSection.ImportSection.Name).EntryCount == _wasmSymbolManager.GetImportCount()); - Debug.Assert(_sections.GetSection(WasmObjectNodeSection.GlobalSection.Name).EntryCount == _wasmSymbolManager.GetDefinitionCount(WasmIndexSpace.Global)); + Debug.Assert(!_sections.Contains(WasmObjectNodeSection.FunctionSection.Name) + || _sections.GetSection(WasmObjectNodeSection.FunctionSection.Name).EntryCount == MethodCount); + Debug.Assert(!_sections.Contains(WasmObjectNodeSection.ImportSection.Name) + || _sections.GetSection(WasmObjectNodeSection.ImportSection.Name).EntryCount == _wasmSymbolManager.GetImportCount()); + Debug.Assert(!_sections.Contains(WasmObjectNodeSection.GlobalSection.Name) + || _sections.GetSection(WasmObjectNodeSection.GlobalSection.Name).EntryCount == _wasmSymbolManager.GetDefinitionCount(WasmIndexSpace.Global)); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs index dfed6e0e5c3eb9..c5c13718eb873c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs @@ -11,14 +11,12 @@ public partial class ReadyToRunGenericHelperNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new PlatformNotSupportedException( - "NativeAOT WebAssembly does not support runtime generic dictionary lookup helpers."); + throw new NotImplementedException(); } protected virtual void EmitLoadGenericContext(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new PlatformNotSupportedException( - "NativeAOT WebAssembly runtime generic dictionary context loading is not supported."); + throw new NotImplementedException(); } } @@ -26,8 +24,7 @@ public partial class ReadyToRunGenericLookupFromTypeNode { protected override void EmitLoadGenericContext(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new PlatformNotSupportedException( - "NativeAOT WebAssembly runtime generic dictionary context loading from a type is not supported."); + throw new NotImplementedException(); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs index 09702fc27ce327..310f7e22c3153d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs @@ -11,7 +11,7 @@ public partial class ReadyToRunHelperNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new PlatformNotSupportedException("NativeAOT WebAssembly ReadyToRun helpers are not supported."); + throw new NotImplementedException(); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs index 253053cd051ea0..0ed23ac753b3c3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs @@ -11,7 +11,7 @@ public partial class UnboxingStubNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new PlatformNotSupportedException("NativeAOT WebAssembly unboxing stubs are not supported."); + throw new NotImplementedException(); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs index a1b006eba54336..5c635bf69e3b4c 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs @@ -110,11 +110,8 @@ protected override void CompileInternal(string outputFile, ObjectDumper dumper) if ((_compilationOptions & RyuJitCompilationOptions.UseDwarf5) != 0) options |= ObjectWritingOptions.UseDwarf5; - if (_debugInformationProvider is not NullDebugInformationProvider && - NodeFactory.Target.Architecture != TargetArchitecture.Wasm32) - { + if (_debugInformationProvider is not NullDebugInformationProvider) options |= ObjectWritingOptions.GenerateDebugInfo; - } if ((_compilationOptions & RyuJitCompilationOptions.ControlFlowGuardAnnotations) != 0) options |= ObjectWritingOptions.ControlFlowGuard; From 14fa67febd17d3ff10b3463606e6df22813ba971 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:14:07 -0700 Subject: [PATCH 10/21] Copy Switch.cs instead of reaching into a far away directory --- .../ILCompiler.Compiler.Tests.Assets.csproj | 4 --- .../SwitchStatement.cs | 31 +++++++++++++++++++ .../WasmSingleMethodTests.cs | 4 +-- 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/SwitchStatement.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj index 2bd02d648b64e7..2e0b217160f958 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj @@ -15,8 +15,4 @@ - - - - diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/SwitchStatement.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/SwitchStatement.cs new file mode 100644 index 00000000000000..58255045407b15 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/SwitchStatement.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Xunit; +public class SwitchTest +{ + const int Pass = 100; + const int Fail = -1; + + [Fact] + public static int TestEntryPoint() + { + int sum =0; + for(int i=2; i < 5; i++) { + switch(i) { + case 2: + sum += i; + break; + case 3: + sum += i; + break; + default: + sum -= 5; + break; + } + } + + return sum == 0 ? Pass : Fail; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs index 2deb78a2d63390..530683f4a624a2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs @@ -62,7 +62,7 @@ public void NativeAotWasmSingleMethodExecutes() if (!WebAssembly.validate(bytes)) { throw new Error("NativeAOT produced an invalid WebAssembly module."); } - const webcil = { + const env = { __stack_pointer: new WebAssembly.Global({ value: "i32", mutable: true }, 65000), __memory_base: new WebAssembly.Global({ value: "i32", mutable: false }, 0), __table_base: new WebAssembly.Global({ value: "i32", mutable: false }, 0), @@ -71,7 +71,7 @@ public void NativeAotWasmSingleMethodExecutes() rtlRestoreContextTag: new WebAssembly.Tag({ parameters: [] }), memory: new WebAssembly.Memory({ initial: 32 }), }; - WebAssembly.instantiate(bytes, { webcil }).then(({ instance }) => { + WebAssembly.instantiate(bytes, { env }).then(({ instance }) => { const result = instance.exports.{{ExportName}}(65000, 0); if (result !== 100) { throw new Error(`Expected 100, got ${result}.`); From cde99827c847a1e6db23bed2c5f9487812d03d4f Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 21 Aug 2026 09:55:27 -0700 Subject: [PATCH 11/21] Don't change MethodCodeNode.GetEmitSection now that ObjectWriter handles it --- .../Compiler/DependencyAnalysis/MethodCodeNode.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs index 1d5c713c5596d8..ad7dcee682698e 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs @@ -50,10 +50,7 @@ public void SetCode(ObjectData data) public override ObjectNodeSection GetSection(NodeFactory factory) { return factory.Target.IsWindows ? - ObjectNodeSection.ManagedCodeWindowsContentSection : - factory.Target.IsWasm ? - ObjectNodeSection.WasmCodeSection : - ObjectNodeSection.ManagedCodeUnixContentSection; + ObjectNodeSection.ManagedCodeWindowsContentSection : ObjectNodeSection.ManagedCodeUnixContentSection; } public override bool StaticDependenciesAreComputed => _methodCode != null; From b7f7c87ba96d0e54ab0fd54d5f09c7a78e407b25 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:02:41 -0700 Subject: [PATCH 12/21] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../WasmSingleMethodTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs index 530683f4a624a2..7f45857284e1c8 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs @@ -71,12 +71,12 @@ public void NativeAotWasmSingleMethodExecutes() rtlRestoreContextTag: new WebAssembly.Tag({ parameters: [] }), memory: new WebAssembly.Memory({ initial: 32 }), }; - WebAssembly.instantiate(bytes, { env }).then(({ instance }) => { - const result = instance.exports.{{ExportName}}(65000, 0); - if (result !== 100) { - throw new Error(`Expected 100, got ${result}.`); - } - }); + const module = new WebAssembly.Module(bytes); + const instance = new WebAssembly.Instance(module, { env }); + const result = instance.exports.{{ExportName}}(65000, 0); + if (result !== 100) { + throw new Error(`Expected 100, got ${result}.`); + } """); ProcessResult result = RunProcess("node", [scriptPath], throwOnError: false); From 53066b92e3e4a19c8ae9d0a9f129b0a42919e4ae Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:41:35 -0700 Subject: [PATCH 13/21] Delete singlemethod tests --- eng/Subsets.props | 6 - .../Compiler/ObjectWriter/WasmObjectWriter.cs | 2 +- .../SwitchStatement.cs | 31 --- .../XunitStubs.cs | 9 - .../ILCompiler.Compiler.Tests.csproj | 20 -- .../WasmSingleMethodTests.cs | 195 ------------------ .../ReadyToRunGenericHelperNode.cs | 4 - 7 files changed, 1 insertion(+), 266 deletions(-) delete mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/SwitchStatement.cs delete mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs delete mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs diff --git a/eng/Subsets.props b/eng/Subsets.props index 9d4fc37e0a0fa2..0674e93cf4ded7 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -339,10 +339,6 @@ $(ClrRuntimeBuildSubsets);ClrWasmJitSubset=true - - $(ClrRuntimeBuildSubsets);ClrWasmJitSubset=true - - $(ClrRuntimeBuildSubsets);ClrPalTestsSubset=true @@ -400,8 +396,6 @@ - <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' != 'wasm' and $(_subset.Contains('+clr.toolstests+'))" Include="ClrAllJitsSubset=true" /> - <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and ('$(TargetArchitecture)' == 'x64' or '$(TargetArchitecture)' == 'arm64') and $(_subset.Contains('+clr.toolstests+')) and ('$(BuildArchitecture)' == 'x64' or '$(BuildArchitecture)' == 'arm64')" Include="ClrWasmJitSubset=true" /> <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' != 'wasm' and ($(_subset.Contains('+clr.tools+')) or $(_subset.Contains('+clr.nativecorelib+')) or $(_subset.Contains('+clr.crossarchtools+')))" Include="ClrJitSubset=true" /> <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' == 'wasm' and ($(_subset.Contains('+clr.tools+')) or $(_subset.Contains('+clr.nativecorelib+')) or $(_subset.Contains('+clr.crossarchtools+')))" Include="ClrWasmJitSubset=true" /> diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index af2fcf8296d2e6..e5b66cbe3af934 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -50,7 +50,7 @@ internal abstract partial class WasmObjectWriter : ObjectWriter WasmObjectNodeSection.ElementSection.Name, WasmObjectNodeSection.DataCountSection.Name, ObjectNodeSection.WasmCodeSection.Name, - WasmObjectNodeSection.DataSection.Name, + // Data section is not emitted as a single ObjectNodeSection, and must be handled separately by the derived class. ]; private protected readonly Dictionary _definedGlobals = new(); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/SwitchStatement.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/SwitchStatement.cs deleted file mode 100644 index 58255045407b15..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/SwitchStatement.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using Xunit; -public class SwitchTest -{ - const int Pass = 100; - const int Fail = -1; - - [Fact] - public static int TestEntryPoint() - { - int sum =0; - for(int i=2; i < 5; i++) { - switch(i) { - case 2: - sum += i; - break; - case 3: - sum += i; - break; - default: - sum -= 5; - break; - } - } - - return sum == 0 ? Pass : Fail; - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs deleted file mode 100644 index 4d4f9ef801ffa6..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Xunit -{ - internal sealed class FactAttribute : System.Attribute - { - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj index ef24d8566e4d3b..69e9d87637f92d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj @@ -26,9 +26,6 @@ - - Configuration=$(CoreCLRConfiguration) - false @@ -46,22 +43,5 @@ - - - - - <_NativeAotWasmTestSupported Condition="('$(BuildArchitecture)' == 'x64' or '$(BuildArchitecture)' == 'arm64') and ('$(TargetArchitecture)' == 'x64' or '$(TargetArchitecture)' == 'arm64')">true - - - - - $(_NativeAotWasmTestSupported) - - - $(BuildArchitecture) - - - $(CoreCLRArtifactsPath) - diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs deleted file mode 100644 index 7f45857284e1c8..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs +++ /dev/null @@ -1,195 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; - -using Microsoft.DotNet.XUnitExtensions; - -using Xunit; - -namespace ILCompiler.Compiler.Tests -{ - public class WasmSingleMethodTests - { - private const string ExportName = "ILCompiler_Compiler_Tests_Assets_SwitchTest__TestEntryPoint"; - private static readonly byte[] WasmHeader = [0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00]; - - public static bool IsWasmCompilationSupported => - string.Equals( - AppContext.GetData("NativeAotWasmTest.IsSupported") as string, - "true", - StringComparison.OrdinalIgnoreCase); - - public static bool IsWasmExecutionSupported => - IsWasmCompilationSupported && - RunProcess( - "node", - ["-e", "process.exit(typeof WebAssembly.Tag === 'function' ? 0 : 1)"], - throwOnError: false).ExitCode == 0; - - [ConditionalFact(nameof(IsWasmCompilationSupported))] - public void NativeAotWasmSingleMethodCompiles() - { - string outputPath = CompileSwitchTest(); - try - { - byte[] output = File.ReadAllBytes(outputPath); - Assert.True(output.Length >= WasmHeader.Length); - Assert.Equal(WasmHeader, output.AsSpan(0, WasmHeader.Length).ToArray()); - } - finally - { - File.Delete(outputPath); - } - } - - [ConditionalFact(nameof(IsWasmExecutionSupported))] - public void NativeAotWasmSingleMethodExecutes() - { - string outputPath = CompileSwitchTest(); - string scriptPath = Path.ChangeExtension(outputPath, ".js"); - try - { - File.WriteAllText(scriptPath, - $$""" - const fs = require("fs"); - const bytes = fs.readFileSync({{ToJavaScriptString(outputPath)}}); - if (!WebAssembly.validate(bytes)) { - throw new Error("NativeAOT produced an invalid WebAssembly module."); - } - const env = { - __stack_pointer: new WebAssembly.Global({ value: "i32", mutable: true }, 65000), - __memory_base: new WebAssembly.Global({ value: "i32", mutable: false }, 0), - __table_base: new WebAssembly.Global({ value: "i32", mutable: false }, 0), - __async_continuation: new WebAssembly.Global({ value: "i32", mutable: true }, 0), - table: new WebAssembly.Table({ initial: 4096, element: "anyfunc" }), - rtlRestoreContextTag: new WebAssembly.Tag({ parameters: [] }), - memory: new WebAssembly.Memory({ initial: 32 }), - }; - const module = new WebAssembly.Module(bytes); - const instance = new WebAssembly.Instance(module, { env }); - const result = instance.exports.{{ExportName}}(65000, 0); - if (result !== 100) { - throw new Error(`Expected 100, got ${result}.`); - } - """); - - ProcessResult result = RunProcess("node", [scriptPath], throwOnError: false); - Assert.True(result.ExitCode == 0, result.Output); - } - finally - { - File.Delete(scriptPath); - File.Delete(outputPath); - } - } - - private static string CompileSwitchTest() - { - string coreClrArtifactsDir = Assert.IsType(AppContext.GetData("NativeAotWasmTest.CoreCLRArtifactsDir")); - string buildArchitecture = Assert.IsType(AppContext.GetData("NativeAotWasmTest.BuildArchitecture")); - string ilcPath = Path.Combine( - coreClrArtifactsDir, - buildArchitecture, - "ilc", - OperatingSystem.IsWindows() ? "ilc.exe" : "ilc"); - string jitFileName = OperatingSystem.IsWindows() - ? $"clrjit_universal_wasm_{buildArchitecture}.dll" - : OperatingSystem.IsMacOS() - ? $"libclrjit_universal_wasm_{buildArchitecture}.dylib" - : $"libclrjit_universal_wasm_{buildArchitecture}.so"; - string jitPath = Path.Combine(coreClrArtifactsDir, jitFileName); - if (!File.Exists(jitPath)) - { - jitPath = Path.Combine(coreClrArtifactsDir, buildArchitecture, jitFileName); - } - - Assert.True(File.Exists(jitPath), $"WASM JIT not found at '{jitPath}'."); - - string outputPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.wasm"); - try - { - RunProcess( - ilcPath, - [ - "--singlemethodtypename", "SwitchTest, ILCompiler.Compiler.Tests.Assets", - "--singlemethodname", "TestEntryPoint", - Path.Combine(AppContext.BaseDirectory, "ILCompiler.Compiler.Tests.Assets.dll"), - $"-r:{Path.Combine(AppContext.BaseDirectory, "Test.CoreLib.dll")}", - "--systemmodule:Test.CoreLib", - $"-o:{outputPath}", - "--targetarch:wasm", - "--targetos:browser", - $"--jitpath:{jitPath}", - "--stacktracedata:none", - "--reflectiondata:none", - ], - throwOnError: true); - - return outputPath; - } - catch - { - File.Delete(outputPath); - throw; - } - } - - private static ProcessResult RunProcess(string fileName, IEnumerable arguments, bool throwOnError) - { - var startInfo = new ProcessStartInfo(fileName) - { - RedirectStandardError = true, - RedirectStandardOutput = true, - UseShellExecute = false, - }; - foreach (string argument in arguments) - { - startInfo.ArgumentList.Add(argument); - } - - try - { - using Process process = Process.Start(startInfo) ?? - throw new InvalidOperationException($"Failed to start '{fileName}'."); - Task standardOutput = process.StandardOutput.ReadToEndAsync(); - Task standardError = process.StandardError.ReadToEndAsync(); - process.WaitForExit(); - - var result = new ProcessResult( - process.ExitCode, - standardOutput.GetAwaiter().GetResult() + standardError.GetAwaiter().GetResult()); - if (throwOnError && result.ExitCode != 0) - { - throw new InvalidOperationException(result.Output); - } - - return result; - } - catch (Exception ex) when (!throwOnError && ex is Win32Exception or InvalidOperationException) - { - return new ProcessResult(-1, ex.ToString()); - } - } - - private static string ToJavaScriptString(string value) => - '"' + value.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("\"", "\\\"", StringComparison.Ordinal) + '"'; - - private readonly struct ProcessResult - { - public ProcessResult(int exitCode, string output) - { - ExitCode = exitCode; - Output = output; - } - - public int ExitCode { get; } - public string Output { get; } - } - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index a8e07d0f528fca..ec6fa5d049cd3f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -13,10 +13,6 @@ namespace ILCompiler.DependencyAnalysis { - /// - /// Represents a NativeAOT runtime generic dictionary lookup helper. - /// "ReadyToRun" refers to the JIT helper ABI used to request the lookup, not to the ReadyToRun compiler. - /// public abstract partial class ReadyToRunGenericHelperNode : AssemblyStubNode, INodeWithRuntimeDeterminedDependencies { private readonly ReadyToRunHelperId _id; From ea9fce9836a80d461ea0607b006024a8ac08fe15 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 26 Aug 2026 12:10:39 -0700 Subject: [PATCH 14/21] Remove changes unnecessary for single method compilations --- src/coreclr/jit/codegenlinear.cpp | 13 +------ src/coreclr/jit/codegenwasm.cpp | 12 +++---- src/coreclr/jit/flowgraph.cpp | 3 -- src/coreclr/jit/lowerwasm.cpp | 34 ++++++------------- .../WasmRelocatableObjectWriter.cs | 10 +++++- 5 files changed, 27 insertions(+), 45 deletions(-) diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index dcdc7fa13b9e5e..f866893b560bf0 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -870,12 +870,6 @@ void CodeGen::genEmitEndBlock(BasicBlock* block) break; case BBJ_SWITCH: -#if defined(TARGET_WASM) - if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) - { - genEmitFunctionEnd(); - } -#endif break; case BBJ_ALWAYS: @@ -926,6 +920,7 @@ void CodeGen::genEmitEndBlock(BasicBlock* block) genEmitFunctionEnd(); } #endif // defined(TARGET_WASM) + break; case BBJ_COND: @@ -936,12 +931,6 @@ void CodeGen::genEmitEndBlock(BasicBlock* block) SetLoopAlignBackEdge(block, block->GetFalseTarget()); #endif // FEATURE_LOOP_ALIGN -#if defined(TARGET_WASM) - if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) - { - genEmitFunctionEnd(); - } -#endif break; default: diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index ac47cfd7964c7a..f030abdd1aadcc 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -398,17 +398,17 @@ void CodeGen::genFnEpilog(BasicBlock* block) { if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) { - genEmitFunctionEnd(/* emitTerminalUnreachable */ false); + instGen(INS_end); } return; } // TODO-WASM: shadow stack maintenance - // Close the root function before the first funclet starts. Other returns - // within the root function leave the remaining root blocks reachable. + // TODO-WASM: we need to handle the end-of-function case if we reach the end of a codegen for a function + // and do NOT have an epilog. In those cases we currently will not emit an end instruction. if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) { - genEmitFunctionEnd(/* emitTerminalUnreachable */ false); + instGen(INS_end); } else { @@ -3287,7 +3287,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) if (target != nullptr) { // Codegen should have already evaluated our target node (last) and pushed it onto the stack, - // ready for call_indirect. Consume it. + // ready for call_indirect. Consume it. genConsumeReg(target); params.callType = EC_INDIR_R; @@ -3425,7 +3425,7 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, params.wasmSignature = m_compiler->info.compCompHnd->getWasmTypeSymbol(types, typeCount); - if (helperIsManaged) + 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 diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 5b781136190bb7..b4b3ba67ae71ab 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -866,10 +866,7 @@ GenTreeCall* Compiler::fgGetSharedCCtor(CORINFO_CLASS_HANDLE cls) { #if defined(TARGET_WASM) // Wasm does not support dynamically created helpers - if (!IsNativeAot()) - { return fgGetStaticsCCtorHelper(cls, CORINFO_HELP_INITCLASS); - } #endif #ifdef FEATURE_READYTORUN diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index dfefc2360908da..ab06ed972328bc 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -66,28 +66,22 @@ void Lowering::LowerPEPCall(GenTreeCall* call) JITDUMP("Begin lowering PEP call\n"); DISPTREERANGE(BlockRange(), call); - GenTree* callTargetForArg; - if (call->gtControlExpr != nullptr) - { - LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); + // PEP call must always have a control expression + assert(call->gtControlExpr != nullptr); + LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); - JITDUMP("Creating new local variable for PEP"); - unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); - callTargetForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); - } - else - { - assert(call->gtDirectCallAddress != nullptr); - callTargetForArg = AddrGen(call->gtDirectCallAddress); - } + JITDUMP("Creating new local variable for PEP"); + unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); + GenTreeLclVar* callTargetLclForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); DISPTREE(call); JITDUMP("Add new arg to call arg list corresponding to PEP target"); - NewCallArg pepTargetArg = NewCallArg::Primitive(callTargetForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); - CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); + NewCallArg pepTargetArg = + NewCallArg::Primitive(callTargetLclForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); + CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); pepArg->SetEarlyNode(nullptr); - pepArg->SetLateNode(callTargetForArg); + pepArg->SetLateNode(callTargetLclForArg); call->gtArgs.PushLateBack(pepArg); // Set up ABI information for this arg; PEP's should be passed as the last param to a wasm function @@ -96,18 +90,12 @@ void Lowering::LowerPEPCall(GenTreeCall* call) pepArg->AbiInfo = ABIPassingInformation::FromSegmentByValue(m_compiler, ABIPassingSegment::InRegister(pepReg, 0, TARGET_POINTER_SIZE)); - BlockRange().InsertBefore(call, callTargetForArg); + BlockRange().InsertBefore(call, callTargetLclForArg); // Lower the new PEP arg now that the call abi info is updated and lcl var is inserted LowerArg(call, pepArg); DISPTREE(call); - if (call->gtControlExpr == nullptr) - { - JITDUMP("Finished lowering direct PEP call\n"); - return; - } - JITDUMP("Rewrite PEP call's control expression to indirect through the new local variable\n"); // Rewrite the call's control expression to have an additional load from the PEP local diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs index 8a7c3c45ca079f..c64ee2d11db7bb 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs @@ -112,6 +112,7 @@ private protected override void EmitRelocations(int sectionIndex, List relocs, long sectionStart = 0) { + // TODO: We also need to emit relocations in the reloc section for the linker to resolve. byte[] relocScratchBuffer = new byte[Relocation.MaxSize]; foreach (SymbolicRelocation reloc in relocs) @@ -158,12 +159,19 @@ private unsafe void ResolveRelocations(int sectionIndex, MemoryStream sectionStr Relocation.WriteValue(reloc.Type, pData, symbol.Index + addend); break; } + 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; + } default: // TODO-WASM: add other cases as needed; // ignoring other reloc types for now throw new NotSupportedException($"Relocation type {reloc.Type} for symbol '{reloc.SymbolName}' at " - + $"offset 0x{reloc.Offset:X} in section {sectionIndex} not yet implemented"); + + $"offset 0x{reloc.Offset:X} in section {_sections[sectionIndex].SectionName} not yet implemented"); } From da4733e196fe8ee56d4557a418343c5302374139 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 26 Aug 2026 18:28:22 -0700 Subject: [PATCH 15/21] Revert tab in flowgraph.cpp --- src/coreclr/jit/flowgraph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index b4b3ba67ae71ab..c94494d517d39d 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -866,7 +866,7 @@ GenTreeCall* Compiler::fgGetSharedCCtor(CORINFO_CLASS_HANDLE cls) { #if defined(TARGET_WASM) // Wasm does not support dynamically created helpers - return fgGetStaticsCCtorHelper(cls, CORINFO_HELP_INITCLASS); + return fgGetStaticsCCtorHelper(cls, CORINFO_HELP_INITCLASS); #endif #ifdef FEATURE_READYTORUN From ab17bd1f34742a6250e4b8dd36c460ca67d5541b Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 26 Aug 2026 18:58:57 -0700 Subject: [PATCH 16/21] Update src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs --- .../tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs index b53d4ad06dd570..815e33b2cb5393 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs @@ -79,6 +79,7 @@ public void AddDefinition(Utf8String name, WasmIndexSpace indexSpace) public void AddAlias(Utf8String alias, Utf8String target) { + Debug.Assert(!_entries.ContainsKey(alias)); Entry entry = _entries[target]; _aliases.Add(alias, entry with { Name = alias }); } From c74880e03a2d0dd873ab4ed02e215d299e20abf9 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:00:58 -0700 Subject: [PATCH 17/21] Move getWasmType to shared CorInfoImpl.cs --- src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | 9 +++++++++ .../JitInterface/CorInfoImpl.ReadyToRun.cs | 8 -------- .../ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs | 8 -------- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 6463dc16006975..7d7045e5c93673 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -3678,6 +3678,15 @@ private void getWasmWellKnownGlobals(ref CORINFO_WASM_WELLKNOWN_GLOBALS pWellKno pWellKnownGlobalsOut.tableBase = (CORINFO_WASM_GLOBAL_SYMBOL_STRUCT_*)ObjectToHandle(factory.GetWellKnownWasmGlobalSymbol(new(WasmWellKnownGlobalSymbolNode.TableBaseName))); pWellKnownGlobalsOut.asyncContinuation = (CORINFO_WASM_GLOBAL_SYMBOL_STRUCT_*)ObjectToHandle(factory.GetWellKnownWasmGlobalSymbol(new(WasmWellKnownGlobalSymbolNode.AsyncContinuationName))); } + + private CORINFO_WASM_TYPE_SYMBOL_STRUCT_* getWasmTypeSymbol(CorInfoWasmType* types, nuint typesSize) + { + CorInfoWasmType[] typeArray = new ReadOnlySpan(types, (int)typesSize).ToArray(); + + WasmTypeNode typeNode = _compilation.NodeFactory.WasmTypeNode(typeArray); + return (CORINFO_WASM_TYPE_SYMBOL_STRUCT_*)ObjectToHandle(typeNode); + } + private CORINFO_METHOD_STRUCT_* getAwaitReturnCall(CORINFO_METHOD_STRUCT_* callerHandle, CORINFO_CONTEXT_STRUCT** contextHandle, ref CORINFO_LOOKUP instArg) { instArg.lookupKind.needsRuntimeLookup = false; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 9f35b84c022184..f0f379a6eafa6e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -3733,14 +3733,6 @@ private bool notifyMethodInfoUsage(CORINFO_METHOD_STRUCT_* ftn) return _compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(method); } - private CORINFO_WASM_TYPE_SYMBOL_STRUCT_* getWasmTypeSymbol(CorInfoWasmType* types, nuint typesSize) - { - CorInfoWasmType[] typeArray = new ReadOnlySpan(types, (int)typesSize).ToArray(); - - WasmTypeNode typeNode = _compilation.NodeFactory.WasmTypeNode(typeArray); - return (CORINFO_WASM_TYPE_SYMBOL_STRUCT_*)ObjectToHandle(typeNode); - } - #pragma warning disable CA1822 // Mark members as static private void getThreadLocalStaticInfo_NativeAOT(CORINFO_THREAD_STATIC_INFO_NATIVEAOT* pInfo) { diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 1b788a6098c652..956677c2dc6b88 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -2503,14 +2503,6 @@ private void getThreadLocalStaticInfo_NativeAOT(CORINFO_THREAD_STATIC_INFO_NATIV pInfo->tlsGetAddrFtnPtr = CreateConstLookupToSymbol(_compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("__tls_get_addr"u8))); } - private CORINFO_WASM_TYPE_SYMBOL_STRUCT_* getWasmTypeSymbol(CorInfoWasmType* types, nuint typesSize) - { - CorInfoWasmType[] typeArray = new ReadOnlySpan(types, (int)typesSize).ToArray(); - - WasmTypeNode typeNode = _compilation.NodeFactory.WasmTypeNode(typeArray); - return (CORINFO_WASM_TYPE_SYMBOL_STRUCT_*)ObjectToHandle(typeNode); - } - #pragma warning disable CA1822 // Mark members as static private bool notifyMethodInfoUsage(CORINFO_METHOD_STRUCT_* ftn) #pragma warning restore CA1822 // Mark members as static From 7c91e19b225caa2814a409b7389de1ffcc49c2a5 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:01:29 -0700 Subject: [PATCH 18/21] Remove PEP param from helper definitions in codegenwasm.cpp --- src/coreclr/jit/codegenwasm.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index f030abdd1aadcc..c36fcb5290c4d9 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -3368,6 +3368,7 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, CorInfoWasmType* types = nullptr; size_t typeCount = 0; bool helperIsManaged = false; + bool helperUsesPep = false; const bool MANAGED = true, UNMANAGED = false; #ifdef TARGET_64BIT @@ -3383,6 +3384,9 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, types = helper_id##_types; \ typeCount = ArrLen(helper_id##_types); \ helperIsManaged = is_managed; \ + helperUsesPep = helperIsManaged && m_compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PORTABLE_ENTRY_POINTS); \ + if (helperIsManaged /* `types` includes PEP */ && !helperUsesPep) \ + typeCount--; \ break; \ } @@ -3425,7 +3429,7 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, params.wasmSignature = m_compiler->info.compCompHnd->getWasmTypeSymbol(types, typeCount); - if (helperIsManaged && m_compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PORTABLE_ENTRY_POINTS)) + if (helperUsesPep) { // 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 From 4bb64681e90f72ef9a4604d9bd8df94faa465836 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 4 Sep 2026 10:53:46 -0700 Subject: [PATCH 19/21] Fix Wasm type lookup after rebase Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Compiler/DependencyAnalysis/NodeFactory.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 2b04541d7d741b..a25655b2f78860 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -1630,6 +1630,11 @@ public WasmTypeNode WasmTypeNode(MethodDesc desc) return _wasmTypeNodes.GetOrAdd(WasmLowering.GetSignature(desc).FuncType); } + public WasmTypeNode WasmTypeNode(CorInfoWasmType[] types) + { + return _wasmTypeNodes.GetOrAdd(WasmFuncType.FromCorInfoSignature(types)); + } + /// /// Returns alternative symbol name that object writer should produce for given symbols /// in addition to the regular one. From 41cb8550cedd64337a6211865c682d3dd3f3c923 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:03:27 -0700 Subject: [PATCH 20/21] Fix a couple locations that assumed PEP is enabled in WASM --- src/coreclr/jit/lowerwasm.cpp | 4 ++-- src/coreclr/tools/Common/JitInterface/WasmLowering.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index ab06ed972328bc..17a63814b27d04 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -35,11 +35,11 @@ void Lowering::SetMultiplyUsed(GenTree* node DEBUGARG(const char* reason)) // IsCallTargetInRange: Can a call target address be encoded in-place? // // Return Value: -// Currently always false for Wasm, all managed calls are indirect through the PEP. +// False when PEP is enabled, true when it is not enabled. // bool Lowering::IsCallTargetInRange(void* addr) { - return false; + return !m_compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PORTABLE_ENTRY_POINTS); } //--------------------------------------------------------------------------------------------- diff --git a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs index 7f26a8ea3fc039..f9aba55a11ef7c 100644 --- a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs +++ b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs @@ -810,11 +810,13 @@ public static WasmSignature GetSignature(MethodSignature signature, LoweringFlag } } +#if READYTORUN if (!flags.HasFlag(LoweringFlags.IsUnmanagedCallersOnly)) { result.Add(pointerType); // PE entrypoint parameter (encoded via 'p' suffix) sigBuilder.Append('p'); } +#endif WasmResultType ps = new(result.ToArray()); WasmResultType ret = returnIsVoid ? new(Array.Empty()) From 6c967c46b8afc77a190aafa4aa3f4be6ed457157 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:03:45 -0700 Subject: [PATCH 21/21] Address copilot feedback --- .../Common/Compiler/ObjectWriter/WasmObjectWriter.cs | 2 +- .../ObjectWriter/WasmRelocatableObjectWriter.cs | 12 +++++++++--- .../Compiler/ObjectWriter/WasmSymbolManager.cs | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index e5b66cbe3af934..af2fcf8296d2e6 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -50,7 +50,7 @@ internal abstract partial class WasmObjectWriter : ObjectWriter WasmObjectNodeSection.ElementSection.Name, WasmObjectNodeSection.DataCountSection.Name, ObjectNodeSection.WasmCodeSection.Name, - // Data section is not emitted as a single ObjectNodeSection, and must be handled separately by the derived class. + WasmObjectNodeSection.DataSection.Name, ]; private protected readonly Dictionary _definedGlobals = new(); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs index c64ee2d11db7bb..276c927aab2b3f 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs @@ -161,9 +161,15 @@ private unsafe void ResolveRelocations(int sectionIndex, MemoryStream sectionStr } 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); + if (_sections[definedSymbol.SectionIndex] is WasmDataSegmentEmitter segment) + { + int targetOffsetFromMemoryBase = segment.GetMemoryAddressOfOffset((int)(definedSymbol.Value + addend)); + Relocation.WriteValue(reloc.Type, pData, targetOffsetFromMemoryBase); + } + else + { + throw new NotImplementedException(); + } break; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs index 815e33b2cb5393..dbb2b52897fe1b 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs @@ -79,7 +79,8 @@ public void AddDefinition(Utf8String name, WasmIndexSpace indexSpace) public void AddAlias(Utf8String alias, Utf8String target) { - Debug.Assert(!_entries.ContainsKey(alias)); + Debug.Assert(!_entries.ContainsKey(alias), "Alias symbol name must not already exist."); + Debug.Assert(_entries.ContainsKey(target), "Target symbol name must exist before adding an alias."); Entry entry = _entries[target]; _aliases.Add(alias, entry with { Name = alias }); }