Skip to content
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.
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);
}
}
125 changes: 100 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,108 @@ public override string ToString()
}
}

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

public enum WasmExternalKind : byte
{
I32Const = 0x41,
I64Const = 0x42
Function = 0x00,
Table = 0x01,
Memory = 0x02,
Global = 0x03,
Tag = 0x04,
Count = 0x05 // Not actually part of the spec; used for counting kinds
}

public class WasmGlobalType : WasmImportType
{
WasmValueType _valueType;
WasmMutabilityType _mutability;

public WasmGlobalType(WasmValueType valueType, WasmMutabilityType mutability)
{
_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;
}

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

public class WasmMemoryType : WasmImportType
{
WasmExprKind _kind;
long ConstValue;
WasmLimitType _limitType;
uint _min;
uint? _max;

public WasmConstExpr(WasmExprKind kind, long value)
public WasmMemoryType(WasmLimitType limitType, uint min, uint? max = null)
{
if (kind == WasmExprKind.I32Const)
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();
}
}
Loading
Loading