diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index d7cb95dde47718..4a8b5b264d1542 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,28 @@ 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); + // 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 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); + + foreach (int index in functionIndices) + writer.WriteULEB128((ulong)index); + + _numElements++; + } + private List _sections = new(); private Dictionary _sectionNameToIndex = new(); private Dictionary _sectionToType = new() @@ -151,6 +174,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 } @@ -171,12 +195,21 @@ protected internal override void UpdateSectionAlignment(int sectionIndex, int al // This is a no-op for now under Wasm } + WasmInstructionGroup GetImageFunctionPointerBaseOffset(int offset) + { + return new WasmInstructionGroup([ + Global.Get(ImageFunctionPointerBaseGlobalIndex), + I32.Const(offset), + I32.Add, + ]); + } + 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 +222,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; } @@ -267,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) @@ -289,6 +322,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 +428,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 +442,15 @@ void WriteRelocFromDataSpan(SymbolicRelocation reloc, byte *pData) } const int StackPointerGlobalIndex = 0; - const int R2RStartGlobalIndex = 1; + const int ImageBaseGlobalIndex = 1; + 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", "__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_function_pointer_base", import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImageFunctionPointerBaseGlobalIndex), }; private void WriteImports() @@ -454,11 +490,28 @@ 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]; + // 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_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. private protected override void EmitSymbolTable(IDictionary definedSymbols, SortedSet undefinedSymbols) { WriteImports(); WriteExports(); + WriteElements(); int funcIdx = _sectionNameToIndex[WasmObjectNodeSection.FunctionSection.Name]; PrependCount(_sections[funcIdx], _methodCount); @@ -469,6 +522,11 @@ private protected override void EmitSymbolTable(IDictionary