From b39fb3494260db731fb36b041b660501288c39c6 Mon Sep 17 00:00:00 2001 From: luccie Date: Sun, 10 Aug 2025 22:10:15 +0200 Subject: [PATCH] Added support for parsing tables --- src/mods/Parser.zig | 27 ++++++++----- src/mods/ir.zig | 54 +++++++++++++++----------- src/mods/vm.zig | 93 +++++++++++++++++++++++++++++---------------- 3 files changed, 111 insertions(+), 63 deletions(-) diff --git a/src/mods/Parser.zig b/src/mods/Parser.zig index 441d0b3..4a8c5d7 100644 --- a/src/mods/Parser.zig +++ b/src/mods/Parser.zig @@ -21,7 +21,7 @@ globalValues: []vm.Value, globalTypes: []Globaltype, const Parser = @This(); -const PAGE_SIZE = 65536; +pub const PAGE_SIZE = 65536; pub const Error = error{ OutOfMemory, @@ -93,7 +93,6 @@ pub fn module(self: *Parser) vm.Module { .min = self.memory.lim.min, .max = self.memory.lim.max, }, - .imported_funcs = self.importCount, .exported_memory = self.exported_memory, .functions = self.functions, .exports = self.exports, @@ -184,6 +183,14 @@ pub fn parseVector(self: *Parser, parse_fn: anytype) ![]VectorFnResult(parse_fn) } return ret; } +pub fn parseVectorU32(self: *Parser) ![]u32 { + const n = try self.readU32(); + const ret = try self.allocator.alloc(u32, n); + for (ret) |*i| { + i.* = try self.readU32(); + } + return ret; +} fn parseNumtype(self: *Parser) !std.wasm.Valtype { return switch (try self.readByte()) { @@ -387,6 +394,8 @@ fn parseImportsec(self: *Parser) !void { } else { std.debug.panic("imported function {s} not supported\n", .{i.name}); } + self.functions = try self.allocator.realloc(self.functions, index + 1); + self.functions[index].typ = .{ .external = index }; index += 1; }, .mem => { @@ -413,10 +422,10 @@ fn parseFuncsec(self: *Parser) !void { const types = try self.parseVector(Parser.readU32); defer self.allocator.free(types); - if (self.functions.len != 0) return Error.duplicated_funcsec; - self.functions = try self.allocator.alloc(vm.Function, types.len); + if (self.functions.len != self.importCount) return Error.duplicated_funcsec; + self.functions = try self.allocator.realloc(self.functions, self.importCount + types.len); - for (types, 0..) |t, i| { + for (types, self.importCount..) |t, i| { self.functions[i].func_type = .{ .parameters = try self.allocator.alloc(vm.Valtype, self.types[t].parameters.len), .returns = try self.allocator.alloc(vm.Valtype, self.types[t].returns.len), @@ -426,7 +435,7 @@ fn parseFuncsec(self: *Parser) !void { } // TODO(ernesto): run this check not only in debug - std.debug.assert(types.len == self.functions.len); + std.debug.assert(types.len + self.importCount == self.functions.len); // TODO: run this check not only on debug std.debug.assert(self.byte_idx == end_idx); @@ -547,7 +556,7 @@ fn parseExportsec(self: *Parser) !void { switch (e.exportdesc) { .func => { if (std.mem.eql(u8, e.name, "init")) { - self.exports.init = e.exportdesc.func; + self.exports.init = e.exportdesc.func + self.importCount; } else { std.log.warn("exported function {s} not supported\n", .{e.name}); } @@ -690,9 +699,9 @@ fn parseCodesec(self: *Parser) !void { const codes = try self.parseVector(Parser.parseCode); defer self.allocator.free(codes); // TODO: run this check not only on debug - std.debug.assert(codes.len == self.functions.len); + std.debug.assert(codes.len == self.functions.len - self.importCount); - for (codes, self.functions) |code, *f| { + for (codes, self.functions[self.importCount..]) |code, *f| { f.typ = .{ .internal = .{ .locals = code.locals, .ir = code.ir, diff --git a/src/mods/ir.zig b/src/mods/ir.zig index 2e1d1a4..48a6700 100644 --- a/src/mods/ir.zig +++ b/src/mods/ir.zig @@ -603,7 +603,9 @@ const IRParserState = struct { parser: *Parser, allocator: Allocator, - branches: std.AutoHashMapUnmanaged(u32, u32), + // 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), @@ -628,7 +630,8 @@ const IRParserState = struct { 0x02...0x03 => self.parseBlock(b), 0x04 => self.parseIf(), 0x0C...0x0D => self.parseBranch(b), - 0x0E => self.parseTableBranch(b), + // 0x0E => self.parseTableBranch(b), + 0x0E => self.parseBrTable(b), 0x0F => self.push(@enumFromInt(b), .{ .u64 = 0 }), 0x10 => self.push(@enumFromInt(b), .{ .u32 = try self.parser.readU32() }), 0x11 => self.push(@enumFromInt(b), .{ .indirect = .{ .y = try self.parser.readU32(), .x = try self.parser.readU32() } }), @@ -790,40 +793,44 @@ const IRParserState = struct { var todel: std.ArrayListUnmanaged(u32) = .{}; defer todel.deinit(self.allocator); - var it = self.branches.iterator(); - while (it.next()) |branch| { - if (start <= branch.key_ptr.* and branch.key_ptr.* < end) { - if (branch.value_ptr.* == 0) { - self.indices.items[branch.key_ptr.*].u32 = jump_addr; - try todel.append(self.allocator, branch.key_ptr.*); + var idx: u32 = 0; + for (self.branches.items) |branch| { + 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)); + idx += 1; } else { - branch.value_ptr.* -= 1; + 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.remove(d); + _ = self.branches.swapRemove(d); } } fn parseBranch(self: *IRParserState, b: u8) !void { const idx = try self.parser.readU32(); - try self.branches.put(self.allocator, @intCast(self.opcodes.items.len), idx); - try self.push(@enumFromInt(b), .{ .u64 = 0 }); + 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 parseTableBranch(self: *IRParserState, b: u8) !void { - const n = try self.parser.readU32(); - const idxs = try self.allocator.alloc(u32, n); - // defer self.allocator.free(idxs); - for (idxs) |*i| { - i.* = try self.parser.readU32(); - try self.branches.put(self.allocator, @intCast(self.opcodes.items.len), i.*); + fn parseBrTable(self: *IRParserState, b: u8) !void { + const idxs = try self.parser.parseVectorU32(); + const idxN = try self.parser.readU32(); + 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.branches.put(self.allocator, @intCast(self.opcodes.items.len), try self.parser.readU32()); - try self.push(@enumFromInt(b), .{ .u64 = 0 }); + try self.push(@enumFromInt(b), .{ .indirect = .{ .x = @intCast(table_vectors_len), .y = @intCast(idxs.len) }}); } fn parseVector(self: *IRParserState) !void { @@ -848,6 +855,7 @@ const IRParserState = struct { pub fn parse(parser: *Parser) !IR { var state = IRParserState{ + .br_table_vectors = .{}, .opcodes = .{}, .indices = .{}, .branches = .{}, @@ -855,7 +863,7 @@ pub fn parse(parser: *Parser) !IR { .allocator = parser.allocator, }; try state.parseFunction(); - if (state.branches.count() != 0) return Parser.Error.unresolved_branch; + // if (state.branches.count() != 0) return Parser.Error.unresolved_branch; return .{ .opcodes = try state.opcodes.toOwnedSlice(state.allocator), .indices = try state.indices.toOwnedSlice(state.allocator), @@ -865,6 +873,7 @@ pub fn parse(parser: *Parser) !IR { pub fn parseGlobalExpr(parser: *Parser) !IR { var state = IRParserState{ + .br_table_vectors = .{}, .opcodes = .{}, .indices = .{}, .branches = .{}, @@ -881,6 +890,7 @@ pub fn parseGlobalExpr(parser: *Parser) !IR { pub fn parseSingleExpr(parser: *Parser) !IR { var state = IRParserState{ + .br_table_vectors = .{}, .opcodes = .{}, .indices = .{}, .branches = .{}, diff --git a/src/mods/vm.zig b/src/mods/vm.zig index 459067d..24e08ae 100644 --- a/src/mods/vm.zig +++ b/src/mods/vm.zig @@ -29,7 +29,7 @@ pub const Function = struct { func_type: Functype, typ: union(enum) { locals: []Valtype, ir: IR, }, - external: void, + external: u32 } }; pub const ExportFunction = enum { @@ -55,7 +55,6 @@ pub const Module = struct { functions: []Function, exports: Exports, exported_memory: u32, - imported_funcs: u32, data: []const u8, tables: []Parser.Tabletype, elems: [][]u32, @@ -72,7 +71,7 @@ pub const Module = struct { allocator.free(f.typ.internal.ir.select_valtypes); allocator.free(f.typ.internal.locals); }, - .external => @panic("UNIMPLEMENTED"), + .external => {} } } allocator.free(self.functions); @@ -142,6 +141,7 @@ pub const Runtime = struct { }, .br_table => @panic("UNIMPLEMENTED"), .@"return" => break :loop, + // TODO: Move this to callExternal .call => { if (index.u32 == self.module.exports.logDebug) { const size: usize = @intCast(self.stack.pop().?.i64); @@ -170,24 +170,24 @@ pub const Runtime = struct { } else { var parameters = std.ArrayList(Value).init(allocator); defer parameters.deinit(); - for (self.module.functions[index.u32 - self.module.imported_funcs].func_type.parameters) |_| { + for (self.module.functions[index.u32].func_type.parameters) |_| { try parameters.append(self.stack.pop().?); } - try self.call(allocator, index.u32 - self.module.imported_funcs, parameters.items); + try self.call(allocator, index.u32, parameters.items); } }, .call_indirect => { - std.debug.panic("call_indirect: {any}\n", .{self.stack.pop().?}); if (self.module.tables[index.indirect.x].et != std.wasm.RefType.funcref) { std.debug.panic("Table at index {any} is not a `funcref` table\n", .{index.indirect.x}); } - const funcIdx = self.module.elems[index.indirect.x][index.indirect.y]; + const j: u32 = @intCast(self.stack.pop().?.i32); + const funcIdx = self.module.elems[index.indirect.x][j]; var parameters = std.ArrayList(Value).init(allocator); defer parameters.deinit(); - for (self.module.functions[funcIdx - self.module.imported_funcs].func_type.parameters) |_| { + for (self.module.functions[funcIdx].func_type.parameters) |_| { try parameters.append(self.stack.pop().?); } - try self.call(allocator, funcIdx - self.module.imported_funcs, parameters.items); + try self.call(allocator, funcIdx, parameters.items); }, .refnull => @panic("UNIMPLEMENTED"), @@ -321,7 +321,12 @@ pub const Runtime = struct { .i64_store32 => @panic("UNIMPLEMENTED"), .memorysize => @panic("UNIMPLEMENTED"), - .memorygrow => @panic("UNIMPLEMENTED"), + .memorygrow => { + const newPages = self.stack.pop().?.i32; + const oldPages: i32 = @intCast(self.memory.len / Parser.PAGE_SIZE); + self.memory = try allocator.realloc(self.memory, self.memory.len + @as(usize, @intCast(newPages * Parser.PAGE_SIZE))); + try self.stack.append(.{ .i32 = oldPages }); + }, .memoryinit => @panic("UNIMPLEMENTED"), .datadrop => @panic("UNIMPLEMENTED"), .memorycopy => { @@ -426,34 +431,36 @@ pub const Runtime = struct { .f64_le => @panic("UNIMPLEMENTED"), .f64_ge => @panic("UNIMPLEMENTED"), - .i32_clz => @panic("UNIMPLEMENTED"), + .i32_clz => { + try self.stack.append(.{ .i32 = @clz(self.stack.pop().?.i32) }); + }, .i32_ctz => @panic("UNIMPLEMENTED"), .i32_popcnt => @panic("UNIMPLEMENTED"), .i32_add => { const a = self.stack.pop().?.i32; const b = self.stack.pop().?.i32; - try self.stack.append(Value{ .i32 = a + b }); + try self.stack.append(.{ .i32 = a + b }); }, .i32_sub => { const a = self.stack.pop().?.i32; const b = self.stack.pop().?.i32; - try self.stack.append(Value{ .i32 = b - a }); + try self.stack.append(.{ .i32 = b - a }); }, .i32_and => { const a = self.stack.pop().?.i32; const b = self.stack.pop().?.i32; - try self.stack.append(Value{ .i32 = a & b }); + try self.stack.append(.{ .i32 = a & b }); }, .i32_mul => { const a = self.stack.pop().?.i32; const b = self.stack.pop().?.i32; - try self.stack.append(Value{ .i32 = a * b }); + try self.stack.append(.{ .i32 = a * b }); }, .i32_div_s => @panic("UNIMPLEMENTED"), .i32_div_u => { const a_unsigned = @as(u32, @bitCast(self.stack.pop().?.i32)); const b_unsigned = @as(u32, @bitCast(self.stack.pop().?.i32)); - try self.stack.append(Value{ .i32 = @bitCast(b_unsigned / a_unsigned) }); + try self.stack.append(.{ .i32 = @bitCast(b_unsigned / a_unsigned) }); }, .i32_rem_s => @panic("UNIMPLEMENTED"), .i32_rem_u => { @@ -462,32 +469,32 @@ pub const Runtime = struct { if (divisor == 0) { std.debug.panic("Divide by 0\n", .{}); } - try self.stack.append(Value{ .i32 = @intCast(dividend - divisor * @divTrunc(dividend, divisor)) }); + try self.stack.append(.{ .i32 = @intCast(dividend - divisor * @divTrunc(dividend, divisor)) }); }, .i32_or => { const a = self.stack.pop().?.i32; const b = self.stack.pop().?.i32; - try self.stack.append(Value{ .i32 = a | b }); + try self.stack.append(.{ .i32 = a | b }); }, .i32_xor => { const a = self.stack.pop().?.i32; const b = self.stack.pop().?.i32; - try self.stack.append(Value{ .i32 = a ^ b }); + try self.stack.append(.{ .i32 = a ^ b }); }, .i32_shl => { const a = self.stack.pop().?.i32; const b = self.stack.pop().?.i32; - try self.stack.append(Value{ .i32 = (b << @as(u5, @intCast(a))) }); + try self.stack.append(.{ .i32 = (b << @as(u5, @intCast(a))) }); }, .i32_shr_s => { const a = self.stack.pop().?.i32; const b = self.stack.pop().?.i32; - try self.stack.append(Value{ .i32 = (b >> @as(u5, @intCast(a))) }); + try self.stack.append(.{ .i32 = (b >> @as(u5, @intCast(a))) }); }, .i32_shr_u => { const a = @as(u32, @intCast(self.stack.pop().?.i32)); const b = @as(u32, @intCast(self.stack.pop().?.i32)); - try self.stack.append(Value{ .i32 = @intCast(b >> @as(u5, @intCast(a))) }); + try self.stack.append(.{ .i32 = @intCast(b >> @as(u5, @intCast(a))) }); }, .i32_rotl => @panic("UNIMPLEMENTED"), .i32_rotr => @panic("UNIMPLEMENTED"), @@ -498,20 +505,40 @@ pub const Runtime = struct { .i64_add => { const a = self.stack.pop().?.i64; const b = self.stack.pop().?.i64; - try self.stack.append(Value{ .i64 = a + b }); + try self.stack.append(.{ .i64 = a + b }); + }, + .i64_sub => { + const a = self.stack.pop().?.i64; + const b = self.stack.pop().?.i64; + try self.stack.append(.{ .i64 = b - a }); + }, + .i64_mul => { + const a = self.stack.pop().?.i64; + const b = self.stack.pop().?.i64; + try self.stack.append(.{ .i64 = a * b }); }, - .i64_sub => @panic("UNIMPLEMENTED"), - .i64_mul => @panic("UNIMPLEMENTED"), .i64_div_s => @panic("UNIMPLEMENTED"), .i64_div_u => @panic("UNIMPLEMENTED"), .i64_rem_s => @panic("UNIMPLEMENTED"), .i64_rem_u => @panic("UNIMPLEMENTED"), - .i64_and => @panic("UNIMPLEMENTED"), + .i64_and => { + const a = self.stack.pop().?.i64; + const b = self.stack.pop().?.i64; + try self.stack.append(.{ .i64 = a & b }); + }, .i64_or => @panic("UNIMPLEMENTED"), .i64_xor => @panic("UNIMPLEMENTED"), - .i64_shl => @panic("UNIMPLEMENTED"), + .i64_shl => { + const a = self.stack.pop().?.i64; + const b = self.stack.pop().?.i64; + try self.stack.append(.{ .i64 = (b << @as(u6, @intCast(a))) }); + }, .i64_shr_s => @panic("UNIMPLEMENTED"), - .i64_shr_u => @panic("UNIMPLEMENTED"), + .i64_shr_u => { + const a = @as(u64, @intCast(self.stack.pop().?.i64)); + const b = @as(u64, @intCast(self.stack.pop().?.i64)); + try self.stack.append(.{ .i64 = @intCast(b >> @as(u6, @intCast(a))) }); + }, .i64_rotl => @panic("UNIMPLEMENTED"), .i64_rotr => @panic("UNIMPLEMENTED"), @@ -545,14 +572,16 @@ pub const Runtime = struct { .f64_max => @panic("UNIMPLEMENTED"), .f64_copysign => @panic("UNIMPLEMENTED"), - .i32_wrap_i64 => @panic("UNIMPLEMENTED"), + .i32_wrap_i64 => { + try self.stack.append(.{ .i32 = @truncate(self.stack.pop().?.i64) }); + }, .i32_trunc_f32_s => @panic("UNIMPLEMENTED"), .i32_trunc_f32_u => @panic("UNIMPLEMENTED"), .i32_trunc_f64_s => @panic("UNIMPLEMENTED"), .i32_trunc_f64_u => @panic("UNIMPLEMENTED"), .i64_extend_i32_s => @panic("UNIMPLEMENTED"), .i64_extend_i32_u => { - try self.stack.append(.{ .i64 = @as(u32, @intCast(self.stack.pop().?.i32)) }); + try self.stack.append(.{ .i64 = @intCast(self.stack.pop().?.i32) }); }, .i64_trunc_f32_s => @panic("UNIMPLEMENTED"), .i64_trunc_f32_u => @panic("UNIMPLEMENTED"), @@ -595,7 +624,7 @@ pub const Runtime = struct { } // TODO: Do name resolution at parseTime - pub fn callExternal(self: *Runtime, allocator: Allocator, name: ExportFunction, parameters: []Value) !void { + pub fn externalCall(self: *Runtime, allocator: Allocator, name: ExportFunction, parameters: []Value) !void { switch (name) { .init => { if (self.module.exports.init) |func| { @@ -657,7 +686,7 @@ pub const Runtime = struct { allocator.free(frame.locals); }, .external => { - std.debug.panic("TODO: Handle external function {any}\n", .{function}); + std.debug.panic("TODO: Handle external function {any} {any}\n", .{f.typ.external, self.module.exports}); // TODO(ernesto): handle external functions // const name = self.module.imports[f.external].name; // if (self.global_runtime.functions.get(name)) |external| {