sideros/src/mods/ir.zig
Ernesto Lanchares 939cdb5f09 Fixed macOS compilation and refactored mods/ir to use std.Io.Reader.
Signed-off-by: Lorenzo Torres <lorenzotorres@outlook.it>
2025-08-30 15:31:44 +02:00

925 lines
28 KiB
Zig

const std = @import("std");
const Parser = @import("Parser.zig");
const vm = @import("vm.zig");
const Allocator = std.mem.Allocator;
const IR = @This();
const Error = error{
invalid_instruction,
invalid_reftype,
double_else,
};
const VectorIndex = packed struct {
opcode: VectorOpcode,
laneidx: u8,
memarg: Memarg,
};
const Memarg = packed struct {
offset: u32,
/// Accordng to spec this should be a `u32` but who the fuck needs that big of an alignment? Moreover
/// if we make this value bigger, then VectorIndex does not fit anymore :(
alignment: u16,
};
const DIndex = packed struct {
x: u32,
y: u32,
};
comptime {
// TODO: is this too big? we could do with 32 bits and a bit more indirection
std.debug.assert(@sizeOf(Index) == 8);
}
/// packed union has no tag
const Index = packed union {
u32: u32,
i32: i32,
u64: u64,
i64: i64,
f32: f32,
f64: f64,
indirect: DIndex,
reftype: std.wasm.RefType,
valtype: std.wasm.Valtype,
memarg: Memarg,
vector: VectorIndex,
};
opcodes: []Opcode,
/// Indices means something different depending on the Opcode.
/// Read the docs of each opcode to know what the index means.
indices: []Index,
// TODO: this could be a byte array and v128.const and i8x16.shuffle could live here too
select_valtypes: []vm.Valtype,
br_table_vectors: []u32,
pub fn print(self: IR, writer: anytype) !void {
for (self.opcodes, 0..) |op, i| {
try writer.print("{x:3} {s}", .{ i, @tagName(op) });
if (op == .br or op == .br_if) {
try writer.print(" {x:3}", .{self.indices[i].u32});
}
_ = try writer.write("\n");
}
}
// TODO(ernesto): This file is unreadable, we should avoid "0xXX" and use names
/// Opcodes.
/// This is a mix of wasm opcodes mixed with a few of our own.
/// Mainly for `0xFC` opcodes we use `0xD3` to `0xE4`.
pub const Opcode = enum(u8) {
// CONTROL INSTRUCTIONS
// The rest of instructions should be implemented in terms of these ones
@"unreachable" = 0x00,
nop = 0x01,
/// Index: `u64`. Meaning: the next instruction pointer
br = 0x0C,
/// Index: `u64`. Meaning: the next instruction pointer
br_if = 0x0D,
/// TODO: this instruction (could be also implemented in terms of br and br_if)
br_table = 0x0E,
@"return" = 0x0F,
/// Index: `u32`. Meaning: The function index to call
call = 0x10,
/// Index: `DIndex`. Meaning: `DIndex.x` is the table index and `DIndex.y` is the typeindex
call_indirect = 0x11,
// REFERENCE INSTRUCTIONS
/// Index: `Parser.Reftype`. Meaning: reftype
refnull = 0xD0,
refisnull = 0xD1,
/// Index: `u32`. Meaning: funcidx
reffunc = 0xD2,
// PARAMETRIC INSTRUCTIONS
drop = 0x1A,
select = 0x1B,
/// Index: `DIndex`. Meaning:
/// `DIndex.x` is the index into `select_valtypes` array and
/// `DIndex.y` is the number of valtypes
select_with_values = 0x1C,
// VARIABLE INSTRUCTIONS
/// Index: `u32`. Meaing: index into local variables
localget = 0x20,
/// Index: `u32`. Meaing: index into local variables
localset = 0x21,
/// Index: `u32`. Meaing: index into local variables
localtee = 0x22,
/// Index: `u32`. Meaing: index into global variables
globalget = 0x23,
/// Index: `u32`. Meaing: index into global variables
globalset = 0x24,
// TABLE INSTRUCTIONS
/// Index: `u32`. Meaning: index into table index
tableget = 0x25,
/// Index: `u32`. Meaning: index into table index
tableset = 0x26,
/// Index: `DIndex`. Meaning: TODO
tableinit = 0xDF,
/// Index: `u32`. Meaning: TODO
elemdrop = 0xE0,
/// Index: `DIndex`. Meaning: `DIndex.x` is destination `DIndex.y` is source
tablecopy = 0xE1,
/// Index: `u32`. Meaning: tableidx
tablegrow = 0xE2,
/// Index: `u32`. Meaning: tableidx
tablesize = 0xE3,
/// Index: `u32`. Meaning: tableidx
tablefill = 0xE4,
// MEMORY INSTRUCTIONS
/// Index: `Memarg`. Meaning: memarg
i32_load = 0x28,
/// Index: `Memarg`. Meaning: memarg
i64_load = 0x29,
/// Index: `Memarg`. Meaning: memarg
f32_load = 0x2A,
/// Index: `Memarg`. Meaning: memarg
f64_load = 0x2B,
/// Index: `Memarg`. Meaning: memarg
i32_load8_s = 0x2C,
/// Index: `Memarg`. Meaning: memarg
i32_load8_u = 0x2D,
/// Index: `Memarg`. Meaning: memarg
i32_load16_s = 0x2E,
/// Index: `Memarg`. Meaning: memarg
i32_load16_u = 0x2F,
/// Index: `Memarg`. Meaning: memarg
i64_load8_s = 0x30,
/// Index: `Memarg`. Meaning: memarg
i64_load8_u = 0x31,
/// Index: `Memarg`. Meaning: memarg
i64_load16_s = 0x32,
/// Index: `Memarg`. Meaning: memarg
i64_load16_u = 0x33,
/// Index: `Memarg`. Meaning: memarg
i64_load32_s = 0x34,
/// Index: `Memarg`. Meaning: memarg
i64_load32_u = 0x35,
/// Index: `Memarg`. Meaning: memarg
i32_store = 0x36,
/// Index: `Memarg`. Meaning: memarg
i64_store = 0x37,
/// Index: `Memarg`. Meaning: memarg
f32_store = 0x38,
/// Index: `Memarg`. Meaning: memarg
f64_store = 0x39,
/// Index: `Memarg`. Meaning: memarg
i32_store8 = 0x3A,
/// Index: `Memarg`. Meaning: memarg
i32_store16 = 0x3B,
/// Index: `Memarg`. Meaning: memarg
i64_store8 = 0x3C,
/// Index: `Memarg`. Meaning: memarg
i64_store16 = 0x3D,
/// Index: `Memarg`. Meaning: memarg
i64_store32 = 0x3E,
memorysize = 0x3F,
memorygrow = 0x40,
/// Index: `u32`. Meaning: dataidx
memoryinit = 0xDB,
/// Index: `u32`. Meaning: dataidx
datadrop = 0xDC,
memorycopy = 0xDD,
memoryfill = 0xDE,
// NUMERIC INSTRUCTION
/// Index: `i32`. Meaning: constant
i32_const = 0x41,
/// Index: `i64`. Meaning: constant
i64_const = 0x42,
/// Index: `f32`. Meaning: constant
f32_const = 0x43,
/// Index: `f64`. Meaning: constant
f64_const = 0x44,
i32_eqz = 0x45,
i32_eq = 0x46,
i32_ne = 0x47,
i32_lt_s = 0x48,
i32_lt_u = 0x49,
i32_gt_s = 0x4A,
i32_gt_u = 0x4B,
i32_le_s = 0x4C,
i32_le_u = 0x4D,
i32_ge_s = 0x4E,
i32_ge_u = 0x4F,
i64_eqz = 0x50,
i64_eq = 0x51,
i64_ne = 0x52,
i64_lt_s = 0x53,
i64_lt_u = 0x54,
i64_gt_s = 0x55,
i64_gt_u = 0x56,
i64_le_s = 0x57,
i64_le_u = 0x58,
i64_ge_s = 0x59,
i64_ge_u = 0x5A,
f32_eq = 0x5B,
f32_ne = 0x5C,
f32_lt = 0x5D,
f32_gt = 0x5E,
f32_le = 0x5F,
f32_ge = 0x60,
f64_eq = 0x61,
f64_ne = 0x62,
f64_lt = 0x63,
f64_gt = 0x64,
f64_le = 0x65,
f64_ge = 0x66,
i32_clz = 0x67,
i32_ctz = 0x68,
i32_popcnt = 0x69,
i32_add = 0x6A,
i32_sub = 0x6B,
i32_mul = 0x6C,
i32_div_s = 0x6D,
i32_div_u = 0x6E,
i32_rem_s = 0x6F,
i32_rem_u = 0x70,
i32_and = 0x71,
i32_or = 0x72,
i32_xor = 0x73,
i32_shl = 0x74,
i32_shr_s = 0x75,
i32_shr_u = 0x76,
i32_rotl = 0x77,
i32_rotr = 0x78,
i64_clz = 0x79,
i64_ctz = 0x7A,
i64_popcnt = 0x7B,
i64_add = 0x7C,
i64_sub = 0x7D,
i64_mul = 0x7E,
i64_div_s = 0x7F,
i64_div_u = 0x80,
i64_rem_s = 0x81,
i64_rem_u = 0x82,
i64_and = 0x83,
i64_or = 0x84,
i64_xor = 0x85,
i64_shl = 0x86,
i64_shr_s = 0x87,
i64_shr_u = 0x88,
i64_rotl = 0x89,
i64_rotr = 0x8A,
f32_abs = 0x8B,
f32_neg = 0x8C,
f32_ceil = 0x8D,
f32_floor = 0x8E,
f32_trunc = 0x8F,
f32_nearest = 0x90,
f32_sqrt = 0x91,
f32_add = 0x92,
f32_sub = 0x93,
f32_mul = 0x94,
f32_div = 0x95,
f32_min = 0x96,
f32_max = 0x97,
f32_copysign = 0x98,
f64_abs = 0x99,
f64_neg = 0x9A,
f64_ceil = 0x9B,
f64_floor = 0x9C,
f64_trunc = 0x9D,
f64_nearest = 0x9E,
f64_sqrt = 0x9F,
f64_add = 0xA0,
f64_sub = 0xA1,
f64_mul = 0xA2,
f64_div = 0xA3,
f64_min = 0xA4,
f64_max = 0xA5,
f64_copysign = 0xA6,
i32_wrap_i64 = 0xA7,
i32_trunc_f32_s = 0xA8,
i32_trunc_f32_u = 0xA9,
i32_trunc_f64_s = 0xAA,
i32_trunc_f64_u = 0xAB,
i64_extend_i32_s = 0xAC,
i64_extend_i32_u = 0xAD,
i64_trunc_f32_s = 0xAE,
i64_trunc_f32_u = 0xAF,
i64_trunc_f64_s = 0xB0,
i64_trunc_f64_u = 0xB1,
f32_convert_i32_s = 0xB2,
f32_convert_i32_u = 0xB3,
f32_convert_i64_s = 0xB4,
f32_convert_i64_u = 0xB5,
f32_demote_f64 = 0xB6,
f64_convert_i32_s = 0xB7,
f64_convert_i32_u = 0xB8,
f64_convert_i64_s = 0xB9,
f64_convert_i64_u = 0xBA,
f64_promote_f32 = 0xBB,
i32_reinterpret_f32 = 0xBC,
i64_reinterpret_f64 = 0xBD,
f32_reinterpret_i32 = 0xBE,
f64_reinterpret_i64 = 0xBF,
i32_extend8_s = 0xC0,
i32_extend16_s = 0xC1,
i64_extend8_s = 0xC2,
i64_extend16_s = 0xC3,
i64_extend32_s = 0xC4,
i32_trunc_sat_f32_s = 0xD3,
i32_trunc_sat_f32_u = 0xD4,
i32_trunc_sat_f64_s = 0xD5,
i32_trunc_sat_f64_u = 0xD6,
i64_trunc_sat_f32_s = 0xD7,
i64_trunc_sat_f32_u = 0xD8,
i64_trunc_sat_f64_s = 0xD9,
i64_trunc_sat_f64_u = 0xDA,
// VECTOR INSTRUCTIONS
/// Index: `VectorIndex`. Meaning: See `VectorOpcode`
vecinst = 0xFD,
};
const VectorOpcode = enum(u8) {
v128_load = 0,
v128_load8x8_s = 1,
v128_load8x8_u = 2,
v128_load16x4_s = 3,
v128_load16x4_u = 4,
v128_load32x2_s = 5,
v128_load32x2_u = 6,
v128_load8_splat = 7,
v128_load16_splat = 8,
v128_load32_splat = 9,
v128_load64_splat = 10,
v128_load32_zero = 92,
v128_load64_zero = 93,
v128_store = 11,
v128_load8_lane = 84,
v128_load16_lane = 85,
v128_load32_lane = 86,
v128_load64_lane = 87,
v128_store8_lane = 88,
v128_store16_lane = 89,
v128_store32_lane = 90,
v128_store64_lane = 91,
/// TODO
v128_const = 12,
/// TODO
i8x16_shuffle = 13,
i8x16_extract_lane_s = 21,
i8x16_extract_lane_u = 22,
i8x16_replace_lane = 23,
i16x8_extract_lane_s = 24,
i16x8_extract_lane_u = 25,
i16x8_replace_lane = 26,
i32x4_extract_lane = 27,
i32x4_replace_lane = 28,
i64x2_extract_lane = 29,
i64x2_replace_lane = 30,
f32x4_extract_lane = 31,
f32x4_replace_lane = 32,
f64x2_extract_lane = 33,
f64x2_replace_lane = 34,
i8x16_swizzle = 14,
i8x16_splat = 15,
i16x8_splat = 16,
i32x4_splat = 17,
i64x2_splat = 18,
f32x4_splat = 19,
f64x2_splat = 20,
i8x16_eq = 35,
i8x16_ne = 36,
i8x16_lt_s = 37,
i8x16_lt_u = 38,
i8x16_gt_s = 39,
i8x16_gt_u = 40,
i8x16_le_s = 41,
i8x16_le_u = 42,
i8x16_ge_s = 43,
i8x16_ge_u = 44,
i16x8_eq = 45,
i16x8_ne = 46,
i16x8_lt_s = 47,
i16x8_lt_u = 48,
i16x8_gt_s = 49,
i16x8_gt_u = 50,
i16x8_le_s = 51,
i16x8_le_u = 52,
i16x8_ge_s = 53,
i16x8_ge_u = 54,
i32x4_eq = 55,
i32x4_ne = 56,
i32x4_lt_s = 57,
i32x4_lt_u = 58,
i32x4_gt_s = 59,
i32x4_gt_u = 60,
i32x4_le_s = 61,
i32x4_le_u = 62,
i32x4_ge_s = 63,
i32x4_ge_u = 64,
i64x2_eq = 214,
i64x2_ne = 215,
i64x2_lt_s = 216,
i64x2_gt_s = 217,
i64x2_le_s = 218,
i64x2_ge_s = 219,
f32x4_eq = 65,
f32x4_ne = 66,
f32x4_lt = 67,
f32x4_gt = 68,
f32x4_le = 69,
f32x4_ge = 70,
f64x2_eq = 71,
f64x2_ne = 72,
f64x2_lt = 73,
f64x2_gt = 74,
f64x2_le = 75,
f64x2_ge = 76,
v128_not = 77,
v128_and = 78,
v128_andnot = 79,
v128_or = 80,
v128x_or = 81,
v128_bitselect = 82,
v128_any_true = 83,
i8x16_abs = 96,
i8x16_neg = 97,
i8x16_popcnt = 98,
i8x16_all_true = 99,
i8x16_bitmask = 100,
i8x16_narrow_i16x8_s = 101,
i8x16_narrow_i16x8_u = 102,
i8x16_shl = 107,
i8x16_shr_s = 108,
i8x16_shr_u = 109,
i8x16_add = 110,
i8x16_add_sat_s = 111,
i8x16_add_sat_u = 112,
i8x16_sub = 113,
i8x16_sub_sat_s = 114,
i8x16_sub_sat_u = 115,
i8x16_min_s = 118,
i8x16_min_u = 119,
i8x16_max_s = 120,
i8x16_max_u = 121,
i8x16_avgr_u = 123,
i16x8_extadd_pairwise_i8x16_s = 124,
i16x8_extadd_pairwise_i8x16_u = 125,
i16x8_abs = 128,
i16x8_neg = 129,
i16x8_q15mulr_sat_s = 130,
i16x8_all_true = 131,
i16x8_bitmask = 132,
i16x8_narrow_i32x4_s = 133,
i16x8_narrow_i32x4_u = 134,
i16x8_extend_low_i8x16_s = 135,
i16x8_extend_high_i8x16_s = 136,
i16x8_extend_low_i8x16_u = 137,
i16x8_extend_high_i8x16_u = 138,
i16x8_shl = 139,
i16x8_shr_s = 140,
i16x8_shr_u = 141,
i16x8_add = 142,
i16x8_add_sat_s = 143,
i16x8_add_sat_u = 144,
i16x8_sub = 145,
i16x8_sub_sat_s = 146,
i16x8_sub_sat_u = 147,
i16x8_mul = 149,
i16x8_min_s = 150,
i16x8_min_u = 151,
i16x8_max_s = 152,
i16x8_max_u = 153,
i16x8_avgr_u = 155,
i16x8_extmul_low_i8x16_s = 156,
i16x8_extmul_high_i8x16_s = 157,
i16x8_extmul_low_i8x16_u = 158,
i16x8_extmul_high_u8x16_u = 159,
i32x4_extadd_pairwise_i16x8_s = 126,
i32x4_extadd_pairwise_i16x8_u = 127,
i32x4_abs = 160,
i32x4_neg = 161,
i32x4_all_true = 163,
i32x4_bitmask = 164,
i32x4_extend_low_i16x8_s = 167,
i32x4_extend_high_i16x8_s = 168,
i32x4_extend_low_i16x8_u = 169,
i32x4_extend_high_i16x8_u = 170,
i32x4_shl = 171,
i32x4_shr_s = 172,
i32x4_shr_u = 173,
i32x4_add = 174,
i32x4_sib = 177,
i32x4_mul = 181,
i32x4_min_s = 182,
i32x4_min_u = 183,
i32x4_max_s = 184,
i32x4_max_u = 185,
i32x4_dot_i16x8_s = 186,
i32x4_extmul_low_i16x8_s = 188,
i32x4_extmul_high_i16x8_s = 189,
i32x4_extmul_los_i16x8_u = 190,
i32x4_extmul_high_i16x8_u = 191,
i64x2_abs = 192,
i64x2_neg = 193,
i64x2_all_true = 195,
i64x2_bitmask = 196,
i64x2_extend_low_i32x4_s = 199,
i64x2_extend_high_i32x4_s = 200,
i64x2_extend_low_i32x4_u = 201,
i64x2_extend_high_i32x4_u = 202,
i64x2_shl = 203,
i64x2_shr_s = 204,
i64x2_shr_u = 205,
i64x2_add = 206,
i64x2_sub = 209,
i64x2_mul = 213,
i64x2_extmul_low_i32x4_s = 220,
i64x2_extmul_high_i32x4_s = 221,
i64x2_extmul_low_i32x4_u = 222,
i64x2_extmul_high_i32x4_u = 223,
f32x4_ceil = 103,
f32x4_floor = 104,
f32x4_trunc = 105,
f32x4_nearest = 106,
f32x4_abs = 224,
f32x4_neg = 225,
f32x4_sqrt = 227,
f32x4_add = 228,
f32x4_sub = 229,
f32x4_mul = 230,
f32x4_div = 231,
f32x4_min = 232,
f32x4_max = 233,
f32x4_pmin = 234,
f32x4_pmax = 235,
f64x2_ceil = 116,
f64x2_floor = 117,
f64x2_trunc = 122,
f64x2_nearest = 148,
f64x2_abs = 236,
f64x2_neg = 237,
f64x2_sqrt = 239,
f64x2_add = 240,
f64x2_sub = 241,
f64x2_mul = 242,
f64x2_div = 243,
f64x2_min = 244,
f64x2_max = 245,
f64x2_pmin = 246,
f64x2_pmax = 247,
i32x4_trunc_sat_f32x4_s = 248,
i32x4_trunc_sat_f32x4_u = 249,
f32x4_convert_i32x4_s = 250,
f32x4_convert_i32x4_u = 251,
i32x4_trunc_sat_f64x2_s_zero = 252,
i32x4_trunc_sat_f64x2_u_zero = 253,
f64x2_convert_low_i32x4_s = 254,
f64x2_convert_low_i32x4_u = 255,
f32x4_demote_f64x2_zero = 94,
f64x2_promote_low_f32x4 = 95,
};
const IRParserState = struct {
reader: *std.Io.Reader,
allocator: Allocator,
// branches: std.AutoHashMapUnmanaged(u32, u32),
branches: std.ArrayListUnmanaged(struct { pc: u32, index: u32, table: bool }),
br_table_vectors: std.ArrayListUnmanaged(u32),
opcodes: std.ArrayListUnmanaged(Opcode),
indices: std.ArrayListUnmanaged(Index),
fn parseFunction(self: *IRParserState) !void {
while (true) {
const op = try self.reader.peekByte();
if (op == 0x0B) {
self.reader.toss(1);
break;
} else {
try self.parseExpression();
}
}
}
const PossibleError = std.Io.Reader.TakeLeb128Error || std.mem.Allocator.Error || IR.Error;
fn parseExpression(self: *IRParserState) PossibleError!void {
const b = try self.reader.takeByte();
try switch (b) {
0x00 => self.push(@enumFromInt(b), .{ .u64 = 0 }),
0x01 => self.push(@enumFromInt(b), .{ .u64 = 0 }),
0x02...0x03 => self.parseBlock(b),
0x04 => self.parseIf(),
0x0C...0x0D => self.parseBranch(b),
0x0E => self.parseBrTable(b),
0x0F => self.push(@enumFromInt(b), .{ .u64 = 0 }),
0x10 => self.push(@enumFromInt(b), .{ .u32 = try self.reader.takeLeb128(u32) }),
0x11 => self.push(@enumFromInt(b), .{ .indirect = .{ .y = try self.reader.takeLeb128(u32), .x = try self.reader.takeLeb128(u32) } }),
0xD0 => self.push(@enumFromInt(b), .{ .reftype = try self.parseReftype() }),
0xD1 => self.push(@enumFromInt(b), .{ .u64 = 0 }),
0xD2 => self.push(@enumFromInt(b), .{ .u32 = try self.reader.takeLeb128(u32) }),
0x1A...0x1C => self.parseParametric(b),
0x20...0x24 => self.push(@enumFromInt(b), .{ .u32 = try self.reader.takeLeb128(u32) }),
0x25...0x26 => self.push(@enumFromInt(b), .{ .u32 = try self.reader.takeLeb128(u32) }),
0x28...0x3E => self.push(@enumFromInt(b), .{ .memarg = try self.parseMemarg() }),
0x3F...0x40 => self.parseMemsizeorgrow(b),
0x41...0x44 => self.parseConst(b),
0x45...0xC4 => self.push(@enumFromInt(b), .{ .u64 = 0 }),
0xFD => self.parseVector(),
0xFC => self.parseMisc(),
else => {
std.log.err("Invalid instruction {x} at position {d}\n", .{ b, self.reader.seek });
return IR.Error.invalid_instruction;
},
};
}
fn push(self: *IRParserState, opcode: Opcode, index: Index) !void {
try self.opcodes.append(self.allocator, opcode);
try self.indices.append(self.allocator, index);
}
fn parseReftype(self: *IRParserState) !std.wasm.RefType {
return switch (try self.reader.takeByte()) {
0x70 => .funcref,
0x6F => .externref,
else => Error.invalid_reftype,
};
}
fn parseMemarg(self: *IRParserState) !Memarg {
return .{
// TODO: assert this intCast does not fail
.alignment = @intCast(try self.reader.takeLeb128(u32)),
.offset = try self.reader.takeLeb128(u32),
};
}
fn parseMemsizeorgrow(self: *IRParserState, b: u8) !void {
if (try self.reader.takeByte() != 0x00) return IR.Error.invalid_instruction;
try self.push(@enumFromInt(b), .{ .u64 = 0 });
}
fn parseConst(self: *IRParserState, b: u8) !void {
try switch (b) {
0x41 => self.push(.i32_const, .{ .i32 = try self.reader.takeLeb128(i32) }),
0x42 => self.push(.i64_const, .{ .i64 = try self.reader.takeLeb128(i64) }),
0x43 => self.push(.f32_const, .{ .f32 = val: {
const bytes = try self.reader.takeArray(4);
break :val std.mem.bytesAsValue(f32, bytes).*;
} }),
0x44 => self.push(.f64_const, .{ .f64 = val: {
const bytes = try self.reader.takeArray(8);
break :val std.mem.bytesAsValue(f64, bytes).*;
} }),
else => unreachable,
};
}
fn parseMisc(self: *IRParserState) !void {
const n = try self.reader.takeLeb128(u32);
try switch (n) {
0...7 => self.push(@enumFromInt(0xD3 + @as(u8, @intCast(n))), .{ .u64 = 0 }),
8...9 => @panic("UNIMPLEMENTED"),
10...11 => {
try self.push(@enumFromInt(0xD3 + @as(u8, @intCast(n))), .{ .u64 = 0 });
// TODO(ernesto): This is sus
try self.reader.discardAll(1);
if (n == 10) {
try self.reader.discardAll(1);
}
},
12...17 => @panic("UNIMPLEMENTED"),
else => {
std.log.err("Invalid misc instruction {d} at position {d}\n", .{ n, self.reader.seek });
return IR.Error.invalid_instruction;
},
};
}
fn parseBlockType(self: *IRParserState) !void {
const b = try self.reader.peekByte();
switch (b) {
0x40 => self.reader.toss(1),
0x6F...0x70, 0x7B...0x7F => self.reader.toss(1),
else => _ = try self.reader.takeLeb128(i33),
}
}
fn parseBlock(self: *IRParserState, b: u8) !void {
// TODO: Should we do something with this?
_ = try self.parseBlockType();
const start: u32 = @intCast(self.opcodes.items.len);
while (true) {
const op = try self.reader.peekByte();
if (op == 0x0B) {
self.reader.toss(1);
break;
} else {
try self.parseExpression();
}
}
const end: u32 = @intCast(self.opcodes.items.len);
const jump_addr: u32 = switch (b) {
0x02 => end,
0x03 => start,
else => unreachable,
};
try self.fix_branches_for_block(start, end, jump_addr);
}
fn parseGlobal(self: *IRParserState) !void {
while (true) {
const op = try self.reader.peekByte();
if (op == 0x0B) {
self.reader.toss(1);
break;
} else {
try self.parseExpression();
}
}
}
fn parseIf(self: *IRParserState) !void {
// TODO: Should we do something with this?
_ = try self.parseBlockType();
try self.push(.br_if, .{ .u32 = @intCast(self.opcodes.items.len + 2) });
const start: u32 = @intCast(self.opcodes.items.len);
try self.push(.br, .{ .u32 = 0 });
var else_addr: u32 = 0;
while (true) {
const op = try self.reader.peekByte();
if (op == 0x05) {
if (else_addr != 0) return IR.Error.double_else;
self.reader.toss(1);
else_addr = @intCast(self.opcodes.items.len);
try self.push(.br, .{ .u32 = 0 });
} else if (op == 0x0B) {
self.reader.toss(1);
break;
} else {
try self.parseExpression();
}
}
const end: u32 = @intCast(self.opcodes.items.len);
if (else_addr > 0) {
self.indices.items[start].u32 = else_addr + 1;
self.indices.items[else_addr].u32 = end;
} else {
self.indices.items[start].u32 = end;
}
try self.fix_branches_for_block(start, end, end);
}
fn parseParametric(self: *IRParserState, b: u8) !void {
try switch (b) {
0x1A...0x1B => self.push(@enumFromInt(b), .{ .u64 = 0 }),
0x1C => @panic("UNIMPLEMENTED"),
else => return IR.Error.invalid_instruction,
};
}
fn fix_branches_for_block(self: *IRParserState, start: u32, end: u32, jump_addr: u32) !void {
var todel: std.ArrayListUnmanaged(u32) = .{};
defer todel.deinit(self.allocator);
for (self.branches.items, 0..) |branch, idx| {
if (start <= branch.pc and branch.pc < end) {
const ptr = if (branch.table) &self.br_table_vectors.items[branch.index] else &self.indices.items[branch.index].u32;
if (ptr.* == 0) {
ptr.* = jump_addr;
try todel.append(self.allocator, @intCast(idx));
} else {
ptr.* -= 1;
}
}
}
// TODO(ernesto): need better way of deleting from the array (this looks ugly)
std.mem.sort(u32, todel.items, {}, comptime std.sort.desc(u32));
for (todel.items) |d| {
// TODO: Do we need to assert this is true?
_ = self.branches.swapRemove(d);
}
}
fn parseBranch(self: *IRParserState, b: u8) !void {
const idx = try self.reader.takeLeb128(u32);
try self.branches.append(self.allocator, .{ .pc = @intCast(self.opcodes.items.len), .index = @intCast(self.indices.items.len), .table = false });
try self.push(@enumFromInt(b), .{ .u32 = idx });
}
fn parseVectorU32(self: *IRParserState) ![]u32 {
const n = try self.reader.takeLeb128(u32);
const vec = try self.allocator.alloc(u32, n);
for (vec) |*i| {
i.* = try self.reader.takeLeb128(u32);
}
return vec;
}
fn parseBrTable(self: *IRParserState, b: u8) !void {
const idxs = try self.parseVectorU32();
const idxN = try self.reader.takeLeb128(u32);
const table_vectors_len = self.br_table_vectors.items.len;
try self.br_table_vectors.appendSlice(self.allocator, idxs);
try self.br_table_vectors.append(self.allocator, idxN);
for (0..idxs.len + 1) |i| {
try self.branches.append(self.allocator, .{ .pc = @intCast(self.opcodes.items.len), .index = @intCast(table_vectors_len + i), .table = true });
}
try self.push(@enumFromInt(b), .{ .indirect = .{ .x = @intCast(table_vectors_len), .y = @intCast(idxs.len) } });
}
fn parseVector(self: *IRParserState) !void {
const n = try self.reader.takeLeb128(u32);
try switch (n) {
0...10, 92...93, 11 => self.push(.vecinst, .{ .vector = .{ .opcode = @enumFromInt(n), .memarg = try self.parseMemarg(), .laneidx = 0 } }),
84...91 => self.push(.vecinst, .{ .vector = .{ .opcode = @enumFromInt(n), .memarg = try self.parseMemarg(), .laneidx = try self.reader.takeByte() } }),
12 => {},
13 => {},
21...34 => self.push(.vecinst, .{ .vector = .{ .opcode = @enumFromInt(n), .memarg = .{ .alignment = 0, .offset = 0 }, .laneidx = try self.reader.takeByte() } }),
// Yes, there are this random gaps in wasm vector instructions don't ask me how I know...
14...20, 35...83, 94...153, 155...161, 163...164, 167...174, 177, 181...186, 188...193, 195...196, 199...206, 209, 213...225, 227...237, 239...255 => {
try self.push(.vecinst, .{ .vector = .{ .opcode = @enumFromInt(n), .memarg = .{ .alignment = 0, .offset = 0 }, .laneidx = 0 } });
},
else => {
std.log.err("Invalid vector instruction {d} at position {d}\n", .{ n, self.reader.seek });
return IR.Error.invalid_instruction;
},
};
}
};
pub fn parse(parser: *Parser) !IR {
var state = IRParserState{
.br_table_vectors = .{},
.opcodes = .{},
.indices = .{},
.branches = .{},
.reader = parser.reader,
.allocator = parser.allocator,
};
try state.parseFunction();
if (state.branches.items.len != 0) return Parser.Error.unresolved_branch;
return .{ .opcodes = try state.opcodes.toOwnedSlice(state.allocator), .indices = try state.indices.toOwnedSlice(state.allocator), .select_valtypes = &.{}, .br_table_vectors = state.br_table_vectors.items };
}
pub fn parseGlobalExpr(parser: *Parser) !IR {
var state = IRParserState{
.br_table_vectors = .{},
.opcodes = .{},
.indices = .{},
.branches = .{},
.reader = parser.reader,
.allocator = parser.allocator,
};
try state.parseGlobal();
return .{ .opcodes = try state.opcodes.toOwnedSlice(state.allocator), .indices = try state.indices.toOwnedSlice(state.allocator), .select_valtypes = &.{}, .br_table_vectors = state.br_table_vectors.items };
}
pub fn parseSingleExpr(parser: *Parser) !IR {
var state = IRParserState{
.br_table_vectors = .{},
.opcodes = .{},
.indices = .{},
.branches = .{},
.reader = parser.reader,
.allocator = parser.allocator,
};
try state.parseExpression();
return .{ .opcodes = try state.opcodes.toOwnedSlice(state.allocator), .indices = try state.indices.toOwnedSlice(state.allocator), .select_valtypes = &.{}, .br_table_vectors = state.br_table_vectors.items };
}