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 });
}