From 5ea3a63c6106abd130b84efc7ab29d848a00761b Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 13 Mar 2026 12:01:52 -0700 Subject: [PATCH 1/5] Checkpoint: Rename r2r_start Add image_pointer_base Scaffolding for element sections --- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index d7cb95dde47718..43fb1b44e30e68 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 CombinedDataSection = new ObjectNodeSection("wasm.alldata", SectionType.Writeable, needsAlign: false); public static readonly ObjectNodeSection FunctionSection = new ObjectNodeSection("wasm.function", SectionType.ReadOnly, needsAlign: false); public static readonly ObjectNodeSection ExportSection = new ObjectNodeSection("wasm.export", SectionType.ReadOnly, needsAlign: false); + public static readonly ObjectNodeSection ElementSection = new ObjectNodeSection("wasm.element", 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); @@ -143,6 +144,14 @@ private void WriteMemoryExport(string name, int memoryIndex) => private void WriteGlobalExport(string name, int globalIndex) => WriteExport(name, WasmExportKind.Global, globalIndex); + private int _numElements; + private void WriteFunctionElement(WasmInstructionGroup e0, ReadOnlySpan functionIndices) + { + SectionWriter writer = GetOrCreateSection(WasmObjectNodeSection.ElementSection); + _numElements++; + throw new NotImplementedException(); + } + private List _sections = new(); private Dictionary _sectionNameToIndex = new(); private Dictionary _sectionToType = new() @@ -151,6 +160,7 @@ private void WriteGlobalExport(string name, int globalIndex) => { WasmObjectNodeSection.FunctionSection, WasmSectionType.Function }, { WasmObjectNodeSection.TableSection, WasmSectionType.Table }, { WasmObjectNodeSection.ExportSection, WasmSectionType.Export }, + { WasmObjectNodeSection.ElementSection, WasmSectionType.Element }, { WasmObjectNodeSection.ImportSection, WasmSectionType.Import }, { ObjectNodeSection.WasmTypeSection, WasmSectionType.Type }, { ObjectNodeSection.WasmCodeSection, WasmSectionType.Code } @@ -173,10 +183,10 @@ protected internal override void UpdateSectionAlignment(int sectionIndex, int al private WasmDataSection CreateCombinedDataSection() { - WasmInstructionGroup GetR2RStartOffset(int offset) + WasmInstructionGroup GetImageBaseOffset(int offset) { return new WasmInstructionGroup([ - Global.Get(R2RStartGlobalIndex), + Global.Get(ImageBaseGlobalIndex), I32.Const(offset), I32.Add, ]); @@ -189,7 +199,7 @@ WasmInstructionGroup GetR2RStartOffset(int offset) { Debug.Assert(wasmSection.Type == WasmSectionType.Data); WasmDataSegment segment = new WasmDataSegment(wasmSection.Stream, wasmSection.Name, WasmDataSectionType.Active, - GetR2RStartOffset(offset)); + GetImageBaseOffset(offset)); segments.Add(segment); offset += segment.ContentSize; } @@ -289,6 +299,7 @@ private WasmSection SectionByName(string name) WasmObjectNodeSection.FunctionSection.Name, WasmObjectNodeSection.TableSection.Name, WasmObjectNodeSection.ExportSection.Name, + WasmObjectNodeSection.ElementSection.Name, ObjectNodeSection.WasmCodeSection.Name, WasmObjectNodeSection.CombinedDataSection.Name, ]; @@ -394,7 +405,7 @@ private unsafe void ResolveRelocations(MemoryStream sectionStream, List ReadRelocToDataSpan(SymbolicRelocation reloc, byte[] buffer) { - Span relocContents = buffer.AsSpan(0, Relocation.GetSize(reloc.Type)); + Span relocContents = buffer.AsSpan(0, Relocation.GetSize(reloc.Type)); sectionStream.Position = reloc.Offset; sectionStream.ReadExactly(relocContents); return relocContents; @@ -408,13 +419,15 @@ void WriteRelocFromDataSpan(SymbolicRelocation reloc, byte *pData) } const int StackPointerGlobalIndex = 0; - const int R2RStartGlobalIndex = 1; + const int ImageBaseGlobalIndex = 1; + const int ImagePointerBaseGlobalIndex = 2; private WasmImport[] _defaultImports = new[] { null, // placeholder for memory, which is set up dynamically in WriteImports() new WasmImport("env", "__stack_pointer", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Mut), index: StackPointerGlobalIndex), - new WasmImport("env", "__r2r_start", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: R2RStartGlobalIndex), + new WasmImport("env", "__image_base", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImageBaseGlobalIndex), + new WasmImport("env", "__image_pointer_base", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImagePointerBaseGlobalIndex), }; private void WriteImports() @@ -469,6 +482,11 @@ private protected override void EmitSymbolTable(IDictionary Date: Fri, 13 Mar 2026 12:18:35 -0700 Subject: [PATCH 2/5] Checkpoint: Generate element containing sequential function pointers for every function in the module --- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 43fb1b44e30e68..dd4c97c674d80f 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -148,8 +148,22 @@ private void WriteGlobalExport(string name, int globalIndex) => private void WriteFunctionElement(WasmInstructionGroup e0, ReadOnlySpan functionIndices) { SectionWriter writer = GetOrCreateSection(WasmObjectNodeSection.ElementSection); + // e0:expr y*:list(funcidx) + // elem (ref func) (ref.func y)* (active 0 e0) + writer.WriteULEB128(0); + + // FIXME: Add a way to encode directly into the writer without a scratch buffer + int bufSize = e0.EncodeSize(); + byte[] buf = new byte[bufSize]; + e0.Encode(buf); + writer.Write(buf); + + writer.WriteULEB128((ulong)functionIndices.Length); + + foreach (int index in functionIndices) + writer.WriteULEB128((ulong)index); + _numElements++; - throw new NotImplementedException(); } private List _sections = new(); @@ -181,6 +195,15 @@ protected internal override void UpdateSectionAlignment(int sectionIndex, int al // This is a no-op for now under Wasm } + WasmInstructionGroup GetImagePointerBaseOffset(int offset) + { + return new WasmInstructionGroup([ + Global.Get(ImagePointerBaseGlobalIndex), + I32.Const(offset), + I32.Add, + ]); + } + private WasmDataSection CreateCombinedDataSection() { WasmInstructionGroup GetImageBaseOffset(int offset) @@ -467,11 +490,23 @@ private void WriteExports() } } + private void WriteElements() + { + // Generate the function pointer table element that contains function pointers for all of our functions + int[] functionIndices = new int[_uniqueSymbols.Count]; + _uniqueSymbols.Values.CopyTo(functionIndices, 0); + // Enforce that the function pointers are sequential so that (image_pointer_base + 0) == ftn index 0 + Array.Sort(functionIndices); + Debug.Assert(functionIndices.FirstOrDefault() == 0); + WriteFunctionElement(GetImagePointerBaseOffset(0), functionIndices); + } + // For now, this function just prepares the function, exports, and type sections for emission by prepending the counts. private protected override void EmitSymbolTable(IDictionary definedSymbols, SortedSet undefinedSymbols) { WriteImports(); WriteExports(); + WriteElements(); int funcIdx = _sectionNameToIndex[WasmObjectNodeSection.FunctionSection.Name]; PrependCount(_sections[funcIdx], _methodCount); From cf1213809b385dd9d7b56768bccf2517cef1d776 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 13 Mar 2026 12:56:30 -0700 Subject: [PATCH 3/5] Fix initial size of our ftn pointer table being 0 --- .../tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index dd4c97c674d80f..f1a97b1e11ff7c 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -300,8 +300,8 @@ private void WriteTableSection() writer.WriteByte(0x01); // number of tables 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 in number of entries + writer.WriteULEB128((ulong)_methodCount); // minimum + writer.WriteULEB128((ulong)_methodCount); // maximum } private void PrependCount(WasmSection section, int count) From d435d705e247d0a0eeac9267e3caba80b1e297ce Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 13 Mar 2026 13:58:21 -0700 Subject: [PATCH 4/5] Improvement from Adam --- .../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 f1a97b1e11ff7c..e8b2af137f6e66 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -153,10 +153,10 @@ private void WriteFunctionElement(WasmInstructionGroup e0, ReadOnlySpan fun writer.WriteULEB128(0); // FIXME: Add a way to encode directly into the writer without a scratch buffer - int bufSize = e0.EncodeSize(); - byte[] buf = new byte[bufSize]; - e0.Encode(buf); - writer.Write(buf); + int encodeSize = e0.EncodeSize(); + int bytesWritten = e0.Encode(writer.Buffer.GetSpan(encodeSize)); + Debug.Assert(bytesWritten == encodeSize); + writer.Buffer.Advance((int)bytesWritten); writer.WriteULEB128((ulong)functionIndices.Length); From b981ec9899725c2f76bcd00a56c32c5a46019419 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 16 Mar 2026 21:06:39 -0700 Subject: [PATCH 5/5] Address feedback --- .../Compiler/ObjectWriter/WasmObjectWriter.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index e8b2af137f6e66..4a8b5b264d1542 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -195,10 +195,10 @@ protected internal override void UpdateSectionAlignment(int sectionIndex, int al // This is a no-op for now under Wasm } - WasmInstructionGroup GetImagePointerBaseOffset(int offset) + WasmInstructionGroup GetImageFunctionPointerBaseOffset(int offset) { return new WasmInstructionGroup([ - Global.Get(ImagePointerBaseGlobalIndex), + Global.Get(ImageFunctionPointerBaseGlobalIndex), I32.Const(offset), I32.Add, ]); @@ -443,14 +443,14 @@ void WriteRelocFromDataSpan(SymbolicRelocation reloc, byte *pData) const int StackPointerGlobalIndex = 0; const int ImageBaseGlobalIndex = 1; - const int ImagePointerBaseGlobalIndex = 2; + const int ImageFunctionPointerBaseGlobalIndex = 2; private WasmImport[] _defaultImports = new[] { null, // placeholder for memory, which is set up dynamically in WriteImports() new WasmImport("env", "__stack_pointer", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Mut), index: StackPointerGlobalIndex), new WasmImport("env", "__image_base", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImageBaseGlobalIndex), - new WasmImport("env", "__image_pointer_base", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImagePointerBaseGlobalIndex), + new WasmImport("env", "__image_function_pointer_base", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImageFunctionPointerBaseGlobalIndex), }; private void WriteImports() @@ -494,11 +494,16 @@ private void WriteElements() { // Generate the function pointer table element that contains function pointers for all of our functions int[] functionIndices = new int[_uniqueSymbols.Count]; + // NOTE: This relies on items in _uniqueSymbols being assigned sequentially and that iteration over Values is order-preserving. + // BCL Dictionary preserves insertion order so as long as we keep using it, we would get the function indices in the order they were added. _uniqueSymbols.Values.CopyTo(functionIndices, 0); - // Enforce that the function pointers are sequential so that (image_pointer_base + 0) == ftn index 0 - Array.Sort(functionIndices); - Debug.Assert(functionIndices.FirstOrDefault() == 0); - WriteFunctionElement(GetImagePointerBaseOffset(0), functionIndices); + // Enforce that the function pointers are sequential so that (image_function_pointer_base + 0) == ftn index 0 +#if DEBUG + for (int i = 0; i < _uniqueSymbols.Count; i++) { + Debug.Assert(functionIndices[i] == i); + } +#endif + WriteFunctionElement(GetImageFunctionPointerBaseOffset(0), functionIndices); } // For now, this function just prepares the function, exports, and type sections for emission by prepending the counts.