Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -176,6 +176,12 @@ public readonly void WriteUtf8StringNoNull(string value)
bufferWriter.Advance(size);
}

public readonly void WriteUtf8WithLength(string value)
{
WriteULEB128((ulong)Encoding.UTF8.GetByteCount(value));
WriteUtf8StringNoNull(value);
}

public readonly void WritePadding(int size) => _sectionData.AppendPadding(size);

public readonly long Position => _sectionData.Length;
Expand Down
174 changes: 174 additions & 0 deletions src/coreclr/tools/Common/Compiler/ObjectWriter/WasmInstructions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;

// 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.
Comment on lines +7 to +10
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There appears to be trailing whitespace in the comment lines here (e.g., after the period). The repo enforces trim_trailing_whitespace = true (.editorconfig:13), so please remove trailing spaces to avoid formatting-only diffs and potential CI/style checks.

Copilot uses AI. Check for mistakes.
namespace ILCompiler.ObjectWriter.WasmInstructions
{
public enum WasmExprKind
{
I32Const = 0x41,
I64Const = 0x42,
GlobalGet = 0x23,
I32Add = 0x6A,
}

public static class WasmExprKindExtensions
{
public static bool IsConstExpr(this WasmExprKind kind)
{
return kind == WasmExprKind.I32Const || kind == WasmExprKind.I64Const;
}

public static bool IsBinaryExpr(this WasmExprKind kind)
{
return kind == WasmExprKind.I32Add;
}

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

// Represents a group of Wasm instructions (expressions) which
// form a complete expression ending with the 'end' opcode.
class WasmInstructionGroup : IWasmEncodable
{
readonly WasmExpr[] _wasmExprs;
public WasmInstructionGroup(WasmExpr[] wasmExprs)
{
_wasmExprs = wasmExprs;
}

public int Encode(Span<byte> buffer)
{
int pos = 0;
foreach (var expr in _wasmExprs)
{
pos += expr.Encode(buffer.Slice(pos));
}
buffer[pos++] = 0x0B; // end opcode
return pos;
}

public int EncodeSize()
{
int size = 0;
foreach (var expr in _wasmExprs)
{
size += expr.EncodeSize();
}
// plus one for the end opcode
return size + 1;
}
}

public abstract class WasmExpr : IWasmEncodable
{
WasmExprKind _kind;
public WasmExpr(WasmExprKind kind)
{
_kind = kind;
}

public virtual int EncodeSize() => 1;
public virtual int Encode(Span<byte> buffer)
{
buffer[0] = (byte)_kind;
return 1;
}
}

// Represents a constant expression (e.g., (i32.const <value>))
class WasmConstExpr : WasmExpr
{
long ConstValue;

public WasmConstExpr(WasmExprKind kind, long value) : base(kind)
{
if (kind == WasmExprKind.I32Const)
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(value, int.MaxValue);
ArgumentOutOfRangeException.ThrowIfLessThan(value, int.MinValue);
}

ConstValue = value;
}

public override int EncodeSize()
{
uint valSize = DwarfHelper.SizeOfSLEB128(ConstValue);
return base.EncodeSize() + (int)valSize;
}

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

return pos;
}
}

// Represents a global variable expression (e.g., (global.get <index))
class WasmGlobalVarExpr : WasmExpr
{
public readonly int GlobalIndex;
public WasmGlobalVarExpr(WasmExprKind kind, int globalIndex) : base(kind)
{
Debug.Assert(globalIndex >= 0);
Debug.Assert(kind.IsGlobalVarExpr());
GlobalIndex = globalIndex;
}

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

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

// Represents a binary expression (e.g., i32.add)
class WasmBinaryExpr : WasmExpr
{
public WasmBinaryExpr(WasmExprKind kind) : base(kind)
{
Debug.Assert(kind.IsBinaryExpr());
}

// base class defaults are sufficient as the base class encodes just the opcode
}

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

static class I32
{
public static WasmExpr Const(long value)
{
return new WasmConstExpr(WasmExprKind.I32Const, value);
}

public static WasmExpr Add => new WasmBinaryExpr(WasmExprKind.I32Add);
}
}
127 changes: 102 additions & 25 deletions src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@

namespace ILCompiler.ObjectWriter
{
public interface IWasmEncodable
{
int EncodeSize();
int Encode(Span<byte> buffer);
}

public enum WasmSectionType
{
Custom = 0,
Expand Down Expand Up @@ -51,6 +57,12 @@ public enum WasmValueType : byte
F64 = 0x7C
}

public enum WasmMutabilityType : byte
{
Const = 0x00,
Mut = 0x01
}

public static class WasmValueTypeExtensions
{
public static string ToTypeString(this WasmValueType valueType)
Expand All @@ -66,7 +78,7 @@ public static string ToTypeString(this WasmValueType valueType)
}
}

#nullable enable
#nullable enable
public readonly struct WasmResultType : IEquatable<WasmResultType>
{
private readonly WasmValueType[] _types;
Expand Down Expand Up @@ -150,15 +162,15 @@ public readonly int Encode(Span<byte> buffer)
buffer[0] = 0x60; // function type indicator

int paramSize = _params.Encode(buffer.Slice(1));
int returnSize = _returns.Encode(buffer.Slice(1+paramSize));
int returnSize = _returns.Encode(buffer.Slice(1 + paramSize));
Debug.Assert(totalSize == 1 + paramSize + returnSize);

return totalSize;
}

public bool Equals(WasmFuncType other)
{
return _params.Equals(other._params) && _returns.Equals(other._returns);
return _params.Equals(other._params) && _returns.Equals(other._returns);
}

public override bool Equals(object? obj)
Expand All @@ -182,45 +194,110 @@ public override string ToString()
}
}

// Represents a WebAssembly expression used in simple contexts for address calculation
enum WasmExprKind
public abstract class WasmImportType : IWasmEncodable
{
I32Const = 0x41,
I64Const = 0x42
public abstract int Encode(Span<byte> buffer);
public abstract int EncodeSize();
}

class WasmConstExpr
public enum WasmExternalKind : byte
{
WasmExprKind _kind;
long ConstValue;
Function = 0x00,
Table = 0x01,
Memory = 0x02,
Global = 0x03,
Tag = 0x04,
Count = 0x05 // Not actually part of the spec; used for counting kinds
}

public WasmConstExpr(WasmExprKind kind, long value)
public class WasmGlobalType : WasmImportType
{
WasmValueType ValueType;
WasmMutabilityType Mutability;

Comment on lines +215 to +217
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are private fields but don't follow the repo naming rule for private/internal fields (_camelCase per .editorconfig:76-83). Please rename to _valueType / _mutability (and similarly for other new private fields below) to match existing conventions.

Copilot uses AI. Check for mistakes.
public WasmGlobalType(WasmValueType valueType, WasmMutabilityType mutability)
{
if (kind == WasmExprKind.I32Const)
ValueType = valueType;
Mutability = mutability;
}

public override int Encode(Span<byte> buffer)
{
buffer[0] = (byte)ValueType;
buffer[1] = (byte)Mutability;
return 2;
}

public override int EncodeSize() => 2;
}

public enum WasmLimitType : byte
{
HasMin = 0x00,
HasMinAndMax = 0x01
}

public class WasmMemoryType : WasmImportType
{
WasmLimitType LimitType;
uint Min;
uint? Max;

Comment on lines +242 to +245
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Private fields LimitType / Min / Max don't follow the repo's _camelCase convention for private/internal fields (.editorconfig:76-83). Renaming to _limitType / _min / _max would keep this consistent with the rest of the file (e.g., _params, _returns).

Copilot uses AI. Check for mistakes.
public WasmMemoryType(WasmLimitType limitType, uint min, uint? max = null)
{
if (limitType == WasmLimitType.HasMinAndMax && !max.HasValue)
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(value, int.MaxValue);
ArgumentOutOfRangeException.ThrowIfLessThan(value, int.MinValue);
throw new ArgumentException("Max must be provided when LimitType is HasMinAndMax");
}

_kind = kind;
ConstValue = value;
LimitType = limitType;
Min = min;
Max = max;
}

public int EncodeSize()
public override int Encode(Span<byte> buffer)
{
uint valSize = DwarfHelper.SizeOfSLEB128(ConstValue);
return 1 + (int)valSize + 1; // opcode + value + end opcode
int pos = 0;
buffer[pos++] = (byte)LimitType;
pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), Min);
if (LimitType == WasmLimitType.HasMinAndMax)
{
pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), Max!.Value);
}
return pos;
}

public int Encode(Span<byte> buffer)
public override int EncodeSize()
{
int pos = 0;
buffer[pos++] = (byte)_kind; // the kind is the opcode, either i32.const or i64.const
uint size = 1 + DwarfHelper.SizeOfULEB128(Min);
if (LimitType == WasmLimitType.HasMinAndMax)
{
size += DwarfHelper.SizeOfULEB128(Max!.Value);
}
return (int)size;
}
}

pos += DwarfHelper.WriteSLEB128(buffer.Slice(pos), ConstValue);
public class WasmImport : IWasmEncodable
{
public readonly string Module;
public readonly string Name;
public readonly WasmExternalKind Kind;
public readonly int? Index;
public readonly WasmImportType Import;

buffer[pos++] = 0x0B; // end opcode
return pos;
public WasmImport(string module, string name, WasmExternalKind kind, WasmImportType import, int? index = null)
{
Module = module;
Name = name;
Kind = kind;
Import = import;
Index = index;
}

public int Encode(Span<byte> buffer) => Import.Encode(buffer);
public int EncodeSize() => Import.EncodeSize();

#nullable disable
Comment on lines +300 to +301
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#nullable disable is placed inside WasmImport and (as written) disables nullability for the remainder of the file without an obvious reason. This can hide nullability warnings in future edits; consider removing it or scoping it more explicitly (e.g., disable/restore around the specific code that needs it).

Suggested change
#nullable disable

Copilot uses AI. Check for mistakes.
}
}
Loading
Loading