From bd0cac5792ebb70b0d2ea1e26965c8c0f7d4f0dd Mon Sep 17 00:00:00 2001 From: adamperlin Date: Tue, 27 Jan 2026 17:00:54 -0800 Subject: [PATCH 01/10] Add basic import section to WasmObject writer, importing memory, stack pointer, and r2r start address by default --- .../Compiler/ObjectWriter/SectionWriter.cs | 6 + .../Compiler/ObjectWriter/WasmNative.cs | 124 ++++++++++++++++++ .../Compiler/ObjectWriter/WasmObjectWriter.cs | 90 +++++++++++-- 3 files changed, 210 insertions(+), 10 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionWriter.cs index e2dd6be3f9e7ef..bf6ced1ca929db 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionWriter.cs @@ -176,6 +176,12 @@ public readonly void WriteUtf8StringNoNull(string value) bufferWriter.Advance(size); } + public readonly void WriteUtf8WithLength(string value) + { + WriteULEB128((ulong)Encoding.UTF8.GetByteCount(value)); + WriteUtf8StringNoNull(value); + } + public readonly void WritePadding(int size) => _sectionData.AppendPadding(size); public readonly long Position => _sectionData.Length; diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index b31a65dcb25963..817a8b2d536120 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -51,6 +51,12 @@ public enum WasmValueType : byte F64 = 0x7C } + public enum WasmMutabilityType : byte + { + Const = 0x00, + Mut = 0x01 + } + public static class WasmValueTypeExtensions { public static string ToTypeString(this WasmValueType valueType) @@ -182,6 +188,7 @@ public override string ToString() } } + // Represents a WebAssembly expression used in simple contexts for address calculation enum WasmExprKind { @@ -223,4 +230,121 @@ public int Encode(Span buffer) return pos; } } + + public enum WasmExternalKind : byte + { + Function = 0x00, + Table = 0x01, + Memory = 0x02, + Global = 0x03, + Tag = 0x04 + } + + public class WasmGlobalType + { + WasmValueType ValueType; + WasmMutabilityType Mutability; + + public WasmGlobalType(WasmValueType valueType, WasmMutabilityType mutability) + { + ValueType = valueType; + Mutability = mutability; + } + + public int Encode(Span buffer) + { + buffer[0] = (byte)ValueType; + buffer[1] = (byte)Mutability; + return 2; + } + + public int EncodeSize() => 2; + } + + public enum WasmLimitType : byte + { + HasMin = 0x00, + HasMinAndMax = 0x01 + } + + public class WasmMemoryType + { + WasmLimitType LimitType; + uint Min; + uint? Max; + + public WasmMemoryType(WasmLimitType limitType, uint min, uint? max = null) + { + if (LimitType == WasmLimitType.HasMinAndMax && !Max.HasValue) + { + throw new ArgumentException("Max must be provided when LimitType is HasMinAndMax"); + } + + LimitType = limitType; + Min = min; + Max = max; + } + + public int Encode(Span buffer) + { + int pos = 0; + buffer[pos++] = (byte)LimitType; + pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), Min); + if (LimitType == WasmLimitType.HasMinAndMax) + { + pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), Max!.Value); + } + return pos; + } + + public int EncodeSize() => 2; + } + + public class WasmImport + { + public string Module; + public string Name; + public WasmExternalKind Kind; + + WasmMemoryType? _wasmMemory = null; + WasmGlobalType? _wasmGlobal = null; + + public WasmMemoryType? WasmMemory + { + get + { + Debug.Assert(_wasmGlobal == null); + ArgumentNullException.ThrowIfNull(_wasmMemory, "WasmImport does not represent a WasmMemoryType"); + return _wasmMemory; + } + } + + public WasmGlobalType? WasmGlobal + { + get + { + Debug.Assert( _wasmMemory == null); + ArgumentNullException.ThrowIfNull(_wasmGlobal, "WasmImport does not represent a WasmGlobalType"); + return _wasmGlobal; + } + } + + public WasmImport(string module, string name, WasmMemoryType wasmMemory) + { + Module = module; + Name = name; + _wasmMemory = wasmMemory; + Kind = WasmExternalKind.Memory; + } + + public WasmImport(string module, string name, WasmGlobalType wasmGlobal) + { + Module = module; + Name = name; + _wasmGlobal = wasmGlobal; + Kind = WasmExternalKind.Global; + } + } +#nullable disable + } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 4b52fa9da4c79c..1016ac41aa7b88 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -29,6 +29,7 @@ internal static class WasmObjectNodeSection public static readonly ObjectNodeSection ExportSection = new ObjectNodeSection("wasm.export", SectionType.ReadOnly, needsAlign: false); public static readonly ObjectNodeSection MemorySection = new ObjectNodeSection("wasm.memory", SectionType.ReadOnly, needsAlign: false); public static readonly ObjectNodeSection TableSection = new ObjectNodeSection("wasm.table", SectionType.ReadOnly, needsAlign: false); + public static readonly ObjectNodeSection ImportSection = new ObjectNodeSection("wasm.import", SectionType.ReadOnly, needsAlign: false); } /// @@ -98,6 +99,49 @@ private void WriteSignatureIndexForFunction(MethodDesc desc) writer.WriteULEB128((ulong)signatureIndex); } + + private int _numImports; + /// + /// Writes the common prefix for an import entry, which includes the module name, import name, and kind. + /// + private SectionWriter WriteImport(WasmImport import) + { + SectionWriter writer = GetOrCreateSection(WasmObjectNodeSection.ImportSection); + writer.WriteUtf8WithLength(import.Module); + writer.WriteUtf8WithLength(import.Name); + writer.WriteByte((byte)import.Kind); + + switch (import.Kind) + { + case WasmExternalKind.Function: + // TODO-WASM: Handle function imports + throw new NotImplementedException("Function imports are not yet implemented."); + case WasmExternalKind.Table: + // TODO-WASM: Handle table imports + throw new NotImplementedException("Table imports are not yet implemented."); + + case WasmExternalKind.Memory: + { + int size = import.WasmMemory.EncodeSize(); + import.WasmMemory.Encode(writer.Buffer.GetSpan(size)); + writer.Buffer.Advance(size); + break; + } + case WasmExternalKind.Global: + { + int size = import.WasmGlobal.EncodeSize(); + import.WasmGlobal.Encode(writer.Buffer.GetSpan(size)); + writer.Buffer.Advance(size); + break; + } + default: + throw new NotImplementedException($"Import kind not implemented: {import.Kind}"); + } + + _numImports++; + return writer; + } + /// /// WebAssembly export descriptor kinds per the spec. /// @@ -134,7 +178,6 @@ private void WriteMemoryExport(string name, int memoryIndex) => private void WriteGlobalExport(string name, int globalIndex) => WriteExport(name, WasmExportKind.Global, globalIndex); - private List _sections = new(); private Dictionary _sectionNameToIndex = new(); private Dictionary _sectionToType = new() @@ -143,10 +186,10 @@ private void WriteGlobalExport(string name, int globalIndex) => { WasmObjectNodeSection.FunctionSection, WasmSectionType.Function }, { WasmObjectNodeSection.TableSection, WasmSectionType.Table }, { WasmObjectNodeSection.ExportSection, WasmSectionType.Export }, + { WasmObjectNodeSection.ImportSection, WasmSectionType.Import }, { WasmObjectNodeSection.TypeSection, WasmSectionType.Type }, { ObjectNodeSection.WasmCodeSection, WasmSectionType.Code } }; - private WasmSectionType GetWasmSectionType(ObjectNodeSection section) { if (!_sectionToType.ContainsKey(section)) @@ -239,8 +282,7 @@ private void WriteMemorySection(ulong contentSize) private protected override void EmitSectionsAndLayout() { GetOrCreateSection(WasmObjectNodeSection.CombinedDataSection); - ulong dataContentSize = (ulong)SectionByName(WasmObjectNodeSection.CombinedDataSection.Name).ContentSize; - WriteMemorySection(dataContentSize + DataStartOffset); + //WriteMemorySection(dataContentSize + DataStartOffset); WriteTableSection(); } @@ -271,12 +313,12 @@ private protected override void EmitObjectFile(Stream outputFileStream) // Type section (1) SectionByName(WasmObjectNodeSection.TypeSection.Name).Emit(outputFileStream); + // Import section (2) + SectionByName(WasmObjectNodeSection.ImportSection.Name).Emit(outputFileStream); // Function section (3) SectionByName(WasmObjectNodeSection.FunctionSection.Name).Emit(outputFileStream); // Table section (4) SectionByName(WasmObjectNodeSection.TableSection.Name).Emit(outputFileStream); - // Memory section (5) - SectionByName(WasmObjectNodeSection.MemorySection.Name).Emit(outputFileStream); // Export section (7) SectionByName(WasmObjectNodeSection.ExportSection.Name).Emit(outputFileStream); // Code section (10) @@ -292,12 +334,31 @@ private protected override void EmitRelocations(int sectionIndex, List definedSymbols, SortedSet undefinedSymbols) + private WasmImport[] _defaultImports = new[] { - WriteMemoryExport("memory", 0); - WriteTableExport("table", 0); + null, // placeholder for memory, which is set up dynamically in WriteImports() + new WasmImport("env", "__stack_pointer", new WasmGlobalType(WasmValueType.I32, WasmMutabilityType.Mut)), + new WasmImport("env", "__r2r_start", new WasmGlobalType(WasmValueType.I32, WasmMutabilityType.Const)), + }; + + private void WriteImports() + { + // Calculate the required memory size based on the combined data section size and data start offset + ulong contentSize = (ulong)SectionByName(WasmObjectNodeSection.CombinedDataSection.Name).ContentSize; + ulong numPages = (contentSize + (1<<16) - 1) >> 16; + + _defaultImports[0] = new WasmImport("env", "memory", + new WasmMemoryType(0x00, (uint)numPages)); // memory limits: flags (0 = only minimum) + + foreach (WasmImport import in _defaultImports) + { + WriteImport(import); + } + } + private void WriteExports() + { + WriteTableExport("table", 0); string[] functionExports = _uniqueSymbols.Keys.ToArray(); // TODO-WASM: Handle exports better (e.g., only export public methods, etc.) // Also, see if we could leverage definedSymbols for this instead of doing our own bookkeeping in _uniqueSymbols. @@ -305,6 +366,13 @@ private protected override void EmitSymbolTable(IDictionary definedSymbols, SortedSet undefinedSymbols) + { + WriteImports(); + WriteExports(); int funcIdx = _sectionNameToIndex[WasmObjectNodeSection.FunctionSection.Name]; PrependCount(_sections[funcIdx], _methodCount); @@ -314,6 +382,8 @@ private protected override void EmitSymbolTable(IDictionary Date: Wed, 28 Jan 2026 15:10:21 -0800 Subject: [PATCH 02/10] Add addtional expression types for wasm instructions Lay out R2R data segments relative to imported __r2r_start global --- .../Compiler/ObjectWriter/WasmNative.cs | 127 ++++++++++++++++-- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 25 ++-- 2 files changed, 130 insertions(+), 22 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index 817a8b2d536120..ccbb5c44471bc7 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -8,6 +8,12 @@ namespace ILCompiler.ObjectWriter { + public interface IWasmEncodable + { + int EncodeSize(); + int Encode(Span buffer); + } + public enum WasmSectionType { Custom = 0, @@ -188,20 +194,85 @@ public override string ToString() } } - // Represents a WebAssembly expression used in simple contexts for address calculation - enum WasmExprKind + public enum WasmExprKind { I32Const = 0x41, - I64Const = 0x42 + I64Const = 0x42, + GlobalGet = 0x23, + I32Add = 0x6A, + } + + public static class WasmExprKindExtensions + { + public static bool IsConstExpr(this WasmExprKind kind) + { + return kind == WasmExprKind.I32Const || kind == WasmExprKind.I64Const; + } + + public static bool IsBinaryExpr(this WasmExprKind kind) + { + return kind == WasmExprKind.I32Add; + } + + public static bool IsGlobalVarExpr(this WasmExprKind kind) + { + return kind == WasmExprKind.GlobalGet; + } + } + + class WasmInstructionGroup : IWasmEncodable + { + readonly WasmExpr[] WasmExprs; + public WasmInstructionGroup(WasmExpr[] wasmExprs) + { + WasmExprs = wasmExprs; + } + + public int Encode(Span buffer) + { + int pos = 0; + foreach (var expr in WasmExprs) + { + pos += expr.Encode(buffer.Slice(pos)); + } + buffer[pos++] = 0x0B; // end opcode + return pos; + } + + public int EncodeSize() + { + int size = 0; + foreach (var expr in WasmExprs) + { + size += expr.EncodeSize(); + } + // plus one for the end opcode + return size + 1; + } } - class WasmConstExpr + public abstract class WasmExpr : IWasmEncodable { WasmExprKind _kind; + public WasmExpr(WasmExprKind kind) + { + _kind = kind; + } + + public virtual int EncodeSize() => 1; + public virtual int Encode(Span buffer) + { + buffer[0] = (byte)_kind; + return 1; + } + } + + class WasmConstExpr : WasmExpr + { long ConstValue; - public WasmConstExpr(WasmExprKind kind, long value) + public WasmConstExpr(WasmExprKind kind, long value): base(kind) { if (kind == WasmExprKind.I32Const) { @@ -209,28 +280,58 @@ public WasmConstExpr(WasmExprKind kind, long value) ArgumentOutOfRangeException.ThrowIfLessThan(value, int.MinValue); } - _kind = kind; ConstValue = value; } - public int EncodeSize() + public override int EncodeSize() { uint valSize = DwarfHelper.SizeOfSLEB128(ConstValue); - return 1 + (int)valSize + 1; // opcode + value + end opcode + return base.EncodeSize() + (int)valSize; } - public int Encode(Span buffer) + public override int Encode(Span buffer) { - int pos = 0; - buffer[pos++] = (byte)_kind; // the kind is the opcode, either i32.const or i64.const - + int pos = base.Encode(buffer); pos += DwarfHelper.WriteSLEB128(buffer.Slice(pos), ConstValue); - buffer[pos++] = 0x0B; // end opcode return pos; } } + class WasmGlobalVarExpr : WasmExpr + { + public readonly int GlobalIndex; + public WasmGlobalVarExpr(WasmExprKind kind, int globalIndex): base(kind) + { + Debug.Assert(globalIndex >= 0); + Debug.Assert(kind.IsGlobalVarExpr()); + GlobalIndex = globalIndex; + } + + public override int Encode(Span buffer) + { + int pos = base.Encode(buffer); + pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), (uint)GlobalIndex); + return pos; + } + + public override int EncodeSize() + { + return base.EncodeSize() + (int)DwarfHelper.SizeOfULEB128((uint)GlobalIndex); + } + } + + // Represents a binary expression (e.g., i32.add) + class WasmBinaryExpr : WasmExpr + { + public WasmBinaryExpr(WasmExprKind kind): base(kind) + { + Debug.Assert(kind.IsBinaryExpr()); + } + + // base class defaults are sufficient as the base class encodes just the opcode + } + public enum WasmExternalKind : byte { Function = 0x00, diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 1016ac41aa7b88..22ac502b6138f0 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -38,7 +38,6 @@ internal static class WasmObjectNodeSection internal sealed class WasmObjectWriter : ObjectWriter { protected override CodeDataLayout LayoutMode => CodeDataLayout.Separate; - private const int DataStartOffset = 0x10000; // Start of linear memory for data segments (leaving 1 page for stack) public WasmObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder) : base(factory, options, outputInfoBuilder) @@ -206,16 +205,25 @@ protected internal override void UpdateSectionAlignment(int sectionIndex, int al // This is a no-op for now under Wasm } - private WasmDataSection CreateCombinedDataSection(int dataStartOffset) + private WasmDataSection CreateCombinedDataSection() { + WasmInstructionGroup GetR2StartOffset(int offset) + { + return new WasmInstructionGroup([ + new WasmConstExpr(WasmExprKind.GlobalGet, 1), // global.get(__r2r_start) + new WasmConstExpr(WasmExprKind.I32Const, offset), // i32.const offset + new WasmBinaryExpr(WasmExprKind.I32Add), // i32.add + ]); + } + IEnumerable dataSections = _sections.Where(s => s.Type == WasmSectionType.Data); - int offset = dataStartOffset; + int offset = 0; List segments = new(); foreach (WasmSection wasmSection in dataSections) { Debug.Assert(wasmSection.Type == WasmSectionType.Data); WasmDataSegment segment = new WasmDataSegment(wasmSection.Stream, wasmSection.Name, WasmDataSectionType.Active, - new WasmConstExpr(WasmExprKind.I32Const, (long)offset)); + GetR2StartOffset(offset)); segments.Add(segment); offset += segment.ContentSize; } @@ -255,7 +263,7 @@ private protected override void CreateSection(ObjectNodeSection section, Utf8Str WasmSection wasmSection; if (section == WasmObjectNodeSection.CombinedDataSection) { - wasmSection = CreateCombinedDataSection(DataStartOffset); + wasmSection = CreateCombinedDataSection(); } else { @@ -282,7 +290,6 @@ private void WriteMemorySection(ulong contentSize) private protected override void EmitSectionsAndLayout() { GetOrCreateSection(WasmObjectNodeSection.CombinedDataSection); - //WriteMemorySection(dataContentSize + DataStartOffset); WriteTableSection(); } @@ -343,7 +350,7 @@ private protected override void EmitRelocations(int sectionIndex, List> 16; @@ -628,9 +635,9 @@ internal class WasmDataSegment // The segments are not sections per se, but they represent data segments within the data section. Stream _stream; WasmDataSectionType _type; - WasmConstExpr _initExpr; + WasmInstructionGroup _initExpr; - public WasmDataSegment(Stream contents, Utf8String name, WasmDataSectionType type, WasmConstExpr initExpr) + public WasmDataSegment(Stream contents, Utf8String name, WasmDataSectionType type, WasmInstructionGroup initExpr) { _stream = contents; _type = type; From 6232eef7fa0f83377bf3a8b61ba9cb3bf5fa697e Mon Sep 17 00:00:00 2001 From: adamperlin Date: Thu, 29 Jan 2026 15:38:49 -0800 Subject: [PATCH 03/10] Slight refactoring: Add WasmExpr dsl and move some encoding logic into WasmImport --- .../Compiler/ObjectWriter/WasmNative.cs | 100 +++++++++--------- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 41 ++----- 2 files changed, 58 insertions(+), 83 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index ccbb5c44471bc7..531ee005bb120e 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -78,7 +78,7 @@ public static string ToTypeString(this WasmValueType valueType) } } - #nullable enable +#nullable enable public readonly struct WasmResultType : IEquatable { private readonly WasmValueType[] _types; @@ -162,7 +162,7 @@ public readonly int Encode(Span buffer) buffer[0] = 0x60; // function type indicator int paramSize = _params.Encode(buffer.Slice(1)); - int returnSize = _returns.Encode(buffer.Slice(1+paramSize)); + int returnSize = _returns.Encode(buffer.Slice(1 + paramSize)); Debug.Assert(totalSize == 1 + paramSize + returnSize); return totalSize; @@ -170,7 +170,7 @@ public readonly int Encode(Span buffer) public bool Equals(WasmFuncType other) { - return _params.Equals(other._params) && _returns.Equals(other._returns); + return _params.Equals(other._params) && _returns.Equals(other._returns); } public override bool Equals(object? obj) @@ -272,7 +272,7 @@ class WasmConstExpr : WasmExpr { long ConstValue; - public WasmConstExpr(WasmExprKind kind, long value): base(kind) + public WasmConstExpr(WasmExprKind kind, long value) : base(kind) { if (kind == WasmExprKind.I32Const) { @@ -285,8 +285,8 @@ public WasmConstExpr(WasmExprKind kind, long value): base(kind) public override int EncodeSize() { - uint valSize = DwarfHelper.SizeOfSLEB128(ConstValue); - return base.EncodeSize() + (int)valSize; + uint valSize = DwarfHelper.SizeOfSLEB128(ConstValue); + return base.EncodeSize() + (int)valSize; } public override int Encode(Span buffer) @@ -298,10 +298,29 @@ public override int Encode(Span buffer) } } + // Simple DSL wrapper for creating Wasm expressions + static class Global + { + public static WasmExpr Get(int index) + { + return new WasmGlobalVarExpr(WasmExprKind.GlobalGet, index); + } + } + + static class I32 + { + public static WasmExpr Const(long value) + { + return new WasmConstExpr(WasmExprKind.I32Const, value); + } + + public static WasmExpr Add => new WasmBinaryExpr(WasmExprKind.I32Add); + } + class WasmGlobalVarExpr : WasmExpr { public readonly int GlobalIndex; - public WasmGlobalVarExpr(WasmExprKind kind, int globalIndex): base(kind) + public WasmGlobalVarExpr(WasmExprKind kind, int globalIndex) : base(kind) { Debug.Assert(globalIndex >= 0); Debug.Assert(kind.IsGlobalVarExpr()); @@ -324,13 +343,19 @@ public override int EncodeSize() // Represents a binary expression (e.g., i32.add) class WasmBinaryExpr : WasmExpr { - public WasmBinaryExpr(WasmExprKind kind): base(kind) + public WasmBinaryExpr(WasmExprKind kind) : base(kind) { Debug.Assert(kind.IsBinaryExpr()); } // base class defaults are sufficient as the base class encodes just the opcode } + public abstract class WasmImportType : IWasmEncodable + { + public abstract int Encode(Span buffer); + public abstract int EncodeSize(); + } + public enum WasmExternalKind : byte { @@ -341,7 +366,7 @@ public enum WasmExternalKind : byte Tag = 0x04 } - public class WasmGlobalType + public class WasmGlobalType : WasmImportType { WasmValueType ValueType; WasmMutabilityType Mutability; @@ -352,14 +377,14 @@ public WasmGlobalType(WasmValueType valueType, WasmMutabilityType mutability) Mutability = mutability; } - public int Encode(Span buffer) + public override int Encode(Span buffer) { buffer[0] = (byte)ValueType; buffer[1] = (byte)Mutability; return 2; } - public int EncodeSize() => 2; + public override int EncodeSize() => 2; } public enum WasmLimitType : byte @@ -367,8 +392,8 @@ public enum WasmLimitType : byte HasMin = 0x00, HasMinAndMax = 0x01 } - - public class WasmMemoryType + + public class WasmMemoryType : WasmImportType { WasmLimitType LimitType; uint Min; @@ -386,7 +411,7 @@ public WasmMemoryType(WasmLimitType limitType, uint min, uint? max = null) Max = max; } - public int Encode(Span buffer) + public override int Encode(Span buffer) { int pos = 0; buffer[pos++] = (byte)LimitType; @@ -398,54 +423,27 @@ public int Encode(Span buffer) return pos; } - public int EncodeSize() => 2; + public override int EncodeSize() => 2; } - public class WasmImport + public class WasmImport : IWasmEncodable { public string Module; public string Name; public WasmExternalKind Kind; + WasmImportType Import; - WasmMemoryType? _wasmMemory = null; - WasmGlobalType? _wasmGlobal = null; - - public WasmMemoryType? WasmMemory - { - get - { - Debug.Assert(_wasmGlobal == null); - ArgumentNullException.ThrowIfNull(_wasmMemory, "WasmImport does not represent a WasmMemoryType"); - return _wasmMemory; - } - } - - public WasmGlobalType? WasmGlobal - { - get - { - Debug.Assert( _wasmMemory == null); - ArgumentNullException.ThrowIfNull(_wasmGlobal, "WasmImport does not represent a WasmGlobalType"); - return _wasmGlobal; - } - } - - public WasmImport(string module, string name, WasmMemoryType wasmMemory) + public WasmImport(string module, string name, WasmExternalKind kind, WasmImportType import) { Module = module; Name = name; - _wasmMemory = wasmMemory; - Kind = WasmExternalKind.Memory; + Kind = kind; + Import = import; } - public WasmImport(string module, string name, WasmGlobalType wasmGlobal) - { - Module = module; - Name = name; - _wasmGlobal = wasmGlobal; - Kind = WasmExternalKind.Global; - } - } -#nullable disable + public int Encode(Span buffer) => Import.Encode(buffer); + public int EncodeSize() => Import.EncodeSize(); +#nullable disable + } } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 22ac502b6138f0..cc1d150cdf1fea 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -110,32 +110,9 @@ private SectionWriter WriteImport(WasmImport import) writer.WriteUtf8WithLength(import.Name); writer.WriteByte((byte)import.Kind); - switch (import.Kind) - { - case WasmExternalKind.Function: - // TODO-WASM: Handle function imports - throw new NotImplementedException("Function imports are not yet implemented."); - case WasmExternalKind.Table: - // TODO-WASM: Handle table imports - throw new NotImplementedException("Table imports are not yet implemented."); - - case WasmExternalKind.Memory: - { - int size = import.WasmMemory.EncodeSize(); - import.WasmMemory.Encode(writer.Buffer.GetSpan(size)); - writer.Buffer.Advance(size); - break; - } - case WasmExternalKind.Global: - { - int size = import.WasmGlobal.EncodeSize(); - import.WasmGlobal.Encode(writer.Buffer.GetSpan(size)); - writer.Buffer.Advance(size); - break; - } - default: - throw new NotImplementedException($"Import kind not implemented: {import.Kind}"); - } + int size = import.EncodeSize(); + import.Encode(writer.Buffer.GetSpan(size)); + writer.Buffer.Advance((int)size); _numImports++; return writer; @@ -210,9 +187,9 @@ private WasmDataSection CreateCombinedDataSection() WasmInstructionGroup GetR2StartOffset(int offset) { return new WasmInstructionGroup([ - new WasmConstExpr(WasmExprKind.GlobalGet, 1), // global.get(__r2r_start) - new WasmConstExpr(WasmExprKind.I32Const, offset), // i32.const offset - new WasmBinaryExpr(WasmExprKind.I32Add), // i32.add + Global.Get(1), // __r2r_start + I32.Const(offset), + I32.Add, ]); } @@ -344,8 +321,8 @@ private protected override void EmitRelocations(int sectionIndex, List> 16; - _defaultImports[0] = new WasmImport("env", "memory", + _defaultImports[0] = new WasmImport("env", "memory", WasmExternalKind.Memory, new WasmMemoryType(0x00, (uint)numPages)); // memory limits: flags (0 = only minimum) foreach (WasmImport import in _defaultImports) From 25a7ff560878c8dc538e2950446a666e5869b80e Mon Sep 17 00:00:00 2001 From: adamperlin Date: Fri, 30 Jan 2026 11:42:50 -0800 Subject: [PATCH 04/10] Address copilot feedback --- .../Compiler/ObjectWriter/WasmNative.cs | 29 +++++++---- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 51 ++++++++++++------- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index 531ee005bb120e..71a4b150606c3f 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -223,16 +223,16 @@ public static bool IsGlobalVarExpr(this WasmExprKind kind) class WasmInstructionGroup : IWasmEncodable { - readonly WasmExpr[] WasmExprs; + readonly WasmExpr[] _wasmExprs; public WasmInstructionGroup(WasmExpr[] wasmExprs) { - WasmExprs = wasmExprs; + _wasmExprs = wasmExprs; } public int Encode(Span buffer) { int pos = 0; - foreach (var expr in WasmExprs) + foreach (var expr in _wasmExprs) { pos += expr.Encode(buffer.Slice(pos)); } @@ -243,7 +243,7 @@ public int Encode(Span buffer) public int EncodeSize() { int size = 0; - foreach (var expr in WasmExprs) + foreach (var expr in _wasmExprs) { size += expr.EncodeSize(); } @@ -350,20 +350,21 @@ public WasmBinaryExpr(WasmExprKind kind) : base(kind) // base class defaults are sufficient as the base class encodes just the opcode } + public abstract class WasmImportType : IWasmEncodable { public abstract int Encode(Span buffer); public abstract int EncodeSize(); } - public enum WasmExternalKind : byte { Function = 0x00, Table = 0x01, Memory = 0x02, Global = 0x03, - Tag = 0x04 + Tag = 0x04, + Count = 0x05 // Not actually part of the spec; used for counting kinds } public class WasmGlobalType : WasmImportType @@ -401,7 +402,7 @@ public class WasmMemoryType : WasmImportType public WasmMemoryType(WasmLimitType limitType, uint min, uint? max = null) { - if (LimitType == WasmLimitType.HasMinAndMax && !Max.HasValue) + if (limitType == WasmLimitType.HasMinAndMax && !max.HasValue) { throw new ArgumentException("Max must be provided when LimitType is HasMinAndMax"); } @@ -423,7 +424,15 @@ public override int Encode(Span buffer) return pos; } - public override int EncodeSize() => 2; + public override int EncodeSize() + { + uint size = 1 + DwarfHelper.SizeOfULEB128(Min); + if (LimitType == WasmLimitType.HasMinAndMax) + { + size += DwarfHelper.SizeOfULEB128(Max!.Value); + } + return (int)size; + } } public class WasmImport : IWasmEncodable @@ -431,14 +440,16 @@ public class WasmImport : IWasmEncodable public string Module; public string Name; public WasmExternalKind Kind; + public int? Index; WasmImportType Import; - public WasmImport(string module, string name, WasmExternalKind kind, WasmImportType import) + public WasmImport(string module, string name, WasmExternalKind kind, WasmImportType import, int? index = null) { Module = module; Name = name; Kind = kind; Import = import; + Index = index; } public int Encode(Span buffer) => Import.Encode(buffer); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index cc1d150cdf1fea..1f0fcbceefeab2 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -110,9 +110,10 @@ private SectionWriter WriteImport(WasmImport import) writer.WriteUtf8WithLength(import.Name); writer.WriteByte((byte)import.Kind); - int size = import.EncodeSize(); - import.Encode(writer.Buffer.GetSpan(size)); - writer.Buffer.Advance((int)size); + int encodeSize = import.EncodeSize(); + int bytesWritten = import.Encode(writer.Buffer.GetSpan(encodeSize)); + Debug.Assert(bytesWritten == encodeSize); + writer.Buffer.Advance((int)bytesWritten); _numImports++; return writer; @@ -184,10 +185,10 @@ protected internal override void UpdateSectionAlignment(int sectionIndex, int al private WasmDataSection CreateCombinedDataSection() { - WasmInstructionGroup GetR2StartOffset(int offset) + WasmInstructionGroup GetR2RStartOffset(int offset) { return new WasmInstructionGroup([ - Global.Get(1), // __r2r_start + Global.Get(R2RStartGlobalIndex), I32.Const(offset), I32.Add, ]); @@ -200,7 +201,7 @@ WasmInstructionGroup GetR2StartOffset(int offset) { Debug.Assert(wasmSection.Type == WasmSectionType.Data); WasmDataSegment segment = new WasmDataSegment(wasmSection.Stream, wasmSection.Name, WasmDataSectionType.Active, - GetR2StartOffset(offset)); + GetR2RStartOffset(offset)); segments.Add(segment); offset += segment.ContentSize; } @@ -261,7 +262,7 @@ private void WriteMemorySection(ulong contentSize) SectionWriter writer = GetOrCreateSection(WasmObjectNodeSection.MemorySection); writer.WriteByte(0x01); // number of memories writer.WriteByte(0x00); // memory limits: flags (0 = only minimum) - writer.WriteULEB128(numPages); // memory limits: initial size in pages (64kb each) + writer.WriteULEB128(numPages); // memory limits: initial encodeSize in pages (64kb each) } private protected override void EmitSectionsAndLayout() @@ -277,7 +278,7 @@ private void WriteTableSection() writer.WriteByte(0x70); // element type: funcref writer.WriteByte(0x01); // table limits: flags (1 = has maximum) writer.WriteULEB128((ulong)0); - writer.WriteULEB128((ulong)_methodCount); // table limits: initial size + writer.WriteULEB128((ulong)_methodCount); // table limits: initial encodeSize } private void PrependCount(WasmSection section, int count) @@ -318,24 +319,38 @@ private protected override void EmitRelocations(int sectionIndex, List> 16; + ulong dataPages = (contentSize + (1<<16) - 1) >> 16; + int numPages = int.Max((int)dataPages, 1); // Ensure at least one page is allocated for the minimum _defaultImports[0] = new WasmImport("env", "memory", WasmExternalKind.Memory, - new WasmMemoryType(0x00, (uint)numPages)); // memory limits: flags (0 = only minimum) + new WasmMemoryType(WasmLimitType.HasMin, (uint)numPages)); // memory limits: flags (0 = only minimum) + int[] assignedImportIndices = new int[(int)WasmExternalKind.Count]; foreach (WasmImport import in _defaultImports) { + if (import.Index.HasValue) + { + int assigned = assignedImportIndices[(int)import.Kind]; + if (assigned != import.Index.Value) + { + throw new InvalidOperationException($"Import {import.Module}.{import.Name} of kind {import.Kind} assigned index {assigned}, needs {import.Index.Value}"); + } + } + assignedImportIndices[(int)import.Kind]++; WriteImport(import); } } @@ -520,7 +535,7 @@ public virtual int EncodeHeader(Span headerBuffer) // Section header consists of: // 1 byte: section type - // ULEB128: size of section + // ULEB128: encodeSize of section headerBuffer[0] = (byte)Type; DwarfHelper.WriteULEB128(headerBuffer.Slice(1), contentSize); @@ -621,7 +636,7 @@ public WasmDataSegment(Stream contents, Utf8String name, WasmDataSectionType typ _initExpr = initExpr; } - // The header size for a data segment consists of just a byte indicating the type of data segment. + // The header encodeSize for a data segment consists of just a byte indicating the type of data segment. public int HeaderSize { get @@ -630,11 +645,11 @@ public int HeaderSize { WasmDataSectionType.Active => (int)DwarfHelper.SizeOfULEB128((ulong)_type) + // type indicator - _initExpr.EncodeSize() + // init expr size - (int)DwarfHelper.SizeOfULEB128((ulong)_stream.Length), // size of data length + _initExpr.EncodeSize() + // init expr encodeSize + (int)DwarfHelper.SizeOfULEB128((ulong)_stream.Length), // encodeSize of data length WasmDataSectionType.Passive => (int)DwarfHelper.SizeOfULEB128((ulong)_type) + // type indicator - (int)DwarfHelper.SizeOfULEB128((ulong)_stream.Length), // size of data length + (int)DwarfHelper.SizeOfULEB128((ulong)_stream.Length), // encodeSize of data length _ => throw new NotImplementedException() }; From 4684843ffcda7bcc653106357336769ee4c394ff Mon Sep 17 00:00:00 2001 From: adamperlin Date: Fri, 30 Jan 2026 12:07:47 -0800 Subject: [PATCH 05/10] Implement review feedback: Place Wasm DSL and associated instruction classes in a separate file and namespace --- .../Compiler/ObjectWriter/WasmInstructions.cs | 174 ++++++++++++++++++ .../Compiler/ObjectWriter/WasmNative.cs | 158 +--------------- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 1 + .../ILCompiler.ReadyToRun.csproj | 1 + 4 files changed, 177 insertions(+), 157 deletions(-) create mode 100644 src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs new file mode 100644 index 00000000000000..f8c04ce37fd874 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs @@ -0,0 +1,174 @@ +// 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.Diagnostics; + +// This namespace implements encodings for certain Wasm expressions (instructions) +// which are used in the object writer. +// For now, these instructions are only used for constructing constant expressions +// to calculate placements for data segments based on imported globals. +namespace ILCompiler.ObjectWriter.WasmInstructions +{ + public enum WasmExprKind + { + I32Const = 0x41, + I64Const = 0x42, + GlobalGet = 0x23, + I32Add = 0x6A, + } + + public static class WasmExprKindExtensions + { + public static bool IsConstExpr(this WasmExprKind kind) + { + return kind == WasmExprKind.I32Const || kind == WasmExprKind.I64Const; + } + + public static bool IsBinaryExpr(this WasmExprKind kind) + { + return kind == WasmExprKind.I32Add; + } + + public static bool IsGlobalVarExpr(this WasmExprKind kind) + { + return kind == WasmExprKind.GlobalGet; + } + } + + // Represents a group of Wasm instructions (expressions) which + // form a complete expression ending with the 'end' opcode. + class WasmInstructionGroup : IWasmEncodable + { + readonly WasmExpr[] _wasmExprs; + public WasmInstructionGroup(WasmExpr[] wasmExprs) + { + _wasmExprs = wasmExprs; + } + + public int Encode(Span buffer) + { + int pos = 0; + foreach (var expr in _wasmExprs) + { + pos += expr.Encode(buffer.Slice(pos)); + } + buffer[pos++] = 0x0B; // end opcode + return pos; + } + + public int EncodeSize() + { + int size = 0; + foreach (var expr in _wasmExprs) + { + size += expr.EncodeSize(); + } + // plus one for the end opcode + return size + 1; + } + } + + public abstract class WasmExpr : IWasmEncodable + { + WasmExprKind _kind; + public WasmExpr(WasmExprKind kind) + { + _kind = kind; + } + + public virtual int EncodeSize() => 1; + public virtual int Encode(Span buffer) + { + buffer[0] = (byte)_kind; + return 1; + } + } + + // Represents a constant expression (e.g., (i32.const )) + class WasmConstExpr : WasmExpr + { + long ConstValue; + + public WasmConstExpr(WasmExprKind kind, long value) : base(kind) + { + if (kind == WasmExprKind.I32Const) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(value, int.MaxValue); + ArgumentOutOfRangeException.ThrowIfLessThan(value, int.MinValue); + } + + ConstValue = value; + } + + public override int EncodeSize() + { + uint valSize = DwarfHelper.SizeOfSLEB128(ConstValue); + return base.EncodeSize() + (int)valSize; + } + + public override int Encode(Span buffer) + { + int pos = base.Encode(buffer); + pos += DwarfHelper.WriteSLEB128(buffer.Slice(pos), ConstValue); + + return pos; + } + } + + // Represents a global variable expression (e.g., (global.get = 0); + Debug.Assert(kind.IsGlobalVarExpr()); + GlobalIndex = globalIndex; + } + + public override int Encode(Span buffer) + { + int pos = base.Encode(buffer); + pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), (uint)GlobalIndex); + return pos; + } + + public override int EncodeSize() + { + return base.EncodeSize() + (int)DwarfHelper.SizeOfULEB128((uint)GlobalIndex); + } + } + + // Represents a binary expression (e.g., i32.add) + class WasmBinaryExpr : WasmExpr + { + public WasmBinaryExpr(WasmExprKind kind) : base(kind) + { + Debug.Assert(kind.IsBinaryExpr()); + } + + // base class defaults are sufficient as the base class encodes just the opcode + } + + // ************************************************ + // Simple DSL wrapper for creating Wasm expressions + // ************************************************ + static class Global + { + public static WasmExpr Get(int index) + { + return new WasmGlobalVarExpr(WasmExprKind.GlobalGet, index); + } + } + + static class I32 + { + public static WasmExpr Const(long value) + { + return new WasmConstExpr(WasmExprKind.I32Const, value); + } + + public static WasmExpr Add => new WasmBinaryExpr(WasmExprKind.I32Add); + } +} diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index 71a4b150606c3f..8cf543c7c3b817 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Collections.Generic; +using ILCompiler.ObjectWriter.WasmInstructions; namespace ILCompiler.ObjectWriter { @@ -194,163 +195,6 @@ public override string ToString() } } - // Represents a WebAssembly expression used in simple contexts for address calculation - public enum WasmExprKind - { - I32Const = 0x41, - I64Const = 0x42, - GlobalGet = 0x23, - I32Add = 0x6A, - } - - public static class WasmExprKindExtensions - { - public static bool IsConstExpr(this WasmExprKind kind) - { - return kind == WasmExprKind.I32Const || kind == WasmExprKind.I64Const; - } - - public static bool IsBinaryExpr(this WasmExprKind kind) - { - return kind == WasmExprKind.I32Add; - } - - public static bool IsGlobalVarExpr(this WasmExprKind kind) - { - return kind == WasmExprKind.GlobalGet; - } - } - - class WasmInstructionGroup : IWasmEncodable - { - readonly WasmExpr[] _wasmExprs; - public WasmInstructionGroup(WasmExpr[] wasmExprs) - { - _wasmExprs = wasmExprs; - } - - public int Encode(Span buffer) - { - int pos = 0; - foreach (var expr in _wasmExprs) - { - pos += expr.Encode(buffer.Slice(pos)); - } - buffer[pos++] = 0x0B; // end opcode - return pos; - } - - public int EncodeSize() - { - int size = 0; - foreach (var expr in _wasmExprs) - { - size += expr.EncodeSize(); - } - // plus one for the end opcode - return size + 1; - } - } - - public abstract class WasmExpr : IWasmEncodable - { - WasmExprKind _kind; - public WasmExpr(WasmExprKind kind) - { - _kind = kind; - } - - public virtual int EncodeSize() => 1; - public virtual int Encode(Span buffer) - { - buffer[0] = (byte)_kind; - return 1; - } - } - - class WasmConstExpr : WasmExpr - { - long ConstValue; - - public WasmConstExpr(WasmExprKind kind, long value) : base(kind) - { - if (kind == WasmExprKind.I32Const) - { - ArgumentOutOfRangeException.ThrowIfGreaterThan(value, int.MaxValue); - ArgumentOutOfRangeException.ThrowIfLessThan(value, int.MinValue); - } - - ConstValue = value; - } - - public override int EncodeSize() - { - uint valSize = DwarfHelper.SizeOfSLEB128(ConstValue); - return base.EncodeSize() + (int)valSize; - } - - public override int Encode(Span buffer) - { - int pos = base.Encode(buffer); - pos += DwarfHelper.WriteSLEB128(buffer.Slice(pos), ConstValue); - - return pos; - } - } - - // Simple DSL wrapper for creating Wasm expressions - static class Global - { - public static WasmExpr Get(int index) - { - return new WasmGlobalVarExpr(WasmExprKind.GlobalGet, index); - } - } - - static class I32 - { - public static WasmExpr Const(long value) - { - return new WasmConstExpr(WasmExprKind.I32Const, value); - } - - public static WasmExpr Add => new WasmBinaryExpr(WasmExprKind.I32Add); - } - - class WasmGlobalVarExpr : WasmExpr - { - public readonly int GlobalIndex; - public WasmGlobalVarExpr(WasmExprKind kind, int globalIndex) : base(kind) - { - Debug.Assert(globalIndex >= 0); - Debug.Assert(kind.IsGlobalVarExpr()); - GlobalIndex = globalIndex; - } - - public override int Encode(Span buffer) - { - int pos = base.Encode(buffer); - pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), (uint)GlobalIndex); - return pos; - } - - public override int EncodeSize() - { - return base.EncodeSize() + (int)DwarfHelper.SizeOfULEB128((uint)GlobalIndex); - } - } - - // Represents a binary expression (e.g., i32.add) - class WasmBinaryExpr : WasmExpr - { - public WasmBinaryExpr(WasmExprKind kind) : base(kind) - { - Debug.Assert(kind.IsBinaryExpr()); - } - - // base class defaults are sufficient as the base class encodes just the opcode - } - public abstract class WasmImportType : IWasmEncodable { public abstract int Encode(Span buffer); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 1f0fcbceefeab2..5b656d4ed13efe 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -16,6 +16,7 @@ using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData; using CodeDataLayout = CodeDataLayoutMode.CodeDataLayout; using System.Collections.Immutable; +using ILCompiler.ObjectWriter.WasmInstructions; namespace ILCompiler.ObjectWriter { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 4e460cb2b14544..b0b13bb1467dc8 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -167,6 +167,7 @@ + From 1d3f7fcf6a7074a364df734e4b4f6158c3c8ea48 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Fri, 30 Jan 2026 13:36:32 -0800 Subject: [PATCH 06/10] Apply additional copilot review feedback --- .../tools/Common/Compiler/ObjectWriter/WasmNative.cs | 11 +++++------ .../Common/Compiler/ObjectWriter/WasmObjectWriter.cs | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index 8cf543c7c3b817..f255c840109790 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Linq; using System.Collections.Generic; -using ILCompiler.ObjectWriter.WasmInstructions; namespace ILCompiler.ObjectWriter { @@ -281,11 +280,11 @@ public override int EncodeSize() public class WasmImport : IWasmEncodable { - public string Module; - public string Name; - public WasmExternalKind Kind; - public int? Index; - WasmImportType Import; + public readonly string Module; + public readonly string Name; + public readonly WasmExternalKind Kind; + public readonly int? Index; + public readonly WasmImportType Import; public WasmImport(string module, string name, WasmExternalKind kind, WasmImportType import, int? index = null) { diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 5b656d4ed13efe..2be50100e646c9 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -334,11 +334,11 @@ private void WriteImports() { // Calculate the required memory encodeSize based on the combined data section encodeSize ulong contentSize = (ulong)SectionByName(WasmObjectNodeSection.CombinedDataSection.Name).ContentSize; - ulong dataPages = (contentSize + (1<<16) - 1) >> 16; - int numPages = int.Max((int)dataPages, 1); // Ensure at least one page is allocated for the minimum + uint dataPages = checked((uint)((contentSize + (1<<16) - 1) >> 16)); + uint numPages = Math.Max(dataPages, 1); // Ensure at least one page is allocated for the minimum _defaultImports[0] = new WasmImport("env", "memory", WasmExternalKind.Memory, - new WasmMemoryType(WasmLimitType.HasMin, (uint)numPages)); // memory limits: flags (0 = only minimum) + new WasmMemoryType(WasmLimitType.HasMin, numPages)); // memory limits: flags (0 = only minimum) int[] assignedImportIndices = new int[(int)WasmExternalKind.Count]; foreach (WasmImport import in _defaultImports) From 5790ae4b2adb4d603d154733d643f0e768f36976 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Fri, 30 Jan 2026 15:33:45 -0800 Subject: [PATCH 07/10] Apply additional copilot review feedback --- .../Common/Compiler/ObjectWriter/WasmObjectWriter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 2be50100e646c9..5db408a0d6f540 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -263,7 +263,7 @@ private void WriteMemorySection(ulong contentSize) SectionWriter writer = GetOrCreateSection(WasmObjectNodeSection.MemorySection); writer.WriteByte(0x01); // number of memories writer.WriteByte(0x00); // memory limits: flags (0 = only minimum) - writer.WriteULEB128(numPages); // memory limits: initial encodeSize in pages (64kb each) + writer.WriteULEB128(numPages); // memory limits: initial size in pages (64kb each) } private protected override void EmitSectionsAndLayout() @@ -279,7 +279,7 @@ private void WriteTableSection() writer.WriteByte(0x70); // element type: funcref writer.WriteByte(0x01); // table limits: flags (1 = has maximum) writer.WriteULEB128((ulong)0); - writer.WriteULEB128((ulong)_methodCount); // table limits: initial encodeSize + writer.WriteULEB128((ulong)_methodCount); // table limits: initial size in number of entries } private void PrependCount(WasmSection section, int count) @@ -332,7 +332,7 @@ private protected override void EmitRelocations(int sectionIndex, List> 16)); uint numPages = Math.Max(dataPages, 1); // Ensure at least one page is allocated for the minimum @@ -536,7 +536,7 @@ public virtual int EncodeHeader(Span headerBuffer) // Section header consists of: // 1 byte: section type - // ULEB128: encodeSize of section + // ULEB128: size of section headerBuffer[0] = (byte)Type; DwarfHelper.WriteULEB128(headerBuffer.Slice(1), contentSize); From 7bfc1242240ca85e63909fef0c48bdf2015e739b Mon Sep 17 00:00:00 2001 From: adamperlin Date: Mon, 2 Feb 2026 11:44:52 -0800 Subject: [PATCH 08/10] Apply additional copilot review feedback --- .../Compiler/ObjectWriter/WasmInstructions.cs | 4 +- .../Compiler/ObjectWriter/WasmNative.cs | 40 +++++++++---------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs index f8c04ce37fd874..0ad405a9658201 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs @@ -5,9 +5,9 @@ using System.Diagnostics; // This namespace implements encodings for certain Wasm expressions (instructions) -// which are used in the object writer. +// which are used in the object writer. // For now, these instructions are only used for constructing constant expressions -// to calculate placements for data segments based on imported globals. +// to calculate placements for data segments based on imported globals. namespace ILCompiler.ObjectWriter.WasmInstructions { public enum WasmExprKind diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index f255c840109790..9dd8e5d1a33436 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -212,19 +212,19 @@ public enum WasmExternalKind : byte public class WasmGlobalType : WasmImportType { - WasmValueType ValueType; - WasmMutabilityType Mutability; + WasmValueType _valueType; + WasmMutabilityType _mutability; public WasmGlobalType(WasmValueType valueType, WasmMutabilityType mutability) { - ValueType = valueType; - Mutability = mutability; + _valueType = valueType; + _mutability = mutability; } public override int Encode(Span buffer) { - buffer[0] = (byte)ValueType; - buffer[1] = (byte)Mutability; + buffer[0] = (byte)_valueType; + buffer[1] = (byte)_mutability; return 2; } @@ -239,9 +239,9 @@ public enum WasmLimitType : byte public class WasmMemoryType : WasmImportType { - WasmLimitType LimitType; - uint Min; - uint? Max; + WasmLimitType _limitType; + uint _min; + uint? _max; public WasmMemoryType(WasmLimitType limitType, uint min, uint? max = null) { @@ -250,29 +250,29 @@ public WasmMemoryType(WasmLimitType limitType, uint min, uint? max = null) throw new ArgumentException("Max must be provided when LimitType is HasMinAndMax"); } - LimitType = limitType; - Min = min; - Max = max; + _limitType = limitType; + _min = min; + _max = max; } public override int Encode(Span buffer) { int pos = 0; - buffer[pos++] = (byte)LimitType; - pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), Min); - if (LimitType == WasmLimitType.HasMinAndMax) + buffer[pos++] = (byte)_limitType; + pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), _min); + if (_limitType == WasmLimitType.HasMinAndMax) { - pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), Max!.Value); + pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), _max!.Value); } return pos; } public override int EncodeSize() { - uint size = 1 + DwarfHelper.SizeOfULEB128(Min); - if (LimitType == WasmLimitType.HasMinAndMax) + uint size = 1 + DwarfHelper.SizeOfULEB128(_min); + if (_limitType == WasmLimitType.HasMinAndMax) { - size += DwarfHelper.SizeOfULEB128(Max!.Value); + size += DwarfHelper.SizeOfULEB128(_max!.Value); } return (int)size; } @@ -297,7 +297,5 @@ public WasmImport(string module, string name, WasmExternalKind kind, WasmImportT public int Encode(Span buffer) => Import.Encode(buffer); public int EncodeSize() => Import.EncodeSize(); - -#nullable disable } } From 0100a7f708878bc78b651be3698d00c5b9530349 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Tue, 3 Feb 2026 11:43:48 -0800 Subject: [PATCH 09/10] Embed kind as part of WasmImportType to prevent mismatches --- .../Common/Compiler/ObjectWriter/WasmNative.cs | 18 +++++++++++------- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 7 +++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index 9dd8e5d1a33436..bcca86d4c73918 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -196,8 +196,13 @@ public override string ToString() public abstract class WasmImportType : IWasmEncodable { + public readonly WasmExternalKind Kind; public abstract int Encode(Span buffer); public abstract int EncodeSize(); + public WasmImportType(WasmExternalKind kind) + { + Kind = kind; + } } public enum WasmExternalKind : byte @@ -210,12 +215,12 @@ public enum WasmExternalKind : byte Count = 0x05 // Not actually part of the spec; used for counting kinds } - public class WasmGlobalType : WasmImportType + public class WasmGlobalImportType : WasmImportType { WasmValueType _valueType; WasmMutabilityType _mutability; - public WasmGlobalType(WasmValueType valueType, WasmMutabilityType mutability) + public WasmGlobalImportType(WasmValueType valueType, WasmMutabilityType mutability) : base (WasmExternalKind.Global) { _valueType = valueType; _mutability = mutability; @@ -237,13 +242,13 @@ public enum WasmLimitType : byte HasMinAndMax = 0x01 } - public class WasmMemoryType : WasmImportType + public class WasmMemoryImportType : WasmImportType { WasmLimitType _limitType; uint _min; uint? _max; - public WasmMemoryType(WasmLimitType limitType, uint min, uint? max = null) + public WasmMemoryImportType(WasmLimitType limitType, uint min, uint? max = null) : base(WasmExternalKind.Memory) { if (limitType == WasmLimitType.HasMinAndMax && !max.HasValue) { @@ -282,15 +287,14 @@ public class WasmImport : IWasmEncodable { public readonly string Module; public readonly string Name; - public readonly WasmExternalKind Kind; + public WasmExternalKind Kind => Import.Kind; public readonly int? Index; public readonly WasmImportType Import; - public WasmImport(string module, string name, WasmExternalKind kind, WasmImportType import, int? index = null) + public WasmImport(string module, string name, WasmImportType import, int? index = null) { Module = module; Name = name; - Kind = kind; Import = import; Index = index; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 5db408a0d6f540..bb1653049044f8 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -326,8 +326,8 @@ private protected override void EmitRelocations(int sectionIndex, List> 16)); uint numPages = Math.Max(dataPages, 1); // Ensure at least one page is allocated for the minimum - _defaultImports[0] = new WasmImport("env", "memory", WasmExternalKind.Memory, - new WasmMemoryType(WasmLimitType.HasMin, numPages)); // memory limits: flags (0 = only minimum) + _defaultImports[0] = new WasmImport("env", "memory", import: new WasmMemoryImportType(WasmLimitType.HasMin, numPages)); // memory limits: flags (0 = only minimum) int[] assignedImportIndices = new int[(int)WasmExternalKind.Count]; foreach (WasmImport import in _defaultImports) From c4b315a2d01ef1d0e85203682af3275d24e5660b Mon Sep 17 00:00:00 2001 From: adamperlin Date: Tue, 3 Feb 2026 11:47:39 -0800 Subject: [PATCH 10/10] Update outdated comment --- .../tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index bb1653049044f8..66927483140a05 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -102,7 +102,7 @@ private void WriteSignatureIndexForFunction(MethodDesc desc) private int _numImports; /// - /// Writes the common prefix for an import entry, which includes the module name, import name, and kind. + /// Writes the given import entry, including its prefix (module/name/kind) and body (external ref). /// private SectionWriter WriteImport(WasmImport import) {