diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 2d79d8b9b14098..4b52fa9da4c79c 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 of linear memory for data segments (leaving 1 page for stack) public WasmObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder) : base(factory, options, outputInfoBuilder) @@ -96,22 +98,50 @@ 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; + 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() + private Dictionary _sectionToType = new() { { WasmObjectNodeSection.MemorySection, WasmSectionType.Memory }, { WasmObjectNodeSection.FunctionSection, WasmSectionType.Function }, + { WasmObjectNodeSection.TableSection, WasmSectionType.Table }, { WasmObjectNodeSection.ExportSection, WasmSectionType.Export }, { WasmObjectNodeSection.TypeSection, WasmSectionType.Type }, { ObjectNodeSection.WasmCodeSection, WasmSectionType.Code } @@ -119,13 +149,13 @@ private void WriteFunctionExport(string methodName, int functionIndex) 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) @@ -133,10 +163,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) { @@ -182,7 +212,7 @@ private protected override void CreateSection(ObjectNodeSection section, Utf8Str WasmSection wasmSection; if (section == WasmObjectNodeSection.CombinedDataSection) { - wasmSection = CreateCombinedDataSection(); + wasmSection = CreateCombinedDataSection(DataStartOffset); } else { @@ -209,8 +239,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 (1 = has maximum) + writer.WriteULEB128((ulong)0); + writer.WriteULEB128((ulong)_methodCount); // table limits: initial size } private void PrependCount(WasmSection section, int count) @@ -228,22 +269,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); - // Memory section + // Table section (4) + SectionByName(WasmObjectNodeSection.TableSection.Name).Emit(outputFileStream); + // 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); } @@ -255,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.) @@ -268,18 +306,111 @@ 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); } }