From 1d64275dee5e5716b1a32f22e2f0ccba885898db Mon Sep 17 00:00:00 2001 From: Lorenzo Torres Date: Mon, 17 Mar 2025 19:44:08 +0100 Subject: Refactored source code structure. --- src/mods/parse.zig | 331 +++++++++++++++++++++++++++++++++ src/mods/vm.zig | 532 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/mods/wasm.zig | 35 ++++ 3 files changed, 898 insertions(+) create mode 100644 src/mods/parse.zig create mode 100644 src/mods/vm.zig create mode 100644 src/mods/wasm.zig (limited to 'src/mods') diff --git a/src/mods/parse.zig b/src/mods/parse.zig new file mode 100644 index 0000000..7080e66 --- /dev/null +++ b/src/mods/parse.zig @@ -0,0 +1,331 @@ +const std = @import("std"); +const wasm = @import("wasm.zig"); +const Allocator = std.mem.Allocator; + +pub fn leb128Result(T: type) type { + return struct { len: usize, val: T }; +} + +pub fn leb128Decode(comptime T: type, stream: anytype) !leb128Result(T) { + switch (@typeInfo(T)) { + .int => {}, + else => @compileError("LEB128 integer decoding only support integers, but got " ++ @typeName(T)), + } + if (@typeInfo(T).int.bits != 32 and @typeInfo(T).int.bits != 64) { + @compileError("LEB128 integer decoding only supports 32 or 64 bits integers but got " ++ std.fmt.comptimePrint("{d} bits", .{@typeInfo(T).int.bits})); + } + + var result: T = 0; + // TODO: is the type of shift important. Reading Wikipedia (not very much tho) it seems like we can use u32 and call it a day... + var shift: if (@typeInfo(T).int.bits == 32) u5 else u6 = 0; + var byte: u8 = undefined; + var len: usize = 0; + while (stream.readByte()) |b| { + len += 1; + result |= @as(T, @intCast((b & 0x7f))) << shift; + if ((b & (0x1 << 7)) == 0) { + byte = b; + break; + } + shift += 7; + } else |err| { + return err; + } + + if (@typeInfo(T).int.signedness == .signed) { + const size = @sizeOf(T) * 8; + if (shift < size and (byte & 0x40) != 0) { + result |= (~@as(T, 0) << shift); + } + } + + return .{ .len = len, .val = result }; +} + +pub const Error = error{ + malformed_wasm, + invalid_utf8, +}; + +pub const Module = struct { + types: []FunctionType, + imports: std.ArrayList(Import), + exports: std.StringHashMap(u32), + functions: []u32, + memory: Memory, + code: []FunctionBody, + funcs: std.ArrayList(Function), + + pub fn deinit(self: *Module, allocator: Allocator) void { + for (self.types) |t| { + t.deinit(allocator); + } + allocator.free(self.types); + + for (self.imports.items) |i| { + i.deinit(allocator); + } + self.imports.deinit(); + + var iter = self.exports.iterator(); + while (iter.next()) |entry| { + allocator.free(entry.key_ptr.*); + } + self.exports.deinit(); + + allocator.free(self.functions); + + for (self.code) |f| { + for (f.locals) |l| { + allocator.free(l.types); + } + allocator.free(f.code); + } + allocator.free(self.code); + + self.funcs.deinit(); + } +}; + +pub const FunctionScope = enum { + external, + internal, +}; + +pub const Function = union(FunctionScope) { + external: u8, + internal: u8, +}; + +// TODO: refactor locals +pub const Local = struct { + types: []u8, +}; + +pub const FunctionBody = struct { + locals: []Local, + code: []u8, +}; + +pub const Memory = struct { + initial: u32, + max: u32, +}; + +pub const FunctionType = struct { + parameters: []u8, + results: []u8, + + pub fn deinit(self: FunctionType, allocator: Allocator) void { + allocator.free(self.parameters); + allocator.free(self.results); + } +}; + +pub const Import = struct { + name: []u8, + module: []u8, + signature: u32, + + pub fn deinit(self: Import, allocator: Allocator) void { + allocator.free(self.name); + allocator.free(self.module); + } +}; + +pub fn parseType(t: u8) wasm.Type { + return @enumFromInt(t); +} + +pub fn parseName(allocator: Allocator, stream: anytype) ![]u8 { + const size = try std.leb.readULEB128(u32, stream); + const str = try allocator.alloc(u8, size); + if (try stream.read(str) != size) { + // TODO: better error + return Error.malformed_wasm; + } + + if (!std.unicode.utf8ValidateSlice(str)) return Error.invalid_utf8; + + return str; +} + +// TODO: parse Global Section +// TODO: Consider Arena allocator +pub fn parseWasm(allocator: Allocator, stream: anytype) !Module { + var types: []FunctionType = undefined; + var imports = std.ArrayList(Import).init(allocator); + var exports = std.StringHashMap(u32).init(allocator); + var funcs = std.ArrayList(Function).init(allocator); + var functions: []u32 = undefined; + var memory: Memory = undefined; + var code: []FunctionBody = undefined; + + // Parse magic + if (!(try stream.isBytes(&[_]u8{ 0x00, 0x61, 0x73, 0x6d }))) return Error.malformed_wasm; + // Parse version + if (!(try stream.isBytes(&[_]u8{ 0x01, 0x00, 0x00, 0x00 }))) return Error.malformed_wasm; + + // NOTE: This ensures that (in this block) illegal behavior is safety-checked. + // This slows down the code but since this function is only called at the start + // I believe it is better to take the ``hit'' in performance (should only be @enumFromInt) + // rather than having undefined behavior when user provides an invalid wasm file. + @setRuntimeSafety(true); + loop: while (stream.readByte()) |byte| { + const section_size = try std.leb.readULEB128(u32, stream); + switch (@as(std.wasm.Section, @enumFromInt(byte))) { + std.wasm.Section.custom => { + // TODO: unimplemented + break :loop; + }, + std.wasm.Section.type => { + const type_count = try std.leb.readULEB128(u32, stream); + types = try allocator.alloc(FunctionType, type_count); + for (types) |*t| { + if (!(try stream.isBytes(&.{0x60}))) return Error.malformed_wasm; + const params_count = try std.leb.readULEB128(u32, stream); + t.parameters = try allocator.alloc(u8, params_count); + if (try stream.read(t.parameters) != params_count) { + // TODO: better errors + return Error.malformed_wasm; + } + const results = try std.leb.readULEB128(u32, stream); + t.results = try allocator.alloc(u8, results); + if (try stream.read(t.results) != results) { + // TODO: better errors + return Error.malformed_wasm; + } + } + }, + std.wasm.Section.import => { + // Can there be more than one import section? + const import_count = try std.leb.readULEB128(u32, stream); + for (0..import_count) |i| { + const mod = try parseName(allocator, stream); + const nm = try parseName(allocator, stream); + + const b = try stream.readByte(); + switch (@as(std.wasm.ExternalKind, @enumFromInt(b))) { + std.wasm.ExternalKind.function => try funcs.append(.{ .external = @intCast(i) }), + // TODO: not implemented + std.wasm.ExternalKind.table => {}, + std.wasm.ExternalKind.memory => {}, + std.wasm.ExternalKind.global => {}, + } + const idx = try std.leb.readULEB128(u32, stream); + try imports.append(.{ + .module = mod, + .name = nm, + .signature = idx, + }); + } + }, + std.wasm.Section.function => { + const function_count = try std.leb.readULEB128(u32, stream); + functions = try allocator.alloc(u32, function_count); + for (functions) |*f| { + f.* = try std.leb.readULEB128(u32, stream); + } + }, + std.wasm.Section.table => { + // TODO: not implemented + try stream.skipBytes(section_size, .{}); + }, + std.wasm.Section.memory => { + const memory_count = try std.leb.readULEB128(u32, stream); + for (0..memory_count) |_| { + const b = try stream.readByte(); + const n = try std.leb.readULEB128(u32, stream); + var m: u32 = 0; + switch (b) { + 0x00 => {}, + 0x01 => m = try std.leb.readULEB128(u32, stream), + else => return Error.malformed_wasm, + } + // TODO: support multiple memories + memory = .{ + .initial = n, + .max = m, + }; + } + }, + std.wasm.Section.global => { + // TODO: unimplemented + try stream.skipBytes(section_size, .{}); + }, + // TODO: Can there be more than one export section? Otherwise we can optimize allocations + std.wasm.Section.@"export" => { + const export_count = try std.leb.readULEB128(u32, stream); + for (0..export_count) |_| { + const nm = try parseName(allocator, stream); + const b = try stream.readByte(); + const idx = try std.leb.readULEB128(u32, stream); + switch (@as(std.wasm.ExternalKind, @enumFromInt(b))) { + std.wasm.ExternalKind.function => try exports.put(nm, idx), + // TODO: unimplemented, + std.wasm.ExternalKind.table => allocator.free(nm), + std.wasm.ExternalKind.memory => allocator.free(nm), + std.wasm.ExternalKind.global => allocator.free(nm), + } + } + }, + std.wasm.Section.start => { + // TODO: unimplemented + try stream.skipBytes(section_size, .{}); + }, + std.wasm.Section.element => { + // TODO: unimplemented + try stream.skipBytes(section_size, .{}); + }, + std.wasm.Section.code => { + const code_count = try std.leb.readULEB128(u32, stream); + code = try allocator.alloc(FunctionBody, code_count); + for (0..code_count) |i| { + const code_size = try std.leb.readULEB128(u32, stream); + var locals_size: usize = 0; + const local_count = try leb128Decode(u32, stream); + locals_size += local_count.len; + const locals = try allocator.alloc(Local, local_count.val); + for (locals) |*l| { + const n = try leb128Decode(u32, stream); + l.types = try allocator.alloc(u8, n.val); + @memset(l.types, try stream.readByte()); + locals_size += n.len + 1; + } + code[i].locals = locals; + + // TODO: maybe is better to parse code into ast here and not do it every frame? + // FIXME: This calculation is plain wrong. Resolving above TODO should help + code[i].code = try allocator.alloc(u8, code_size - locals_size); + // TODO: better error reporting + if (try stream.read(code[i].code) != code_size - locals_size) return Error.malformed_wasm; + + const f = Function{ .internal = @intCast(i) }; + try funcs.append(f); + } + }, + std.wasm.Section.data => { + // TODO: unimplemented + try stream.skipBytes(section_size, .{}); + }, + std.wasm.Section.data_count => { + // TODO: unimplemented + try stream.skipBytes(section_size, .{}); + }, + else => return Error.malformed_wasm, + } + } else |err| switch (err) { + error.EndOfStream => {}, + else => return err, + } + + return Module{ + .types = types, + .imports = imports, + .functions = functions, + .memory = memory, + .exports = exports, + .code = code, + .funcs = funcs, + }; +} diff --git a/src/mods/vm.zig b/src/mods/vm.zig new file mode 100644 index 0000000..f8c7db5 --- /dev/null +++ b/src/mods/vm.zig @@ -0,0 +1,532 @@ +const std = @import("std"); +const wasm = @import("wasm.zig"); +const Parser = @import("parse.zig"); +const Allocator = std.mem.Allocator; +const AllocationError = error{OutOfMemory}; + +fn leb128Decode(comptime T: type, bytes: []u8) Parser.leb128Result(T) { + var fbs = std.io.fixedBufferStream(bytes); + // TODO: this catch should be unrecheable + return Parser.leb128Decode(T, fbs.reader()) catch .{ .len = 0, .val = 0 }; +} + +pub fn decodeLittleEndian(comptime T: type, bytes: []u8) T { + if (T != i32 and T != i64) { + return @as(T, 0); + } else { + var value = @as(T, 0); + for (0..@sizeOf(T)) |b| { + value |= ((bytes[b]) << @intCast((@sizeOf(T) - b - 1))); + } + return value; + } +} + +pub fn encodeLittleEndian(comptime T: type, bytes: *[]u8, value: T) void { + for (0..@sizeOf(T)) |b| { + bytes.*[b] = @intCast(((value >> @intCast((@sizeOf(T) - b - 1))) & 0xff)); + } +} + +pub const CallFrame = struct { + program_counter: usize, + code: []u8, + locals: []Value, +}; + +const ValueType = enum { + i32, + i64, +}; + +pub const Value = union(ValueType) { + i32: i32, + i64: i64, +}; + +pub const Runtime = struct { + module: Parser.Module, + stack: std.ArrayList(Value), + call_stack: std.ArrayList(CallFrame), + memory: []u8, + global_runtime: *wasm.GlobalRuntime, + labels: std.ArrayList(usize), + + pub fn init(allocator: Allocator, module: Parser.Module, global_runtime: *wasm.GlobalRuntime) !Runtime { + const memory = try allocator.alloc(u8, module.memory.max); + return Runtime{ + .module = module, + .stack = try std.ArrayList(Value).initCapacity(allocator, 10), + .call_stack = try std.ArrayList(CallFrame).initCapacity(allocator, 5), + .labels = try std.ArrayList(usize).initCapacity(allocator, 2), + .memory = memory, + .global_runtime = global_runtime, + }; + } + + pub fn deinit(self: *Runtime, allocator: Allocator) void { + self.module.deinit(allocator); + self.stack.deinit(); + self.labels.deinit(); + self.call_stack.deinit(); + allocator.free(self.memory); + } + + pub fn executeFrame(self: *Runtime, allocator: Allocator, frame: *CallFrame) !void { + var for_loop = false; + loop: while (true) { + const byte: u8 = frame.code[frame.program_counter]; + frame.program_counter += 1; + std.debug.print("b: {x}\n", .{byte}); + switch (byte) { + 0x03 => { + try self.labels.append(frame.program_counter); + frame.program_counter += 1; + //const a = frame.code[frame.program_counter]; + for_loop = true; + }, + 0x0d => { + const label = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += label.len; + var address = @as(usize, 0); + for (0..(label.val)) |_| { + address = self.labels.pop().?; + } + + if (self.stack.pop().?.i32 != 0) { + frame.program_counter = address; + } + }, + + 0x20 => { + const integer = leb128Decode(u32, frame.code[frame.program_counter..]); + + frame.program_counter += integer.len; + try self.stack.append(frame.locals[integer.val]); + }, + 0x21 => { + const integer = leb128Decode(u32, frame.code[frame.program_counter..]); + + frame.program_counter += integer.len; + frame.locals[integer.val] = self.stack.pop().?; + }, + 0x22 => { + const integer = leb128Decode(u32, frame.code[frame.program_counter..]); + + frame.program_counter += integer.len; + frame.locals[integer.val] = self.stack.pop().?; + try self.stack.append(Value{ .i32 = @intCast(integer.val) }); + }, + 0x28 => { + const address = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += address.len; + const offset = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += offset.len; + const start = (address.val + offset.val); + const end = start + @sizeOf(u32); + try self.stack.append(Value{ .i32 = decodeLittleEndian(i32, self.memory[start..end]) }); + }, + 0x29 => { + const address = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += address.len; + const offset = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += offset.len; + const start = (address.val + offset.val); + const end = start + @sizeOf(u64); + try self.stack.append(Value{ .i64 = decodeLittleEndian(i64, self.memory[start..end]) }); + }, + 0x36 => { + const address = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += address.len; + const offset = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += offset.len; + const start = (address.val + offset.val); + const end = start + @sizeOf(u32); + try self.stack.append(Value{ .i32 = decodeLittleEndian(i32, self.memory[start..end]) }); + }, + 0x37 => { + const address = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += address.len; + const offset = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += offset.len; + const start = (address.val + offset.val); + const end = start + @sizeOf(u32); + encodeLittleEndian(i32, @constCast(&self.memory[start..end]), self.stack.pop().?.i32); + }, + 0x38 => { + const address = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += address.len; + const offset = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += offset.len; + const start = (address.val + offset.val); + const end = start + @sizeOf(u64); + encodeLittleEndian(i64, @constCast(&self.memory[start..end]), self.stack.pop().?.i64); + }, + 0x41 => { + const integer = leb128Decode(i32, frame.code[frame.program_counter..]); + + frame.program_counter += integer.len; + try self.stack.append(Value{ .i32 = integer.val }); + }, + 0x42 => { + const integer = leb128Decode(i64, frame.code[frame.program_counter..]); + + frame.program_counter += integer.len; + try self.stack.append(Value{ .i64 = integer.val }); + }, + 0x45 => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i32 == 0))) }); + }, + 0x46 => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i32 == self.stack.pop().?.i32))) }); + }, + 0x47 => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i32 != self.stack.pop().?.i32))) }); + }, + 0x48 => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i32 < self.stack.pop().?.i32))) }); + }, + 0x49 => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(@as(u32, @bitCast(self.stack.pop().?.i32)) < @as(u32, @bitCast(self.stack.pop().?.i32))))) }); + }, + 0x4a => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i32 > self.stack.pop().?.i32))) }); + }, + 0x4b => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(@as(u32, @bitCast(self.stack.pop().?.i32)) > @as(u32, @bitCast(self.stack.pop().?.i32))))) }); + }, + 0x4c => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i32 <= self.stack.pop().?.i32))) }); + }, + 0x4d => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(@as(u32, @bitCast(self.stack.pop().?.i32)) <= @as(u32, @bitCast(self.stack.pop().?.i32))))) }); + }, + 0x4e => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i32 >= self.stack.pop().?.i32))) }); + }, + 0x4f => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(@as(u32, @bitCast(self.stack.pop().?.i32)) >= @as(u32, @bitCast(self.stack.pop().?.i32))))) }); + }, + + 0x50 => { + try self.stack.append(Value{ .i64 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i64 == 0))) }); + }, + 0x51 => { + try self.stack.append(Value{ .i64 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i64 == self.stack.pop().?.i64))) }); + }, + 0x52 => { + try self.stack.append(Value{ .i64 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i64 != self.stack.pop().?.i64))) }); + }, + 0x53 => { + try self.stack.append(Value{ .i64 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i64 < self.stack.pop().?.i64))) }); + }, + 0x54 => { + try self.stack.append(Value{ .i64 = @intCast(@as(u1, @bitCast(@as(u64, @bitCast(self.stack.pop().?.i64)) < @as(u64, @bitCast(self.stack.pop().?.i64))))) }); + }, + 0x55 => { + try self.stack.append(Value{ .i64 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i64 > self.stack.pop().?.i64))) }); + }, + 0x56 => { + try self.stack.append(Value{ .i64 = @intCast(@as(u1, @bitCast(@as(u64, @bitCast(self.stack.pop().?.i64)) > @as(u64, @bitCast(self.stack.pop().?.i64))))) }); + }, + 0x57 => { + try self.stack.append(Value{ .i64 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i64 <= self.stack.pop().?.i64))) }); + }, + 0x58 => { + try self.stack.append(Value{ .i64 = @intCast(@as(u1, @bitCast(@as(u64, @bitCast(self.stack.pop().?.i64)) <= @as(u64, @bitCast(self.stack.pop().?.i64))))) }); + }, + 0x59 => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(self.stack.pop().?.i64 >= self.stack.pop().?.i64))) }); + }, + 0x5a => { + try self.stack.append(Value{ .i32 = @intCast(@as(u1, @bitCast(@as(u64, @bitCast(self.stack.pop().?.i64)) >= @as(u64, @bitCast(self.stack.pop().?.i64))))) }); + }, + + 0x67 => { + var i = @as(i32, 0); + const number = self.stack.pop().?.i32; + for (0..@sizeOf(i32)) |b| { + if (number & (@as(i32, 0x1) << @intCast((@sizeOf(i32) - b - 1))) == 1) { + break; + } + i += 1; + } + try self.stack.append(Value{ .i32 = i }); + }, + 0x68 => { + var i = @as(i32, 0); + const number = self.stack.pop().?.i32; + for (0..@sizeOf(i32)) |b| { + if (number & (@as(i32, 0x1) << @intCast(b)) == 1) { + break; + } + i += 1; + } + try self.stack.append(Value{ .i32 = i }); + }, + 0x69 => { + var i = @as(i32, 0); + const number = self.stack.pop().?.i32; + for (0..@sizeOf(i32)) |b| { + if (number & (@as(i32, 0x1) << @intCast(b)) == 1) { + i += 1; + } + } + try self.stack.append(Value{ .i32 = i }); + }, + 0x6a => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = a.i32 + b.i32 }); + }, + 0x6b => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = a.i32 - b.i32 }); + }, + 0x6c => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = a.i32 * b.i32 }); + }, + 0x6d => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = @divTrunc(a.i32, b.i32) }); + }, + 0x6e => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = @as(i32, @bitCast(@as(u32, @bitCast(a.i32)) / @as(u32, @bitCast(b.i32)))) }); + }, + 0x6f => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = @rem(a.i32, b.i32) }); + }, + 0x70 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = @as(i32, @bitCast(@as(u32, @bitCast(a.i32)) % @as(u32, @bitCast(b.i32)))) }); + }, + 0x71 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = a.i32 & b.i32 }); + }, + 0x72 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = a.i32 | b.i32 }); + }, + 0x73 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = a.i32 ^ b.i32 }); + }, + 0x74 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = a.i32 << @intCast(b.i32) }); + }, + 0x75 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = a.i32 >> @intCast(b.i32) }); + }, + 0x76 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = @as(i32, @bitCast(@as(u32, @bitCast(a.i32)) >> @intCast(@as(u32, @bitCast(b.i32))))) }); + }, + 0x77 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = (a.i32 << @intCast(@as(u32, @bitCast(b.i32)))) | (a.i32 >> @intCast((@sizeOf(u32) * 8 - b.i32))) }); + }, + 0x78 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i32 = (a.i32 >> @intCast(@as(u32, @bitCast(b.i32)))) | (a.i32 << @intCast((@sizeOf(u32) * 8 - b.i32))) }); + }, + + 0x79 => { + var i = @as(i64, 0); + const number = self.stack.pop().?.i64; + for (0..@sizeOf(i64)) |b| { + if (number & (@as(i64, 0x1) << @intCast((@sizeOf(i64) - b - 1))) == 1) { + break; + } + i += 1; + } + try self.stack.append(Value{ .i64 = i }); + }, + 0x7a => { + var i = @as(i64, 0); + const number = self.stack.pop().?.i64; + for (0..@sizeOf(i64)) |b| { + if (number & (@as(i64, 0x1) << @intCast(b)) == 1) { + break; + } + i += 1; + } + try self.stack.append(Value{ .i64 = i }); + }, + 0x7b => { + var i = @as(i64, 0); + const number = self.stack.pop().?.i64; + for (0..@sizeOf(i64)) |b| { + if (number & (@as(i64, 0x1) << @intCast(b)) == 1) { + i += 1; + } + } + try self.stack.append(Value{ .i64 = i }); + }, + 0x7c => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = a.i64 + b.i64 }); + }, + 0x7d => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = a.i64 - b.i64 }); + }, + 0x7e => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = a.i64 * b.i64 }); + }, + 0x7f => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = @divTrunc(a.i64, b.i64) }); + }, + 0x80 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = @as(i64, @bitCast(@as(u64, @bitCast(a.i64)) / @as(u64, @bitCast(b.i64)))) }); + }, + 0x81 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = @rem(a.i64, b.i64) }); + }, + 0x82 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = @as(i64, @bitCast(@as(u64, @bitCast(a.i64)) % @as(u64, @bitCast(b.i64)))) }); + }, + 0x83 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = a.i64 & b.i64 }); + }, + 0x84 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = a.i64 | b.i64 }); + }, + 0x85 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = a.i64 ^ b.i64 }); + }, + 0x86 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = a.i64 << @intCast(b.i64) }); + }, + 0x87 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = a.i64 >> @intCast(b.i64) }); + }, + 0x88 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = @as(i64, @bitCast(@as(u64, @bitCast(a.i64)) >> @intCast(@as(u64, @bitCast(b.i64))))) }); + }, + 0x89 => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = (a.i64 << @intCast(@as(u64, @bitCast(b.i64)))) | (a.i64 >> @intCast((@sizeOf(u64) * 8 - b.i64))) }); + }, + 0x8a => { + const a = self.stack.pop().?; + const b = self.stack.pop().?; + try self.stack.append(.{ .i64 = (a.i64 >> @intCast(@as(u64, @bitCast(b.i64)))) | (a.i64 << @intCast((@sizeOf(u64) * 8 - b.i64))) }); + }, + + 0x10 => { + const integer = leb128Decode(u32, frame.code[frame.program_counter..]); + frame.program_counter += integer.len; + + self.call(allocator, integer.val, &[_]usize{}) catch {}; + }, + 0xb => { + if (for_loop) { + frame.program_counter += 1; + } else { + break :loop; + } + }, + else => std.debug.print("instruction {} not implemented\n", .{byte}), + } + } + } + + pub fn callExternal(self: *Runtime, allocator: Allocator, name: []const u8, parameters: []usize) !void { + if (self.module.exports.get(name)) |function| { + try self.call(allocator, function, parameters); + } + } + + pub fn call(self: *Runtime, allocator: Allocator, function: usize, parameters: []usize) AllocationError!void { + const f = self.module.funcs.items[function]; + switch (f) { + .internal => { + const function_type = self.module.types[self.module.functions[f.internal]]; + var frame = CallFrame{ + .code = self.module.code[f.internal].code, + .program_counter = 0x0, + .locals = try allocator.alloc(Value, self.module.code[f.internal].locals.len + function_type.parameters.len), + }; + + for (parameters, 0..) |p, i| { + switch (Parser.parseType(function_type.parameters[i])) { + .i32 => { + frame.locals[i] = .{ .i32 = @intCast(p) }; + }, + .i64 => { + frame.locals[i] = .{ .i64 = @intCast(p) }; + }, + else => unreachable, + } + } + + for (self.module.code[f.internal].locals, function_type.parameters.len..) |local, i| { + switch (Parser.parseType(local.types[0])) { + .i32 => { + frame.locals[i] = .{ .i32 = 0 }; + }, + .i64 => { + frame.locals[i] = .{ .i64 = 0 }; + }, + else => unreachable, + } + } + + try self.executeFrame(allocator, &frame); + + allocator.free(frame.locals); + }, + .external => { + const name = self.module.imports.items[f.external].name; + if (self.global_runtime.functions.get(name)) |external| { + external(&self.stack); + } + }, + } + } +}; diff --git a/src/mods/wasm.zig b/src/mods/wasm.zig new file mode 100644 index 0000000..579309b --- /dev/null +++ b/src/mods/wasm.zig @@ -0,0 +1,35 @@ +const vm = @import("vm.zig"); +const std = @import("std"); +const Allocator = std.mem.Allocator; + +pub const Type = enum(u8) { + i32 = 0x7f, + i64 = 0x7e, + f32 = 0x7d, + f64 = 0x7c, + v128 = 0x7b, +}; + +pub const GlobalRuntime = struct { + functions: std.StringHashMap(*const fn (stack: *std.ArrayList(vm.Value)) void), + + pub fn init(allocator: Allocator) GlobalRuntime { + return GlobalRuntime{ + .functions = std.StringHashMap(*const fn (stack: *std.ArrayList(vm.Value)) void).init(allocator), + }; + } + + pub fn deinit(self: *GlobalRuntime) void { + self.functions.deinit(); + } + + pub fn addFunction(self: *GlobalRuntime, name: []const u8, function: *const fn (stack: *std.ArrayList(vm.Value)) void) !void { + try self.functions.put(name, function); + } +}; + +pub fn debug(stack: *std.ArrayList(vm.Value)) void { + const a = stack.pop().?; + + std.debug.print("{}\n", .{a.i32}); +} -- cgit v1.2.3