Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
42a21db
WIP: build webcil data segment
adamperlin Feb 27, 2026
0481359
Merge branch 'main' of github.com:dotnet/runtime into adamperlin/wasm…
adamperlin Mar 2, 2026
d0519e6
WIP resolve relocs + emit webcil data segment
adamperlin Mar 5, 2026
0e0f4e9
First pass at building up Webcil data segment and resolving relocations
adamperlin Mar 7, 2026
33cb437
Fix several bugs in relocation resolution for Webcil sections
adamperlin Mar 8, 2026
4ad2dc8
Resolve Cor header/debug directory symbols + size and place in webcil…
adamperlin Mar 10, 2026
81ee517
Add some instructions to support Webcil header/size getters
adamperlin Mar 10, 2026
95f991f
Add webcil payload/size getters to output module; fix some padding bu…
adamperlin Mar 11, 2026
509c5f6
Add inter data segment padding support (needed for Webcil)
adamperlin Mar 11, 2026
f9ed9eb
Add WasmGlobal class and use to write globals section; export webcilV…
adamperlin Mar 12, 2026
ea3845c
Merge branch 'main' of github.com:dotnet/runtime into adamperlin/wasm…
adamperlin Mar 12, 2026
d4ed00b
Add Microsoft.NET.WebAssembly.Webcil.Validator program; various fixes
adamperlin Mar 13, 2026
76b21d9
Properly override UpdateSectionAlignment() to set minimal alignment f…
adamperlin Mar 13, 2026
e1fe575
Remove Console.WriteLine's; Remove a bit of dead code; Clarify some c…
adamperlin Mar 13, 2026
277ed87
Remove old fix for adding type signatures as dependencies MethodWithG…
adamperlin Mar 13, 2026
cd0696d
Feedback
adamperlin Mar 13, 2026
ccb8e4f
Apply suggestions from code review
adamperlin Mar 13, 2026
781b9e4
Feedback: make PaddingHelper non static, and pre-allocate buffer of p…
adamperlin Mar 13, 2026
351044e
Additional feedback
adamperlin Mar 13, 2026
aa4bc36
Potential fix for pull request finding
adamperlin Mar 13, 2026
fc510ce
Fix static field bug
adamperlin Mar 13, 2026
cc33f84
Merge branch 'adamperlin/wasm-object-writer-webcil' of github.com:ada…
adamperlin Mar 13, 2026
b3a1fa5
Small formatting/linting fixes
adamperlin Mar 13, 2026
8ba6c94
Review feedback: Put Webcil under ReadyToRun ifdef in WasmObjectWrite…
adamperlin Mar 16, 2026
71a3a84
Remove ilc webcil include
adamperlin Mar 16, 2026
ec88c5b
Refactor: Move webcil header/constant definitions to tools/Common/Was…
adamperlin Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,17 @@ public AssemblyStubNode()
/// </summary>
protected virtual bool IsVisibleFromManagedCode => true;

public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection;
public override ObjectNodeSection GetSection(NodeFactory factory)
{
if (factory.Target.IsWasm)
{
// TODO-Wasm: Change section placement here once we have adapted these nodes
// to emit the proper wasm structure (including per-call type signatures)
return ObjectNodeSection.ReadOnlyDataSection;
}

return ObjectNodeSection.TextSection;
}

public override bool StaticDependenciesAreComputed => true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public enum RelocType
public struct Relocation
{
// NOTE: Keep in sync with emitwasm.cpp
private const int WASM_PADDED_RELOC_SIZE_32 = 5;
public const int WASM_PADDED_RELOC_SIZE_32 = 5;

public readonly RelocType RelocType;
public readonly int Offset;
Expand Down
29 changes: 10 additions & 19 deletions src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
using System.Linq;
using System.Xml.Linq;
using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysis.Wasm;
using ILCompiler.DependencyAnalysisFramework;
using Internal.Text;
using Internal.TypeSystem;

using static ILCompiler.DependencyAnalysis.ObjectNode;
using static ILCompiler.DependencyAnalysis.RelocType;
using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData;

using CodeDataLayout = CodeDataLayoutMode.CodeDataLayout;

namespace ILCompiler.ObjectWriter
Expand Down Expand Up @@ -187,7 +187,7 @@ or IMAGE_REL_BASED_THUMB_BRANCH24 or IMAGE_REL_BASED_THUMB_MOV32_PCREL &&
definedSymbol.SectionIndex == sectionIndex)
{
// Resolve the relocation to already defined symbol and write it into data
fixed (byte *pData = data)
fixed (byte* pData = data)
{
// RyuJIT generates the Thumb bit in the addend and we also get it from
// the symbol value. The AAELF ABI specification defines the R_ARM_THM_JUMP24
Expand Down Expand Up @@ -332,13 +332,13 @@ private SortedSet<Utf8String> GetUndefinedSymbols()
{
SortedSet<Utf8String> undefinedSymbolSet = new SortedSet<Utf8String>();
foreach (var relocationList in _sectionIndexToRelocations)
foreach (var symbolicRelocation in relocationList)
{
if (!_definedSymbols.ContainsKey(symbolicRelocation.SymbolName))
foreach (var symbolicRelocation in relocationList)
{
undefinedSymbolSet.Add(symbolicRelocation.SymbolName);
if (!_definedSymbols.ContainsKey(symbolicRelocation.SymbolName))
{
undefinedSymbolSet.Add(symbolicRelocation.SymbolName);
}
}
}
return undefinedSymbolSet;
}

Expand Down Expand Up @@ -379,11 +379,10 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection<Depe
List<ChecksumsToCalculate> checksumRelocations = [];
foreach (DependencyNode depNode in nodes)
{

// TODO-WASM: emit symbol ranges properly when code and data are separated
// Right now we still need to determine placements for some traditionally text-placed nodes,
// such as DebugDirectoryEntryNode and AssemblyStubNode
if (depNode is ISymbolRangeNode symbolRange && LayoutMode == CodeDataLayout.Unified)
if (depNode is ISymbolRangeNode symbolRange)
{
symbolRangeNodes.Add(symbolRange);
continue;
Expand Down Expand Up @@ -436,20 +435,11 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection<Depe
// R2R records the thumb bit in the addend when needed, so we don't have to do it here.
long thumbBit = 0;
#endif

if (node is WasmTypeNode signature)
{
RecordMethodSignature(signature);
}


if (node is AssemblyStubNode && _nodeFactory.Target.IsWasm)
{
// TODO-Wasm: Handle AssemblyStubNode.
// It is the other primary IWasmCodeNode implementation we should see for R2R. (NativeAOT will have others)
continue;
}

if (node is INodeWithTypeSignature codeNode && _nodeFactory.Target.IsWasm)
{
Debug.Assert(codeNode.Signature != null, $"Wasm code node {codeNode.GetType()} has null signature");
Expand Down Expand Up @@ -573,7 +563,8 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection<Depe
if (startNode is null)
{
// Emit empty symbol ranges as an empty symbol at the end of the text section.
var writer = GetOrCreateSection(ObjectNodeSection.TextSection);
var writer = _nodeFactory.Target.IsWasm ? GetOrCreateSection(ObjectNodeSection.ReadOnlyDataSection) :
GetOrCreateSection(ObjectNodeSection.TextSection);
writer.EmitSymbolDefinition(GetMangledName(range));
continue;
}
Expand Down
130 changes: 128 additions & 2 deletions src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,56 @@

using System;
using System.Diagnostics;
using ILCompiler.DependencyAnalysis.Wasm;

// This namespace implements encodings for certain Wasm expressions (instructions)
// which are used in the object writer.
// For now, these instructions are only used for constructing constant expressions
// to calculate placements for data segments based on imported globals.
namespace ILCompiler.ObjectWriter.WasmInstructions
{
// Represents a Wasm function body in the code section.
// Encodes as: ULEB128(0) (zero local declarations) + instructions + 0x0B end
public class WasmFunctionBody : IWasmEncodable
{
public readonly WasmFuncType Signature;
private readonly WasmInstructionGroup _body;

public WasmFunctionBody(WasmFuncType signature, WasmExpr[] instructions)
{
Signature = signature;
_body = new WasmInstructionGroup(instructions);
}

private int BodyContentSize()
{
// 1 byte for ULEB128(0) local declarations + instruction group (instructions + end opcode)
return 1 + _body.EncodeSize();
}

public int EncodeSize()
{
return BodyContentSize();
}

public int Encode(Span<byte> buffer)
{
int pos = 0;
buffer[pos++] = 0x00; // zero local declarations
pos += _body.Encode(buffer.Slice(pos));

return pos;
}
}
public enum WasmExprKind
{
LocalGet = 0x20,
GlobalGet = 0x23,
I32Const = 0x41,
I64Const = 0x42,
GlobalGet = 0x23,
I32Add = 0x6A,
// Sentinel value — not directly cast to a byte; WasmMemoryInitExpr overrides Encode().
MemoryInit = 0xFC08,
}

public static class WasmExprKindExtensions
Expand All @@ -30,15 +67,25 @@ public static bool IsBinaryExpr(this WasmExprKind kind)
return kind == WasmExprKind.I32Add;
}

public static bool IsLocalVarExpr(this WasmExprKind kind)
{
return kind == WasmExprKind.LocalGet;
}

public static bool IsGlobalVarExpr(this WasmExprKind kind)
{
return kind == WasmExprKind.GlobalGet;
}

public static bool IsMemoryExpr(this WasmExprKind kind)
{
return kind == WasmExprKind.MemoryInit;
}
}

// Represents a group of Wasm instructions (expressions) which
// form a complete expression ending with the 'end' opcode.
class WasmInstructionGroup : IWasmEncodable
public class WasmInstructionGroup : IWasmEncodable
{
readonly WasmExpr[] _wasmExprs;
public WasmInstructionGroup(WasmExpr[] wasmExprs)
Expand Down Expand Up @@ -116,6 +163,31 @@ public override int Encode(Span<byte> buffer)
}
}

// Represents a local variable expression (e.g., (local.get <index>))
class WasmLocalVarExpr : WasmExpr
{
public readonly int LocalIndex;
public WasmLocalVarExpr(WasmExprKind kind, int localIndex) : base(kind)
{
Debug.Assert(localIndex >= 0);
Debug.Assert(kind.IsLocalVarExpr());
LocalIndex = localIndex;
}

public override int Encode(Span<byte> buffer)
{
int pos = base.Encode(buffer);
pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), (uint)LocalIndex);

return pos;
}

public override int EncodeSize()
{
return base.EncodeSize() + (int)DwarfHelper.SizeOfULEB128((uint)LocalIndex);
}
}

// Represents a global variable expression (e.g., (global.get <index))
class WasmGlobalVarExpr : WasmExpr
{
Expand Down Expand Up @@ -151,9 +223,55 @@ public WasmBinaryExpr(WasmExprKind kind) : base(kind)
// base class defaults are sufficient as the base class encodes just the opcode
}

// Represents a memory.init expression.
// Binary encoding: 0xFC prefix + u32(8) sub-opcode + u32(dataSegmentIndex) + u32(memoryIndex)
class WasmMemoryInitExpr : WasmExpr
{
private const byte ExtendedPrefix = 0xFC;
private const uint MemoryInitSubOpcode = 8;

public readonly int DataSegmentIndex;
public readonly int MemoryIndex;

public WasmMemoryInitExpr(int dataSegmentIndex, int memoryIndex = 0) : base(WasmExprKind.MemoryInit)
{
Debug.Assert(dataSegmentIndex >= 0);
Debug.Assert(memoryIndex >= 0);
DataSegmentIndex = dataSegmentIndex;
MemoryIndex = memoryIndex;
}

public override int Encode(Span<byte> buffer)
{
int pos = 0;
buffer[pos++] = ExtendedPrefix;
pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), MemoryInitSubOpcode);
pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), (uint)DataSegmentIndex);
pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), (uint)MemoryIndex);

return pos;
}

public override int EncodeSize()
{
return 1
+ (int)DwarfHelper.SizeOfULEB128(MemoryInitSubOpcode)
+ (int)DwarfHelper.SizeOfULEB128((uint)DataSegmentIndex)
+ (int)DwarfHelper.SizeOfULEB128((uint)MemoryIndex);
}
}

// ************************************************
// Simple DSL wrapper for creating Wasm expressions
// ************************************************
static class Local
{
public static WasmExpr Get(int index)
{
return new WasmLocalVarExpr(WasmExprKind.LocalGet, index);
}
}

static class Global
{
public static WasmExpr Get(int index)
Expand All @@ -171,4 +289,12 @@ public static WasmExpr Const(long value)

public static WasmExpr Add => new WasmBinaryExpr(WasmExprKind.I32Add);
}

static class Memory
{
public static WasmExpr Init(int dataSegmentIndex, int memoryIndex = 0)
{
return new WasmMemoryInitExpr(dataSegmentIndex, memoryIndex);
}
}
}
31 changes: 28 additions & 3 deletions src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Linq;
using System.Collections.Generic;

using ILCompiler.DependencyAnalysis.Wasm;
using ILCompiler.ObjectWriter.WasmInstructions;

namespace ILCompiler.ObjectWriter
{
Expand Down Expand Up @@ -154,4 +152,31 @@ public WasmImport(string module, string name, WasmImportType import, int? index
public int Encode(Span<byte> buffer) => Import.Encode(buffer);
public int EncodeSize() => Import.EncodeSize();
}

public class WasmGlobal : IWasmEncodable
{
public readonly int Index;
public readonly string Name;
private readonly WasmValueType _valueType;
private readonly WasmMutabilityType _mutability;
private readonly WasmInstructionGroup _initExpr;

public WasmGlobal(int index, string name, WasmValueType valueType, WasmMutabilityType mutability, WasmInstructionGroup initExpr)
{
Index = index;
Name = name;
_valueType = valueType;
_mutability = mutability;
_initExpr = initExpr;
}

public int Encode(Span<byte> buffer)
{
buffer[0] = (byte)_valueType;
buffer[1] = (byte)_mutability;
return 2 + _initExpr.Encode(buffer.Slice(2));
}

public int EncodeSize() => 2 + _initExpr.EncodeSize();
}
}
Loading