From 836d00cf490bf12a7fd43227ab444e1f2011a9b6 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Tue, 20 Jan 2026 14:51:37 -0800 Subject: [PATCH 1/7] Add support for emitting empty table section to WasmObjectWriter --- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 2d79d8b9b14098..f774a242d70eb3 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -28,6 +28,7 @@ internal static class WasmObjectNodeSection public static readonly ObjectNodeSection TypeSection = new ObjectNodeSection("wasm.type", SectionType.ReadOnly, needsAlign: false); 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); } /// @@ -36,6 +37,7 @@ internal static class WasmObjectNodeSection internal sealed class WasmObjectWriter : ObjectWriter { protected override CodeDataLayout LayoutMode => CodeDataLayout.Separate; + private const int DataStartOffset = 0x10000; // Start data at 64KB offset to leave space for stack, etc. public WasmObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder) : base(factory, options, outputInfoBuilder) @@ -112,6 +114,7 @@ private void WriteFunctionExport(string methodName, int functionIndex) { { WasmObjectNodeSection.MemorySection, WasmSectionType.Memory }, { WasmObjectNodeSection.FunctionSection, WasmSectionType.Function }, + { WasmObjectNodeSection.TableSection, WasmSectionType.Table }, { WasmObjectNodeSection.ExportSection, WasmSectionType.Export }, { WasmObjectNodeSection.TypeSection, WasmSectionType.Type }, { ObjectNodeSection.WasmCodeSection, WasmSectionType.Code } @@ -209,8 +212,19 @@ private void WriteMemorySection(ulong contentSize) private protected override void EmitSectionsAndLayout() { GetOrCreateSection(WasmObjectNodeSection.CombinedDataSection); - ulong contentSize = (ulong)SectionByName(WasmObjectNodeSection.CombinedDataSection.Name).ContentSize; - WriteMemorySection(contentSize); + ulong dataContentSize = (ulong)SectionByName(WasmObjectNodeSection.CombinedDataSection.Name).ContentSize; + WriteMemorySection(dataContentSize + DataStartOffset); + WriteTableSection(); + } + + private void WriteTableSection() + { + SectionWriter writer = GetOrCreateSection(WasmObjectNodeSection.TableSection); + writer.WriteByte(0x01); // number of tables + writer.WriteByte(0x70); // element type: funcref + writer.WriteByte(0x01); // table limits: flags (0 = only minimum) + writer.WriteULEB128((ulong)0); + writer.WriteULEB128((ulong)_methodCount); // table limits: initial size } private void PrependCount(WasmSection section, int count) @@ -235,6 +249,8 @@ private protected override void EmitObjectFile(Stream outputFileStream) SectionByName(WasmObjectNodeSection.TypeSection.Name).Emit(outputFileStream); // Function section SectionByName(WasmObjectNodeSection.FunctionSection.Name).Emit(outputFileStream); + // Table section + SectionByName(WasmObjectNodeSection.TableSection.Name).Emit(outputFileStream); // Memory section SectionByName(WasmObjectNodeSection.MemorySection.Name).Emit(outputFileStream); // Export section From 6f4540866d1990dc7406c478fca4721e37de463a Mon Sep 17 00:00:00 2001 From: adamperlin Date: Thu, 22 Jan 2026 11:46:02 -0800 Subject: [PATCH 2/7] Add basic MethodDesc -> Wasm Signature lowering logic to WasmObjectWriter Fix section incorrect section ordering after addition of table section --- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 110 +++++++++++++++--- 1 file changed, 97 insertions(+), 13 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index f774a242d70eb3..5a28ca3174e6ea 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -242,24 +242,21 @@ private protected override void EmitObjectFile(Stream outputFileStream) { EmitWasmHeader(outputFileStream); - // TODO-WASM: Consider refactoring to loop over an in-order list of sections, skipping any that are missing, - // So that we can maintain section ordering invariants without assuming the existence of certain sections. - - // Type section + // Type section (1) SectionByName(WasmObjectNodeSection.TypeSection.Name).Emit(outputFileStream); - // Function section + // Function section (3) SectionByName(WasmObjectNodeSection.FunctionSection.Name).Emit(outputFileStream); - // Table section + // Table section (4) SectionByName(WasmObjectNodeSection.TableSection.Name).Emit(outputFileStream); - // Memory section + // Memory section (5) SectionByName(WasmObjectNodeSection.MemorySection.Name).Emit(outputFileStream); - // Export section + // Export section (7) SectionByName(WasmObjectNodeSection.ExportSection.Name).Emit(outputFileStream); - // Code section + // Code section (10) WasmSection codeSection = SectionByName(ObjectNodeSection.WasmCodeSection.Name); PrependCount(codeSection, _methodCount); codeSection.Emit(outputFileStream); - // Data section (all segments) + // Data section (11) (all data segments combined) SectionByName(WasmObjectNodeSection.CombinedDataSection.Name).Emit(outputFileStream); } @@ -289,13 +286,100 @@ private protected override void EmitSymbolTable(IDictionary + /// Gets the Wasm-level signature for a given MethodDesc. + /// + /// Parameters for managed Wasm calls have the following layout: + /// i32 (SP), loweredParam0, ..., loweredParamN, i32 (PE entrypoint) + /// + /// For unmanaged callers only (reverse P/Invoke), the layout is simply the native signature + /// which is just the lowered parameters+return. + /// + /// + /// public static WasmFuncType GetSignature(MethodDesc method) { - return PlaceholderValues.CreateWasmFunc_i32_i32(); + // TODO-WASM: handle struct by-value return (extra parameter pointing to buffer must be in signature) + // TODO-WASM: handle seemingly by-value struct arguments that are actually passed implicitly by reference + + MethodSignature signature = method.Signature; + TypeDesc returnType = signature.ReturnType; + Span wasmParameters, lowered; + if (method.IsUnmanagedCallersOnly) // reverse P/Invoke + { + wasmParameters = new WasmValueType[signature.Length]; + lowered = wasmParameters; + } + else // managed call + { + wasmParameters = new WasmValueType[signature.Length + 2]; + wasmParameters[0] = WasmValueType.I32; // Stack pointer parameter + wasmParameters[wasmParameters.Length - 1] = WasmValueType.I32; // PE entrypoint parameter + + lowered = wasmParameters.Slice(1, wasmParameters.Length - 2); + } + + Debug.Assert(lowered.Length == signature.Length); + for (int i = 0; i < signature.Length; i++) + { + lowered[i] = LowerType(signature[i]); + } + + WasmResultType ps = new(wasmParameters.ToArray()); + WasmResultType ret = signature.ReturnType.IsVoid ? new(Array.Empty()) + : new([LowerType(returnType)]); + + return new WasmFuncType(ps, ret); } } From 47aff239d659b63858140f460bb6a237fad1d49c Mon Sep 17 00:00:00 2001 From: adamperlin Date: Tue, 20 Jan 2026 14:31:52 -0800 Subject: [PATCH 3/7] Add DataStartOffset to reserve space in the R2R image for a runtime stack Fix {i32, i64}.const encoding (const should only ever be encoded as SLEB128 --- .../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 5a28ca3174e6ea..e4e27f30392379 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -37,7 +37,7 @@ internal static class WasmObjectNodeSection internal sealed class WasmObjectWriter : ObjectWriter { protected override CodeDataLayout LayoutMode => CodeDataLayout.Separate; - private const int DataStartOffset = 0x10000; // Start data at 64KB offset to leave space for stack, etc. + 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) @@ -136,10 +136,10 @@ protected internal override void UpdateSectionAlignment(int sectionIndex, int al // This is a no-op for now under Wasm } - private WasmDataSection CreateCombinedDataSection() + private WasmDataSection CreateCombinedDataSection(int dataStartOffset) { IEnumerable dataSections = _sections.Where(s => s.Type == WasmSectionType.Data); - int offset = 0; + int offset = dataStartOffset; List segments = new(); foreach (WasmSection wasmSection in dataSections) { @@ -185,7 +185,7 @@ private protected override void CreateSection(ObjectNodeSection section, Utf8Str WasmSection wasmSection; if (section == WasmObjectNodeSection.CombinedDataSection) { - wasmSection = CreateCombinedDataSection(); + wasmSection = CreateCombinedDataSection(DataStartOffset); } else { From 0b1c9d94193bba4e0ed72515cbe019bd96c1cc64 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Fri, 23 Jan 2026 17:40:51 -0800 Subject: [PATCH 4/7] Export table and memory as hardcoded 'table' and 'memory' in WasmObjectWriter --- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index e4e27f30392379..03331f3ca05e5e 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -98,16 +98,43 @@ private void WriteSignatureIndexForFunction(MethodDesc desc) writer.WriteULEB128((ulong)signatureIndex); } - private void WriteFunctionExport(string methodName, int functionIndex) + /// + /// WebAssembly export descriptor kinds per the spec. + internal enum WasmExportKind : byte + { + Function = 0x00, + Table = 0x01, + Memory = 0x02, + Global = 0x03 + } + + private int NumExports => _numExports; + private int _numExports; + private void WriteExport(string name, WasmExportKind kind, int index) { SectionWriter writer = GetOrCreateSection(WasmObjectNodeSection.ExportSection); - int length = Encoding.UTF8.GetByteCount(methodName); + int length = Encoding.UTF8.GetByteCount(name); writer.WriteULEB128((ulong)length); - writer.WriteUtf8StringNoNull(methodName); - writer.WriteByte(0x00); // export kind: function - writer.WriteULEB128((ulong)functionIndex); + writer.WriteUtf8StringNoNull(name); + writer.WriteByte((byte)kind); + writer.WriteULEB128((ulong)index); + _numExports++; } - + + // 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 List _sections = new(); private Dictionary _sectionNameToIndex = new(); private Dictionary sectionToType = new() @@ -268,10 +295,8 @@ private protected override void EmitRelocations(int sectionIndex, List definedSymbols, SortedSet undefinedSymbols) { - int funcIdx = _sectionNameToIndex[WasmObjectNodeSection.FunctionSection.Name]; - PrependCount(_sections[funcIdx], _methodCount); - int typeIdx = _sectionNameToIndex[WasmObjectNodeSection.TypeSection.Name]; - PrependCount(_sections[typeIdx], _uniqueSignatures.Count); + WriteMemoryExport("memory", 0); + WriteTableExport("table", 0); string[] functionExports = _uniqueSymbols.Keys.ToArray(); // TODO-WASM: Handle exports better (e.g., only export public methods, etc.) @@ -281,8 +306,14 @@ private protected override void EmitSymbolTable(IDictionary Date: Sat, 24 Jan 2026 14:08:13 -0800 Subject: [PATCH 5/7] Update src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../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 03331f3ca05e5e..b6df69925dd8d7 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -249,7 +249,7 @@ private void WriteTableSection() SectionWriter writer = GetOrCreateSection(WasmObjectNodeSection.TableSection); writer.WriteByte(0x01); // number of tables writer.WriteByte(0x70); // element type: funcref - writer.WriteByte(0x01); // table limits: flags (0 = only minimum) + writer.WriteByte(0x01); // table limits: flags (1 = has maximum) writer.WriteULEB128((ulong)0); writer.WriteULEB128((ulong)_methodCount); // table limits: initial size } From 4a43824cb7207792b36676bcb0fbc011ddeab1db Mon Sep 17 00:00:00 2001 From: Adam Perlin Date: Sat, 24 Jan 2026 14:08:31 -0800 Subject: [PATCH 6/7] Update src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index b6df69925dd8d7..9324707c66bfc7 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -100,6 +100,7 @@ private void WriteSignatureIndexForFunction(MethodDesc desc) /// /// WebAssembly export descriptor kinds per the spec. + /// internal enum WasmExportKind : byte { Function = 0x00, From 35590fa4151fd90791e0f02ab29e1554f6dfb83a Mon Sep 17 00:00:00 2001 From: adamperlin Date: Sat, 24 Jan 2026 14:20:17 -0800 Subject: [PATCH 7/7] Remove unnecessary property and address copilot feedback on naming convention --- .../Common/Compiler/ObjectWriter/WasmObjectWriter.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 9324707c66bfc7..4b52fa9da4c79c 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -109,7 +109,6 @@ internal enum WasmExportKind : byte Global = 0x03 } - private int NumExports => _numExports; private int _numExports; private void WriteExport(string name, WasmExportKind kind, int index) { @@ -138,7 +137,7 @@ private void WriteGlobalExport(string name, int globalIndex) => private List _sections = new(); private Dictionary _sectionNameToIndex = new(); - private Dictionary sectionToType = new() + private Dictionary _sectionToType = new() { { WasmObjectNodeSection.MemorySection, WasmSectionType.Memory }, { WasmObjectNodeSection.FunctionSection, WasmSectionType.Function }, @@ -150,13 +149,13 @@ private void WriteGlobalExport(string name, int globalIndex) => private WasmSectionType GetWasmSectionType(ObjectNodeSection section) { - if (!sectionToType.ContainsKey(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]; + return _sectionToType[section]; } protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment) @@ -314,7 +313,7 @@ private protected override void EmitSymbolTable(IDictionary