From 0a3293ce17ad35e932fc505fed115fd857a3e94f Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:27:49 -0700 Subject: [PATCH 01/12] Fix NativeAOT warnings in wasm code --- .../Compiler/ObjectWriter/WasmInstructions.cs | 84 +++++++++---------- .../Compiler/ObjectWriter/WasmNative.cs | 14 ++-- .../ObjectWriter/WasmObjectWriter.Base.cs | 16 +--- .../tools/Common/JitInterface/WasmLowering.cs | 2 +- 4 files changed, 54 insertions(+), 62 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs index d2723dccfadd08..3f0a8ea003e318 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs @@ -199,11 +199,11 @@ public static bool IsVariableLengthInstruction(this WasmExprKind kind) } } - // Represents a group of Wasm instructions (expressions) which + // Represents a group of Wasm instructions (expressions) which // form a complete expression ending with the 'end' opcode. public class WasmInstructionGroup : IWasmEncodable { - readonly WasmExpr[] _wasmExprs; + private readonly WasmExpr[] _wasmExprs; public WasmInstructionGroup(WasmExpr[] wasmExprs) { _wasmExprs = wasmExprs; @@ -266,7 +266,7 @@ public int EncodeRelocations(Span buffer) public abstract class WasmExpr : IWasmEncodable { - WasmExprKind _kind; + private WasmExprKind _kind; public WasmExpr(WasmExprKind kind) { _kind = kind; @@ -303,7 +303,7 @@ public static void OffsetRelocationsByOffset(Span buffer, int offset } } - readonly struct WasmEncodableULong : IWasmEncodable + internal readonly struct WasmEncodableULong : IWasmEncodable { private readonly ulong _value; public WasmEncodableULong(ulong value) @@ -322,7 +322,7 @@ public int Encode(Span buffer) public int EncodeRelocations(Span buffer) => 0; } - readonly struct WasmEncodableSymbol : IWasmEncodable + internal readonly struct WasmEncodableSymbol : IWasmEncodable { private readonly ISymbolNode _symbol; private readonly RelocType _relocType; @@ -374,10 +374,10 @@ public int EncodeRelocations(Span buffer) } } - class WasmMemoryArgInstruction : WasmExpr where TOffset : IWasmEncodable + internal sealed class WasmMemoryArgInstruction : WasmExpr where TOffset : IWasmEncodable { - readonly uint _align; - readonly TOffset _offset; + private readonly uint _align; + private readonly TOffset _offset; public WasmMemoryArgInstruction(WasmExprKind kind, uint align, TOffset offset) : base(kind) { @@ -418,9 +418,9 @@ public override int EncodeRelocations(Span buffer) } // Represents a constant expression (e.g., (i32.const )) - class WasmConstExpr : WasmExpr + internal sealed class WasmConstExpr : WasmExpr { - readonly long ConstValue; + private readonly long ConstValue; public WasmConstExpr(WasmExprKind kind, long value) : base(kind) { @@ -448,10 +448,10 @@ public override int Encode(Span buffer) } } - sealed class WasmIndirectCallInstruction : WasmExpr + internal sealed class WasmIndirectCallInstruction : WasmExpr { - ISymbolNode _type; - uint _tableIndex; + private ISymbolNode _type; + private uint _tableIndex; public WasmIndirectCallInstruction(WasmExprKind kind, ISymbolNode type, uint tableIndex) : base(kind) { @@ -484,9 +484,9 @@ public override int EncodeRelocations(Span buffer) } } - sealed class WasmLEBConstantReloc : WasmExpr + internal sealed class WasmLEBConstantReloc : WasmExpr { - readonly WasmEncodableSymbol _symbol; + private readonly WasmEncodableSymbol _symbol; public WasmLEBConstantReloc(WasmExprKind kind, ISymbolNode symbol, RelocType relocType) : base(kind) { @@ -511,7 +511,7 @@ public override int EncodeRelocations(Span buffer) } // Represents a local variable expression (e.g., (local.get )) - class WasmLocalVarExpr : WasmExpr + internal sealed class WasmLocalVarExpr : WasmExpr { public readonly int LocalIndex; public WasmLocalVarExpr(WasmExprKind kind, int localIndex) : base(kind) @@ -536,7 +536,7 @@ public override int EncodeSize() } // Represents a global variable expression (e.g., (global.get () - class WasmMemoryCopyExpr : WasmExpr + internal sealed class WasmMemoryCopyExpr : WasmExpr { public readonly int DstMemoryIndex; public readonly int SrcMemoryIndex; @@ -614,7 +614,7 @@ public override int EncodeSize() // Represents a memory.fill expression. // Binary encoding: 0xFC prefix + u32(11) sub-opcode + u32(memoryIndex) // Stack operands: (dst: i32, val: i32, len: i32) -> () - class WasmMemoryFillExpr : WasmExpr + internal sealed class WasmMemoryFillExpr : WasmExpr { public readonly int MemoryIndex; @@ -640,7 +640,7 @@ public override int EncodeSize() } // Represents a memory.init expression. - class WasmMemoryInitExpr : WasmExpr + internal sealed class WasmMemoryInitExpr : WasmExpr { public readonly int DataSegmentIndex; public readonly int MemoryIndex; @@ -672,7 +672,7 @@ public override int EncodeSize() // Represents a table.init expression. // Binary encoding: 0xFC prefix + u32(12) sub-opcode + u32(elemidx) + u32(tableidx) - class WasmTableInitExpr : WasmExpr + internal sealed class WasmTableInitExpr : WasmExpr { public readonly int ElemIndex; public readonly int TableIndex; @@ -702,7 +702,7 @@ public override int EncodeSize() } } - class WasmTableGrowExpr : WasmExpr + internal sealed class WasmTableGrowExpr : WasmExpr { public readonly uint TableIndex; @@ -723,14 +723,14 @@ public override int EncodeSize() } } - enum WasmAbsHeapType : byte + internal enum WasmAbsHeapType : byte { Func = 0x70, } - class WasmRefNullExpr : WasmExpr + internal sealed class WasmRefNullExpr : WasmExpr { - WasmAbsHeapType absheaptype; + private WasmAbsHeapType absheaptype; public WasmRefNullExpr(WasmAbsHeapType heapType) : base(WasmExprKind.RefNull) { @@ -749,7 +749,7 @@ public override int EncodeSize() } } - enum WasmBlockType : byte + internal enum WasmBlockType : byte { Empty = 0x40, I32 = 0x7F, @@ -758,9 +758,9 @@ enum WasmBlockType : byte F64 = 0x7C, V128 = 0x7B, } - class WasmBlockStartExpr : WasmExpr + internal sealed class WasmBlockStartExpr : WasmExpr { - WasmBlockType BlockType; + private WasmBlockType BlockType; public WasmBlockStartExpr(WasmExprKind kind, WasmBlockType blockType) : base(kind) { BlockType = blockType; @@ -780,7 +780,7 @@ public override int EncodeSize() // ************************************************ // Simple DSL wrapper for creating Wasm expressions // ************************************************ - static class Local + internal static class Local { public static WasmExpr Get(int index) { @@ -796,7 +796,7 @@ public static WasmExpr Tee(int index) } } - static class Global + internal static class Global { public static WasmExpr Get(int index) { @@ -808,7 +808,7 @@ public static WasmExpr Set(int index) } } - static class I32 + internal static class I32 { public static WasmExpr Const(long value) { @@ -827,7 +827,7 @@ public static WasmExpr ConstRVA(ISymbolNode symbolNode) public static WasmExpr Store(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.I32Store, 4, new WasmEncodableULong(offset)); } - static class I64 + internal static class I64 { public static WasmExpr Const(long value) { @@ -837,25 +837,25 @@ public static WasmExpr Const(long value) public static WasmExpr Store(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.I64Store, 8, new WasmEncodableULong(offset)); } - static class F32 + internal static class F32 { public static WasmExpr Load(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.F32Load, 4, new WasmEncodableULong(offset)); public static WasmExpr Store(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.F32Store, 4, new WasmEncodableULong(offset)); } - static class F64 + internal static class F64 { public static WasmExpr Load(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.F64Load, 8, new WasmEncodableULong(offset)); public static WasmExpr Store(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.F64Store, 8, new WasmEncodableULong(offset)); } - static class V128 + internal static class V128 { public static WasmExpr Load(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.V128Load, 16, new WasmEncodableULong(offset)); public static WasmExpr Store(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.V128Store, 16, new WasmEncodableULong(offset)); } - static class Memory + internal static class Memory { public static WasmExpr Copy(int dstMemoryIndex = 0, int srcMemoryIndex = 0) { @@ -872,20 +872,20 @@ public static WasmExpr Init(int dataSegmentIndex, int memoryIndex = 0) return new WasmMemoryInitExpr(dataSegmentIndex, memoryIndex); } } - static class ControlFlow + internal static class ControlFlow { public static WasmExpr CallIndirect(ISymbolNode funcType, uint tableIndex) => new WasmIndirectCallInstruction(WasmExprKind.CallIndirect, funcType, tableIndex); } - static class Table + internal static class Table { public static WasmExpr Grow(uint tableIndex) => new WasmTableGrowExpr(tableIndex); public static WasmExpr Init(int elemSegmentIndex, int tableIndex = 0) => new WasmTableInitExpr(elemSegmentIndex, tableIndex); } - static class Ref + internal static class Ref { public static WasmExpr NullFuncRef => new WasmRefNullExpr(WasmAbsHeapType.Func); } - static class Block + internal static class Block { public static WasmExpr If(WasmBlockType blockType) => new WasmBlockStartExpr(WasmExprKind.If, blockType); public static WasmExpr End => new WasmUnaryExpr(WasmExprKind.End); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index 6441eef518f560..9bc77de3a9f201 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -85,8 +85,8 @@ internal enum WasmExportKind : byte 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) { @@ -115,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; @@ -131,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/WasmObjectWriter.Base.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs index 9e2f2ae580f4ab..b6dfc5b12a655a 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs @@ -21,11 +21,6 @@ namespace ILCompiler.ObjectWriter /// internal abstract partial class WasmObjectWriter : ObjectWriter { - public const int StackPointerGlobalIndex = WasmGlobalImports.StackPointerGlobalIndex; - public const int ImageBaseGlobalIndex = WasmGlobalImports.ImageBaseGlobalIndex; - public const int TableBaseGlobalIndex = WasmGlobalImports.TableBaseGlobalIndex; - public const int AsyncContinuationGlobalIndex = WasmGlobalImports.AsyncContinuationGlobalIndex; - private readonly Dictionary _sectionToType = new() { { WasmObjectNodeSection.MemorySection, WasmSectionType.Memory }, @@ -74,13 +69,10 @@ private protected int[] SectionEmitOrder { get { - if (_sectionEmitOrder is null) - { - _sectionEmitOrder = _sectionOrder - .Where(name => _sections.Contains(name)) - .Select(name => _sections.GetSectionIndex(name)) - .ToArray(); - } + _sectionEmitOrder ??= _sectionOrder + .Where(_sections.Contains) + .Select(_sections.GetSectionIndex) + .ToArray(); return _sectionEmitOrder; } diff --git a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs index 592ec82427ad16..1d7bdd03e6d639 100644 --- a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs +++ b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs @@ -394,7 +394,7 @@ public static WasmSignature GetSignature(MethodSignature signature, LoweringFlag { if (!flags.HasFlag(LoweringFlags.IsUnmanagedCallersOnly) && signature.Flags.HasFlag(MethodSignatureFlags.UnmanagedCallingConvention)) { - flags = flags | LoweringFlags.IsUnmanagedCallersOnly; + flags |= LoweringFlags.IsUnmanagedCallersOnly; } TypeDesc returnType = signature.ReturnType; From baaecb067803d3e8931e0ba84ac06d5c9183e10a Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 4 Aug 2026 12:42:33 -0700 Subject: [PATCH 02/12] Add reproZero project --- .../tools/aot/ILCompiler/ILCompiler.props | 1 + .../tools/aot/ILCompiler/reproZero/Program.cs | 129 ++++++++++++++++++ .../aot/ILCompiler/reproZero/reproZero.csproj | 64 +++++++++ 3 files changed, 194 insertions(+) create mode 100644 src/coreclr/tools/aot/ILCompiler/reproZero/Program.cs create mode 100644 src/coreclr/tools/aot/ILCompiler/reproZero/reproZero.csproj diff --git a/src/coreclr/tools/aot/ILCompiler/ILCompiler.props b/src/coreclr/tools/aot/ILCompiler/ILCompiler.props index e901d56df167fb..84855fc2f189ad 100644 --- a/src/coreclr/tools/aot/ILCompiler/ILCompiler.props +++ b/src/coreclr/tools/aot/ILCompiler/ILCompiler.props @@ -52,5 +52,6 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler/reproZero/Program.cs b/src/coreclr/tools/aot/ILCompiler/reproZero/Program.cs new file mode 100644 index 00000000000000..a9f7df1193567b --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler/reproZero/Program.cs @@ -0,0 +1,129 @@ +// 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.Runtime; +using System.Runtime.InteropServices; + +#region A couple very basic things +namespace System +{ + public class Object + { +#pragma warning disable 169 + // The layout of object is a contract with the compiler. + private IntPtr m_pMethodTable; +#pragma warning restore 169 + } + public struct Void { } + + // The layout of primitive types is special cased because it would be recursive. + // These really don't need any fields to work. + public struct Boolean { } + public struct Char { } + public struct SByte { } + public struct Byte { } + public struct Int16 { } + public struct UInt16 { } + public struct Int32 { } + public struct UInt32 { } + public struct Int64 { } + public struct UInt64 { } + public struct IntPtr { } + public struct UIntPtr { } + public struct Single { } + public struct Double { } + + public abstract class ValueType { } + public abstract class Enum : ValueType { } + + public struct Nullable where T : struct { } + + public sealed class String { public readonly int Length; } + public abstract class Array { } + public abstract class Delegate { } + public abstract class MulticastDelegate : Delegate { } + + public struct RuntimeTypeHandle { } + public struct RuntimeMethodHandle { } + public struct RuntimeFieldHandle { } + + public class Attribute { } + + public enum AttributeTargets { } + + public sealed class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + public bool AllowMultiple { get; set; } + public bool Inherited { get; set; } + } + + public class AppContext + { + public static void SetData(string s, object o) { } + } + + namespace Runtime.CompilerServices + { + public class RuntimeHelpers + { + public static unsafe int OffsetToStringData => sizeof(IntPtr) + sizeof(int); + } + } +} +namespace System.Runtime.InteropServices +{ + public sealed class DllImportAttribute : Attribute + { + public DllImportAttribute(string dllName) { } + } +} +#endregion + +#region Things needed by ILC +namespace System +{ + namespace Runtime + { + internal sealed class RuntimeExportAttribute : Attribute + { + public RuntimeExportAttribute(string entry) { } + } + } + + class Array : Array { } +} + +namespace Internal.Runtime.CompilerHelpers +{ + // A class that the compiler looks for that has helpers to initialize the + // process. The compiler can gracefully handle the helpers not being present, + // but the class itself being absent is unhandled. Let's add an empty class. + class StartupCodeHelpers + { + // A couple symbols the generated code will need we park them in this class + // for no particular reason. These aid in transitioning to/from managed code. + // Since we don't have a GC, the transition is a no-op. + [RuntimeExport("RhpReversePInvoke")] + static void RhpReversePInvoke(IntPtr frame) { } + [RuntimeExport("RhpReversePInvokeReturn")] + static void RhpReversePInvokeReturn(IntPtr frame) { } + [RuntimeExport("RhpPInvoke")] + static void RhpPInvoke(IntPtr frame) { } + [RuntimeExport("RhpPInvokeReturn")] + static void RhpPInvokeReturn(IntPtr frame) { } + + [RuntimeExport("RhpFallbackFailFast")] + static void RhpFallbackFailFast() { while (true) ; } + } +} +#endregion + +unsafe class Program +{ + static int Main() + { + return 42; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler/reproZero/reproZero.csproj b/src/coreclr/tools/aot/ILCompiler/reproZero/reproZero.csproj new file mode 100644 index 00000000000000..9a501e553cfbcd --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler/reproZero/reproZero.csproj @@ -0,0 +1,64 @@ + + + + $(NetCoreAppToolCurrent) + Exe + x64;x86;wasm + AnyCPU + false + false + Debug;Release;Checked + true + false + true + true + false + false + false + v4.0.30319 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f1057e9c9600288390e09a8ea104f773fae7e770 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:25:16 -0700 Subject: [PATCH 03/12] Create a NativeAOT wasm objectwriter --- .../Compiler/ObjectWriter/ObjectWriter.cs | 1 + .../WasmRelocatableObjectWriter.cs | 82 +++++++++++++++++++ .../ILCompiler.Compiler.csproj | 8 ++ 3 files changed, 91 insertions(+) create mode 100644 src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index c2cf6b7fdf4505..54fe6c4a91617b 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -713,6 +713,7 @@ public static void EmitObject(string objectFilePath, IReadOnlyCollection relocationList) + { + // foreach (var reloc in relocationList) + // { + // if (!_resolvableRelocations.TryGetValue(sectionIndex, out List resolvable)) + // { + // _resolvableRelocations[sectionIndex] = resolvable = new List(); + // } + // // Unconditionally add the reloc to our resolvable list; we do some amount of relocation resolution + // // for all relocation types. + // resolvable.Add(reloc); + + // // A few relocation types (table indices and IMAGE_REL type relocs in Webcil) need + // // an additional runtime reloc as well to add a base address. + // // We defer the actual RVA computation to EmitObjectFile, where webcil section + // // VirtualAddresses will have been assigned. Here we just record the raw info. + // RelocType fileRelocType = Relocation.GetFileRelocationType(reloc.Type); + // if (fileRelocType is not RelocType.IMAGE_REL_BASED_ABSOLUTE) + // { + // Debug.Assert(WasmSections[sectionIndex] is WebcilSection); + // _pendingBaseRelocs.Add(new PendingBaseReloc(sectionIndex, reloc.Offset, fileRelocType)); + // } + // } + } + + protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment) + { + } + private protected override void WriteGlobalSection() + { + } + + private protected override void WriteImports() => throw new NotImplementedException(); + private protected override void WriteExports() => throw new NotImplementedException(); + private protected override void WriteElements() => throw new NotImplementedException(); + + } + + // AOT + internal sealed partial class WasmRelocatableObjectWriter : WasmObjectWriter + { + private protected override void EmitUnwindInfo(SectionWriter sectionWriter, INodeWithCodeInfo nodeWithCodeInfo, Utf8String currentSymbolName) => throw new NotImplementedException(); + private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder() => throw new NotImplementedException(); + private protected override void EmitDebugFunctionInfo(uint methodTypeIndex, Utf8String methodName, SymbolDefinition methodSymbol, INodeWithDebugInfo debugNode, bool hasSequencePoints) => throw new NotImplementedException(); + private protected override void EmitDebugSections(IDictionary definedSymbols) => throw new NotImplementedException(); + private protected override void CreateEhSections() => throw new NotImplementedException(); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index e26214457ac8c3..014d87408377bc 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -377,6 +377,14 @@ + + + + + + + + From 4c4b00e41e04df1ca373f52be1b70fbf416813da 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 04/12] Merge work from 131556 --- eng/Subsets.props | 5 + src/coreclr/jit/codegenlinear.cpp | 13 +- src/coreclr/jit/codegenwasm.cpp | 45 ++-- src/coreclr/jit/emitwasm.cpp | 2 +- src/coreclr/jit/flowgraph.cpp | 5 +- src/coreclr/jit/lower.cpp | 16 +- src/coreclr/jit/lowerwasm.cpp | 34 ++- .../Target_Wasm/WasmEmitter.cs | 20 +- .../ILCompiler.Compiler.Tests.Assets.csproj | 4 + .../XunitStubs.cs | 9 + .../ILCompiler.Compiler.Tests.csproj | 20 ++ .../WasmSingleMethodTests.cs | 195 ++++++++++++++++++ .../DependencyAnalysis/NodeFactory.cs | 12 +- .../ReadyToRunGenericHelperNode.cs | 4 + .../Target_Wasm/WasmJumpStubNode.cs | 2 +- .../WasmReadyToRunGenericHelperNode.cs | 9 +- .../Target_Wasm/WasmReadyToRunHelperNode.cs | 2 +- .../Target_Wasm/WasmTentativeMethodNode.cs | 2 +- .../Target_Wasm/WasmUnboxingStubNode.cs | 2 +- .../DependencyAnalysis/MethodCodeNode.cs | 7 +- .../Compiler/RyuJitCompilation.cs | 5 +- .../ILCompiler.RyuJit.csproj | 10 +- .../JitInterface/CorInfoImpl.RyuJit.cs | 6 +- 23 files changed, 366 insertions(+), 63 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 337bca6fa63caf..8201b66e9fe187 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -338,6 +338,10 @@ $(ClrRuntimeBuildSubsets);ClrWasmJitSubset=true + + $(ClrRuntimeBuildSubsets);ClrWasmJitSubset=true + + $(ClrRuntimeBuildSubsets);ClrPalTestsSubset=true @@ -394,6 +398,7 @@ <_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 c9dc081a0d4b23..b9a50148748e8a 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -872,6 +872,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: @@ -922,7 +928,6 @@ void CodeGen::genEmitEndBlock(BasicBlock* block) genEmitFunctionEnd(); } #endif // defined(TARGET_WASM) - break; case BBJ_COND: @@ -933,6 +938,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 96cf252708742b..1ce06482125015 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -416,17 +416,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 { @@ -3184,7 +3184,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; @@ -3195,16 +3195,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 { @@ -3247,9 +3253,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; } @@ -3309,12 +3314,14 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, if (helperIsManaged) { - // Push PEP onto the stack because we are calling a managed helper that expects it as the last parameter. - // The helper function address is the address of an indirection cell, so we load from the cell to get the PEP - // address to push. - assert(helperFunction.accessType == IAT_PVALUE); GetEmitter()->emitAddressConstant(helperFunction.addr); - GetEmitter()->emitIns_I(INS_I_load, EA_PTRSIZE, 0); + if (helperFunction.accessType != IAT_VALUE) + { + // 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 load the PEP from it. + assert(helperFunction.accessType == IAT_PVALUE); + GetEmitter()->emitIns_I(INS_I_load, EA_PTRSIZE, 0); + } } if (params.callType == EC_INDIR_R) diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index cced3becef1a9b..bf7eb773a948ee 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -320,7 +320,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 85bd63121d5559..3c4068e88c85d3 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 099e0400e1794c..2174e99f68d12a 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -7384,21 +7384,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. @@ -7415,7 +7421,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 5f73d554c8bf99..44d45ea6fa1aa1 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/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmEmitter.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmEmitter.cs index 900b7a089812e4..50ee58b73eb434 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmEmitter.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmEmitter.cs @@ -13,14 +13,26 @@ namespace ILCompiler.DependencyAnalysis.Wasm { - public struct WasmEmitter(NodeFactory factory, bool relocsOnly) + public struct WasmEmitter { #if READYTORUN public WasmFunctionBody FunctionBody = null; #endif - public bool Is64Bit => factory.Target.PointerSize == 8; - public bool RelocsOnly => relocsOnly; + private readonly NodeFactory _factory; + private readonly bool _relocsOnly; + + public WasmEmitter(NodeFactory factory, bool relocsOnly) + { + _factory = factory; + _relocsOnly = relocsOnly; +#if READYTORUN + FunctionBody = null; +#endif + } + + public bool Is64Bit => _factory.Target.PointerSize == 8; + public bool RelocsOnly => _relocsOnly; public ObjectNode.ObjectData Encode(ISymbolDefinitionNode symbolDefinitionNode) { @@ -33,7 +45,7 @@ public ObjectNode.ObjectData Encode(ISymbolDefinitionNode symbolDefinitionNode) return new ObjectNode.ObjectData(encodedThunk, relocs, 1, new ISymbolDefinitionNode[] { symbolDefinitionNode }); #else - return default(ObjectNode.ObjectData); + throw new PlatformNotSupportedException("NativeAOT WebAssembly assembly stubs are not supported."); #endif } } 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/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 636bf28595a23f..035042b3d012ff 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -11,6 +11,7 @@ using ILCompiler.DependencyAnalysisFramework; using Internal.IL; +using Internal.JitInterface; using Internal.NativeFormat; using Internal.Runtime; using Internal.Text; @@ -1618,9 +1619,14 @@ public AnalysisCharacteristicNode AnalysisCharacteristic(string ch) // memory efficiency on lookup public WasmTypeNode WasmTypeNode(MethodDesc desc) { - // TODO-Wasm: Construct proper function type based on the passed in MethodDesc - // once we have defined lowering rules for signatures in NativeAOT. - throw new NotImplementedException("NAOT wasm type signature lowering not yet implemented"); + WasmFuncType funcType = WasmLowering.GetSignature(desc).FuncType; + return _wasmTypeNodes.GetOrAdd(funcType); + } + + public WasmTypeNode WasmTypeNode(CorInfoWasmType[] types) + { + WasmFuncType funcType = WasmFuncType.FromCorInfoSignature(types); + return _wasmTypeNodes.GetOrAdd(funcType); } /// 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/WasmJumpStubNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmJumpStubNode.cs index 0adaef32572541..eca26b3792cc9a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmJumpStubNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmJumpStubNode.cs @@ -11,7 +11,7 @@ public partial class JumpStubNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException("NativeAOT WebAssembly jump stubs are not supported."); } } } 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/WasmTentativeMethodNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmTentativeMethodNode.cs index 7596bdeec2a9dd..f20476c7d37649 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmTentativeMethodNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmTentativeMethodNode.cs @@ -11,7 +11,7 @@ public partial class TentativeMethodNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException("NativeAOT WebAssembly tentative method stubs 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/ILCompiler.RyuJit.csproj b/src/coreclr/tools/aot/ILCompiler.RyuJit/ILCompiler.RyuJit.csproj index 12164a8e937f9f..c80cac8a2dfc77 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/ILCompiler.RyuJit.csproj +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/ILCompiler.RyuJit.csproj @@ -50,12 +50,6 @@ Compiler\JitHelper.cs - - ObjectWriter\WasmNative.cs - - - ObjectWriter\WasmInstructions.cs - IL\HelperExtensions.cs @@ -95,9 +89,7 @@ JitInterface\SwiftPhysicalLowering.cs - - JitInterface\WasmLowering.cs - + Pgo\TypeSystemEntityOrUnknown.cs 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 14f0f9b6c1e33a..4a74c65d1c0073 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 @@ -2518,7 +2519,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 9df520e36109a0b68f22f7aa5cfbc5d0c1cee367 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:31:23 -0700 Subject: [PATCH 05/12] Fix rebase issues --- .../Compiler/ObjectWriter/ObjectWriter.cs | 2 + .../Compiler/ObjectWriter/Wasm/WasmSection.cs | 6 +- ...jectWriter.Base.cs => WasmObjectWriter.cs} | 72 ++-- .../WasmRelocatableObjectWriter.cs | 12 +- .../ObjectWriter/WebCilObjectWriter.cs | 322 +----------------- .../ILCompiler.ReadyToRun.csproj | 1 + 6 files changed, 62 insertions(+), 353 deletions(-) rename src/coreclr/tools/Common/Compiler/ObjectWriter/{WasmObjectWriter.Base.cs => WasmObjectWriter.cs} (88%) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 54fe6c4a91617b..a6b608fe79d602 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -713,7 +713,9 @@ public static void EmitObject(string objectFilePath, IReadOnlyCollection /// Interface for types that can be emitted to an object stream. /// - interface IWasmEmittable + internal interface IWasmEmittable { int EmitToStream(Stream outputFileStream); int EncodedSize(); @@ -21,7 +21,7 @@ interface IWasmEmittable /// /// Interface for types that represent a WebAssembly section. /// - interface IWasmSection : IWasmEmittable + internal interface IWasmSection : IWasmEmittable { WasmSectionType Type { get; } } @@ -140,7 +140,7 @@ protected WasmVectorSection(WasmSectionType type, Stream stream, Utf8String name // the WasmSection abstraction. internal sealed class WasmExternallyCountedSection : WasmVectorSection { - private bool _entryCountSet = false; + private bool _entryCountSet; public WasmExternallyCountedSection(WasmSectionType type, Stream stream, Utf8String name, int sectionIndex) : base(type, stream, name, sectionIndex) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs similarity index 88% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index b6dfc5b12a655a..7ef3fb9242a66c 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -97,7 +97,7 @@ private protected override void CreateSection( Stream sectionStream) { WasmSectionType sectionType = GetWasmSectionType(section); - WasmSection wasmSection; + SectionDataEmitter wasmSection; if (sectionType == WasmSectionType.Data) { wasmSection = CreateDataSection(section, sectionIndex, sectionStream); @@ -107,13 +107,13 @@ private protected override void CreateSection( Utf8String sectionName = new(section.Name); wasmSection = sectionType switch { - WasmSectionType.Type or WasmSectionType.Code => new WasmExternallyCountedSection(sectionType, sectionStream, sectionName), - WasmSectionType.Import => new WasmImportSection(sectionStream, sectionName), - WasmSectionType.Function => new WasmFunctionSection(sectionStream, sectionName), - WasmSectionType.Global => new WasmGlobalSection(sectionStream, sectionName), - WasmSectionType.Export => new WasmExportSection(sectionStream, sectionName), - WasmSectionType.Element => new WasmElementSection(sectionStream, sectionName), - _ => new WasmSection(sectionType, sectionStream, sectionName), + WasmSectionType.Type or WasmSectionType.Code => new WasmExternallyCountedSection(sectionType, sectionStream, sectionName, sectionIndex), + WasmSectionType.Import => new WasmImportSection(sectionStream, sectionName, sectionIndex), + WasmSectionType.Function => new WasmFunctionSection(sectionStream, sectionName, sectionIndex), + WasmSectionType.Global => new WasmGlobalSection(sectionStream, sectionName, sectionIndex), + WasmSectionType.Export => new WasmExportSection(sectionStream, sectionName, sectionIndex), + WasmSectionType.Element => new WasmElementSection(sectionStream, sectionName, sectionIndex), + _ => new WasmSection(sectionType, sectionStream, sectionName, sectionIndex), }; } @@ -121,11 +121,10 @@ private protected override void CreateSection( _sections.Add(section.Name, sectionIndex, wasmSection); } - private protected virtual WasmSection CreateDataSection( + private protected abstract SectionDataEmitter CreateDataSection( ObjectNodeSection section, int sectionIndex, - Stream sectionStream) => - new(WasmSectionType.Data, sectionStream, new Utf8String(section.Name)); + Stream sectionStream); private protected override void RecordMethodSignature(WasmTypeNode signature) { @@ -189,9 +188,9 @@ private static WasmFuncType GetFuncletType(FuncletKind funcletKind, WasmValueTyp }; } - private void AddFunctionEntry(int signatureIndex) + private void WriteFunctionEntry(int signatureIndex) { - WasmFunctionSection section = GetOrCreateWasmSection( + WasmFunctionSection section = GetOrCreateSection( WasmObjectNodeSection.FunctionSection, out SectionWriter writer); section.WriteEntry(writer, signatureIndex); @@ -209,18 +208,18 @@ private void WriteSignatureIndexForFunction( throw new InvalidOperationException($"Signature index of {key} not found for function: {node.ToString()}"); } - AddFunctionEntry(signatureSymbol.Index); + WriteFunctionEntry(signatureSymbol.Index); } /// /// Adds the given import entry, including its prefix (module/name/kind) and body (external ref). /// - private protected void AddImport(WasmImport import) + private protected void WriteImport(WasmImport import) { Utf8String symbolName = new(import.Name); _wasmSymbolManager.AddImport(symbolName, GetIndexSpace(import.Kind), import.Index); - WasmImportSection section = GetOrCreateWasmSection( + WasmImportSection section = GetOrCreateSection( WasmObjectNodeSection.ImportSection, out SectionWriter writer); section.WriteEntry(writer, import); @@ -240,38 +239,45 @@ private protected void AddImport(WasmImport import) _ => throw new ArgumentOutOfRangeException(nameof(kind)), }; - private void AddExport(string name, WasmExportKind kind, int index) + private void WriteExport(string name, WasmExportKind kind, int index) { - WasmExportSection section = GetOrCreateWasmSection( + WasmExportSection section = GetOrCreateSection( WasmObjectNodeSection.ExportSection, out SectionWriter writer); section.WriteEntry(writer, new WasmExport(name, kind, index)); } - private protected void AddFunctionExport(string name, int functionIndex) => - AddExport(name, WasmExportKind.Function, functionIndex); + private protected void WriteFunctionExport(string name, int functionIndex) => + WriteExport(name, WasmExportKind.Function, functionIndex); - private protected void AddTableExport(string name, int tableIndex) => - AddExport(name, WasmExportKind.Table, tableIndex); + private protected void WriteTableExport(string name, int tableIndex) => + WriteExport(name, WasmExportKind.Table, tableIndex); - private protected void AddMemoryExport(string name, int memoryIndex) => - AddExport(name, WasmExportKind.Memory, memoryIndex); + private protected void WriteMemoryExport(string name, int memoryIndex) => + WriteExport(name, WasmExportKind.Memory, memoryIndex); - private protected void AddGlobalExport(string name, int globalIndex) => - AddExport(name, WasmExportKind.Global, globalIndex); + private protected void WriteGlobalExport(string name, int globalIndex) => + WriteExport(name, WasmExportKind.Global, globalIndex); - private protected void AddElementSegment(ReadOnlyMemory functionIndices) + private protected void WriteElementSegment(ReadOnlyMemory functionIndices) { - WasmElementSection section = GetOrCreateWasmSection( + WasmElementSection section = GetOrCreateSection( WasmObjectNodeSection.ElementSection, out SectionWriter writer); section.WriteEntry(writer, functionIndices); } - private protected TSection GetOrCreateWasmSection( + private protected SectionDataEmitter GetOrCreateSection( ObjectNodeSection section, out SectionWriter writer) - where TSection : WasmSection + { + return GetOrCreateSection(section, out writer); + } + + private protected TSection GetOrCreateSection( + ObjectNodeSection section, + out SectionWriter writer) + where TSection : SectionDataEmitter { writer = GetOrCreateSection(section); return _sections.GetSection(writer.SectionIndex); @@ -291,7 +297,7 @@ private WasmSectionType GetWasmSectionType(ObjectNodeSection section) // TODO-WASM: In the future, we may want to consider representing Wasm globals in the dependency graph so that they // can be referenced by other nodes and we can make effective use of them. - private protected void AddGlobal( + private protected void WriteGlobal( string name, WasmValueType valueType, WasmMutabilityType mutability, @@ -309,7 +315,7 @@ private protected void AddGlobal( bool added = _definedGlobals.TryAdd(name, global); Debug.Assert(added, $"Duplicate global name: {name}"); - WasmGlobalSection section = GetOrCreateWasmSection( + WasmGlobalSection section = GetOrCreateSection( WasmObjectNodeSection.GlobalSection, out SectionWriter writer); section.WriteEntry(writer, global); @@ -324,7 +330,7 @@ private protected void RegisterFunctionSymbol(Utf8String name) => private protected void RegisterStubIndexAndSignature(WasmFuncType signature) { int signatureIndex = RegisterSignature(signature); - AddFunctionEntry(signatureIndex); + WriteFunctionEntry(signatureIndex); } private protected void InsertWasmStub(Utf8String name, WasmFunctionBody body) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs index f1effac41a0563..4040029798d978 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs @@ -27,8 +27,8 @@ private protected override void EmitObjectFile(Stream outputFileStream) EmitWasmHeader(outputFileStream); foreach (int index in SectionEmitOrder) { - WasmSection section = _sections[index]; - section.Emit(outputFileStream); + SectionDataEmitter section = _sections[index]; + section.EmitToStream(outputFileStream); } } @@ -57,6 +57,14 @@ private protected override void EmitRelocations(int sectionIndex, List /// WebCIL object file format writer. /// @@ -90,195 +68,16 @@ private protected override void RecordWellKnownSymbol(Utf8String currentSymbolNa } } - private protected override WasmSection CreateDataSection( + private protected override SectionDataEmitter CreateDataSection( ObjectNodeSection section, int sectionIndex, Stream sectionStream) { -<<<<<<< HEAD - var mangledNameBuilder = new Utf8StringBuilder(); - signature.AppendMangledName(_nodeFactory.NameMangler, mangledNameBuilder); - Utf8String mangledName = mangledNameBuilder.ToUtf8String(); - // Record the signature's wasm type index in the shared symbol table. The signature bytes - // are emitted by the node's own data; here we only assign its index. - _wasmSymbolManager.AddDefinition(mangledName, WasmIndexSpace.Type); - } - - private protected override void RecordMethodDeclaration(INodeWithTypeSignature node) - { - WasmLowering.LoweringFlags flags = WasmLowering.LoweringFlags.None; - if (node.HasGenericContextArg) - { - flags |= WasmLowering.LoweringFlags.HasGenericContextArg; - } - if (node.IsAsyncCall) - { - flags |= WasmLowering.LoweringFlags.IsAsyncCall; - } - if (node.IsUnmanagedCallersOnly) - { - flags |= WasmLowering.LoweringFlags.IsUnmanagedCallersOnly; - } - WriteSignatureIndexForFunction(node.Signature, flags, node); - RegisterFunctionSymbol(new Utf8String(node.GetMangledName(_nodeFactory.NameMangler))); - if (node is INodeWithFunclets nodeWithFunclets) - { - RecordFunclets(nodeWithFunclets); - } - } - - private void RecordFunclets(INodeWithFunclets nodeWithFunclets) - { - FuncletKind[] funcletKinds = nodeWithFunclets.GetFuncletKinds(); - if (funcletKinds.Length < 1) - { - return; - } - - WasmValueType pointerType = _nodeFactory.Target.PointerSize == 8 ? WasmValueType.I64 : WasmValueType.I32; - string mangledNodeName = nodeWithFunclets.GetMangledName(_nodeFactory.NameMangler); - - for (int i = 0; i < funcletKinds.Length; i++) - { - WasmFuncType funcletSignature = GetFuncletType(funcletKinds[i], pointerType); - RegisterFunctionSymbol(new Utf8String($"{mangledNodeName}_funclet_{i}")); - RegisterStubIndexAndSignature(funcletSignature); - } - } - - private WasmFuncType GetFuncletType(FuncletKind funcletKind, WasmValueType pointerType) - { - return funcletKind switch - { - FuncletKind.CatchOrFilterHandler or FuncletKind.Filter => new WasmFuncType( - new([pointerType, pointerType, pointerType]), new([pointerType])), // (FP, SP, EXN) -> RESULT - _ => new WasmFuncType(new([pointerType, pointerType]), new([])), // (FP, SP) -> void - }; - } - - private void WriteFunctionEntry(int signatureIndex) - { - WasmFunctionSection section = GetOrCreateSection( - WasmObjectNodeSection.FunctionSection, - out SectionWriter writer); - section.WriteEntry(writer, signatureIndex); - } - - private void WriteSignatureIndexForFunction(MethodSignature managedSignature, WasmLowering.LoweringFlags flags, ISymbolNode node) - { - WasmFuncType signature = WasmLowering.GetSignature(managedSignature, flags).FuncType; - Utf8String key = signature.GetMangledName(_nodeFactory.NameMangler); - if (!_wasmSymbolManager.TryGetSymbol(key, out WasmSymbol signatureSymbol)) - { - throw new InvalidOperationException($"Signature index of {key} not found for function: {node.ToString()}"); - } - - WriteFunctionEntry(signatureSymbol.Index); - } - - /// - /// Adds the given import entry, including its prefix (module/name/kind) and body (external ref). - /// - private void WriteImport(WasmImport import) - { - Utf8String symbolName = new(import.Name); - _wasmSymbolManager.AddImport(symbolName, GetIndexSpace(import.Kind), import.Index); - - WasmImportSection section = GetOrCreateSection( - WasmObjectNodeSection.ImportSection, - out SectionWriter writer); - section.WriteEntry(writer, import); - } - - private void WriteExport(string name, WasmExportKind kind, int index) - { - WasmExportSection section = GetOrCreateSection( - WasmObjectNodeSection.ExportSection, - out SectionWriter writer); - section.WriteEntry(writer, new WasmExport(name, kind, index)); - } - - // Convenience methods for specific export types - private void WriteFunctionExport(string name, int functionIndex) => - WriteExport(name, WasmExportKind.Function, functionIndex); - - private void WriteTableExport(string name, int tableIndex) => - WriteExport(name, WasmExportKind.Table, tableIndex); - - private void WriteMemoryExport(string name, int memoryIndex) => - WriteExport(name, WasmExportKind.Memory, memoryIndex); - - private void WriteGlobalExport(string name, int globalIndex) => - WriteExport(name, WasmExportKind.Global, globalIndex); - - private void WriteElementSegment(ReadOnlyMemory functionIndices) - { - WasmElementSection section = GetOrCreateSection( - WasmObjectNodeSection.ElementSection, - out SectionWriter writer); - section.WriteEntry(writer, functionIndices); - } - - private WasmSections _sections = new(); - - private TSection GetOrCreateSection(ObjectNodeSection section, out SectionWriter writer) - where TSection : SectionDataEmitter - { - writer = base.GetOrCreateSection(section); - return _sections.GetSection(writer.SectionIndex); - } - - private TSection GetOrCreateSection(ObjectNodeSection section) - where TSection : SectionDataEmitter - { - var writer = base.GetOrCreateSection(section); - return _sections.GetSection(writer.SectionIndex); - } - - private SectionDataEmitter GetOrCreateSection(ObjectNodeSection section, out SectionWriter writer) - { - return GetOrCreateSection(section, out writer); - } - - private new SectionDataEmitter GetOrCreateSection(ObjectNodeSection section) - { - return GetOrCreateSection(section, out _); - } - - private Dictionary _sectionToType = new() - { - { WasmObjectNodeSection.MemorySection, WasmSectionType.Memory }, - { WasmObjectNodeSection.FunctionSection, WasmSectionType.Function }, - { WasmObjectNodeSection.TableSection, WasmSectionType.Table }, - { WasmObjectNodeSection.ElementSection, WasmSectionType.Element }, - { WasmObjectNodeSection.ExportSection, WasmSectionType.Export }, - { WasmObjectNodeSection.ImportSection, WasmSectionType.Import }, - { WasmObjectNodeSection.GlobalSection, WasmSectionType.Global }, - { ObjectNodeSection.WasmTypeSection, WasmSectionType.Type }, - { ObjectNodeSection.WasmCodeSection, WasmSectionType.Code }, - { WasmObjectNodeSection.DataCountSection, WasmSectionType.DataCount }, - }; - - private WasmSectionType GetWasmSectionType(ObjectNodeSection section) - { - if (!_sectionToType.ContainsKey(section)) - { - // All other sections map to generic data segments in Wasm - // TODO-WASM: Consider making the mapping explicit for every possible node type. - return WasmSectionType.Data; - } - return _sectionToType[section]; -======= -#if READYTORUN return new WebcilSection( new Utf8String(section.Name), default(WebcilSectionHeader), sectionStream, sectionIndex); -#else - return base.CreateDataSection(section, sectionIndex, sectionStream); -#endif ->>>>>>> 9bc33a024e8 (Move shared WASM logic to base writer) } protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment) @@ -371,32 +170,6 @@ public int GetFlatMappedSize() ] ); -<<<<<<< HEAD - // This effectively recreates the logic of RecordMethodBody/RecordMethodDeclaration, but for manually inserted stubs that are not - // represented by nodes in the dependency graph. - // TODO-Wasm: for maintability, we should try and push some of this into the dependency graph when we do more stub generation. - private void RegisterStubIndexAndSignature(WasmFuncType signature) - { - int signatureIndex = RegisterSignature(signature); - WriteFunctionEntry(signatureIndex); - } - - private void InsertWasmStub(Utf8String name, WasmFunctionBody body) - { - SectionDataEmitter codeSection = GetOrCreateSection(ObjectNodeSection.WasmCodeSection, out SectionWriter codeWriter); - - int codeSize = body.EncodeSize(); - byte[] data = new byte[codeSize]; - body.Encode(data); - - codeWriter.EmitSymbolDefinition(name); - codeWriter.EmitData(data); - - RegisterFunctionSymbol(name); - RegisterStubIndexAndSignature(body.Signature); - } -======= ->>>>>>> 9bc33a024e8 (Move shared WASM logic to base writer) private long ResolveSymbolRVA(WebcilSection[] sections, SymbolDefinition definition) { for (int i = 0; i < sections.Length; i++) @@ -506,41 +279,6 @@ private protected override ObjectNodeSection GetEmitSection(ObjectNodeSection se return section; } -<<<<<<< HEAD - private protected override void CreateSection(ObjectNodeSection section, Utf8String comdatName, Utf8String symbolName, int sectionIndex, Stream sectionStream) - { - WasmSectionType sectionType = GetWasmSectionType(section); - SectionDataEmitter wasmSection = null; - if (sectionType == WasmSectionType.Data) - { -#if READYTORUN - // This is a section which is internally wrapping a Webcil section - wasmSection = new WebcilSection(new Utf8String(section.Name), default(WebcilSectionHeader), sectionStream, sectionIndex); -#else - wasmSection = new WasmSection(WasmSectionType.Data, sectionStream, new Utf8String(section.Name), sectionIndex); -#endif - } - else - { - Utf8String sectionName = new(section.Name); - wasmSection = sectionType switch - { - WasmSectionType.Type or WasmSectionType.Code => new WasmExternallyCountedSection(sectionType, sectionStream, sectionName, sectionIndex), - WasmSectionType.Import => new WasmImportSection(sectionStream, sectionName, sectionIndex), - WasmSectionType.Function => new WasmFunctionSection(sectionStream, sectionName, sectionIndex), - WasmSectionType.Global => new WasmGlobalSection(sectionStream, sectionName, sectionIndex), - WasmSectionType.Export => new WasmExportSection(sectionStream, sectionName, sectionIndex), - WasmSectionType.Element => new WasmElementSection(sectionStream, sectionName, sectionIndex), - _ => new WasmSection(sectionType, sectionStream, sectionName, sectionIndex), - }; - } - - Debug.Assert(_sections.Sections.Count == sectionIndex); - _sections.Add(section.Name, sectionIndex, wasmSection); - } - -======= ->>>>>>> 9bc33a024e8 (Move shared WASM logic to base writer) private void WriteDataCountSection() { SectionDataEmitter section = GetOrCreateSection(WasmObjectNodeSection.DataCountSection, out SectionWriter writer); @@ -559,36 +297,7 @@ private protected override void EmitSectionsAndLayout() WriteDataCountSection(); } -<<<<<<< HEAD - private Dictionary _definedGlobals = new(); - - // TODO-Wasm: In the future, we may want to consider representing Wasm globals in the dependency graph so that they - // can be referenced by other nodes and we can make effective use of them. - private void WriteGlobal(string name, WasmValueType valueType, WasmMutabilityType mutability, WasmInstructionGroup initExpr) - { - Utf8String symbolName = new(name); - _wasmSymbolManager.AddDefinition(symbolName, WasmIndexSpace.Global); - int index = _wasmSymbolManager.GetSymbol(symbolName).Index; - WasmGlobal global = new WasmGlobal( - index, - name: name, - valueType, - mutability, - initExpr); - bool added = _definedGlobals.TryAdd(name, global); - Debug.Assert(added, $"Duplicate global name: {name}"); - - WasmGlobalSection section = GetOrCreateSection( - WasmObjectNodeSection.GlobalSection, - out SectionWriter writer); - section.WriteEntry(writer, global); - } - - - private void WriteGlobalSection() -======= private protected override void WriteGlobalSection() ->>>>>>> 9bc33a024e8 (Move shared WASM logic to base writer) { // webcilVersion: i32 const = 0 WriteGlobal("webcilVersion", WasmValueType.I32, WasmMutabilityType.Const, @@ -633,7 +342,7 @@ private protected override void EmitObjectFile(Stream outputFileStream) if (_pendingBaseRelocs.Count > 0) { - GetOrCreateSection(WebcilRelocSection); + GetOrCreateSection(WebcilRelocSection, out _); } WebcilSection[] webcilSections = _sections.Sections.OfType().ToArray(); @@ -991,6 +700,11 @@ void WriteRelocFromDataSpan(SymbolicRelocation reloc, byte* pData, long sectionS new([]), new([])); + internal const int StackPointerGlobalIndex = 0; + internal const int ImageBaseGlobalIndex = 1; + internal const int TableBaseGlobalIndex = 2; + internal const int AsyncContinuationGlobalIndex = 3; + private WasmImport[] CreateDefaultGlobalImports() { int rtlRestoreContextTagTypeIndex = RegisterSignature(RtlRestoreContextTagSignature); @@ -1006,29 +720,7 @@ private WasmImport[] CreateDefaultGlobalImports() ]; } -<<<<<<< HEAD - private int RegisterSignature(WasmFuncType signature) - { - Utf8String signatureKey = signature.GetMangledName(_nodeFactory.NameMangler); - if (_wasmSymbolManager.TryGetSymbol(signatureKey, out WasmSymbol signatureSymbol)) - { - return signatureSymbol.Index; - } - - GetOrCreateSection(ObjectNodeSection.WasmTypeSection, out SectionWriter typeSectionWriter); - byte[] encodedSignature = new byte[signature.EncodeSize()]; - signature.Encode(encodedSignature); - _wasmSymbolManager.AddDefinition(signatureKey, WasmIndexSpace.Type); - typeSectionWriter.EmitSymbolDefinition(signatureKey); - typeSectionWriter.EmitData(encodedSignature); - - return _wasmSymbolManager.GetSymbol(signatureKey).Index; - } - - private void WriteImports() -======= private protected override void WriteImports() ->>>>>>> 9bc33a024e8 (Move shared WASM logic to base writer) { foreach (WasmImport import in CreateDefaultGlobalImports()) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 3f2e7c1de78dfd..2fad7885d05cc8 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -201,6 +201,7 @@ + From a322ac9666594bdea9afef55c9c04da125c8af1a Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:41:09 -0700 Subject: [PATCH 06/12] Introduce WebCIL object writer type Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ObjectWriter/WasmObjectWriter.Base.cs | 25 ++ .../Compiler/ObjectWriter/WasmObjectWriter.cs | 415 ------------------ 2 files changed, 25 insertions(+), 415 deletions(-) create mode 100644 src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs delete mode 100644 src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs new file mode 100644 index 00000000000000..37d2ec72f5e74d --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILCompiler.DependencyAnalysis; + +namespace ILCompiler.ObjectWriter +{ + /// + /// Base class for WebAssembly object file format writers. + /// + internal abstract partial class WasmObjectWriter : ObjectWriter + { + public const int StackPointerGlobalIndex = WasmGlobalImports.StackPointerGlobalIndex; + public const int ImageBaseGlobalIndex = WasmGlobalImports.ImageBaseGlobalIndex; + public const int TableBaseGlobalIndex = WasmGlobalImports.TableBaseGlobalIndex; + public const int AsyncContinuationGlobalIndex = WasmGlobalImports.AsyncContinuationGlobalIndex; + + public const int WebcilSectionAlignment = 16; + + protected WasmObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder) + : base(factory, options, outputInfoBuilder) + { + } + } +} diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs deleted file mode 100644 index 7ef3fb9242a66c..00000000000000 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ /dev/null @@ -1,415 +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.Diagnostics; -using System.IO; -using System.Linq; -using ILCompiler.DependencyAnalysis; -using ILCompiler.DependencyAnalysis.Wasm; -using ILCompiler.DependencyAnalysisFramework; -using ILCompiler.ObjectWriter.WasmInstructions; -using Internal.JitInterface; -using Internal.Text; -using Internal.TypeSystem; - -namespace ILCompiler.ObjectWriter -{ - /// - /// Base class for WebAssembly object file format writers. - /// - internal abstract partial class WasmObjectWriter : ObjectWriter - { - private readonly Dictionary _sectionToType = new() - { - { WasmObjectNodeSection.MemorySection, WasmSectionType.Memory }, - { WasmObjectNodeSection.FunctionSection, WasmSectionType.Function }, - { WasmObjectNodeSection.TableSection, WasmSectionType.Table }, - { WasmObjectNodeSection.ElementSection, WasmSectionType.Element }, - { WasmObjectNodeSection.ExportSection, WasmSectionType.Export }, - { WasmObjectNodeSection.ImportSection, WasmSectionType.Import }, - { WasmObjectNodeSection.GlobalSection, WasmSectionType.Global }, - { ObjectNodeSection.WasmTypeSection, WasmSectionType.Type }, - { ObjectNodeSection.WasmCodeSection, WasmSectionType.Code }, - { WasmObjectNodeSection.DataCountSection, WasmSectionType.DataCount }, - }; - - // Sections emitted before data segments. - private readonly string[] _sectionOrder = - [ - ObjectNodeSection.WasmTypeSection.Name, - WasmObjectNodeSection.ImportSection.Name, - WasmObjectNodeSection.FunctionSection.Name, - WasmObjectNodeSection.TableSection.Name, - WasmObjectNodeSection.MemorySection.Name, - WasmObjectNodeSection.GlobalSection.Name, - WasmObjectNodeSection.ExportSection.Name, - WasmObjectNodeSection.ElementSection.Name, - WasmObjectNodeSection.DataCountSection.Name, - ObjectNodeSection.WasmCodeSection.Name, - ]; - - private protected readonly Dictionary _definedGlobals = new(); - private protected readonly WasmSections _sections = new(); - private protected readonly WasmSymbolManager _wasmSymbolManager = new(); - /// - /// Maps symbol names to their location in the object file. These definitions do not encode - /// logical WebAssembly indices and must not be used to resolve index relocations. - /// - private protected Dictionary _definedSymbols; - private int[] _sectionEmitOrder; - - /// - /// The number of methods in the Function section. - /// - private protected int MethodCount => _wasmSymbolManager.GetDefinitionCount(WasmIndexSpace.Function); - - private protected int[] SectionEmitOrder - { - get - { - _sectionEmitOrder ??= _sectionOrder - .Where(_sections.Contains) - .Select(_sections.GetSectionIndex) - .ToArray(); - - return _sectionEmitOrder; - } - } - - protected WasmObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder) - : base(factory, options, outputInfoBuilder) - { - } - - private protected static void EmitWasmHeader(Stream outputFileStream) - { - outputFileStream.Write("\0asm"u8); - outputFileStream.Write([0x1, 0x0, 0x0, 0x0]); - } - - private protected override void CreateSection( - ObjectNodeSection section, - Utf8String comdatName, - Utf8String symbolName, - int sectionIndex, - Stream sectionStream) - { - WasmSectionType sectionType = GetWasmSectionType(section); - SectionDataEmitter wasmSection; - if (sectionType == WasmSectionType.Data) - { - wasmSection = CreateDataSection(section, sectionIndex, sectionStream); - } - else - { - Utf8String sectionName = new(section.Name); - wasmSection = sectionType switch - { - WasmSectionType.Type or WasmSectionType.Code => new WasmExternallyCountedSection(sectionType, sectionStream, sectionName, sectionIndex), - WasmSectionType.Import => new WasmImportSection(sectionStream, sectionName, sectionIndex), - WasmSectionType.Function => new WasmFunctionSection(sectionStream, sectionName, sectionIndex), - WasmSectionType.Global => new WasmGlobalSection(sectionStream, sectionName, sectionIndex), - WasmSectionType.Export => new WasmExportSection(sectionStream, sectionName, sectionIndex), - WasmSectionType.Element => new WasmElementSection(sectionStream, sectionName, sectionIndex), - _ => new WasmSection(sectionType, sectionStream, sectionName, sectionIndex), - }; - } - - Debug.Assert(_sections.Sections.Count == sectionIndex); - _sections.Add(section.Name, sectionIndex, wasmSection); - } - - private protected abstract SectionDataEmitter CreateDataSection( - ObjectNodeSection section, - int sectionIndex, - Stream sectionStream); - - private protected override void RecordMethodSignature(WasmTypeNode signature) - { - Utf8StringBuilder mangledNameBuilder = new(); - signature.AppendMangledName(_nodeFactory.NameMangler, mangledNameBuilder); - Utf8String mangledName = mangledNameBuilder.ToUtf8String(); - // Record the signature's wasm type index in the shared symbol table. The signature bytes - // are emitted by the node's own data; here we only assign its index. - _wasmSymbolManager.AddDefinition(mangledName, WasmIndexSpace.Type); - } - - private protected override void RecordMethodDeclaration(INodeWithTypeSignature node) - { - WasmLowering.LoweringFlags flags = WasmLowering.LoweringFlags.None; - if (node.HasGenericContextArg) - { - flags |= WasmLowering.LoweringFlags.HasGenericContextArg; - } - if (node.IsAsyncCall) - { - flags |= WasmLowering.LoweringFlags.IsAsyncCall; - } - if (node.IsUnmanagedCallersOnly) - { - flags |= WasmLowering.LoweringFlags.IsUnmanagedCallersOnly; - } - WriteSignatureIndexForFunction(node.Signature, flags, node); - RegisterFunctionSymbol(new Utf8String(node.GetMangledName(_nodeFactory.NameMangler))); - if (node is INodeWithFunclets nodeWithFunclets) - { - RecordFunclets(nodeWithFunclets); - } - } - - private void RecordFunclets(INodeWithFunclets nodeWithFunclets) - { - FuncletKind[] funcletKinds = nodeWithFunclets.GetFuncletKinds(); - if (funcletKinds.Length < 1) - { - return; - } - - WasmValueType pointerType = _nodeFactory.Target.PointerSize == 8 ? WasmValueType.I64 : WasmValueType.I32; - string mangledNodeName = nodeWithFunclets.GetMangledName(_nodeFactory.NameMangler); - - for (int i = 0; i < funcletKinds.Length; i++) - { - WasmFuncType funcletSignature = GetFuncletType(funcletKinds[i], pointerType); - RegisterFunctionSymbol(new Utf8String($"{mangledNodeName}_funclet_{i}")); - RegisterStubIndexAndSignature(funcletSignature); - } - } - - private static WasmFuncType GetFuncletType(FuncletKind funcletKind, WasmValueType pointerType) - { - return funcletKind switch - { - FuncletKind.CatchOrFilterHandler or FuncletKind.Filter => new WasmFuncType( - new([pointerType, pointerType, pointerType]), new([pointerType])), // (FP, SP, EXN) -> RESULT - _ => new WasmFuncType(new([pointerType, pointerType]), new([])), // (FP, SP) -> void - }; - } - - private void WriteFunctionEntry(int signatureIndex) - { - WasmFunctionSection section = GetOrCreateSection( - WasmObjectNodeSection.FunctionSection, - out SectionWriter writer); - section.WriteEntry(writer, signatureIndex); - } - - private void WriteSignatureIndexForFunction( - MethodSignature managedSignature, - WasmLowering.LoweringFlags flags, - ISymbolNode node) - { - WasmFuncType signature = WasmLowering.GetSignature(managedSignature, flags).FuncType; - Utf8String key = signature.GetMangledName(_nodeFactory.NameMangler); - if (!_wasmSymbolManager.TryGetSymbol(key, out WasmSymbol signatureSymbol)) - { - throw new InvalidOperationException($"Signature index of {key} not found for function: {node.ToString()}"); - } - - WriteFunctionEntry(signatureSymbol.Index); - } - - /// - /// Adds the given import entry, including its prefix (module/name/kind) and body (external ref). - /// - private protected void WriteImport(WasmImport import) - { - Utf8String symbolName = new(import.Name); - _wasmSymbolManager.AddImport(symbolName, GetIndexSpace(import.Kind), import.Index); - - WasmImportSection section = GetOrCreateSection( - WasmObjectNodeSection.ImportSection, - out SectionWriter writer); - section.WriteEntry(writer, import); - } - - /// - /// Maps an import kind to the index space where it can be referenced. - /// Imports are always the first logical entries in their respective index spaces. - /// - private static WasmIndexSpace GetIndexSpace(WasmExternalKind kind) => kind switch - { - WasmExternalKind.Function => WasmIndexSpace.Function, - WasmExternalKind.Table => WasmIndexSpace.Table, - WasmExternalKind.Memory => WasmIndexSpace.Memory, - WasmExternalKind.Global => WasmIndexSpace.Global, - WasmExternalKind.Tag => WasmIndexSpace.Tag, - _ => throw new ArgumentOutOfRangeException(nameof(kind)), - }; - - private void WriteExport(string name, WasmExportKind kind, int index) - { - WasmExportSection section = GetOrCreateSection( - WasmObjectNodeSection.ExportSection, - out SectionWriter writer); - section.WriteEntry(writer, new WasmExport(name, kind, index)); - } - - private protected void WriteFunctionExport(string name, int functionIndex) => - WriteExport(name, WasmExportKind.Function, functionIndex); - - private protected void WriteTableExport(string name, int tableIndex) => - WriteExport(name, WasmExportKind.Table, tableIndex); - - private protected void WriteMemoryExport(string name, int memoryIndex) => - WriteExport(name, WasmExportKind.Memory, memoryIndex); - - private protected void WriteGlobalExport(string name, int globalIndex) => - WriteExport(name, WasmExportKind.Global, globalIndex); - - private protected void WriteElementSegment(ReadOnlyMemory functionIndices) - { - WasmElementSection section = GetOrCreateSection( - WasmObjectNodeSection.ElementSection, - out SectionWriter writer); - section.WriteEntry(writer, functionIndices); - } - - private protected SectionDataEmitter GetOrCreateSection( - ObjectNodeSection section, - out SectionWriter writer) - { - return GetOrCreateSection(section, out writer); - } - - private protected TSection GetOrCreateSection( - ObjectNodeSection section, - out SectionWriter writer) - where TSection : SectionDataEmitter - { - writer = GetOrCreateSection(section); - return _sections.GetSection(writer.SectionIndex); - } - - private WasmSectionType GetWasmSectionType(ObjectNodeSection section) - { - if (!_sectionToType.TryGetValue(section, out WasmSectionType sectionType)) - { - // All other sections map to generic data segments in Wasm. - // TODO-WASM: Consider making the mapping explicit for every possible node type. - return WasmSectionType.Data; - } - - return sectionType; - } - - // TODO-WASM: In the future, we may want to consider representing Wasm globals in the dependency graph so that they - // can be referenced by other nodes and we can make effective use of them. - private protected void WriteGlobal( - string name, - WasmValueType valueType, - WasmMutabilityType mutability, - WasmInstructionGroup initExpr) - { - Utf8String symbolName = new(name); - _wasmSymbolManager.AddDefinition(symbolName, WasmIndexSpace.Global); - int index = _wasmSymbolManager.GetSymbol(symbolName).Index; - WasmGlobal global = new( - index, - name: name, - valueType, - mutability, - initExpr); - bool added = _definedGlobals.TryAdd(name, global); - Debug.Assert(added, $"Duplicate global name: {name}"); - - WasmGlobalSection section = GetOrCreateSection( - WasmObjectNodeSection.GlobalSection, - out SectionWriter writer); - section.WriteEntry(writer, global); - } - - private protected void RegisterFunctionSymbol(Utf8String name) => - _wasmSymbolManager.AddDefinition(name, WasmIndexSpace.Function); - - // This effectively recreates the logic of RecordMethodBody/RecordMethodDeclaration, but for manually inserted stubs that are not - // represented by nodes in the dependency graph. - // TODO-Wasm: for maintability, we should try and push some of this into the dependency graph when we do more stub generation. - private protected void RegisterStubIndexAndSignature(WasmFuncType signature) - { - int signatureIndex = RegisterSignature(signature); - WriteFunctionEntry(signatureIndex); - } - - private protected void InsertWasmStub(Utf8String name, WasmFunctionBody body) - { - SectionWriter codeWriter = GetOrCreateSection(ObjectNodeSection.WasmCodeSection); - - int codeSize = body.EncodeSize(); - byte[] data = new byte[codeSize]; - body.Encode(data); - - codeWriter.EmitSymbolDefinition(name); - codeWriter.EmitData(data); - - RegisterFunctionSymbol(name); - RegisterStubIndexAndSignature(body.Signature); - } - - private protected int RegisterSignature(WasmFuncType signature) - { - Utf8String signatureKey = signature.GetMangledName(_nodeFactory.NameMangler); - if (_wasmSymbolManager.TryGetSymbol(signatureKey, out WasmSymbol signatureSymbol)) - { - return signatureSymbol.Index; - } - - SectionWriter typeSectionWriter = GetOrCreateSection(ObjectNodeSection.WasmTypeSection); - byte[] encodedSignature = new byte[signature.EncodeSize()]; - signature.Encode(encodedSignature); - _wasmSymbolManager.AddDefinition(signatureKey, WasmIndexSpace.Type); - typeSectionWriter.EmitSymbolDefinition(signatureKey); - typeSectionWriter.EmitData(encodedSignature); - - return _wasmSymbolManager.GetSymbol(signatureKey).Index; - } - - // Populate sections whose entries are derived from the completed symbol table. - private protected override void EmitSymbolTable( - IDictionary definedSymbols, - SortedSet undefinedSymbols) - { - WriteImports(); - WriteGlobalSection(); - WriteExports(); - WriteElements(); - - // Register defined symbols for future use during relocation resolution. - _definedSymbols = new Dictionary(definedSymbols); - } - - private protected abstract void WriteImports(); - private protected abstract void WriteGlobalSection(); - private protected abstract void WriteExports(); - private protected abstract void WriteElements(); - - private protected void FinalizeSectionEntryCounts() - { - _sections.GetSection(ObjectNodeSection.WasmTypeSection.Name) - .SetEntryCount(_wasmSymbolManager.GetDefinitionCount(WasmIndexSpace.Type)); - _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)); - } - } - - internal static class WasmObjectNodeSection - { - // TODO-WASM: Consider alignment needs for data sections - public static readonly ObjectNodeSection DataSection = new("wasm.data", SectionType.Writeable, needsAlign: false); - public static readonly ObjectNodeSection DataCountSection = new("wasm.datacount", SectionType.ReadOnly, needsAlign: false); - public static readonly ObjectNodeSection CombinedDataSection = new("wasm.alldata", SectionType.Writeable, needsAlign: false); - public static readonly ObjectNodeSection FunctionSection = new("wasm.function", SectionType.ReadOnly, needsAlign: false); - public static readonly ObjectNodeSection ExportSection = new("wasm.export", SectionType.ReadOnly, needsAlign: false); - public static readonly ObjectNodeSection ElementSection = new("wasm.element", SectionType.ReadOnly, needsAlign: false); - public static readonly ObjectNodeSection MemorySection = new("wasm.memory", SectionType.ReadOnly, needsAlign: false); - public static readonly ObjectNodeSection TableSection = new("wasm.table", SectionType.ReadOnly, needsAlign: false); - public static readonly ObjectNodeSection ImportSection = new("wasm.import", SectionType.ReadOnly, needsAlign: false); - public static readonly ObjectNodeSection GlobalSection = new("wasm.global", SectionType.ReadOnly, needsAlign: false); - } -} From ac94805dd3c15aeda43eae2b1b678e2c83ef0c0b Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:44:03 -0700 Subject: [PATCH 07/12] Keep WebCIL alignment with WebCIL writer Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs index 37d2ec72f5e74d..d2d0fe6fdf95f2 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs @@ -15,8 +15,6 @@ internal abstract partial class WasmObjectWriter : ObjectWriter public const int TableBaseGlobalIndex = WasmGlobalImports.TableBaseGlobalIndex; public const int AsyncContinuationGlobalIndex = WasmGlobalImports.AsyncContinuationGlobalIndex; - public const int WebcilSectionAlignment = 16; - protected WasmObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder) : base(factory, options, outputInfoBuilder) { From dab07be3e24f48d4f196eb4b255e162d88f9b550 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 3 Aug 2026 15:48:24 -0700 Subject: [PATCH 08/12] Move shared WASM logic to base writer Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ObjectWriter/WasmObjectWriter.Base.cs | 394 ++++++++++++++++++ 1 file changed, 394 insertions(+) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs index d2d0fe6fdf95f2..9e2f2ae580f4ab 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs @@ -1,7 +1,18 @@ // 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; +using System.IO; +using System.Linq; using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysis.Wasm; +using ILCompiler.DependencyAnalysisFramework; +using ILCompiler.ObjectWriter.WasmInstructions; +using Internal.JitInterface; +using Internal.Text; +using Internal.TypeSystem; namespace ILCompiler.ObjectWriter { @@ -15,9 +26,392 @@ internal abstract partial class WasmObjectWriter : ObjectWriter public const int TableBaseGlobalIndex = WasmGlobalImports.TableBaseGlobalIndex; public const int AsyncContinuationGlobalIndex = WasmGlobalImports.AsyncContinuationGlobalIndex; + private readonly Dictionary _sectionToType = new() + { + { WasmObjectNodeSection.MemorySection, WasmSectionType.Memory }, + { WasmObjectNodeSection.FunctionSection, WasmSectionType.Function }, + { WasmObjectNodeSection.TableSection, WasmSectionType.Table }, + { WasmObjectNodeSection.ElementSection, WasmSectionType.Element }, + { WasmObjectNodeSection.ExportSection, WasmSectionType.Export }, + { WasmObjectNodeSection.ImportSection, WasmSectionType.Import }, + { WasmObjectNodeSection.GlobalSection, WasmSectionType.Global }, + { ObjectNodeSection.WasmTypeSection, WasmSectionType.Type }, + { ObjectNodeSection.WasmCodeSection, WasmSectionType.Code }, + { WasmObjectNodeSection.DataCountSection, WasmSectionType.DataCount }, + }; + + // Sections emitted before data segments. + private readonly string[] _sectionOrder = + [ + ObjectNodeSection.WasmTypeSection.Name, + WasmObjectNodeSection.ImportSection.Name, + WasmObjectNodeSection.FunctionSection.Name, + WasmObjectNodeSection.TableSection.Name, + WasmObjectNodeSection.MemorySection.Name, + WasmObjectNodeSection.GlobalSection.Name, + WasmObjectNodeSection.ExportSection.Name, + WasmObjectNodeSection.ElementSection.Name, + WasmObjectNodeSection.DataCountSection.Name, + ObjectNodeSection.WasmCodeSection.Name, + ]; + + private protected readonly Dictionary _definedGlobals = new(); + private protected readonly WasmSections _sections = new(); + private protected readonly WasmSymbolManager _wasmSymbolManager = new(); + /// + /// Maps symbol names to their location in the object file. These definitions do not encode + /// logical WebAssembly indices and must not be used to resolve index relocations. + /// + private protected Dictionary _definedSymbols; + private int[] _sectionEmitOrder; + + /// + /// The number of methods in the Function section. + /// + private protected int MethodCount => _wasmSymbolManager.GetDefinitionCount(WasmIndexSpace.Function); + + private protected int[] SectionEmitOrder + { + get + { + if (_sectionEmitOrder is null) + { + _sectionEmitOrder = _sectionOrder + .Where(name => _sections.Contains(name)) + .Select(name => _sections.GetSectionIndex(name)) + .ToArray(); + } + + return _sectionEmitOrder; + } + } + protected WasmObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder) : base(factory, options, outputInfoBuilder) { } + + private protected static void EmitWasmHeader(Stream outputFileStream) + { + outputFileStream.Write("\0asm"u8); + outputFileStream.Write([0x1, 0x0, 0x0, 0x0]); + } + + private protected override void CreateSection( + ObjectNodeSection section, + Utf8String comdatName, + Utf8String symbolName, + int sectionIndex, + Stream sectionStream) + { + WasmSectionType sectionType = GetWasmSectionType(section); + WasmSection wasmSection; + if (sectionType == WasmSectionType.Data) + { + wasmSection = CreateDataSection(section, sectionIndex, sectionStream); + } + else + { + Utf8String sectionName = new(section.Name); + wasmSection = sectionType switch + { + WasmSectionType.Type or WasmSectionType.Code => new WasmExternallyCountedSection(sectionType, sectionStream, sectionName), + WasmSectionType.Import => new WasmImportSection(sectionStream, sectionName), + WasmSectionType.Function => new WasmFunctionSection(sectionStream, sectionName), + WasmSectionType.Global => new WasmGlobalSection(sectionStream, sectionName), + WasmSectionType.Export => new WasmExportSection(sectionStream, sectionName), + WasmSectionType.Element => new WasmElementSection(sectionStream, sectionName), + _ => new WasmSection(sectionType, sectionStream, sectionName), + }; + } + + Debug.Assert(_sections.Sections.Count == sectionIndex); + _sections.Add(section.Name, sectionIndex, wasmSection); + } + + private protected virtual WasmSection CreateDataSection( + ObjectNodeSection section, + int sectionIndex, + Stream sectionStream) => + new(WasmSectionType.Data, sectionStream, new Utf8String(section.Name)); + + private protected override void RecordMethodSignature(WasmTypeNode signature) + { + Utf8StringBuilder mangledNameBuilder = new(); + signature.AppendMangledName(_nodeFactory.NameMangler, mangledNameBuilder); + Utf8String mangledName = mangledNameBuilder.ToUtf8String(); + // Record the signature's wasm type index in the shared symbol table. The signature bytes + // are emitted by the node's own data; here we only assign its index. + _wasmSymbolManager.AddDefinition(mangledName, WasmIndexSpace.Type); + } + + private protected override void RecordMethodDeclaration(INodeWithTypeSignature node) + { + WasmLowering.LoweringFlags flags = WasmLowering.LoweringFlags.None; + if (node.HasGenericContextArg) + { + flags |= WasmLowering.LoweringFlags.HasGenericContextArg; + } + if (node.IsAsyncCall) + { + flags |= WasmLowering.LoweringFlags.IsAsyncCall; + } + if (node.IsUnmanagedCallersOnly) + { + flags |= WasmLowering.LoweringFlags.IsUnmanagedCallersOnly; + } + WriteSignatureIndexForFunction(node.Signature, flags, node); + RegisterFunctionSymbol(new Utf8String(node.GetMangledName(_nodeFactory.NameMangler))); + if (node is INodeWithFunclets nodeWithFunclets) + { + RecordFunclets(nodeWithFunclets); + } + } + + private void RecordFunclets(INodeWithFunclets nodeWithFunclets) + { + FuncletKind[] funcletKinds = nodeWithFunclets.GetFuncletKinds(); + if (funcletKinds.Length < 1) + { + return; + } + + WasmValueType pointerType = _nodeFactory.Target.PointerSize == 8 ? WasmValueType.I64 : WasmValueType.I32; + string mangledNodeName = nodeWithFunclets.GetMangledName(_nodeFactory.NameMangler); + + for (int i = 0; i < funcletKinds.Length; i++) + { + WasmFuncType funcletSignature = GetFuncletType(funcletKinds[i], pointerType); + RegisterFunctionSymbol(new Utf8String($"{mangledNodeName}_funclet_{i}")); + RegisterStubIndexAndSignature(funcletSignature); + } + } + + private static WasmFuncType GetFuncletType(FuncletKind funcletKind, WasmValueType pointerType) + { + return funcletKind switch + { + FuncletKind.CatchOrFilterHandler or FuncletKind.Filter => new WasmFuncType( + new([pointerType, pointerType, pointerType]), new([pointerType])), // (FP, SP, EXN) -> RESULT + _ => new WasmFuncType(new([pointerType, pointerType]), new([])), // (FP, SP) -> void + }; + } + + private void AddFunctionEntry(int signatureIndex) + { + WasmFunctionSection section = GetOrCreateWasmSection( + WasmObjectNodeSection.FunctionSection, + out SectionWriter writer); + section.WriteEntry(writer, signatureIndex); + } + + private void WriteSignatureIndexForFunction( + MethodSignature managedSignature, + WasmLowering.LoweringFlags flags, + ISymbolNode node) + { + WasmFuncType signature = WasmLowering.GetSignature(managedSignature, flags).FuncType; + Utf8String key = signature.GetMangledName(_nodeFactory.NameMangler); + if (!_wasmSymbolManager.TryGetSymbol(key, out WasmSymbol signatureSymbol)) + { + throw new InvalidOperationException($"Signature index of {key} not found for function: {node.ToString()}"); + } + + AddFunctionEntry(signatureSymbol.Index); + } + + /// + /// Adds the given import entry, including its prefix (module/name/kind) and body (external ref). + /// + private protected void AddImport(WasmImport import) + { + Utf8String symbolName = new(import.Name); + _wasmSymbolManager.AddImport(symbolName, GetIndexSpace(import.Kind), import.Index); + + WasmImportSection section = GetOrCreateWasmSection( + WasmObjectNodeSection.ImportSection, + out SectionWriter writer); + section.WriteEntry(writer, import); + } + + /// + /// Maps an import kind to the index space where it can be referenced. + /// Imports are always the first logical entries in their respective index spaces. + /// + private static WasmIndexSpace GetIndexSpace(WasmExternalKind kind) => kind switch + { + WasmExternalKind.Function => WasmIndexSpace.Function, + WasmExternalKind.Table => WasmIndexSpace.Table, + WasmExternalKind.Memory => WasmIndexSpace.Memory, + WasmExternalKind.Global => WasmIndexSpace.Global, + WasmExternalKind.Tag => WasmIndexSpace.Tag, + _ => throw new ArgumentOutOfRangeException(nameof(kind)), + }; + + private void AddExport(string name, WasmExportKind kind, int index) + { + WasmExportSection section = GetOrCreateWasmSection( + WasmObjectNodeSection.ExportSection, + out SectionWriter writer); + section.WriteEntry(writer, new WasmExport(name, kind, index)); + } + + private protected void AddFunctionExport(string name, int functionIndex) => + AddExport(name, WasmExportKind.Function, functionIndex); + + private protected void AddTableExport(string name, int tableIndex) => + AddExport(name, WasmExportKind.Table, tableIndex); + + private protected void AddMemoryExport(string name, int memoryIndex) => + AddExport(name, WasmExportKind.Memory, memoryIndex); + + private protected void AddGlobalExport(string name, int globalIndex) => + AddExport(name, WasmExportKind.Global, globalIndex); + + private protected void AddElementSegment(ReadOnlyMemory functionIndices) + { + WasmElementSection section = GetOrCreateWasmSection( + WasmObjectNodeSection.ElementSection, + out SectionWriter writer); + section.WriteEntry(writer, functionIndices); + } + + private protected TSection GetOrCreateWasmSection( + ObjectNodeSection section, + out SectionWriter writer) + where TSection : WasmSection + { + writer = GetOrCreateSection(section); + return _sections.GetSection(writer.SectionIndex); + } + + private WasmSectionType GetWasmSectionType(ObjectNodeSection section) + { + if (!_sectionToType.TryGetValue(section, out WasmSectionType sectionType)) + { + // All other sections map to generic data segments in Wasm. + // TODO-WASM: Consider making the mapping explicit for every possible node type. + return WasmSectionType.Data; + } + + return sectionType; + } + + // TODO-WASM: In the future, we may want to consider representing Wasm globals in the dependency graph so that they + // can be referenced by other nodes and we can make effective use of them. + private protected void AddGlobal( + string name, + WasmValueType valueType, + WasmMutabilityType mutability, + WasmInstructionGroup initExpr) + { + Utf8String symbolName = new(name); + _wasmSymbolManager.AddDefinition(symbolName, WasmIndexSpace.Global); + int index = _wasmSymbolManager.GetSymbol(symbolName).Index; + WasmGlobal global = new( + index, + name: name, + valueType, + mutability, + initExpr); + bool added = _definedGlobals.TryAdd(name, global); + Debug.Assert(added, $"Duplicate global name: {name}"); + + WasmGlobalSection section = GetOrCreateWasmSection( + WasmObjectNodeSection.GlobalSection, + out SectionWriter writer); + section.WriteEntry(writer, global); + } + + private protected void RegisterFunctionSymbol(Utf8String name) => + _wasmSymbolManager.AddDefinition(name, WasmIndexSpace.Function); + + // This effectively recreates the logic of RecordMethodBody/RecordMethodDeclaration, but for manually inserted stubs that are not + // represented by nodes in the dependency graph. + // TODO-Wasm: for maintability, we should try and push some of this into the dependency graph when we do more stub generation. + private protected void RegisterStubIndexAndSignature(WasmFuncType signature) + { + int signatureIndex = RegisterSignature(signature); + AddFunctionEntry(signatureIndex); + } + + private protected void InsertWasmStub(Utf8String name, WasmFunctionBody body) + { + SectionWriter codeWriter = GetOrCreateSection(ObjectNodeSection.WasmCodeSection); + + int codeSize = body.EncodeSize(); + byte[] data = new byte[codeSize]; + body.Encode(data); + + codeWriter.EmitSymbolDefinition(name); + codeWriter.EmitData(data); + + RegisterFunctionSymbol(name); + RegisterStubIndexAndSignature(body.Signature); + } + + private protected int RegisterSignature(WasmFuncType signature) + { + Utf8String signatureKey = signature.GetMangledName(_nodeFactory.NameMangler); + if (_wasmSymbolManager.TryGetSymbol(signatureKey, out WasmSymbol signatureSymbol)) + { + return signatureSymbol.Index; + } + + SectionWriter typeSectionWriter = GetOrCreateSection(ObjectNodeSection.WasmTypeSection); + byte[] encodedSignature = new byte[signature.EncodeSize()]; + signature.Encode(encodedSignature); + _wasmSymbolManager.AddDefinition(signatureKey, WasmIndexSpace.Type); + typeSectionWriter.EmitSymbolDefinition(signatureKey); + typeSectionWriter.EmitData(encodedSignature); + + return _wasmSymbolManager.GetSymbol(signatureKey).Index; + } + + // Populate sections whose entries are derived from the completed symbol table. + private protected override void EmitSymbolTable( + IDictionary definedSymbols, + SortedSet undefinedSymbols) + { + WriteImports(); + WriteGlobalSection(); + WriteExports(); + WriteElements(); + + // Register defined symbols for future use during relocation resolution. + _definedSymbols = new Dictionary(definedSymbols); + } + + private protected abstract void WriteImports(); + private protected abstract void WriteGlobalSection(); + private protected abstract void WriteExports(); + private protected abstract void WriteElements(); + + private protected void FinalizeSectionEntryCounts() + { + _sections.GetSection(ObjectNodeSection.WasmTypeSection.Name) + .SetEntryCount(_wasmSymbolManager.GetDefinitionCount(WasmIndexSpace.Type)); + _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)); + } + } + + internal static class WasmObjectNodeSection + { + // TODO-WASM: Consider alignment needs for data sections + public static readonly ObjectNodeSection DataSection = new("wasm.data", SectionType.Writeable, needsAlign: false); + public static readonly ObjectNodeSection DataCountSection = new("wasm.datacount", SectionType.ReadOnly, needsAlign: false); + public static readonly ObjectNodeSection CombinedDataSection = new("wasm.alldata", SectionType.Writeable, needsAlign: false); + public static readonly ObjectNodeSection FunctionSection = new("wasm.function", SectionType.ReadOnly, needsAlign: false); + public static readonly ObjectNodeSection ExportSection = new("wasm.export", SectionType.ReadOnly, needsAlign: false); + public static readonly ObjectNodeSection ElementSection = new("wasm.element", SectionType.ReadOnly, needsAlign: false); + public static readonly ObjectNodeSection MemorySection = new("wasm.memory", SectionType.ReadOnly, needsAlign: false); + public static readonly ObjectNodeSection TableSection = new("wasm.table", SectionType.ReadOnly, needsAlign: false); + public static readonly ObjectNodeSection ImportSection = new("wasm.import", SectionType.ReadOnly, needsAlign: false); + public static readonly ObjectNodeSection GlobalSection = new("wasm.global", SectionType.ReadOnly, needsAlign: false); } } From ed2a9459bbe0d221fb384811c69415957ca38264 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:27:49 -0700 Subject: [PATCH 09/12] Fix NativeAOT warnings in wasm code --- .../ObjectWriter/WasmObjectWriter.Base.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs index 9e2f2ae580f4ab..b6dfc5b12a655a 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs @@ -21,11 +21,6 @@ namespace ILCompiler.ObjectWriter /// internal abstract partial class WasmObjectWriter : ObjectWriter { - public const int StackPointerGlobalIndex = WasmGlobalImports.StackPointerGlobalIndex; - public const int ImageBaseGlobalIndex = WasmGlobalImports.ImageBaseGlobalIndex; - public const int TableBaseGlobalIndex = WasmGlobalImports.TableBaseGlobalIndex; - public const int AsyncContinuationGlobalIndex = WasmGlobalImports.AsyncContinuationGlobalIndex; - private readonly Dictionary _sectionToType = new() { { WasmObjectNodeSection.MemorySection, WasmSectionType.Memory }, @@ -74,13 +69,10 @@ private protected int[] SectionEmitOrder { get { - if (_sectionEmitOrder is null) - { - _sectionEmitOrder = _sectionOrder - .Where(name => _sections.Contains(name)) - .Select(name => _sections.GetSectionIndex(name)) - .ToArray(); - } + _sectionEmitOrder ??= _sectionOrder + .Where(_sections.Contains) + .Select(_sections.GetSectionIndex) + .ToArray(); return _sectionEmitOrder; } From 163025cd2745418309377a6917fc899b54f285d7 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:46:22 -0700 Subject: [PATCH 10/12] Fix merge issue --- ...jectWriter.Base.cs => WasmObjectWriter.cs} | 72 ++++++++++--------- 1 file changed, 39 insertions(+), 33 deletions(-) rename src/coreclr/tools/Common/Compiler/ObjectWriter/{WasmObjectWriter.Base.cs => WasmObjectWriter.cs} (88%) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs similarity index 88% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index b6dfc5b12a655a..7ef3fb9242a66c 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.Base.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -97,7 +97,7 @@ private protected override void CreateSection( Stream sectionStream) { WasmSectionType sectionType = GetWasmSectionType(section); - WasmSection wasmSection; + SectionDataEmitter wasmSection; if (sectionType == WasmSectionType.Data) { wasmSection = CreateDataSection(section, sectionIndex, sectionStream); @@ -107,13 +107,13 @@ private protected override void CreateSection( Utf8String sectionName = new(section.Name); wasmSection = sectionType switch { - WasmSectionType.Type or WasmSectionType.Code => new WasmExternallyCountedSection(sectionType, sectionStream, sectionName), - WasmSectionType.Import => new WasmImportSection(sectionStream, sectionName), - WasmSectionType.Function => new WasmFunctionSection(sectionStream, sectionName), - WasmSectionType.Global => new WasmGlobalSection(sectionStream, sectionName), - WasmSectionType.Export => new WasmExportSection(sectionStream, sectionName), - WasmSectionType.Element => new WasmElementSection(sectionStream, sectionName), - _ => new WasmSection(sectionType, sectionStream, sectionName), + WasmSectionType.Type or WasmSectionType.Code => new WasmExternallyCountedSection(sectionType, sectionStream, sectionName, sectionIndex), + WasmSectionType.Import => new WasmImportSection(sectionStream, sectionName, sectionIndex), + WasmSectionType.Function => new WasmFunctionSection(sectionStream, sectionName, sectionIndex), + WasmSectionType.Global => new WasmGlobalSection(sectionStream, sectionName, sectionIndex), + WasmSectionType.Export => new WasmExportSection(sectionStream, sectionName, sectionIndex), + WasmSectionType.Element => new WasmElementSection(sectionStream, sectionName, sectionIndex), + _ => new WasmSection(sectionType, sectionStream, sectionName, sectionIndex), }; } @@ -121,11 +121,10 @@ private protected override void CreateSection( _sections.Add(section.Name, sectionIndex, wasmSection); } - private protected virtual WasmSection CreateDataSection( + private protected abstract SectionDataEmitter CreateDataSection( ObjectNodeSection section, int sectionIndex, - Stream sectionStream) => - new(WasmSectionType.Data, sectionStream, new Utf8String(section.Name)); + Stream sectionStream); private protected override void RecordMethodSignature(WasmTypeNode signature) { @@ -189,9 +188,9 @@ private static WasmFuncType GetFuncletType(FuncletKind funcletKind, WasmValueTyp }; } - private void AddFunctionEntry(int signatureIndex) + private void WriteFunctionEntry(int signatureIndex) { - WasmFunctionSection section = GetOrCreateWasmSection( + WasmFunctionSection section = GetOrCreateSection( WasmObjectNodeSection.FunctionSection, out SectionWriter writer); section.WriteEntry(writer, signatureIndex); @@ -209,18 +208,18 @@ private void WriteSignatureIndexForFunction( throw new InvalidOperationException($"Signature index of {key} not found for function: {node.ToString()}"); } - AddFunctionEntry(signatureSymbol.Index); + WriteFunctionEntry(signatureSymbol.Index); } /// /// Adds the given import entry, including its prefix (module/name/kind) and body (external ref). /// - private protected void AddImport(WasmImport import) + private protected void WriteImport(WasmImport import) { Utf8String symbolName = new(import.Name); _wasmSymbolManager.AddImport(symbolName, GetIndexSpace(import.Kind), import.Index); - WasmImportSection section = GetOrCreateWasmSection( + WasmImportSection section = GetOrCreateSection( WasmObjectNodeSection.ImportSection, out SectionWriter writer); section.WriteEntry(writer, import); @@ -240,38 +239,45 @@ private protected void AddImport(WasmImport import) _ => throw new ArgumentOutOfRangeException(nameof(kind)), }; - private void AddExport(string name, WasmExportKind kind, int index) + private void WriteExport(string name, WasmExportKind kind, int index) { - WasmExportSection section = GetOrCreateWasmSection( + WasmExportSection section = GetOrCreateSection( WasmObjectNodeSection.ExportSection, out SectionWriter writer); section.WriteEntry(writer, new WasmExport(name, kind, index)); } - private protected void AddFunctionExport(string name, int functionIndex) => - AddExport(name, WasmExportKind.Function, functionIndex); + private protected void WriteFunctionExport(string name, int functionIndex) => + WriteExport(name, WasmExportKind.Function, functionIndex); - private protected void AddTableExport(string name, int tableIndex) => - AddExport(name, WasmExportKind.Table, tableIndex); + private protected void WriteTableExport(string name, int tableIndex) => + WriteExport(name, WasmExportKind.Table, tableIndex); - private protected void AddMemoryExport(string name, int memoryIndex) => - AddExport(name, WasmExportKind.Memory, memoryIndex); + private protected void WriteMemoryExport(string name, int memoryIndex) => + WriteExport(name, WasmExportKind.Memory, memoryIndex); - private protected void AddGlobalExport(string name, int globalIndex) => - AddExport(name, WasmExportKind.Global, globalIndex); + private protected void WriteGlobalExport(string name, int globalIndex) => + WriteExport(name, WasmExportKind.Global, globalIndex); - private protected void AddElementSegment(ReadOnlyMemory functionIndices) + private protected void WriteElementSegment(ReadOnlyMemory functionIndices) { - WasmElementSection section = GetOrCreateWasmSection( + WasmElementSection section = GetOrCreateSection( WasmObjectNodeSection.ElementSection, out SectionWriter writer); section.WriteEntry(writer, functionIndices); } - private protected TSection GetOrCreateWasmSection( + private protected SectionDataEmitter GetOrCreateSection( ObjectNodeSection section, out SectionWriter writer) - where TSection : WasmSection + { + return GetOrCreateSection(section, out writer); + } + + private protected TSection GetOrCreateSection( + ObjectNodeSection section, + out SectionWriter writer) + where TSection : SectionDataEmitter { writer = GetOrCreateSection(section); return _sections.GetSection(writer.SectionIndex); @@ -291,7 +297,7 @@ private WasmSectionType GetWasmSectionType(ObjectNodeSection section) // TODO-WASM: In the future, we may want to consider representing Wasm globals in the dependency graph so that they // can be referenced by other nodes and we can make effective use of them. - private protected void AddGlobal( + private protected void WriteGlobal( string name, WasmValueType valueType, WasmMutabilityType mutability, @@ -309,7 +315,7 @@ private protected void AddGlobal( bool added = _definedGlobals.TryAdd(name, global); Debug.Assert(added, $"Duplicate global name: {name}"); - WasmGlobalSection section = GetOrCreateWasmSection( + WasmGlobalSection section = GetOrCreateSection( WasmObjectNodeSection.GlobalSection, out SectionWriter writer); section.WriteEntry(writer, global); @@ -324,7 +330,7 @@ private protected void RegisterFunctionSymbol(Utf8String name) => private protected void RegisterStubIndexAndSignature(WasmFuncType signature) { int signatureIndex = RegisterSignature(signature); - AddFunctionEntry(signatureIndex); + WriteFunctionEntry(signatureIndex); } private protected void InsertWasmStub(Utf8String name, WasmFunctionBody body) From c1c242f6acf529f2f490cd9f88ee8df999ca3762 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:11:08 -0700 Subject: [PATCH 11/12] WIP - nearly validated wasm output --- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 6 +- .../WasmRelocatableObjectWriter.cs | 182 +++++++++++++++--- 2 files changed, 156 insertions(+), 32 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 7ef3fb9242a66c..adf7f620337dfb 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -392,9 +392,9 @@ 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(GetOrCreateSection(WasmObjectNodeSection.ImportSection, out _).EntryCount == _wasmSymbolManager.GetImportCount()); + Debug.Assert(GetOrCreateSection(WasmObjectNodeSection.FunctionSection, out _).EntryCount == MethodCount); + Debug.Assert(GetOrCreateSection(WasmObjectNodeSection.GlobalSection, out _).EntryCount == _wasmSymbolManager.GetDefinitionCount(WasmIndexSpace.Global)); } } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs index 4040029798d978..bc267f37baed92 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs @@ -9,6 +9,7 @@ using Internal.JitInterface; using Internal.Text; using Internal.TypeSystem.TypesDebugInfo; +using ILCompiler.DependencyAnalysis.Wasm; namespace ILCompiler.ObjectWriter { @@ -25,36 +26,108 @@ private protected override void EmitObjectFile(Stream outputFileStream) FinalizeSectionEntryCounts(); EmitWasmHeader(outputFileStream); + foreach (int index in SectionEmitOrder) { SectionDataEmitter section = _sections[index]; + if (_resolvableRelocations.TryGetValue(index, out List relocations) && + section is WasmSection) + { + using (Stream originalStream = section.ContentReadStream) + { + MemoryStream stream = new MemoryStream((int)originalStream.Length); + originalStream.Position = 0; + originalStream.CopyTo(stream); + ResolveRelocations(index, stream, relocations, sectionStart: 0); + section.ContentReadStream = stream; + // originalStream may be disposed, section.Stream now points to resolved stream + } + } + section.EmitToStream(outputFileStream); } } + private Dictionary> _resolvableRelocations = new(); private protected override void EmitRelocations(int sectionIndex, List relocationList) { - // foreach (var reloc in relocationList) - // { - // if (!_resolvableRelocations.TryGetValue(sectionIndex, out List resolvable)) - // { - // _resolvableRelocations[sectionIndex] = resolvable = new List(); - // } - // // Unconditionally add the reloc to our resolvable list; we do some amount of relocation resolution - // // for all relocation types. - // resolvable.Add(reloc); - - // // A few relocation types (table indices and IMAGE_REL type relocs in Webcil) need - // // an additional runtime reloc as well to add a base address. - // // We defer the actual RVA computation to EmitObjectFile, where webcil section - // // VirtualAddresses will have been assigned. Here we just record the raw info. - // RelocType fileRelocType = Relocation.GetFileRelocationType(reloc.Type); - // if (fileRelocType is not RelocType.IMAGE_REL_BASED_ABSOLUTE) - // { - // Debug.Assert(WasmSections[sectionIndex] is WebcilSection); - // _pendingBaseRelocs.Add(new PendingBaseReloc(sectionIndex, reloc.Offset, fileRelocType)); - // } - // } + foreach (var reloc in relocationList) + { + if (!_resolvableRelocations.TryGetValue(sectionIndex, out List resolvable)) + { + _resolvableRelocations[sectionIndex] = resolvable = new List(); + } + // Unconditionally add the reloc to our resolvable list; we do some amount of relocation resolution + // for all relocation types. + resolvable.Add(reloc); + } + } + + private unsafe void ResolveRelocations(int sectionIndex, MemoryStream sectionStream, List relocs, long sectionStart = 0) + { + byte[] relocScratchBuffer = new byte[Relocation.MaxSize]; + + foreach (SymbolicRelocation reloc in relocs) + { + int size = Relocation.GetSize(reloc.Type); + if (size > relocScratchBuffer.Length) + { + throw new InvalidOperationException($"Unsupported relocation size for relocation: {reloc.Type}"); + } + + SymbolDefinition definedSymbol = _definedSymbols[reloc.SymbolName]; + + // We need a pinned raw pointer here for manipulation with Relocation.WriteValue + fixed (byte* pData = ReadRelocToDataSpan(reloc, relocScratchBuffer, sectionStart)) + { + long addend = Relocation.ReadValue(reloc.Type, pData); + int relocLength = Relocation.GetSize(reloc.Type); + + switch (reloc.Type) + { + case RelocType.WASM_TYPE_INDEX_LEB: + case RelocType.WASM_GLOBAL_INDEX_LEB: + case RelocType.WASM_TABLE_INDEX_I32: + case RelocType.WASM_TABLE_INDEX_I64: + case RelocType.WASM_TABLE_INDEX_SLEB: + case RelocType.WASM_TABLE_INDEX_REL_I32: + case RelocType.WASM_FUNCTION_INDEX_LEB: + case RelocType.WASM_MEMORY_ADDR_REL_SLEB when _sections.GetSection(definedSymbol.SectionIndex).Type == WasmSectionType.Code: + { + // These relocations reference a wasm structural index (function, type, + // table entry, or well-known global). For R2R we self-resolve them here to + // the index assigned when the symbol was registered into its index space. + if (!_wasmSymbolManager.TryGetSymbol(reloc.SymbolName, out WasmSymbol symbol)) + { + throw new InvalidOperationException($"Symbol '{reloc.SymbolName}' was not registered. Relocation type {reloc.Type}."); + } + Relocation.WriteValue(reloc.Type, pData, symbol.Index + addend); + break; + } + + default: + // TODO-WASM: add other cases as needed; + // ignoring other reloc types for now + throw new NotSupportedException($"Relocation type {reloc.Type} not yet implemented"); + } + + WriteRelocFromDataSpan(reloc, pData, sectionStart); + } + } + + Span ReadRelocToDataSpan(SymbolicRelocation reloc, byte[] buffer, long sectionStart) + { + Span relocContents = buffer.AsSpan(0, Relocation.GetSize(reloc.Type)); + sectionStream.Position = reloc.Offset + sectionStart; + sectionStream.ReadExactly(relocContents); + return relocContents; + } + + void WriteRelocFromDataSpan(SymbolicRelocation reloc, byte* pData, long sectionStart) + { + sectionStream.Position = reloc.Offset + sectionStart; + sectionStream.Write(new Span(pData, Relocation.GetSize(reloc.Type))); + } } private protected override SectionDataEmitter CreateDataSection( @@ -72,19 +145,70 @@ private protected override void WriteGlobalSection() { } - private protected override void WriteImports() => throw new NotImplementedException(); - private protected override void WriteExports() => throw new NotImplementedException(); - private protected override void WriteElements() => throw new NotImplementedException(); + private const int RtlRestoreContextTagIndex = 0; + private static readonly WasmFuncType RtlRestoreContextTagSignature = new( + new([]), + new([])); + private const int StackPointerGlobalIndex = 0; + private const int ImageBaseGlobalIndex = 1; + private const int TableBaseGlobalIndex = 2; + private const int AsyncContinuationGlobalIndex = 3; + private static readonly Utf8String RtlRestoreContextTagName = new Utf8String("RtlRestoreContextTag"); + private WasmImport[] CreateDefaultGlobalImports() + { + int rtlRestoreContextTagTypeIndex = RegisterSignature(RtlRestoreContextTagSignature); + + return + [ + new WasmImport("webcil", WasmWellKnownGlobalSymbolNode.StackPointerName, import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Mut), index: StackPointerGlobalIndex), + new WasmImport("webcil", WasmWellKnownGlobalSymbolNode.ImageBaseName, import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImageBaseGlobalIndex), + new WasmImport("webcil", WasmWellKnownGlobalSymbolNode.TableBaseName, import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: TableBaseGlobalIndex), + new WasmImport("webcil", WasmWellKnownGlobalSymbolNode.AsyncContinuationName, import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Mut), index: AsyncContinuationGlobalIndex), + new WasmImport("webcil", "table", import: new WasmTableImportType(), index: 0), + new WasmImport("webcil", RtlRestoreContextTagName.ToString(), import: new WasmTagImportType(rtlRestoreContextTagTypeIndex), index: RtlRestoreContextTagIndex), + new WasmImport("webcil", "memory", import: new WasmMemoryImportType(WasmLimitType.HasMin, /* TODO: This is an arbitrary number */ 32)) + ]; + } + + private protected override void WriteImports() + { + foreach (WasmImport import in CreateDefaultGlobalImports()) + { + WriteImport(import); + } + } + + private protected override void WriteExports() + { + } + private protected override void WriteElements() + { + } } // AOT internal sealed partial class WasmRelocatableObjectWriter : WasmObjectWriter { - private protected override void EmitUnwindInfo(SectionWriter sectionWriter, INodeWithCodeInfo nodeWithCodeInfo, Utf8String currentSymbolName) => throw new NotImplementedException(); - private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder() => throw new NotImplementedException(); - private protected override void EmitDebugFunctionInfo(uint methodTypeIndex, Utf8String methodName, SymbolDefinition methodSymbol, INodeWithDebugInfo debugNode, bool hasSequencePoints) => throw new NotImplementedException(); - private protected override void EmitDebugSections(IDictionary definedSymbols) => throw new NotImplementedException(); - private protected override void CreateEhSections() => throw new NotImplementedException(); + private protected override void EmitUnwindInfo(SectionWriter sectionWriter, INodeWithCodeInfo nodeWithCodeInfo, Utf8String currentSymbolName) + { + } + + private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder() + { + return null; + } + + private protected override void EmitDebugFunctionInfo(uint methodTypeIndex, Utf8String methodName, SymbolDefinition methodSymbol, INodeWithDebugInfo debugNode, bool hasSequencePoints) + { + } + + private protected override void EmitDebugSections(IDictionary definedSymbols) + { + } + + private protected override void CreateEhSections() + { + } } } From dee786457479a64f7ff61217b685ceb36ea53a29 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:38:45 -0700 Subject: [PATCH 12/12] Add alias tracking to WasmSymbolManager --- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 10 +++++++++- .../Compiler/ObjectWriter/WasmSymbolManager.cs | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index adf7f620337dfb..09ac49a740e2bb 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -152,7 +152,15 @@ private protected override void RecordMethodDeclaration(INodeWithTypeSignature n flags |= WasmLowering.LoweringFlags.IsUnmanagedCallersOnly; } WriteSignatureIndexForFunction(node.Signature, flags, node); - RegisterFunctionSymbol(new Utf8String(node.GetMangledName(_nodeFactory.NameMangler))); + Utf8String functionName = GetMangledName(node); + RegisterFunctionSymbol(functionName); + + Utf8String alternateName = _nodeFactory.GetSymbolAlternateName(node, out _); + if (!alternateName.IsNull) + { + _wasmSymbolManager.AddAlias(ExternCName(alternateName), functionName); + } + if (node is INodeWithFunclets nodeWithFunclets) { RecordFunclets(nodeWithFunclets); 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) =>