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);
}
}