├── .gitignore ├── LICENSE ├── README.md ├── build.zig └── src ├── io └── c_reader.zig ├── main.c └── main.zig /.gitignore: -------------------------------------------------------------------------------- 1 | /zig-cache/ 2 | /zig-out/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (Expat) 2 | 3 | Copyright (c) Andrew Kelley and Jacob Young 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minimal WASI Interpreter 2 | 3 | This codebase contains both a Zig implementation and a C implementation of a 4 | WASI interpreter that is capable of interpreting a WASI build of the Zig 5 | compiler, built with `-Dwasi-bootstrap`, and then optimized with 6 | `wasm-opt -Oz --enable-bulk-memory`. 7 | 8 | ## Status 9 | 10 | It works! 11 | 12 | This repository is now used for experimentation while the main development 13 | takes place upstream, currently in the wasi-bootstrap branch, but soon to be 14 | merged into master. 15 | 16 | ## Inspiration 17 | 18 | * [fengb/wazm](https://github.com/fengb/wazm/) 19 | * [malcolmstill/zware](https://github.com/malcolmstill/zware) 20 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.build.Builder) void { 4 | // Standard target options allows the person running `zig build` to choose 5 | // what target to build for. Here we do not override the defaults, which 6 | // means any target is allowed, and the default is native. Other options 7 | // for restricting supported target set are available. 8 | const target = b.standardTargetOptions(.{}); 9 | 10 | // Standard release options allow the person running `zig build` to select 11 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. 12 | const mode = b.standardReleaseOptions(); 13 | 14 | const exe = b.addExecutable("zig-wasi", "src/main.zig"); 15 | exe.linkLibC(); 16 | exe.setTarget(target); 17 | exe.setBuildMode(mode); 18 | exe.install(); 19 | 20 | const c_exe = b.addExecutable("c-wasi", null); 21 | c_exe.addCSourceFiles(&.{"src/main.c"}, &.{ "-std=c99", "-Wall", "-Werror" }); 22 | c_exe.linkLibC(); 23 | c_exe.setTarget(target); 24 | c_exe.setBuildMode(mode); 25 | c_exe.install(); 26 | } 27 | -------------------------------------------------------------------------------- /src/io/c_reader.zig: -------------------------------------------------------------------------------- 1 | //! copied from lib/std/io/c_writer.zig, unaudited 2 | 3 | const std = @import("std"); 4 | 5 | pub const CReader = std.io.Reader(*std.c.FILE, std.fs.File.ReadError, cReaderRead); 6 | 7 | pub fn cReader(c_file: *std.c.FILE) CReader { 8 | return .{ .context = c_file }; 9 | } 10 | 11 | fn cReaderRead(c_file: *std.c.FILE, bytes: []u8) std.fs.File.ReadError!usize { 12 | const amt_read = std.c.fread(bytes.ptr, 1, bytes.len, c_file); 13 | if (amt_read >= 0) return amt_read; 14 | switch (@intToEnum(std.os.E, std.c._errno().*)) { 15 | .SUCCESS => unreachable, 16 | .INVAL => unreachable, 17 | .FAULT => unreachable, 18 | .AGAIN => unreachable, // this is a blocking API 19 | .BADF => unreachable, // always a race condition 20 | .DESTADDRREQ => unreachable, // connect was never called 21 | .DQUOT => return error.DiskQuota, 22 | .FBIG => return error.FileTooBig, 23 | .IO => return error.InputOutput, 24 | .NOSPC => return error.NoSpaceLeft, 25 | .PERM => return error.AccessDenied, 26 | .PIPE => return error.BrokenPipe, 27 | else => |err| return std.os.unexpectedErrno(err), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const cReader = @import("io/c_reader.zig").cReader; 3 | const assert = std.debug.assert; 4 | const fs = std.fs; 5 | const mem = std.mem; 6 | const wasm = std.wasm; 7 | const wasi = std.os.wasi; 8 | const os = std.os; 9 | const math = std.math; 10 | const leb = std.leb; 11 | const decode_log = std.log.scoped(.decode); 12 | const stats_log = std.log.scoped(.stats); 13 | const trace_log = std.log.scoped(.trace); 14 | const cpu_log = std.log.scoped(.cpu); 15 | const func_log = std.log.scoped(.func); 16 | 17 | const SEEK = enum(c_int) { SET, CUR, END }; 18 | pub extern "c" fn fseek(stream: *std.c.FILE, offset: c_long, whence: SEEK) c_int; 19 | 20 | pub fn log( 21 | comptime level: std.log.Level, 22 | comptime scope: @TypeOf(.EnumLiteral), 23 | comptime format: []const u8, 24 | args: anytype, 25 | ) void { 26 | if (scope == .decode) return; 27 | if (scope == .stats) return; 28 | if (scope == .cpu) return; 29 | if (scope == .trace) return; 30 | if (scope == .func) return; 31 | std.debug.print(format ++ "\n", args); 32 | _ = level; 33 | } 34 | 35 | const max_memory = 3 * 1024 * 1024 * 1024; // 3 GiB 36 | 37 | pub export fn main(argc: c_int, argv: [*c][*:0]u8) c_int { 38 | main2(argv[0..@intCast(usize, argc)]) catch |e| std.debug.print("{s}\n", .{@errorName(e)}); 39 | return 1; 40 | } 41 | 42 | fn main2(args: []const [*:0]const u8) !void { 43 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.raw_c_allocator); 44 | defer arena_instance.deinit(); 45 | const arena = arena_instance.allocator(); 46 | 47 | var vm: VirtualMachine = undefined; 48 | vm.memory = try os.mmap( 49 | null, 50 | max_memory, 51 | os.PROT.READ | os.PROT.WRITE, 52 | os.MAP.PRIVATE | os.MAP.ANONYMOUS, 53 | -1, 54 | 0, 55 | ); 56 | 57 | const zig_lib_dir_path = args[1]; 58 | const zig_cache_dir_path = mem.sliceTo(args[2], 0); 59 | vm.args = args[3..]; 60 | const wasm_file = vm.args[0]; 61 | 62 | const cwd = try fs.cwd().openDir(".", .{}); 63 | const cache_dir = try cwd.makeOpenPath(zig_cache_dir_path, .{}); 64 | const zig_lib_dir = try cwd.openDirZ(zig_lib_dir_path, .{}, false); 65 | 66 | addPreopen(0, "stdin", os.STDIN_FILENO); 67 | addPreopen(1, "stdout", os.STDOUT_FILENO); 68 | addPreopen(2, "stderr", os.STDERR_FILENO); 69 | addPreopen(3, ".", cwd.fd); 70 | addPreopen(4, "/cache", cache_dir.fd); 71 | addPreopen(5, "/lib", zig_lib_dir.fd); 72 | 73 | var start_fn_idx: u32 = undefined; 74 | { 75 | const module_file = std.c.fopen(wasm_file, "rb") orelse return error.FileNotFound; 76 | defer _ = std.c.fclose(module_file); 77 | const module_reader = cReader(module_file); 78 | 79 | var magic: [4]u8 = undefined; 80 | try module_reader.readNoEof(&magic); 81 | if (!mem.eql(u8, &magic, "\x00asm")) return error.NotWasm; 82 | 83 | const version = try module_reader.readIntLittle(u32); 84 | if (version != 1) return error.BadWasmVersion; 85 | 86 | while (@intToEnum(wasm.Section, try module_reader.readByte()) != .type) 87 | assert(fseek(module_file, @intCast(c_long, try leb.readULEB128(u32, module_reader)), .CUR) == 0); 88 | _ = try leb.readULEB128(u32, module_reader); 89 | 90 | var max_param_count: u64 = 0; 91 | vm.types = try arena.alloc(TypeInfo, try leb.readULEB128(u32, module_reader)); 92 | for (vm.types) |*@"type"| { 93 | assert(try leb.readILEB128(i33, module_reader) == -0x20); 94 | 95 | @"type".param_count = try leb.readULEB128(u32, module_reader); 96 | assert(@"type".param_count <= 32); 97 | @"type".param_types = TypeInfo.ParamTypes.initEmpty(); 98 | max_param_count = @max(@"type".param_count, max_param_count); 99 | var param_index: u32 = 0; 100 | while (param_index < @"type".param_count) : (param_index += 1) { 101 | const param_type = try leb.readILEB128(i33, module_reader); 102 | @"type".param_types.setValue(param_index, switch (param_type) { 103 | -1, -3 => false, 104 | -2, -4 => true, 105 | else => unreachable, 106 | }); 107 | } 108 | 109 | @"type".result_count = try leb.readULEB128(u32, module_reader); 110 | assert(@"type".result_count <= 1); 111 | @"type".result_types = TypeInfo.ResultTypes.initEmpty(); 112 | var result_index: u32 = 0; 113 | while (result_index < @"type".result_count) : (result_index += 1) { 114 | const result_type = try leb.readILEB128(i33, module_reader); 115 | @"type".result_types.setValue(result_index, switch (result_type) { 116 | -1, -3 => false, 117 | -2, -4 => true, 118 | else => unreachable, 119 | }); 120 | } 121 | } 122 | 123 | while (@intToEnum(wasm.Section, try module_reader.readByte()) != .import) 124 | assert(fseek(module_file, @intCast(c_long, try leb.readULEB128(u32, module_reader)), .CUR) == 0); 125 | _ = try leb.readULEB128(u32, module_reader); 126 | 127 | { 128 | vm.imports = try arena.alloc(Import, try leb.readULEB128(u32, module_reader)); 129 | 130 | comptime var max_str_len: usize = 0; 131 | inline for (.{ Import.Mod, Import.Name }) |Enum| { 132 | inline for (comptime std.meta.fieldNames(Enum)) |str| { 133 | max_str_len = @max(str.len, max_str_len); 134 | } 135 | } 136 | var str_buf: [max_str_len]u8 = undefined; 137 | 138 | for (vm.imports) |*import| { 139 | const mod = str_buf[0..try leb.readULEB128(u32, module_reader)]; 140 | try module_reader.readNoEof(mod); 141 | import.mod = std.meta.stringToEnum(Import.Mod, mod).?; 142 | 143 | const name = str_buf[0..try leb.readULEB128(u32, module_reader)]; 144 | try module_reader.readNoEof(name); 145 | import.name = std.meta.stringToEnum(Import.Name, name).?; 146 | 147 | const kind = @intToEnum(wasm.ExternalKind, try module_reader.readByte()); 148 | const idx = try leb.readULEB128(u32, module_reader); 149 | switch (kind) { 150 | .function => import.type_idx = idx, 151 | .table, .memory, .global => unreachable, 152 | } 153 | } 154 | } 155 | 156 | while (@intToEnum(wasm.Section, try module_reader.readByte()) != .function) 157 | assert(fseek(module_file, @intCast(c_long, try leb.readULEB128(u32, module_reader)), .CUR) == 0); 158 | _ = try leb.readULEB128(u32, module_reader); 159 | 160 | vm.functions = try arena.alloc(Function, try leb.readULEB128(u32, module_reader)); 161 | for (vm.functions) |*function, func_idx| { 162 | function.id = @intCast(u32, vm.imports.len + func_idx); 163 | function.type_idx = try leb.readULEB128(u32, module_reader); 164 | } 165 | 166 | while (@intToEnum(wasm.Section, try module_reader.readByte()) != .table) 167 | assert(fseek(module_file, @intCast(c_long, try leb.readULEB128(u32, module_reader)), .CUR) == 0); 168 | _ = try leb.readULEB128(u32, module_reader); 169 | 170 | { 171 | const table_count = try leb.readULEB128(u32, module_reader); 172 | if (table_count == 1) { 173 | assert(try leb.readILEB128(i33, module_reader) == -0x10); 174 | const limits_kind = try module_reader.readByte(); 175 | vm.table = try arena.alloc(u32, try leb.readULEB128(u32, module_reader)); 176 | switch (limits_kind) { 177 | 0x00 => {}, 178 | 0x01 => _ = try leb.readULEB128(u32, module_reader), 179 | else => unreachable, 180 | } 181 | } else assert(table_count == 0); 182 | } 183 | 184 | while (@intToEnum(wasm.Section, try module_reader.readByte()) != .memory) 185 | assert(fseek(module_file, @intCast(c_long, try leb.readULEB128(u32, module_reader)), .CUR) == 0); 186 | _ = try leb.readULEB128(u32, module_reader); 187 | 188 | { 189 | assert(try leb.readULEB128(u32, module_reader) == 1); 190 | const limits_kind = try module_reader.readByte(); 191 | vm.memory_len = try leb.readULEB128(u32, module_reader) * wasm.page_size; 192 | switch (limits_kind) { 193 | 0x00 => {}, 194 | 0x01 => _ = try leb.readULEB128(u32, module_reader), 195 | else => unreachable, 196 | } 197 | } 198 | 199 | while (@intToEnum(wasm.Section, try module_reader.readByte()) != .global) 200 | assert(fseek(module_file, @intCast(c_long, try leb.readULEB128(u32, module_reader)), .CUR) == 0); 201 | _ = try leb.readULEB128(u32, module_reader); 202 | 203 | vm.globals = try arena.alloc(u32, try leb.readULEB128(u32, module_reader)); 204 | for (vm.globals) |*global| { 205 | assert(try leb.readILEB128(i33, module_reader) == -1); 206 | _ = @intToEnum(Mutability, try module_reader.readByte()); 207 | assert(@intToEnum(wasm.Opcode, try module_reader.readByte()) == .i32_const); 208 | global.* = @bitCast(u32, try leb.readILEB128(i32, module_reader)); 209 | assert(@intToEnum(wasm.Opcode, try module_reader.readByte()) == .end); 210 | } 211 | 212 | while (@intToEnum(wasm.Section, try module_reader.readByte()) != .@"export") 213 | assert(fseek(module_file, @intCast(c_long, try leb.readULEB128(u32, module_reader)), .CUR) == 0); 214 | _ = try leb.readULEB128(u32, module_reader); 215 | 216 | { 217 | var found_start_fn = false; 218 | const start_name = "_start"; 219 | var str_buf: [start_name.len]u8 = undefined; 220 | 221 | var export_count = try leb.readULEB128(u32, module_reader); 222 | while (export_count > 0) : (export_count -= 1) { 223 | const name_len = try leb.readULEB128(u32, module_reader); 224 | var is_start_fn = false; 225 | if (name_len == start_name.len) { 226 | try module_reader.readNoEof(&str_buf); 227 | is_start_fn = mem.eql(u8, &str_buf, start_name); 228 | found_start_fn = found_start_fn or is_start_fn; 229 | } else assert(fseek(module_file, @intCast(c_long, name_len), .CUR) == 0); 230 | 231 | const kind = @intToEnum(wasm.ExternalKind, try module_reader.readByte()); 232 | const idx = try leb.readULEB128(u32, module_reader); 233 | switch (kind) { 234 | .function => if (is_start_fn) { 235 | start_fn_idx = idx; 236 | }, 237 | .table, .memory, .global => {}, 238 | } 239 | } 240 | assert(found_start_fn); 241 | } 242 | 243 | while (@intToEnum(wasm.Section, try module_reader.readByte()) != .element) 244 | assert(fseek(module_file, @intCast(c_long, try leb.readULEB128(u32, module_reader)), .CUR) == 0); 245 | _ = try leb.readULEB128(u32, module_reader); 246 | 247 | { 248 | var segment_count = try leb.readULEB128(u32, module_reader); 249 | while (segment_count > 0) : (segment_count -= 1) { 250 | const flags = @intCast(u3, try leb.readULEB128(u32, module_reader)); 251 | assert(flags & 0b001 == 0b000); 252 | if (flags & 0b010 == 0b010) assert(try leb.readULEB128(u32, module_reader) == 0); 253 | 254 | assert(@intToEnum(wasm.Opcode, try module_reader.readByte()) == .i32_const); 255 | var offset = @bitCast(u32, try leb.readILEB128(i32, module_reader)); 256 | assert(@intToEnum(wasm.Opcode, try module_reader.readByte()) == .end); 257 | 258 | const element_type = if (flags & 0b110 != 0b110) idx: { 259 | if (flags & 0b010 == 0b010) assert(try module_reader.readByte() == 0x00); 260 | break :idx -0x10; 261 | } else try leb.readILEB128(i33, module_reader); 262 | assert(element_type == -0x10); 263 | 264 | var element_count = try leb.readULEB128(u32, module_reader); 265 | while (element_count > 0) : ({ 266 | offset += 1; 267 | element_count -= 1; 268 | }) { 269 | if (flags & 0b010 == 0b010) 270 | assert(try module_reader.readByte() == 0xD2); 271 | vm.table[offset] = try leb.readULEB128(u32, module_reader); 272 | if (flags & 0b010 == 0b010) 273 | assert(@intToEnum(wasm.Opcode, try module_reader.readByte()) == .end); 274 | } 275 | } 276 | } 277 | 278 | while (@intToEnum(wasm.Section, try module_reader.readByte()) != .code) 279 | assert(fseek(module_file, @intCast(c_long, try leb.readULEB128(u32, module_reader)), .CUR) == 0); 280 | _ = try leb.readULEB128(u32, module_reader); 281 | 282 | var max_frame_size: u64 = 0; 283 | { 284 | vm.opcodes = try arena.alloc(u8, 5000000); 285 | vm.operands = try arena.alloc(u32, 5000000); 286 | 287 | assert(try leb.readULEB128(u32, module_reader) == vm.functions.len); 288 | var pc = ProgramCounter{ .opcode = 0, .operand = 0 }; 289 | var stack: StackInfo = undefined; 290 | for (vm.functions) |*func| { 291 | _ = try leb.readULEB128(u32, module_reader); 292 | 293 | stack = .{}; 294 | const type_info = vm.types[func.type_idx]; 295 | var param_i: u32 = 0; 296 | while (param_i < type_info.param_count) : (param_i += 1) stack.push(@intToEnum( 297 | StackInfo.EntryType, 298 | @boolToInt(type_info.param_types.isSet(param_i)), 299 | )); 300 | const params_size = stack.top_offset; 301 | 302 | var local_sets_count = try leb.readULEB128(u32, module_reader); 303 | while (local_sets_count > 0) : (local_sets_count -= 1) { 304 | var local_set_count = try leb.readULEB128(u32, module_reader); 305 | const local_type = switch (try leb.readILEB128(i33, module_reader)) { 306 | -1, -3 => StackInfo.EntryType.i32, 307 | -2, -4 => StackInfo.EntryType.i64, 308 | else => unreachable, 309 | }; 310 | while (local_set_count > 0) : (local_set_count -= 1) 311 | stack.push(local_type); 312 | } 313 | func.locals_size = stack.top_offset - params_size; 314 | max_frame_size = @max(params_size + func.locals_size, max_frame_size); 315 | 316 | func.entry_pc = pc; 317 | decode_log.debug("decoding func id {d} with pc {d}:{d}", .{ func.id, pc.opcode, pc.operand }); 318 | try vm.decodeCode(module_reader, type_info, &pc, &stack); 319 | } 320 | 321 | var opcode_counts = [1]u64{0} ** 0x100; 322 | var prefix: ?Opcode = null; 323 | for (vm.opcodes[0..pc.opcode]) |opcode| { 324 | if (prefix) |pre| { 325 | switch (pre) { 326 | else => unreachable, 327 | } 328 | prefix = null; 329 | } else switch (@intToEnum(Opcode, opcode)) { 330 | else => opcode_counts[opcode] += 1, 331 | } 332 | } 333 | 334 | stats_log.debug("{} opcodes", .{pc.opcode}); 335 | stats_log.debug("{} operands", .{pc.operand}); 336 | for (opcode_counts) |opcode_count, opcode| { 337 | if (opcode > @enumToInt(Opcode.last)) continue; 338 | stats_log.debug("{} {s}", .{ opcode_count, @tagName(@intToEnum(Opcode, opcode)) }); 339 | } 340 | stats_log.debug("{} zero offsets", .{offset_counts[0]}); 341 | stats_log.debug("{} non-zero offsets", .{offset_counts[1]}); 342 | stats_log.debug("{} max offset", .{max_offset}); 343 | stats_log.debug("{} max label depth", .{max_label_depth}); 344 | stats_log.debug("{} max frame size", .{max_frame_size}); 345 | stats_log.debug("{} max param count", .{max_param_count}); 346 | } 347 | 348 | while (@intToEnum(wasm.Section, try module_reader.readByte()) != .data) 349 | assert(fseek(module_file, @intCast(c_long, try leb.readULEB128(u32, module_reader)), .CUR) == 0); 350 | _ = try leb.readULEB128(u32, module_reader); 351 | 352 | { 353 | var segment_count = try leb.readULEB128(u32, module_reader); 354 | while (segment_count > 0) : (segment_count -= 1) { 355 | const flags = @intCast(u2, try leb.readULEB128(u32, module_reader)); 356 | assert(flags & 0b001 == 0b000); 357 | if (flags & 0b010 == 0b010) assert(try leb.readULEB128(u32, module_reader) == 0); 358 | 359 | assert(@intToEnum(wasm.Opcode, try module_reader.readByte()) == .i32_const); 360 | const offset = @bitCast(u32, try leb.readILEB128(i32, module_reader)); 361 | assert(@intToEnum(wasm.Opcode, try module_reader.readByte()) == .end); 362 | 363 | const length = try leb.readULEB128(u32, module_reader); 364 | try module_reader.readNoEof(vm.memory[offset..][0..length]); 365 | } 366 | } 367 | } 368 | 369 | vm.stack = try arena.alloc(u32, 10000000); 370 | vm.stack_top = 0; 371 | vm.call(&vm.functions[start_fn_idx - @intCast(u32, vm.imports.len)]); 372 | vm.run(); 373 | } 374 | 375 | const Opcode = enum { 376 | @"unreachable", 377 | br_void, 378 | br_32, 379 | br_64, 380 | br_nez_void, 381 | br_nez_32, 382 | br_nez_64, 383 | br_eqz_void, 384 | br_eqz_32, 385 | br_eqz_64, 386 | br_table_void, 387 | br_table_32, 388 | br_table_64, 389 | return_void, 390 | return_32, 391 | return_64, 392 | call_import, 393 | call_func, 394 | call_indirect, 395 | drop_32, 396 | drop_64, 397 | select_32, 398 | select_64, 399 | local_get_32, 400 | local_get_64, 401 | local_set_32, 402 | local_set_64, 403 | local_tee_32, 404 | local_tee_64, 405 | global_get_0_32, 406 | global_get_32, 407 | global_set_0_32, 408 | global_set_32, 409 | load_0_8, 410 | load_8, 411 | load_0_16, 412 | load_16, 413 | load_0_32, 414 | load_32, 415 | load_0_64, 416 | load_64, 417 | store_0_8, 418 | store_8, 419 | store_0_16, 420 | store_16, 421 | store_0_32, 422 | store_32, 423 | store_0_64, 424 | store_64, 425 | mem_size, 426 | mem_grow, 427 | const_0_32, 428 | const_0_64, 429 | const_1_32, 430 | const_1_64, 431 | const_32, 432 | const_64, 433 | const_umax_32, 434 | const_umax_64, 435 | eqz_32, 436 | eq_32, 437 | ne_32, 438 | slt_32, 439 | ult_32, 440 | sgt_32, 441 | ugt_32, 442 | sle_32, 443 | ule_32, 444 | sge_32, 445 | uge_32, 446 | eqz_64, 447 | eq_64, 448 | ne_64, 449 | slt_64, 450 | ult_64, 451 | sgt_64, 452 | ugt_64, 453 | sle_64, 454 | ule_64, 455 | sge_64, 456 | uge_64, 457 | feq_32, 458 | fne_32, 459 | flt_32, 460 | fgt_32, 461 | fle_32, 462 | fge_32, 463 | feq_64, 464 | fne_64, 465 | flt_64, 466 | fgt_64, 467 | fle_64, 468 | fge_64, 469 | clz_32, 470 | ctz_32, 471 | popcnt_32, 472 | add_32, 473 | sub_32, 474 | mul_32, 475 | sdiv_32, 476 | udiv_32, 477 | srem_32, 478 | urem_32, 479 | and_32, 480 | or_32, 481 | xor_32, 482 | shl_32, 483 | ashr_32, 484 | lshr_32, 485 | rol_32, 486 | ror_32, 487 | clz_64, 488 | ctz_64, 489 | popcnt_64, 490 | add_64, 491 | sub_64, 492 | mul_64, 493 | sdiv_64, 494 | udiv_64, 495 | srem_64, 496 | urem_64, 497 | and_64, 498 | or_64, 499 | xor_64, 500 | shl_64, 501 | ashr_64, 502 | lshr_64, 503 | rol_64, 504 | ror_64, 505 | fabs_32, 506 | fneg_32, 507 | ceil_32, 508 | floor_32, 509 | trunc_32, 510 | nearest_32, 511 | sqrt_32, 512 | fadd_32, 513 | fsub_32, 514 | fmul_32, 515 | fdiv_32, 516 | fmin_32, 517 | fmax_32, 518 | copysign_32, 519 | fabs_64, 520 | fneg_64, 521 | ceil_64, 522 | floor_64, 523 | trunc_64, 524 | nearest_64, 525 | sqrt_64, 526 | fadd_64, 527 | fsub_64, 528 | fmul_64, 529 | fdiv_64, 530 | fmin_64, 531 | fmax_64, 532 | copysign_64, 533 | ftos_32_32, 534 | ftou_32_32, 535 | ftos_32_64, 536 | ftou_32_64, 537 | sext_64_32, 538 | ftos_64_32, 539 | ftou_64_32, 540 | ftos_64_64, 541 | ftou_64_64, 542 | stof_32_32, 543 | utof_32_32, 544 | stof_32_64, 545 | utof_32_64, 546 | ftof_32_64, 547 | stof_64_32, 548 | utof_64_32, 549 | stof_64_64, 550 | utof_64_64, 551 | ftof_64_32, 552 | sext8_32, 553 | sext16_32, 554 | sext8_64, 555 | sext16_64, 556 | sext32_64, 557 | memcpy, 558 | memset, 559 | 560 | const wrap_32_64 = Opcode.drop_32; 561 | const zext_64_32 = Opcode.const_0_32; 562 | const last = Opcode.memset; 563 | }; 564 | 565 | var offset_counts = [2]u64{ 0, 0 }; 566 | var max_offset: u64 = 0; 567 | 568 | var max_label_depth: u64 = 0; 569 | 570 | const ProgramCounter = struct { opcode: u32, operand: u32 }; 571 | 572 | const Mutability = enum { @"const", @"var" }; 573 | 574 | const TypeInfo = struct { 575 | const ParamTypes = std.StaticBitSet(1 << 5); 576 | const ResultTypes = std.StaticBitSet(1); 577 | 578 | param_count: u32, 579 | param_types: ParamTypes, 580 | result_count: u32, 581 | result_types: ResultTypes, 582 | }; 583 | 584 | const Function = struct { 585 | id: u32, 586 | entry_pc: ProgramCounter, 587 | type_idx: u32, 588 | locals_size: u32, 589 | }; 590 | 591 | const Import = struct { 592 | const Mod = enum { 593 | wasi_snapshot_preview1, 594 | }; 595 | const Name = enum { 596 | args_get, 597 | args_sizes_get, 598 | clock_time_get, 599 | debug, 600 | debug_slice, 601 | environ_get, 602 | environ_sizes_get, 603 | fd_close, 604 | fd_fdstat_get, 605 | fd_filestat_get, 606 | fd_filestat_set_size, 607 | fd_filestat_set_times, 608 | fd_pread, 609 | fd_prestat_dir_name, 610 | fd_prestat_get, 611 | fd_pwrite, 612 | fd_read, 613 | fd_readdir, 614 | fd_write, 615 | path_create_directory, 616 | path_filestat_get, 617 | path_open, 618 | path_remove_directory, 619 | path_rename, 620 | path_unlink_file, 621 | proc_exit, 622 | random_get, 623 | }; 624 | 625 | mod: Mod, 626 | name: Name, 627 | type_idx: u32, 628 | }; 629 | 630 | const Label = struct { 631 | opcode: wasm.Opcode, 632 | stack_index: u32, 633 | stack_offset: u32, 634 | type_info: TypeInfo, 635 | // this is a maxInt terminated linked list that is stored in the operands array 636 | ref_list: u32 = math.maxInt(u32), 637 | extra: union { 638 | loop_pc: ProgramCounter, 639 | else_ref: u32, 640 | } = undefined, 641 | 642 | fn operandCount(self: Label) u32 { 643 | return if (self.opcode == .loop) self.type_info.param_count else self.type_info.result_count; 644 | } 645 | 646 | fn operandType(self: Label, index: u32) StackInfo.EntryType { 647 | return StackInfo.EntryType.fromBool(if (self.opcode == .loop) 648 | self.type_info.param_types.isSet(index) 649 | else 650 | self.type_info.result_types.isSet(index)); 651 | } 652 | }; 653 | 654 | const StackInfo = struct { 655 | // f32 is stored as i32 and f64 is stored as i64 656 | const EntryType = enum { 657 | i32, 658 | i64, 659 | 660 | fn size(self: EntryType) u32 { 661 | return switch (self) { 662 | .i32 => 1, 663 | .i64 => 2, 664 | }; 665 | } 666 | 667 | fn toBool(self: EntryType) bool { 668 | return self != .i32; 669 | } 670 | 671 | fn fromBool(self: bool) EntryType { 672 | return @intToEnum(EntryType, @boolToInt(self)); 673 | } 674 | }; 675 | const max_stack_depth = 1 << 12; 676 | 677 | top_index: u32 = 0, 678 | top_offset: u32 = 0, 679 | types: std.StaticBitSet(max_stack_depth) = undefined, 680 | offsets: [max_stack_depth]u32 = undefined, 681 | 682 | fn push(self: *StackInfo, entry_type: EntryType) void { 683 | self.types.setValue(self.top_index, entry_type.toBool()); 684 | self.offsets[self.top_index] = self.top_offset; 685 | self.top_index += 1; 686 | self.top_offset += entry_type.size(); 687 | } 688 | 689 | fn pop(self: *StackInfo, entry_type: EntryType) void { 690 | assert(self.top() == entry_type); 691 | self.top_index -= 1; 692 | self.top_offset -= entry_type.size(); 693 | assert(self.top_offset == self.offsets[self.top_index]); 694 | } 695 | 696 | fn top(self: StackInfo) EntryType { 697 | return EntryType.fromBool(self.types.isSet(self.top_index - 1)); 698 | } 699 | 700 | fn local(self: StackInfo, local_idx: u32) EntryType { 701 | return EntryType.fromBool(self.types.isSet(local_idx)); 702 | } 703 | }; 704 | 705 | const VirtualMachine = struct { 706 | stack: []u32, 707 | /// Points to one after the last stack item. 708 | stack_top: u32, 709 | pc: ProgramCounter, 710 | memory_len: u32, 711 | opcodes: []u8, 712 | operands: []u32, 713 | functions: []Function, 714 | types: []TypeInfo, 715 | globals: []u32, 716 | memory: []u8, 717 | imports: []Import, 718 | args: []const [*:0]const u8, 719 | table: []u32, 720 | 721 | fn decodeCode( 722 | vm: *VirtualMachine, 723 | reader: anytype, 724 | func_type_info: TypeInfo, 725 | pc: *ProgramCounter, 726 | stack: *StackInfo, 727 | ) !void { 728 | const opcodes = vm.opcodes; 729 | const operands = vm.operands; 730 | 731 | // push return address 732 | const frame_size = stack.top_offset; 733 | stack.push(.i32); 734 | stack.push(.i32); 735 | 736 | var unreachable_depth: u32 = 0; 737 | var label_i: u32 = 0; 738 | var labels: [1 << 9]Label = undefined; 739 | labels[label_i] = .{ 740 | .opcode = .block, 741 | .stack_index = stack.top_index, 742 | .stack_offset = stack.top_offset, 743 | .type_info = func_type_info, 744 | }; 745 | 746 | var state: enum { default, bool_not } = .default; 747 | 748 | while (true) { 749 | assert(stack.top_index >= labels[0].stack_index); 750 | assert(stack.top_offset >= labels[0].stack_offset); 751 | const opcode = try reader.readByte(); 752 | var prefixed_opcode: u8 = if (@intToEnum(wasm.Opcode, opcode) == .prefixed) 753 | @intCast(u8, try leb.readULEB128(u32, reader)) 754 | else 755 | undefined; 756 | 757 | //decode_log.debug("stack.top_index = {}, stack.top_offset = {}, opcode = {s}, prefixed_opcode = {s}", .{ 758 | // stack.top_index, 759 | // stack.top_offset, 760 | // @tagName(@intToEnum(wasm.Opcode, opcode)), 761 | // if (@intToEnum(wasm.Opcode, opcode) == .prefixed) 762 | // @tagName(@intToEnum(wasm.PrefixedOpcode, prefixed_opcode)) 763 | // else 764 | // "(none)", 765 | //}); 766 | 767 | decode_log.debug("decodeCode opcode=0x{x} pc={d}:{d}", .{ opcode, pc.opcode, pc.operand }); 768 | const old_pc = pc.*; 769 | 770 | if (unreachable_depth == 0) switch (@intToEnum(wasm.Opcode, opcode)) { 771 | .@"unreachable", 772 | .nop, 773 | .block, 774 | .loop, 775 | .@"else", 776 | .end, 777 | .br, 778 | .@"return", 779 | .call, 780 | .local_get, 781 | .local_set, 782 | .local_tee, 783 | .global_get, 784 | .global_set, 785 | .drop, 786 | .select, 787 | => {}, // handled manually below 788 | 789 | .@"if", 790 | .br_if, 791 | .br_table, 792 | .call_indirect, 793 | => stack.pop(.i32), 794 | 795 | .memory_size, 796 | .i32_const, 797 | .f32_const, 798 | => stack.push(.i32), 799 | 800 | .i64_const, 801 | .f64_const, 802 | => stack.push(.i64), 803 | 804 | .i32_load, 805 | .f32_load, 806 | .i32_load8_s, 807 | .i32_load8_u, 808 | .i32_load16_s, 809 | .i32_load16_u, 810 | => { 811 | stack.pop(.i32); 812 | stack.push(.i32); 813 | }, 814 | 815 | .i64_load, 816 | .f64_load, 817 | .i64_load8_s, 818 | .i64_load8_u, 819 | .i64_load16_s, 820 | .i64_load16_u, 821 | .i64_load32_s, 822 | .i64_load32_u, 823 | => { 824 | stack.pop(.i32); 825 | stack.push(.i64); 826 | }, 827 | 828 | .memory_grow, 829 | .i32_eqz, 830 | .i32_clz, 831 | .i32_ctz, 832 | .i32_popcnt, 833 | .f32_abs, 834 | .f32_neg, 835 | .f32_ceil, 836 | .f32_floor, 837 | .f32_trunc, 838 | .f32_nearest, 839 | .f32_sqrt, 840 | .i32_trunc_f32_s, 841 | .i32_trunc_f32_u, 842 | .f32_convert_i32_s, 843 | .f32_convert_i32_u, 844 | .i32_reinterpret_f32, 845 | .f32_reinterpret_i32, 846 | .i32_extend8_s, 847 | .i32_extend16_s, 848 | => { 849 | stack.pop(.i32); 850 | stack.push(.i32); 851 | }, 852 | 853 | .i64_eqz, 854 | .i32_wrap_i64, 855 | .i32_trunc_f64_s, 856 | .i32_trunc_f64_u, 857 | .f32_convert_i64_s, 858 | .f32_convert_i64_u, 859 | .f32_demote_f64, 860 | => { 861 | stack.pop(.i64); 862 | stack.push(.i32); 863 | }, 864 | 865 | .i64_clz, 866 | .i64_ctz, 867 | .i64_popcnt, 868 | .f64_abs, 869 | .f64_neg, 870 | .f64_ceil, 871 | .f64_floor, 872 | .f64_trunc, 873 | .f64_nearest, 874 | .f64_sqrt, 875 | .i64_trunc_f64_s, 876 | .i64_trunc_f64_u, 877 | .f64_convert_i64_s, 878 | .f64_convert_i64_u, 879 | .i64_reinterpret_f64, 880 | .f64_reinterpret_i64, 881 | .i64_extend8_s, 882 | .i64_extend16_s, 883 | .i64_extend32_s, 884 | => { 885 | stack.pop(.i64); 886 | stack.push(.i64); 887 | }, 888 | 889 | .i64_extend_i32_s, 890 | .i64_extend_i32_u, 891 | .i64_trunc_f32_s, 892 | .i64_trunc_f32_u, 893 | .f64_convert_i32_s, 894 | .f64_convert_i32_u, 895 | .f64_promote_f32, 896 | => { 897 | stack.pop(.i32); 898 | stack.push(.i64); 899 | }, 900 | 901 | .i32_store, 902 | .f32_store, 903 | .i32_store8, 904 | .i32_store16, 905 | => { 906 | stack.pop(.i32); 907 | stack.pop(.i32); 908 | }, 909 | 910 | .i64_store, 911 | .f64_store, 912 | .i64_store8, 913 | .i64_store16, 914 | .i64_store32, 915 | => { 916 | stack.pop(.i64); 917 | stack.pop(.i32); 918 | }, 919 | 920 | .i32_eq, 921 | .i32_ne, 922 | .i32_lt_s, 923 | .i32_lt_u, 924 | .i32_gt_s, 925 | .i32_gt_u, 926 | .i32_le_s, 927 | .i32_le_u, 928 | .i32_ge_s, 929 | .i32_ge_u, 930 | .f32_eq, 931 | .f32_ne, 932 | .f32_lt, 933 | .f32_gt, 934 | .f32_le, 935 | .f32_ge, 936 | => { 937 | stack.pop(.i32); 938 | stack.pop(.i32); 939 | stack.push(.i32); 940 | }, 941 | 942 | .i64_eq, 943 | .i64_ne, 944 | .i64_lt_s, 945 | .i64_lt_u, 946 | .i64_gt_s, 947 | .i64_gt_u, 948 | .i64_le_s, 949 | .i64_le_u, 950 | .i64_ge_s, 951 | .i64_ge_u, 952 | .f64_eq, 953 | .f64_ne, 954 | .f64_lt, 955 | .f64_gt, 956 | .f64_le, 957 | .f64_ge, 958 | => { 959 | stack.pop(.i64); 960 | stack.pop(.i64); 961 | stack.push(.i32); 962 | }, 963 | 964 | .i32_add, 965 | .i32_sub, 966 | .i32_mul, 967 | .i32_div_s, 968 | .i32_div_u, 969 | .i32_rem_s, 970 | .i32_rem_u, 971 | .i32_and, 972 | .i32_or, 973 | .i32_xor, 974 | .i32_shl, 975 | .i32_shr_s, 976 | .i32_shr_u, 977 | .i32_rotl, 978 | .i32_rotr, 979 | .f32_add, 980 | .f32_sub, 981 | .f32_mul, 982 | .f32_div, 983 | .f32_min, 984 | .f32_max, 985 | .f32_copysign, 986 | => { 987 | stack.pop(.i32); 988 | stack.pop(.i32); 989 | stack.push(.i32); 990 | }, 991 | 992 | .i64_add, 993 | .i64_sub, 994 | .i64_mul, 995 | .i64_div_s, 996 | .i64_div_u, 997 | .i64_rem_s, 998 | .i64_rem_u, 999 | .i64_and, 1000 | .i64_or, 1001 | .i64_xor, 1002 | .i64_shl, 1003 | .i64_shr_s, 1004 | .i64_shr_u, 1005 | .i64_rotl, 1006 | .i64_rotr, 1007 | .f64_add, 1008 | .f64_sub, 1009 | .f64_mul, 1010 | .f64_div, 1011 | .f64_min, 1012 | .f64_max, 1013 | .f64_copysign, 1014 | => { 1015 | stack.pop(.i64); 1016 | stack.pop(.i64); 1017 | stack.push(.i64); 1018 | }, 1019 | 1020 | .prefixed => switch (@intToEnum(wasm.PrefixedOpcode, prefixed_opcode)) { 1021 | .i32_trunc_sat_f32_s, 1022 | .i32_trunc_sat_f32_u, 1023 | => { 1024 | stack.pop(.i32); 1025 | stack.push(.i32); 1026 | }, 1027 | 1028 | .i32_trunc_sat_f64_s, 1029 | .i32_trunc_sat_f64_u, 1030 | => { 1031 | stack.pop(.i64); 1032 | stack.push(.i32); 1033 | }, 1034 | 1035 | .i64_trunc_sat_f32_s, 1036 | .i64_trunc_sat_f32_u, 1037 | => { 1038 | stack.pop(.i32); 1039 | stack.push(.i64); 1040 | }, 1041 | 1042 | .i64_trunc_sat_f64_s, 1043 | .i64_trunc_sat_f64_u, 1044 | => { 1045 | stack.pop(.i64); 1046 | stack.push(.i64); 1047 | }, 1048 | 1049 | .memory_init, 1050 | .memory_copy, 1051 | .memory_fill, 1052 | .table_init, 1053 | .table_copy, 1054 | => { 1055 | stack.pop(.i32); 1056 | stack.pop(.i32); 1057 | stack.pop(.i32); 1058 | }, 1059 | 1060 | .table_fill => { 1061 | stack.pop(.i32); 1062 | stack.pop(unreachable); 1063 | stack.pop(.i32); 1064 | }, 1065 | 1066 | .data_drop, 1067 | .elem_drop, 1068 | => {}, 1069 | 1070 | .table_grow => { 1071 | stack.pop(.i32); 1072 | stack.pop(unreachable); 1073 | stack.push(.i32); 1074 | }, 1075 | 1076 | .table_size => stack.push(.i32), 1077 | 1078 | _ => unreachable, 1079 | }, 1080 | 1081 | _ => unreachable, 1082 | }; 1083 | switch (@intToEnum(wasm.Opcode, opcode)) { 1084 | .@"unreachable" => if (unreachable_depth == 0) { 1085 | opcodes[pc.opcode] = @enumToInt(Opcode.@"unreachable"); 1086 | pc.opcode += 1; 1087 | unreachable_depth += 1; 1088 | }, 1089 | .nop, 1090 | .i32_reinterpret_f32, 1091 | .i64_reinterpret_f64, 1092 | .f32_reinterpret_i32, 1093 | .f64_reinterpret_i64, 1094 | => {}, 1095 | .block, .loop, .@"if" => |opc| { 1096 | const block_type = try leb.readILEB128(i33, reader); 1097 | if (unreachable_depth == 0) { 1098 | label_i += 1; 1099 | max_label_depth = @max(label_i, max_label_depth); 1100 | const label = &labels[label_i]; 1101 | const type_info = if (block_type < 0) TypeInfo{ 1102 | .param_count = 0, 1103 | .param_types = TypeInfo.ParamTypes.initEmpty(), 1104 | .result_count = @boolToInt(block_type != -0x40), 1105 | .result_types = switch (block_type) { 1106 | -0x40, -1, -3 => TypeInfo.ResultTypes.initEmpty(), 1107 | -2, -4 => TypeInfo.ResultTypes.initFull(), 1108 | else => unreachable, 1109 | }, 1110 | } else vm.types[@intCast(u32, block_type)]; 1111 | 1112 | var param_i = type_info.param_count; 1113 | while (param_i > 0) { 1114 | param_i -= 1; 1115 | stack.pop(StackInfo.EntryType.fromBool( 1116 | type_info.param_types.isSet(param_i), 1117 | )); 1118 | } 1119 | label.* = .{ 1120 | .opcode = opc, 1121 | .stack_index = stack.top_index, 1122 | .stack_offset = stack.top_offset, 1123 | .type_info = type_info, 1124 | }; 1125 | while (param_i < type_info.param_count) : (param_i += 1) 1126 | stack.push(StackInfo.EntryType.fromBool( 1127 | type_info.param_types.isSet(param_i), 1128 | )); 1129 | 1130 | switch (opc) { 1131 | .block => {}, 1132 | .loop => { 1133 | label.extra = .{ .loop_pc = pc.* }; 1134 | }, 1135 | .@"if" => { 1136 | const bool_not = state == .bool_not; 1137 | if (bool_not) pc.opcode -= 1; 1138 | opcodes[pc.opcode] = @enumToInt(if (bool_not) 1139 | Opcode.br_nez_void 1140 | else 1141 | Opcode.br_eqz_void); 1142 | pc.opcode += 1; 1143 | operands[pc.operand] = 0; 1144 | label.extra = .{ .else_ref = pc.operand + 1 }; 1145 | pc.operand += 3; 1146 | }, 1147 | else => unreachable, 1148 | } 1149 | } else unreachable_depth += 1; 1150 | }, 1151 | .@"else" => if (unreachable_depth <= 1) { 1152 | const label = &labels[label_i]; 1153 | assert(label.opcode == .@"if"); 1154 | label.opcode = .@"else"; 1155 | 1156 | if (unreachable_depth == 0) { 1157 | const operand_count = label.operandCount(); 1158 | var operand_i = operand_count; 1159 | while (operand_i > 0) { 1160 | operand_i -= 1; 1161 | stack.pop(label.operandType(operand_i)); 1162 | } 1163 | assert(stack.top_index == label.stack_index); 1164 | assert(stack.top_offset == label.stack_offset); 1165 | 1166 | opcodes[pc.opcode] = @enumToInt(switch (operand_count) { 1167 | 0 => Opcode.br_void, 1168 | 1 => switch (label.operandType(0)) { 1169 | .i32 => Opcode.br_32, 1170 | .i64 => Opcode.br_64, 1171 | }, 1172 | else => unreachable, 1173 | }); 1174 | pc.opcode += 1; 1175 | operands[pc.operand + 0] = stack.top_offset - label.stack_offset; 1176 | operands[pc.operand + 1] = label.ref_list; 1177 | label.ref_list = pc.operand + 1; 1178 | pc.operand += 3; 1179 | } else unreachable_depth = 0; 1180 | 1181 | operands[label.extra.else_ref + 0] = pc.opcode; 1182 | operands[label.extra.else_ref + 1] = pc.operand; 1183 | label.extra = undefined; 1184 | 1185 | stack.top_index = label.stack_index; 1186 | stack.top_offset = label.stack_offset; 1187 | var param_i: u32 = 0; 1188 | while (param_i < label.type_info.param_count) : (param_i += 1) 1189 | stack.push(StackInfo.EntryType.fromBool( 1190 | label.type_info.param_types.isSet(param_i), 1191 | )); 1192 | }, 1193 | .end => { 1194 | if (unreachable_depth <= 1) { 1195 | const label = &labels[label_i]; 1196 | const target_pc = if (label.opcode == .loop) &label.extra.loop_pc else pc; 1197 | if (label.opcode == .@"if") { 1198 | operands[label.extra.else_ref + 0] = target_pc.opcode; 1199 | operands[label.extra.else_ref + 1] = target_pc.operand; 1200 | label.extra = undefined; 1201 | } 1202 | var ref = label.ref_list; 1203 | while (ref != math.maxInt(u32)) { 1204 | const next_ref = operands[ref]; 1205 | operands[ref + 0] = target_pc.opcode; 1206 | operands[ref + 1] = target_pc.operand; 1207 | ref = next_ref; 1208 | } 1209 | 1210 | if (unreachable_depth == 0) { 1211 | var result_i = label.type_info.result_count; 1212 | while (result_i > 0) { 1213 | result_i -= 1; 1214 | stack.pop(StackInfo.EntryType.fromBool( 1215 | label.type_info.result_types.isSet(result_i), 1216 | )); 1217 | } 1218 | } else unreachable_depth = 0; 1219 | 1220 | if (label_i == 0) { 1221 | assert(stack.top_index == label.stack_index); 1222 | assert(stack.top_offset == label.stack_offset); 1223 | 1224 | opcodes[pc.opcode] = @enumToInt(switch (labels[0].type_info.result_count) { 1225 | 0 => Opcode.return_void, 1226 | 1 => switch (StackInfo.EntryType.fromBool( 1227 | labels[0].type_info.result_types.isSet(0), 1228 | )) { 1229 | .i32 => Opcode.return_32, 1230 | .i64 => Opcode.return_64, 1231 | }, 1232 | else => unreachable, 1233 | }); 1234 | pc.opcode += 1; 1235 | operands[pc.operand + 0] = stack.top_offset - labels[0].stack_offset; 1236 | operands[pc.operand + 1] = frame_size; 1237 | pc.operand += 2; 1238 | return; 1239 | } 1240 | label_i -= 1; 1241 | 1242 | stack.top_index = label.stack_index; 1243 | stack.top_offset = label.stack_offset; 1244 | var result_i: u32 = 0; 1245 | while (result_i < label.type_info.result_count) : (result_i += 1) 1246 | stack.push(StackInfo.EntryType.fromBool( 1247 | label.type_info.result_types.isSet(result_i), 1248 | )); 1249 | } else unreachable_depth -= 1; 1250 | }, 1251 | .br, 1252 | .br_if, 1253 | => |opc| { 1254 | const label_idx = try leb.readULEB128(u32, reader); 1255 | if (unreachable_depth == 0) { 1256 | const label = &labels[label_i - label_idx]; 1257 | const operand_count = label.operandCount(); 1258 | var operand_i = operand_count; 1259 | while (operand_i > 0) { 1260 | operand_i -= 1; 1261 | stack.pop(label.operandType(operand_i)); 1262 | } 1263 | 1264 | const bool_not = state == .bool_not and opc == .br_if; 1265 | if (bool_not) pc.opcode -= 1; 1266 | opcodes[pc.opcode] = @enumToInt(switch (opc) { 1267 | .br => switch (operand_count) { 1268 | 0 => Opcode.br_void, 1269 | 1 => switch (label.operandType(0)) { 1270 | .i32 => Opcode.br_32, 1271 | .i64 => Opcode.br_64, 1272 | }, 1273 | else => unreachable, 1274 | }, 1275 | .br_if => switch (label.type_info.result_count) { 1276 | 0 => if (bool_not) Opcode.br_eqz_void else Opcode.br_nez_void, 1277 | 1 => switch (label.operandType(0)) { 1278 | .i32 => if (bool_not) Opcode.br_eqz_32 else Opcode.br_nez_32, 1279 | .i64 => if (bool_not) Opcode.br_eqz_64 else Opcode.br_nez_64, 1280 | }, 1281 | else => unreachable, 1282 | }, 1283 | else => unreachable, 1284 | }); 1285 | pc.opcode += 1; 1286 | operands[pc.operand + 0] = stack.top_offset - label.stack_offset; 1287 | operands[pc.operand + 1] = label.ref_list; 1288 | label.ref_list = pc.operand + 1; 1289 | pc.operand += 3; 1290 | 1291 | switch (opc) { 1292 | .br => unreachable_depth += 1, 1293 | .br_if => while (operand_i < operand_count) : (operand_i += 1) 1294 | stack.push(label.operandType(operand_i)), 1295 | else => unreachable, 1296 | } 1297 | } 1298 | }, 1299 | .br_table => { 1300 | const labels_len = try leb.readULEB128(u32, reader); 1301 | var i: u32 = 0; 1302 | while (i <= labels_len) : (i += 1) { 1303 | const label_idx = try leb.readULEB128(u32, reader); 1304 | if (unreachable_depth != 0) continue; 1305 | const label = &labels[label_i - label_idx]; 1306 | if (i == 0) { 1307 | const operand_count = label.operandCount(); 1308 | var operand_i = operand_count; 1309 | while (operand_i > 0) { 1310 | operand_i -= 1; 1311 | stack.pop(label.operandType(operand_i)); 1312 | } 1313 | 1314 | opcodes[pc.opcode] = @enumToInt(switch (operand_count) { 1315 | 0 => Opcode.br_table_void, 1316 | 1 => switch (label.operandType(0)) { 1317 | .i32 => Opcode.br_table_32, 1318 | .i64 => Opcode.br_table_64, 1319 | }, 1320 | else => unreachable, 1321 | }); 1322 | pc.opcode += 1; 1323 | operands[pc.operand] = labels_len; 1324 | pc.operand += 1; 1325 | } 1326 | operands[pc.operand + 0] = stack.top_offset - label.stack_offset; 1327 | operands[pc.operand + 1] = label.ref_list; 1328 | label.ref_list = pc.operand + 1; 1329 | pc.operand += 3; 1330 | } 1331 | if (unreachable_depth == 0) unreachable_depth += 1; 1332 | }, 1333 | .@"return" => if (unreachable_depth == 0) { 1334 | var result_i = labels[0].type_info.result_count; 1335 | while (result_i > 0) { 1336 | result_i -= 1; 1337 | stack.pop(StackInfo.EntryType.fromBool( 1338 | labels[0].type_info.result_types.isSet(result_i), 1339 | )); 1340 | } 1341 | 1342 | opcodes[pc.opcode] = @enumToInt(switch (labels[0].type_info.result_count) { 1343 | 0 => Opcode.return_void, 1344 | 1 => switch (StackInfo.EntryType.fromBool( 1345 | labels[0].type_info.result_types.isSet(0), 1346 | )) { 1347 | .i32 => Opcode.return_32, 1348 | .i64 => Opcode.return_64, 1349 | }, 1350 | else => unreachable, 1351 | }); 1352 | pc.opcode += 1; 1353 | operands[pc.operand + 0] = stack.top_offset - labels[0].stack_offset; 1354 | operands[pc.operand + 1] = frame_size; 1355 | pc.operand += 2; 1356 | unreachable_depth += 1; 1357 | }, 1358 | .call => { 1359 | const fn_id = try leb.readULEB128(u32, reader); 1360 | if (unreachable_depth == 0) { 1361 | const type_info = &vm.types[ 1362 | if (fn_id < vm.imports.len) type_idx: { 1363 | opcodes[pc.opcode + 0] = @enumToInt(Opcode.call_import); 1364 | opcodes[pc.opcode + 1] = @intCast(u8, fn_id); 1365 | pc.opcode += 2; 1366 | break :type_idx vm.imports[fn_id].type_idx; 1367 | } else type_idx: { 1368 | const fn_idx = fn_id - @intCast(u32, vm.imports.len); 1369 | opcodes[pc.opcode] = @enumToInt(Opcode.call_func); 1370 | pc.opcode += 1; 1371 | operands[pc.operand] = fn_idx; 1372 | pc.operand += 1; 1373 | break :type_idx vm.functions[fn_idx].type_idx; 1374 | } 1375 | ]; 1376 | 1377 | var param_i = type_info.param_count; 1378 | while (param_i > 0) { 1379 | param_i -= 1; 1380 | stack.pop(StackInfo.EntryType.fromBool( 1381 | type_info.param_types.isSet(param_i), 1382 | )); 1383 | } 1384 | var result_i: u32 = 0; 1385 | while (result_i < type_info.result_count) : (result_i += 1) 1386 | stack.push(StackInfo.EntryType.fromBool( 1387 | type_info.result_types.isSet(result_i), 1388 | )); 1389 | } 1390 | }, 1391 | .call_indirect => { 1392 | const type_idx = try leb.readULEB128(u32, reader); 1393 | assert(try leb.readULEB128(u32, reader) == 0); 1394 | if (unreachable_depth == 0) { 1395 | opcodes[pc.opcode] = @enumToInt(Opcode.call_indirect); 1396 | pc.opcode += 1; 1397 | 1398 | const type_info = &vm.types[type_idx]; 1399 | var param_i = type_info.param_count; 1400 | while (param_i > 0) { 1401 | param_i -= 1; 1402 | stack.pop(StackInfo.EntryType.fromBool( 1403 | type_info.param_types.isSet(param_i), 1404 | )); 1405 | } 1406 | var result_i: u32 = 0; 1407 | while (result_i < type_info.result_count) : (result_i += 1) 1408 | stack.push(StackInfo.EntryType.fromBool( 1409 | type_info.result_types.isSet(result_i), 1410 | )); 1411 | } 1412 | }, 1413 | .select, 1414 | .drop, 1415 | => |opc| if (unreachable_depth == 0) { 1416 | if (opc == .select) stack.pop(.i32); 1417 | const operand_type = stack.top(); 1418 | stack.pop(operand_type); 1419 | if (opc == .select) { 1420 | stack.pop(operand_type); 1421 | stack.push(operand_type); 1422 | } 1423 | opcodes[pc.opcode] = @enumToInt(switch (opc) { 1424 | .select => switch (operand_type) { 1425 | .i32 => Opcode.select_32, 1426 | .i64 => Opcode.select_64, 1427 | }, 1428 | .drop => switch (operand_type) { 1429 | .i32 => Opcode.drop_32, 1430 | .i64 => Opcode.drop_64, 1431 | }, 1432 | else => unreachable, 1433 | }); 1434 | pc.opcode += 1; 1435 | }, 1436 | .local_get, 1437 | .local_set, 1438 | .local_tee, 1439 | => |opc| { 1440 | const local_idx = try leb.readULEB128(u32, reader); 1441 | if (unreachable_depth == 0) { 1442 | const local_type = stack.local(local_idx); 1443 | opcodes[pc.opcode] = @enumToInt(switch (opc) { 1444 | .local_get => switch (local_type) { 1445 | .i32 => Opcode.local_get_32, 1446 | .i64 => Opcode.local_get_64, 1447 | }, 1448 | .local_set => switch (local_type) { 1449 | .i32 => Opcode.local_set_32, 1450 | .i64 => Opcode.local_set_64, 1451 | }, 1452 | .local_tee => switch (local_type) { 1453 | .i32 => Opcode.local_tee_32, 1454 | .i64 => Opcode.local_tee_64, 1455 | }, 1456 | else => unreachable, 1457 | }); 1458 | pc.opcode += 1; 1459 | operands[pc.operand] = stack.top_offset - stack.offsets[local_idx]; 1460 | pc.operand += 1; 1461 | switch (opc) { 1462 | .local_get => stack.push(local_type), 1463 | .local_set => stack.pop(local_type), 1464 | .local_tee => { 1465 | stack.pop(local_type); 1466 | stack.push(local_type); 1467 | }, 1468 | else => unreachable, 1469 | } 1470 | } 1471 | }, 1472 | .global_get, 1473 | .global_set, 1474 | => |opc| { 1475 | const global_idx = try leb.readULEB128(u32, reader); 1476 | if (unreachable_depth == 0) { 1477 | const global_type = StackInfo.EntryType.i32; // all globals assumed to be i32 1478 | opcodes[pc.opcode] = @enumToInt(switch (opc) { 1479 | .global_get => switch (global_idx) { 1480 | 0 => Opcode.global_get_0_32, 1481 | else => Opcode.global_get_32, 1482 | }, 1483 | .global_set => switch (global_idx) { 1484 | 0 => Opcode.global_set_0_32, 1485 | else => Opcode.global_set_32, 1486 | }, 1487 | else => unreachable, 1488 | }); 1489 | pc.opcode += 1; 1490 | if (global_idx != 0) { 1491 | operands[pc.operand] = global_idx; 1492 | pc.operand += 1; 1493 | } 1494 | switch (opc) { 1495 | .global_get => stack.push(global_type), 1496 | .global_set => stack.pop(global_type), 1497 | else => unreachable, 1498 | } 1499 | } 1500 | }, 1501 | .i32_load, 1502 | .i64_load, 1503 | .f32_load, 1504 | .f64_load, 1505 | .i32_load8_s, 1506 | .i32_load8_u, 1507 | .i32_load16_s, 1508 | .i32_load16_u, 1509 | .i64_load8_s, 1510 | .i64_load8_u, 1511 | .i64_load16_s, 1512 | .i64_load16_u, 1513 | .i64_load32_s, 1514 | .i64_load32_u, 1515 | .i32_store, 1516 | .i64_store, 1517 | .f32_store, 1518 | .f64_store, 1519 | .i32_store8, 1520 | .i32_store16, 1521 | .i64_store8, 1522 | .i64_store16, 1523 | .i64_store32, 1524 | => |opc| { 1525 | const alignment = try leb.readULEB128(u32, reader); 1526 | const offset = try leb.readULEB128(u32, reader); 1527 | _ = alignment; 1528 | if (unreachable_depth == 0) { 1529 | switch (opc) { 1530 | else => {}, 1531 | .i64_store8, .i64_store16, .i64_store32 => { 1532 | opcodes[pc.opcode] = @enumToInt(Opcode.drop_32); 1533 | pc.opcode += 1; 1534 | }, 1535 | } 1536 | opcodes[pc.opcode] = @enumToInt(switch (opc) { 1537 | .i32_load8_s, .i32_load8_u, .i64_load8_s, .i64_load8_u => switch (offset) { 1538 | 0 => Opcode.load_0_8, 1539 | else => Opcode.load_8, 1540 | }, 1541 | .i32_load16_s, .i32_load16_u, .i64_load16_s, .i64_load16_u => switch (offset) { 1542 | 0 => Opcode.load_0_16, 1543 | else => Opcode.load_16, 1544 | }, 1545 | .i32_load, .f32_load, .i64_load32_s, .i64_load32_u => switch (offset) { 1546 | 0 => Opcode.load_0_32, 1547 | else => Opcode.load_32, 1548 | }, 1549 | .i64_load, .f64_load => switch (offset) { 1550 | 0 => Opcode.load_0_64, 1551 | else => Opcode.load_64, 1552 | }, 1553 | .i32_store8, .i64_store8 => switch (offset) { 1554 | 0 => Opcode.store_0_8, 1555 | else => Opcode.store_8, 1556 | }, 1557 | .i32_store16, .i64_store16 => switch (offset) { 1558 | 0 => Opcode.store_0_16, 1559 | else => Opcode.store_16, 1560 | }, 1561 | .i32_store, .f32_store, .i64_store32 => switch (offset) { 1562 | 0 => Opcode.store_0_32, 1563 | else => Opcode.store_32, 1564 | }, 1565 | .i64_store, .f64_store => switch (offset) { 1566 | 0 => Opcode.store_0_64, 1567 | else => Opcode.store_64, 1568 | }, 1569 | else => unreachable, 1570 | }); 1571 | pc.opcode += 1; 1572 | switch (offset) { 1573 | 0 => {}, 1574 | else => { 1575 | operands[pc.operand] = offset; 1576 | pc.operand += 1; 1577 | }, 1578 | } 1579 | switch (opc) { 1580 | else => {}, 1581 | .i32_load8_s, .i64_load8_s => { 1582 | opcodes[pc.opcode] = @enumToInt(Opcode.sext8_32); 1583 | pc.opcode += 1; 1584 | }, 1585 | .i32_load16_s, .i64_load16_s => { 1586 | opcodes[pc.opcode] = @enumToInt(Opcode.sext16_32); 1587 | pc.opcode += 1; 1588 | }, 1589 | } 1590 | switch (opc) { 1591 | else => {}, 1592 | .i64_load8_s, .i64_load16_s, .i64_load32_s => { 1593 | opcodes[pc.opcode] = @enumToInt(Opcode.sext_64_32); 1594 | pc.opcode += 1; 1595 | }, 1596 | .i64_load8_u, .i64_load16_u, .i64_load32_u => { 1597 | opcodes[pc.opcode] = @enumToInt(Opcode.zext_64_32); 1598 | pc.opcode += 1; 1599 | }, 1600 | } 1601 | 1602 | offset_counts[@boolToInt(offset != 0)] += 1; 1603 | max_offset = @max(offset, max_offset); 1604 | } 1605 | }, 1606 | .memory_size, 1607 | .memory_grow, 1608 | => |opc| { 1609 | assert(try reader.readByte() == 0); 1610 | if (unreachable_depth == 0) { 1611 | opcodes[pc.opcode] = @enumToInt(switch (opc) { 1612 | .memory_size => Opcode.mem_size, 1613 | .memory_grow => Opcode.mem_grow, 1614 | else => unreachable, 1615 | }); 1616 | pc.opcode += 1; 1617 | } 1618 | }, 1619 | .i32_const, 1620 | .f32_const, 1621 | => |opc| { 1622 | const value = switch (opc) { 1623 | .i32_const => @bitCast(u32, try leb.readILEB128(i32, reader)), 1624 | .f32_const => try reader.readIntLittle(u32), 1625 | else => unreachable, 1626 | }; 1627 | if (unreachable_depth == 0) { 1628 | switch (value) { 1629 | 0 => opcodes[pc.opcode] = @enumToInt(Opcode.const_0_32), 1630 | 1 => opcodes[pc.opcode] = @enumToInt(Opcode.const_1_32), 1631 | else => { 1632 | opcodes[pc.opcode] = @enumToInt(Opcode.const_32); 1633 | operands[pc.operand] = value; 1634 | pc.operand += 1; 1635 | }, 1636 | math.maxInt(u32) => opcodes[pc.opcode] = @enumToInt(Opcode.const_umax_32), 1637 | } 1638 | pc.opcode += 1; 1639 | } 1640 | }, 1641 | .i64_const, 1642 | .f64_const, 1643 | => |opc| { 1644 | const value = switch (opc) { 1645 | .i64_const => @bitCast(u64, try leb.readILEB128(i64, reader)), 1646 | .f64_const => try reader.readIntLittle(u64), 1647 | else => unreachable, 1648 | }; 1649 | if (unreachable_depth == 0) { 1650 | switch (value) { 1651 | 0 => opcodes[pc.opcode] = @enumToInt(Opcode.const_0_64), 1652 | 1 => opcodes[pc.opcode] = @enumToInt(Opcode.const_1_64), 1653 | else => { 1654 | opcodes[pc.opcode] = @enumToInt(Opcode.const_64); 1655 | operands[pc.operand + 0] = @truncate(u32, value >> 0); 1656 | operands[pc.operand + 1] = @truncate(u32, value >> 32); 1657 | pc.operand += 2; 1658 | }, 1659 | math.maxInt(u64) => opcodes[pc.opcode] = @enumToInt(Opcode.const_umax_64), 1660 | } 1661 | pc.opcode += 1; 1662 | } 1663 | }, 1664 | else => |opc| if (unreachable_depth == 0) { 1665 | opcodes[pc.opcode] = @enumToInt(switch (opc) { 1666 | .i32_eqz => Opcode.eqz_32, 1667 | .i32_eq => Opcode.eq_32, 1668 | .i32_ne => Opcode.ne_32, 1669 | .i32_lt_s => Opcode.slt_32, 1670 | .i32_lt_u => Opcode.ult_32, 1671 | .i32_gt_s => Opcode.sgt_32, 1672 | .i32_gt_u => Opcode.ugt_32, 1673 | .i32_le_s => Opcode.sle_32, 1674 | .i32_le_u => Opcode.ule_32, 1675 | .i32_ge_s => Opcode.sge_32, 1676 | .i32_ge_u => Opcode.uge_32, 1677 | .i64_eqz => Opcode.eqz_64, 1678 | .i64_eq => Opcode.eq_64, 1679 | .i64_ne => Opcode.ne_64, 1680 | .i64_lt_s => Opcode.slt_64, 1681 | .i64_lt_u => Opcode.ult_64, 1682 | .i64_gt_s => Opcode.sgt_64, 1683 | .i64_gt_u => Opcode.ugt_64, 1684 | .i64_le_s => Opcode.sle_64, 1685 | .i64_le_u => Opcode.ule_64, 1686 | .i64_ge_s => Opcode.sge_64, 1687 | .i64_ge_u => Opcode.uge_64, 1688 | .f32_eq => Opcode.feq_32, 1689 | .f32_ne => Opcode.fne_32, 1690 | .f32_lt => Opcode.flt_32, 1691 | .f32_gt => Opcode.fgt_32, 1692 | .f32_le => Opcode.fle_32, 1693 | .f32_ge => Opcode.fge_32, 1694 | .f64_eq => Opcode.feq_64, 1695 | .f64_ne => Opcode.fne_64, 1696 | .f64_lt => Opcode.flt_64, 1697 | .f64_gt => Opcode.fgt_64, 1698 | .f64_le => Opcode.fle_64, 1699 | .f64_ge => Opcode.fge_64, 1700 | .i32_clz => Opcode.clz_32, 1701 | .i32_ctz => Opcode.ctz_32, 1702 | .i32_popcnt => Opcode.popcnt_32, 1703 | .i32_add => Opcode.add_32, 1704 | .i32_sub => Opcode.sub_32, 1705 | .i32_mul => Opcode.mul_32, 1706 | .i32_div_s => Opcode.sdiv_32, 1707 | .i32_div_u => Opcode.udiv_32, 1708 | .i32_rem_s => Opcode.srem_32, 1709 | .i32_rem_u => Opcode.urem_32, 1710 | .i32_and => Opcode.and_32, 1711 | .i32_or => Opcode.or_32, 1712 | .i32_xor => Opcode.xor_32, 1713 | .i32_shl => Opcode.shl_32, 1714 | .i32_shr_s => Opcode.ashr_32, 1715 | .i32_shr_u => Opcode.lshr_32, 1716 | .i32_rotl => Opcode.rol_32, 1717 | .i32_rotr => Opcode.ror_32, 1718 | .i64_clz => Opcode.clz_64, 1719 | .i64_ctz => Opcode.ctz_64, 1720 | .i64_popcnt => Opcode.popcnt_64, 1721 | .i64_add => Opcode.add_64, 1722 | .i64_sub => Opcode.sub_64, 1723 | .i64_mul => Opcode.mul_64, 1724 | .i64_div_s => Opcode.sdiv_64, 1725 | .i64_div_u => Opcode.udiv_64, 1726 | .i64_rem_s => Opcode.srem_64, 1727 | .i64_rem_u => Opcode.urem_64, 1728 | .i64_and => Opcode.and_64, 1729 | .i64_or => Opcode.or_64, 1730 | .i64_xor => Opcode.xor_64, 1731 | .i64_shl => Opcode.shl_64, 1732 | .i64_shr_s => Opcode.ashr_64, 1733 | .i64_shr_u => Opcode.lshr_64, 1734 | .i64_rotl => Opcode.rol_64, 1735 | .i64_rotr => Opcode.ror_64, 1736 | .f32_abs => Opcode.fabs_32, 1737 | .f32_neg => Opcode.fneg_32, 1738 | .f32_ceil => Opcode.ceil_32, 1739 | .f32_floor => Opcode.floor_32, 1740 | .f32_trunc => Opcode.trunc_32, 1741 | .f32_nearest => Opcode.nearest_32, 1742 | .f32_sqrt => Opcode.sqrt_32, 1743 | .f32_add => Opcode.fadd_32, 1744 | .f32_sub => Opcode.fsub_32, 1745 | .f32_mul => Opcode.fmul_32, 1746 | .f32_div => Opcode.fdiv_32, 1747 | .f32_min => Opcode.fmin_32, 1748 | .f32_max => Opcode.fmax_32, 1749 | .f32_copysign => Opcode.copysign_32, 1750 | .f64_abs => Opcode.fabs_64, 1751 | .f64_neg => Opcode.fneg_64, 1752 | .f64_ceil => Opcode.ceil_64, 1753 | .f64_floor => Opcode.floor_64, 1754 | .f64_trunc => Opcode.trunc_64, 1755 | .f64_nearest => Opcode.nearest_64, 1756 | .f64_sqrt => Opcode.sqrt_64, 1757 | .f64_add => Opcode.fadd_64, 1758 | .f64_sub => Opcode.fsub_64, 1759 | .f64_mul => Opcode.fmul_64, 1760 | .f64_div => Opcode.fdiv_64, 1761 | .f64_min => Opcode.fmin_64, 1762 | .f64_max => Opcode.fmax_64, 1763 | .f64_copysign => Opcode.copysign_64, 1764 | .i32_wrap_i64 => Opcode.wrap_32_64, 1765 | .i32_trunc_f32_s => Opcode.ftos_32_32, 1766 | .i32_trunc_f32_u => Opcode.ftou_32_32, 1767 | .i32_trunc_f64_s => Opcode.ftos_32_64, 1768 | .i32_trunc_f64_u => Opcode.ftou_32_64, 1769 | .i64_extend_i32_s => Opcode.sext_64_32, 1770 | .i64_extend_i32_u => Opcode.zext_64_32, 1771 | .i64_trunc_f32_s => Opcode.ftos_64_32, 1772 | .i64_trunc_f32_u => Opcode.ftou_64_32, 1773 | .i64_trunc_f64_s => Opcode.ftos_64_64, 1774 | .i64_trunc_f64_u => Opcode.ftou_64_64, 1775 | .f32_convert_i32_s => Opcode.stof_32_32, 1776 | .f32_convert_i32_u => Opcode.utof_32_32, 1777 | .f32_convert_i64_s => Opcode.stof_32_64, 1778 | .f32_convert_i64_u => Opcode.utof_32_64, 1779 | .f32_demote_f64 => Opcode.ftof_32_64, 1780 | .f64_convert_i32_s => Opcode.stof_64_32, 1781 | .f64_convert_i32_u => Opcode.utof_64_32, 1782 | .f64_convert_i64_s => Opcode.stof_64_64, 1783 | .f64_convert_i64_u => Opcode.utof_64_64, 1784 | .f64_promote_f32 => Opcode.ftof_64_32, 1785 | .i32_extend8_s => Opcode.sext8_32, 1786 | .i32_extend16_s => Opcode.sext16_32, 1787 | .i64_extend8_s => Opcode.sext8_64, 1788 | .i64_extend16_s => Opcode.sext16_64, 1789 | .i64_extend32_s => Opcode.sext32_64, 1790 | else => unreachable, 1791 | }); 1792 | pc.opcode += 1; 1793 | }, 1794 | .prefixed => switch (@intToEnum(wasm.PrefixedOpcode, prefixed_opcode)) { 1795 | .memory_copy => { 1796 | assert(try reader.readByte() == 0 and try reader.readByte() == 0); 1797 | if (unreachable_depth == 0) { 1798 | opcodes[pc.opcode] = @enumToInt(Opcode.memcpy); 1799 | pc.opcode += 1; 1800 | } 1801 | }, 1802 | .memory_fill => { 1803 | assert(try reader.readByte() == 0); 1804 | if (unreachable_depth == 0) { 1805 | opcodes[pc.opcode] = @enumToInt(Opcode.memset); 1806 | pc.opcode += 1; 1807 | } 1808 | }, 1809 | else => unreachable, 1810 | }, 1811 | } 1812 | state = switch (@intToEnum(wasm.Opcode, opcode)) { 1813 | else => .default, 1814 | .i32_eqz => .bool_not, 1815 | }; 1816 | 1817 | for (opcodes[old_pc.opcode..pc.opcode]) |o, i| { 1818 | decode_log.debug("decoded opcode[{d}] = {d}", .{ old_pc.opcode + i, o }); 1819 | } 1820 | for (operands[old_pc.operand..pc.operand]) |o, i| { 1821 | decode_log.debug("decoded operand[{d}] = {d}", .{ old_pc.operand + i, o }); 1822 | } 1823 | } 1824 | } 1825 | 1826 | fn br(vm: *VirtualMachine, comptime Result: type) void { 1827 | const stack_adjust = vm.operands[vm.pc.operand]; 1828 | 1829 | const result = vm.pop(Result); 1830 | vm.stack_top -= stack_adjust; 1831 | vm.push(Result, result); 1832 | 1833 | vm.pc.opcode = vm.operands[vm.pc.operand + 1]; 1834 | vm.pc.operand = vm.operands[vm.pc.operand + 2]; 1835 | } 1836 | 1837 | fn @"return"(vm: *VirtualMachine, comptime Result: type) void { 1838 | const stack_adjust = vm.operands[vm.pc.operand + 0]; 1839 | const frame_size = vm.operands[vm.pc.operand + 1]; 1840 | 1841 | const result = vm.pop(Result); 1842 | 1843 | vm.stack_top -= stack_adjust; 1844 | vm.pc.operand = vm.pop(u32); 1845 | vm.pc.opcode = vm.pop(u32); 1846 | 1847 | vm.stack_top -= frame_size; 1848 | vm.push(Result, result); 1849 | } 1850 | 1851 | fn call(vm: *VirtualMachine, func: *const Function) void { 1852 | const type_info = &vm.types[func.type_idx]; 1853 | func_log.debug("enter fn_id: {d}, param_count: {d}, result_count: {d}, locals_size: {d}", .{ 1854 | func.id, type_info.param_count, type_info.result_count, func.locals_size, 1855 | }); 1856 | 1857 | // Push zeroed locals to stack 1858 | mem.set(u32, vm.stack[vm.stack_top..][0..func.locals_size], 0); 1859 | vm.stack_top += func.locals_size; 1860 | 1861 | vm.push(u32, vm.pc.opcode); 1862 | vm.push(u32, vm.pc.operand); 1863 | 1864 | vm.pc = func.entry_pc; 1865 | } 1866 | 1867 | fn callImport(vm: *VirtualMachine, import: *const Import) void { 1868 | switch (import.mod) { 1869 | .wasi_snapshot_preview1 => switch (import.name) { 1870 | .fd_prestat_get => { 1871 | const buf = vm.pop(u32); 1872 | const fd = vm.pop(i32); 1873 | vm.push(u32, @enumToInt(wasi_fd_prestat_get(vm, fd, buf))); 1874 | }, 1875 | .fd_prestat_dir_name => { 1876 | const path_len = vm.pop(u32); 1877 | const path = vm.pop(u32); 1878 | const fd = vm.pop(i32); 1879 | vm.push(u32, @enumToInt(wasi_fd_prestat_dir_name(vm, fd, path, path_len))); 1880 | }, 1881 | .fd_close => { 1882 | const fd = vm.pop(i32); 1883 | vm.push(u32, @enumToInt(wasi_fd_close(vm, fd))); 1884 | }, 1885 | .fd_read => { 1886 | const nread = vm.pop(u32); 1887 | const iovs_len = vm.pop(u32); 1888 | const iovs = vm.pop(u32); 1889 | const fd = vm.pop(i32); 1890 | vm.push(u32, @enumToInt(wasi_fd_read(vm, fd, iovs, iovs_len, nread))); 1891 | }, 1892 | .fd_filestat_get => { 1893 | const buf = vm.pop(u32); 1894 | const fd = vm.pop(i32); 1895 | vm.push(u32, @enumToInt(wasi_fd_filestat_get(vm, fd, buf))); 1896 | }, 1897 | .fd_filestat_set_size => { 1898 | const size = vm.pop(u64); 1899 | const fd = vm.pop(i32); 1900 | vm.push(u32, @enumToInt(wasi_fd_filestat_set_size(vm, fd, size))); 1901 | }, 1902 | .fd_filestat_set_times => { 1903 | @panic("TODO implement fd_filestat_set_times"); 1904 | }, 1905 | .fd_fdstat_get => { 1906 | const buf = vm.pop(u32); 1907 | const fd = vm.pop(i32); 1908 | vm.push(u32, @enumToInt(wasi_fd_fdstat_get(vm, fd, buf))); 1909 | }, 1910 | .fd_readdir => { 1911 | @panic("TODO implement fd_readdir"); 1912 | }, 1913 | .fd_write => { 1914 | const nwritten = vm.pop(u32); 1915 | const iovs_len = vm.pop(u32); 1916 | const iovs = vm.pop(u32); 1917 | const fd = vm.pop(i32); 1918 | vm.push(u32, @enumToInt(wasi_fd_write(vm, fd, iovs, iovs_len, nwritten))); 1919 | }, 1920 | .fd_pwrite => { 1921 | const nwritten = vm.pop(u32); 1922 | const offset = vm.pop(u64); 1923 | const iovs_len = vm.pop(u32); 1924 | const iovs = vm.pop(u32); 1925 | const fd = vm.pop(i32); 1926 | vm.push(u32, @enumToInt(wasi_fd_pwrite(vm, fd, iovs, iovs_len, offset, nwritten))); 1927 | }, 1928 | .proc_exit => { 1929 | stats_log.debug("memory length = {}\n", .{vm.memory_len}); 1930 | std.c.exit(@intCast(c_int, vm.pop(wasi.exitcode_t))); 1931 | unreachable; 1932 | }, 1933 | .args_sizes_get => { 1934 | const argv_buf_size = vm.pop(u32); 1935 | const argc = vm.pop(u32); 1936 | vm.push(u32, @enumToInt(wasi_args_sizes_get(vm, argc, argv_buf_size))); 1937 | }, 1938 | .args_get => { 1939 | const argv_buf = vm.pop(u32); 1940 | const argv = vm.pop(u32); 1941 | vm.push(u32, @enumToInt(wasi_args_get(vm, argv, argv_buf))); 1942 | }, 1943 | .random_get => { 1944 | const buf_len = vm.pop(u32); 1945 | const buf = vm.pop(u32); 1946 | vm.push(u32, @enumToInt(wasi_random_get(vm, buf, buf_len))); 1947 | }, 1948 | .environ_sizes_get => { 1949 | @panic("TODO implement environ_sizes_get"); 1950 | }, 1951 | .environ_get => { 1952 | @panic("TODO implement environ_get"); 1953 | }, 1954 | .path_filestat_get => { 1955 | const buf = vm.pop(u32); 1956 | const path_len = vm.pop(u32); 1957 | const path = vm.pop(u32); 1958 | const flags = vm.pop(u32); 1959 | const fd = vm.pop(i32); 1960 | vm.push(u32, @enumToInt(wasi_path_filestat_get(vm, fd, flags, path, path_len, buf))); 1961 | }, 1962 | .path_create_directory => { 1963 | const path_len = vm.pop(u32); 1964 | const path = vm.pop(u32); 1965 | const fd = vm.pop(i32); 1966 | vm.push(u32, @enumToInt(wasi_path_create_directory(vm, fd, path, path_len))); 1967 | }, 1968 | .path_rename => { 1969 | const new_path_len = vm.pop(u32); 1970 | const new_path = vm.pop(u32); 1971 | const new_fd = vm.pop(i32); 1972 | const old_path_len = vm.pop(u32); 1973 | const old_path = vm.pop(u32); 1974 | const old_fd = vm.pop(i32); 1975 | vm.push(u32, @enumToInt(wasi_path_rename( 1976 | vm, 1977 | old_fd, 1978 | old_path, 1979 | old_path_len, 1980 | new_fd, 1981 | new_path, 1982 | new_path_len, 1983 | ))); 1984 | }, 1985 | .path_open => { 1986 | const fd = vm.pop(u32); 1987 | const fs_flags = vm.pop(u32); 1988 | const fs_rights_inheriting = vm.pop(u64); 1989 | const fs_rights_base = vm.pop(u64); 1990 | const oflags = vm.pop(u32); 1991 | const path_len = vm.pop(u32); 1992 | const path = vm.pop(u32); 1993 | const dirflags = vm.pop(u32); 1994 | const dirfd = vm.pop(i32); 1995 | vm.push(u32, @enumToInt(wasi_path_open( 1996 | vm, 1997 | dirfd, 1998 | dirflags, 1999 | path, 2000 | path_len, 2001 | @intCast(u16, oflags), 2002 | fs_rights_base, 2003 | fs_rights_inheriting, 2004 | @intCast(u16, fs_flags), 2005 | fd, 2006 | ))); 2007 | }, 2008 | .path_remove_directory => { 2009 | @panic("TODO implement path_remove_directory"); 2010 | }, 2011 | .path_unlink_file => { 2012 | @panic("TODO implement path_unlink_file"); 2013 | }, 2014 | .clock_time_get => { 2015 | const timestamp = vm.pop(u32); 2016 | const precision = vm.pop(u64); 2017 | const clock_id = vm.pop(u32); 2018 | vm.push(u32, @enumToInt(wasi_clock_time_get(vm, clock_id, precision, timestamp))); 2019 | }, 2020 | .fd_pread => { 2021 | @panic("TODO implement fd_pread"); 2022 | }, 2023 | .debug => { 2024 | const number = vm.pop(u64); 2025 | const text = vm.pop(u32); 2026 | wasi_debug(vm, text, number); 2027 | }, 2028 | .debug_slice => { 2029 | const len = vm.pop(u32); 2030 | const ptr = vm.pop(u32); 2031 | wasi_debug_slice(vm, ptr, len); 2032 | }, 2033 | }, 2034 | } 2035 | } 2036 | 2037 | fn push(vm: *VirtualMachine, comptime T: type, value: T) void { 2038 | if (@sizeOf(T) == 0) return; 2039 | switch (@bitSizeOf(T)) { 2040 | 32 => { 2041 | vm.stack[vm.stack_top + 0] = @bitCast(u32, value); 2042 | vm.stack_top += 1; 2043 | }, 2044 | 64 => { 2045 | vm.stack[vm.stack_top + 0] = @truncate(u32, @bitCast(u64, value) >> 0); 2046 | vm.stack[vm.stack_top + 1] = @truncate(u32, @bitCast(u64, value) >> 32); 2047 | vm.stack_top += 2; 2048 | }, 2049 | else => @compileError("bad push type"), 2050 | } 2051 | } 2052 | 2053 | fn pop(vm: *VirtualMachine, comptime T: type) T { 2054 | if (@sizeOf(T) == 0) return undefined; 2055 | switch (@bitSizeOf(T)) { 2056 | 32 => { 2057 | vm.stack_top -= 1; 2058 | return @bitCast(T, vm.stack[vm.stack_top + 0]); 2059 | }, 2060 | 64 => { 2061 | vm.stack_top -= 2; 2062 | return @bitCast(T, vm.stack[vm.stack_top + 0] | @as(u64, vm.stack[vm.stack_top + 1]) << 32); 2063 | }, 2064 | else => @compileError("bad pop type"), 2065 | } 2066 | } 2067 | 2068 | fn run(vm: *VirtualMachine) noreturn { 2069 | const opcodes = vm.opcodes; 2070 | const operands = vm.operands; 2071 | const pc = &vm.pc; 2072 | var global_0: u32 = vm.globals[0]; 2073 | defer vm.globals[0] = global_0; 2074 | while (true) { 2075 | const op = @intToEnum(Opcode, opcodes[pc.opcode]); 2076 | cpu_log.debug("stack[{d}:{d}]={x}:{x} pc={x}:{x} op={d}", .{ 2077 | vm.stack_top - 2, 2078 | vm.stack_top - 1, 2079 | vm.stack[vm.stack_top - 2], 2080 | vm.stack[vm.stack_top - 1], 2081 | pc.opcode, 2082 | pc.operand, 2083 | @enumToInt(op), 2084 | }); 2085 | cpu_log.debug("op={s}", .{@tagName(op)}); 2086 | pc.opcode += 1; 2087 | switch (op) { 2088 | .@"unreachable" => @panic("unreachable reached"), 2089 | .br_void => { 2090 | vm.br(void); 2091 | }, 2092 | .br_32 => { 2093 | vm.br(u32); 2094 | }, 2095 | .br_64 => { 2096 | vm.br(u64); 2097 | }, 2098 | .br_nez_void => { 2099 | if (vm.pop(u32) != 0) { 2100 | vm.br(void); 2101 | } else { 2102 | pc.operand += 3; 2103 | } 2104 | }, 2105 | .br_nez_32 => { 2106 | if (vm.pop(u32) != 0) { 2107 | vm.br(u32); 2108 | } else { 2109 | pc.operand += 3; 2110 | } 2111 | }, 2112 | .br_nez_64 => { 2113 | if (vm.pop(u32) != 0) { 2114 | vm.br(u64); 2115 | } else { 2116 | pc.operand += 3; 2117 | } 2118 | }, 2119 | .br_eqz_void => { 2120 | if (vm.pop(u32) == 0) { 2121 | vm.br(void); 2122 | } else { 2123 | pc.operand += 3; 2124 | } 2125 | }, 2126 | .br_eqz_32 => { 2127 | if (vm.pop(u32) == 0) { 2128 | vm.br(u32); 2129 | } else { 2130 | pc.operand += 3; 2131 | } 2132 | }, 2133 | .br_eqz_64 => { 2134 | if (vm.pop(u32) == 0) { 2135 | vm.br(u64); 2136 | } else { 2137 | pc.operand += 3; 2138 | } 2139 | }, 2140 | .br_table_void => { 2141 | const index = @min(vm.pop(u32), operands[pc.operand]); 2142 | pc.operand += 1 + index * 3; 2143 | vm.br(void); 2144 | }, 2145 | .br_table_32 => { 2146 | const index = @min(vm.pop(u32), operands[pc.operand]); 2147 | pc.operand += 1 + index * 3; 2148 | vm.br(u32); 2149 | }, 2150 | .br_table_64 => { 2151 | const index = @min(vm.pop(u32), operands[pc.operand]); 2152 | pc.operand += 1 + index * 3; 2153 | vm.br(u64); 2154 | }, 2155 | .return_void => { 2156 | vm.@"return"(void); 2157 | }, 2158 | .return_32 => { 2159 | vm.@"return"(u32); 2160 | }, 2161 | .return_64 => { 2162 | vm.@"return"(u64); 2163 | }, 2164 | .call_import => { 2165 | const import_idx = opcodes[pc.opcode]; 2166 | pc.opcode += 1; 2167 | vm.callImport(&vm.imports[import_idx]); 2168 | }, 2169 | .call_func => { 2170 | const func_idx = operands[pc.operand]; 2171 | pc.operand += 1; 2172 | vm.call(&vm.functions[func_idx]); 2173 | }, 2174 | .call_indirect => { 2175 | const fn_id = vm.table[vm.pop(u32)]; 2176 | if (fn_id < vm.imports.len) 2177 | vm.callImport(&vm.imports[fn_id]) 2178 | else 2179 | vm.call(&vm.functions[fn_id - @intCast(u32, vm.imports.len)]); 2180 | }, 2181 | .drop_32 => { 2182 | vm.stack_top -= 1; 2183 | }, 2184 | .drop_64 => { 2185 | vm.stack_top -= 2; 2186 | }, 2187 | .select_32 => { 2188 | const c = vm.pop(u32); 2189 | const b = vm.pop(u32); 2190 | const a = vm.pop(u32); 2191 | const result = if (c != 0) a else b; 2192 | vm.push(u32, result); 2193 | }, 2194 | .select_64 => { 2195 | const c = vm.pop(u32); 2196 | const b = vm.pop(u64); 2197 | const a = vm.pop(u64); 2198 | const result = if (c != 0) a else b; 2199 | vm.push(u64, result); 2200 | }, 2201 | .local_get_32 => { 2202 | const local = &vm.stack[vm.stack_top - operands[pc.operand]]; 2203 | pc.operand += 1; 2204 | vm.push(u32, @intCast(u32, local.*)); 2205 | }, 2206 | .local_get_64 => { 2207 | const local = vm.stack[vm.stack_top - operands[pc.operand] ..][0..2]; 2208 | pc.operand += 1; 2209 | vm.push(u64, local[0] | @as(u64, local[1]) << 32); 2210 | }, 2211 | .local_set_32 => { 2212 | const local = &vm.stack[vm.stack_top - operands[pc.operand]]; 2213 | pc.operand += 1; 2214 | local.* = vm.pop(u32); 2215 | }, 2216 | .local_set_64 => { 2217 | const local = vm.stack[vm.stack_top - operands[pc.operand] ..][0..2]; 2218 | pc.operand += 1; 2219 | const value = vm.pop(u64); 2220 | local[0] = @truncate(u32, value >> 0); 2221 | local[1] = @truncate(u32, value >> 32); 2222 | }, 2223 | .local_tee_32 => { 2224 | const local = &vm.stack[vm.stack_top - operands[pc.operand]]; 2225 | pc.operand += 1; 2226 | local.* = vm.stack[vm.stack_top - 1]; 2227 | }, 2228 | .local_tee_64 => { 2229 | const local = vm.stack[vm.stack_top - operands[pc.operand] ..][0..2]; 2230 | pc.operand += 1; 2231 | local[0] = vm.stack[vm.stack_top - 2]; 2232 | local[1] = vm.stack[vm.stack_top - 1]; 2233 | }, 2234 | .global_get_0_32 => { 2235 | vm.push(u32, global_0); 2236 | }, 2237 | .global_get_32 => { 2238 | const idx = operands[pc.operand]; 2239 | pc.operand += 1; 2240 | vm.push(u32, vm.globals[idx]); 2241 | }, 2242 | .global_set_0_32 => { 2243 | global_0 = vm.pop(u32); 2244 | }, 2245 | .global_set_32 => { 2246 | const idx = operands[pc.operand]; 2247 | pc.operand += 1; 2248 | vm.globals[idx] = vm.pop(u32); 2249 | }, 2250 | .load_0_8 => { 2251 | const address = vm.pop(u32); 2252 | vm.push(u32, vm.memory[address]); 2253 | }, 2254 | .load_8 => { 2255 | const address = vm.pop(u32) + operands[pc.operand]; 2256 | pc.operand += 1; 2257 | vm.push(u32, vm.memory[address]); 2258 | }, 2259 | .load_0_16 => { 2260 | const address = vm.pop(u32); 2261 | vm.push(u32, mem.readIntLittle(u16, vm.memory[address..][0..2])); 2262 | }, 2263 | .load_16 => { 2264 | const address = vm.pop(u32) + operands[pc.operand]; 2265 | pc.operand += 1; 2266 | vm.push(u32, mem.readIntLittle(u16, vm.memory[address..][0..2])); 2267 | }, 2268 | .load_0_32 => { 2269 | const address = vm.pop(u32); 2270 | vm.push(u32, mem.readIntLittle(u32, vm.memory[address..][0..4])); 2271 | }, 2272 | .load_32 => { 2273 | const address = vm.pop(u32) + operands[pc.operand]; 2274 | pc.operand += 1; 2275 | vm.push(u32, mem.readIntLittle(u32, vm.memory[address..][0..4])); 2276 | }, 2277 | .load_0_64 => { 2278 | const address = vm.pop(u32); 2279 | vm.push(u64, mem.readIntLittle(u64, vm.memory[address..][0..8])); 2280 | }, 2281 | .load_64 => { 2282 | const address = vm.pop(u32) + operands[pc.operand]; 2283 | pc.operand += 1; 2284 | vm.push(u64, mem.readIntLittle(u64, vm.memory[address..][0..8])); 2285 | }, 2286 | .store_0_8 => { 2287 | const value = @truncate(u8, vm.pop(u32)); 2288 | const address = vm.pop(u32); 2289 | vm.memory[address] = value; 2290 | }, 2291 | .store_8 => { 2292 | const value = @truncate(u8, vm.pop(u32)); 2293 | const address = vm.pop(u32) + operands[pc.operand]; 2294 | pc.operand += 1; 2295 | vm.memory[address] = value; 2296 | }, 2297 | .store_0_16 => { 2298 | const value = @truncate(u16, vm.pop(u32)); 2299 | const address = vm.pop(u32); 2300 | mem.writeIntLittle(u16, vm.memory[address..][0..2], value); 2301 | }, 2302 | .store_16 => { 2303 | const value = @truncate(u16, vm.pop(u32)); 2304 | const address = vm.pop(u32) + operands[pc.operand]; 2305 | pc.operand += 1; 2306 | mem.writeIntLittle(u16, vm.memory[address..][0..2], value); 2307 | }, 2308 | .store_0_32 => { 2309 | const value = vm.pop(u32); 2310 | const address = vm.pop(u32); 2311 | mem.writeIntLittle(u32, vm.memory[address..][0..4], value); 2312 | }, 2313 | .store_32 => { 2314 | const value = vm.pop(u32); 2315 | const address = vm.pop(u32) + operands[pc.operand]; 2316 | pc.operand += 1; 2317 | mem.writeIntLittle(u32, vm.memory[address..][0..4], value); 2318 | }, 2319 | .store_0_64 => { 2320 | const value = vm.pop(u64); 2321 | const address = vm.pop(u32); 2322 | mem.writeIntLittle(u64, vm.memory[address..][0..8], value); 2323 | }, 2324 | .store_64 => { 2325 | const value = vm.pop(u64); 2326 | const address = vm.pop(u32) + operands[pc.operand]; 2327 | pc.operand += 1; 2328 | mem.writeIntLittle(u64, vm.memory[address..][0..8], value); 2329 | }, 2330 | .mem_size => { 2331 | const page_count = @intCast(u32, vm.memory_len / wasm.page_size); 2332 | vm.push(u32, page_count); 2333 | }, 2334 | .mem_grow => { 2335 | const page_count = vm.pop(u32); 2336 | const old_page_count = @intCast(u32, vm.memory_len / wasm.page_size); 2337 | const new_len = vm.memory_len + page_count * wasm.page_size; 2338 | if (new_len > vm.memory.len) { 2339 | vm.push(i32, -1); 2340 | } else { 2341 | vm.memory_len = new_len; 2342 | vm.push(u32, old_page_count); 2343 | } 2344 | }, 2345 | .const_0_32 => { 2346 | vm.push(i32, @as(i32, 0)); 2347 | }, 2348 | .const_0_64 => { 2349 | vm.push(i64, @as(i64, 0)); 2350 | }, 2351 | .const_1_32 => { 2352 | vm.push(i32, @as(i32, 1)); 2353 | }, 2354 | .const_1_64 => { 2355 | vm.push(i64, @as(i64, 1)); 2356 | }, 2357 | .const_32 => { 2358 | const x = operands[pc.operand]; 2359 | pc.operand += 1; 2360 | vm.push(i32, @bitCast(i32, x)); 2361 | }, 2362 | .const_64 => { 2363 | const x = operands[pc.operand] | @as(u64, operands[pc.operand + 1]) << 32; 2364 | pc.operand += 2; 2365 | vm.push(i64, @bitCast(i64, x)); 2366 | }, 2367 | .const_umax_32 => { 2368 | vm.push(i32, -1); 2369 | }, 2370 | .const_umax_64 => { 2371 | vm.push(i64, -1); 2372 | }, 2373 | .eqz_32 => { 2374 | const lhs = vm.pop(u32); 2375 | vm.push(u32, @boolToInt(lhs == 0)); 2376 | }, 2377 | .eq_32 => { 2378 | const rhs = vm.pop(u32); 2379 | const lhs = vm.pop(u32); 2380 | vm.push(u32, @boolToInt(lhs == rhs)); 2381 | }, 2382 | .ne_32 => { 2383 | const rhs = vm.pop(u32); 2384 | const lhs = vm.pop(u32); 2385 | vm.push(u32, @boolToInt(lhs != rhs)); 2386 | }, 2387 | .slt_32 => { 2388 | const rhs = vm.pop(i32); 2389 | const lhs = vm.pop(i32); 2390 | vm.push(u32, @boolToInt(lhs < rhs)); 2391 | }, 2392 | .ult_32 => { 2393 | const rhs = vm.pop(u32); 2394 | const lhs = vm.pop(u32); 2395 | vm.push(u32, @boolToInt(lhs < rhs)); 2396 | }, 2397 | .sgt_32 => { 2398 | const rhs = vm.pop(i32); 2399 | const lhs = vm.pop(i32); 2400 | vm.push(u32, @boolToInt(lhs > rhs)); 2401 | }, 2402 | .ugt_32 => { 2403 | const rhs = vm.pop(u32); 2404 | const lhs = vm.pop(u32); 2405 | vm.push(u32, @boolToInt(lhs > rhs)); 2406 | }, 2407 | .sle_32 => { 2408 | const rhs = vm.pop(i32); 2409 | const lhs = vm.pop(i32); 2410 | vm.push(u32, @boolToInt(lhs <= rhs)); 2411 | }, 2412 | .ule_32 => { 2413 | const rhs = vm.pop(u32); 2414 | const lhs = vm.pop(u32); 2415 | vm.push(u32, @boolToInt(lhs <= rhs)); 2416 | }, 2417 | .sge_32 => { 2418 | const rhs = vm.pop(i32); 2419 | const lhs = vm.pop(i32); 2420 | vm.push(u32, @boolToInt(lhs >= rhs)); 2421 | }, 2422 | .uge_32 => { 2423 | const rhs = vm.pop(u32); 2424 | const lhs = vm.pop(u32); 2425 | vm.push(u32, @boolToInt(lhs >= rhs)); 2426 | }, 2427 | .eqz_64 => { 2428 | const lhs = vm.pop(u64); 2429 | vm.push(u32, @boolToInt(lhs == 0)); 2430 | }, 2431 | .eq_64 => { 2432 | const rhs = vm.pop(u64); 2433 | const lhs = vm.pop(u64); 2434 | vm.push(u32, @boolToInt(lhs == rhs)); 2435 | }, 2436 | .ne_64 => { 2437 | const rhs = vm.pop(u64); 2438 | const lhs = vm.pop(u64); 2439 | vm.push(u32, @boolToInt(lhs != rhs)); 2440 | }, 2441 | .slt_64 => { 2442 | const rhs = vm.pop(i64); 2443 | const lhs = vm.pop(i64); 2444 | vm.push(u32, @boolToInt(lhs < rhs)); 2445 | }, 2446 | .ult_64 => { 2447 | const rhs = vm.pop(u64); 2448 | const lhs = vm.pop(u64); 2449 | vm.push(u32, @boolToInt(lhs < rhs)); 2450 | }, 2451 | .sgt_64 => { 2452 | const rhs = vm.pop(i64); 2453 | const lhs = vm.pop(i64); 2454 | vm.push(u32, @boolToInt(lhs > rhs)); 2455 | }, 2456 | .ugt_64 => { 2457 | const rhs = vm.pop(u64); 2458 | const lhs = vm.pop(u64); 2459 | vm.push(u32, @boolToInt(lhs > rhs)); 2460 | }, 2461 | .sle_64 => { 2462 | const rhs = vm.pop(i64); 2463 | const lhs = vm.pop(i64); 2464 | vm.push(u32, @boolToInt(lhs <= rhs)); 2465 | }, 2466 | .ule_64 => { 2467 | const rhs = vm.pop(u64); 2468 | const lhs = vm.pop(u64); 2469 | vm.push(u32, @boolToInt(lhs <= rhs)); 2470 | }, 2471 | .sge_64 => { 2472 | const rhs = vm.pop(i64); 2473 | const lhs = vm.pop(i64); 2474 | vm.push(u32, @boolToInt(lhs >= rhs)); 2475 | }, 2476 | .uge_64 => { 2477 | const rhs = vm.pop(u64); 2478 | const lhs = vm.pop(u64); 2479 | vm.push(u32, @boolToInt(lhs >= rhs)); 2480 | }, 2481 | .feq_32 => { 2482 | const rhs = vm.pop(f32); 2483 | const lhs = vm.pop(f32); 2484 | vm.push(u32, @boolToInt(lhs == rhs)); 2485 | }, 2486 | .fne_32 => { 2487 | const rhs = vm.pop(f32); 2488 | const lhs = vm.pop(f32); 2489 | vm.push(u32, @boolToInt(lhs != rhs)); 2490 | }, 2491 | .flt_32 => { 2492 | const rhs = vm.pop(f32); 2493 | const lhs = vm.pop(f32); 2494 | vm.push(u32, @boolToInt(lhs < rhs)); 2495 | }, 2496 | .fgt_32 => { 2497 | const rhs = vm.pop(f32); 2498 | const lhs = vm.pop(f32); 2499 | vm.push(u32, @boolToInt(lhs > rhs)); 2500 | }, 2501 | .fle_32 => { 2502 | const rhs = vm.pop(f32); 2503 | const lhs = vm.pop(f32); 2504 | vm.push(u32, @boolToInt(lhs <= rhs)); 2505 | }, 2506 | .fge_32 => { 2507 | const rhs = vm.pop(f32); 2508 | const lhs = vm.pop(f32); 2509 | vm.push(u32, @boolToInt(lhs >= rhs)); 2510 | }, 2511 | .feq_64 => { 2512 | const rhs = vm.pop(f64); 2513 | const lhs = vm.pop(f64); 2514 | vm.push(u32, @boolToInt(lhs == rhs)); 2515 | }, 2516 | .fne_64 => { 2517 | const rhs = vm.pop(f64); 2518 | const lhs = vm.pop(f64); 2519 | vm.push(u32, @boolToInt(lhs != rhs)); 2520 | }, 2521 | .flt_64 => { 2522 | const rhs = vm.pop(f64); 2523 | const lhs = vm.pop(f64); 2524 | vm.push(u32, @boolToInt(lhs <= rhs)); 2525 | }, 2526 | .fgt_64 => { 2527 | const rhs = vm.pop(f64); 2528 | const lhs = vm.pop(f64); 2529 | vm.push(u32, @boolToInt(lhs > rhs)); 2530 | }, 2531 | .fle_64 => { 2532 | const rhs = vm.pop(f64); 2533 | const lhs = vm.pop(f64); 2534 | vm.push(u32, @boolToInt(lhs <= rhs)); 2535 | }, 2536 | .fge_64 => { 2537 | const rhs = vm.pop(f64); 2538 | const lhs = vm.pop(f64); 2539 | vm.push(u32, @boolToInt(lhs >= rhs)); 2540 | }, 2541 | .clz_32 => { 2542 | vm.push(u32, @clz(vm.pop(u32))); 2543 | }, 2544 | .ctz_32 => { 2545 | vm.push(u32, @ctz(vm.pop(u32))); 2546 | }, 2547 | .popcnt_32 => { 2548 | vm.push(u32, @popCount(vm.pop(u32))); 2549 | }, 2550 | .add_32 => { 2551 | const rhs = vm.pop(u32); 2552 | const lhs = vm.pop(u32); 2553 | vm.push(u32, lhs +% rhs); 2554 | }, 2555 | .sub_32 => { 2556 | const rhs = vm.pop(u32); 2557 | const lhs = vm.pop(u32); 2558 | vm.push(u32, lhs -% rhs); 2559 | }, 2560 | .mul_32 => { 2561 | const rhs = vm.pop(u32); 2562 | const lhs = vm.pop(u32); 2563 | vm.push(u32, lhs *% rhs); 2564 | }, 2565 | .sdiv_32 => { 2566 | const rhs = vm.pop(i32); 2567 | const lhs = vm.pop(i32); 2568 | vm.push(i32, @divTrunc(lhs, rhs)); 2569 | }, 2570 | .udiv_32 => { 2571 | const rhs = vm.pop(u32); 2572 | const lhs = vm.pop(u32); 2573 | vm.push(u32, @divTrunc(lhs, rhs)); 2574 | }, 2575 | .srem_32 => { 2576 | const rhs = vm.pop(i32); 2577 | const lhs = vm.pop(i32); 2578 | vm.push(i32, @rem(lhs, rhs)); 2579 | }, 2580 | .urem_32 => { 2581 | const rhs = vm.pop(u32); 2582 | const lhs = vm.pop(u32); 2583 | vm.push(u32, @rem(lhs, rhs)); 2584 | }, 2585 | .and_32 => { 2586 | const rhs = vm.pop(u32); 2587 | const lhs = vm.pop(u32); 2588 | vm.push(u32, lhs & rhs); 2589 | }, 2590 | .or_32 => { 2591 | const rhs = vm.pop(u32); 2592 | const lhs = vm.pop(u32); 2593 | vm.push(u32, lhs | rhs); 2594 | }, 2595 | .xor_32 => { 2596 | const rhs = vm.pop(u32); 2597 | const lhs = vm.pop(u32); 2598 | vm.push(u32, lhs ^ rhs); 2599 | }, 2600 | .shl_32 => { 2601 | const rhs = vm.pop(u32); 2602 | const lhs = vm.pop(u32); 2603 | vm.push(u32, lhs << @truncate(u5, rhs)); 2604 | }, 2605 | .ashr_32 => { 2606 | const rhs = vm.pop(u32); 2607 | const lhs = vm.pop(i32); 2608 | vm.push(i32, lhs >> @truncate(u5, rhs)); 2609 | }, 2610 | .lshr_32 => { 2611 | const rhs = vm.pop(u32); 2612 | const lhs = vm.pop(u32); 2613 | vm.push(u32, lhs >> @truncate(u5, rhs)); 2614 | }, 2615 | .rol_32 => { 2616 | const rhs = vm.pop(u32); 2617 | const lhs = vm.pop(u32); 2618 | vm.push(u32, math.rotl(u32, lhs, rhs % 32)); 2619 | }, 2620 | .ror_32 => { 2621 | const rhs = vm.pop(u32); 2622 | const lhs = vm.pop(u32); 2623 | vm.push(u32, math.rotr(u32, lhs, rhs % 32)); 2624 | }, 2625 | .clz_64 => { 2626 | vm.push(u64, @clz(vm.pop(u64))); 2627 | }, 2628 | .ctz_64 => { 2629 | vm.push(u64, @ctz(vm.pop(u64))); 2630 | }, 2631 | .popcnt_64 => { 2632 | vm.push(u64, @popCount(vm.pop(u64))); 2633 | }, 2634 | .add_64 => { 2635 | const rhs = vm.pop(u64); 2636 | const lhs = vm.pop(u64); 2637 | vm.push(u64, lhs +% rhs); 2638 | }, 2639 | .sub_64 => { 2640 | const rhs = vm.pop(u64); 2641 | const lhs = vm.pop(u64); 2642 | vm.push(u64, lhs -% rhs); 2643 | }, 2644 | .mul_64 => { 2645 | const rhs = vm.pop(u64); 2646 | const lhs = vm.pop(u64); 2647 | vm.push(u64, lhs *% rhs); 2648 | }, 2649 | .sdiv_64 => { 2650 | const rhs = vm.pop(i64); 2651 | const lhs = vm.pop(i64); 2652 | vm.push(i64, @divTrunc(lhs, rhs)); 2653 | }, 2654 | .udiv_64 => { 2655 | const rhs = vm.pop(u64); 2656 | const lhs = vm.pop(u64); 2657 | vm.push(u64, @divTrunc(lhs, rhs)); 2658 | }, 2659 | .srem_64 => { 2660 | const rhs = vm.pop(i64); 2661 | const lhs = vm.pop(i64); 2662 | vm.push(i64, @rem(lhs, rhs)); 2663 | }, 2664 | .urem_64 => { 2665 | const rhs = vm.pop(u64); 2666 | const lhs = vm.pop(u64); 2667 | vm.push(u64, @rem(lhs, rhs)); 2668 | }, 2669 | .and_64 => { 2670 | const rhs = vm.pop(u64); 2671 | const lhs = vm.pop(u64); 2672 | vm.push(u64, lhs & rhs); 2673 | }, 2674 | .or_64 => { 2675 | const rhs = vm.pop(u64); 2676 | const lhs = vm.pop(u64); 2677 | vm.push(u64, lhs | rhs); 2678 | }, 2679 | .xor_64 => { 2680 | const rhs = vm.pop(u64); 2681 | const lhs = vm.pop(u64); 2682 | vm.push(u64, lhs ^ rhs); 2683 | }, 2684 | .shl_64 => { 2685 | const rhs = vm.pop(u64); 2686 | const lhs = vm.pop(u64); 2687 | vm.push(u64, lhs << @truncate(u6, rhs)); 2688 | }, 2689 | .ashr_64 => { 2690 | const rhs = vm.pop(u64); 2691 | const lhs = vm.pop(i64); 2692 | vm.push(i64, lhs >> @truncate(u6, rhs)); 2693 | }, 2694 | .lshr_64 => { 2695 | const rhs = vm.pop(u64); 2696 | const lhs = vm.pop(u64); 2697 | vm.push(u64, lhs >> @truncate(u6, rhs)); 2698 | }, 2699 | .rol_64 => { 2700 | const rhs = vm.pop(u64); 2701 | const lhs = vm.pop(u64); 2702 | vm.push(u64, math.rotl(u64, lhs, rhs % 64)); 2703 | }, 2704 | .ror_64 => { 2705 | const rhs = vm.pop(u64); 2706 | const lhs = vm.pop(u64); 2707 | vm.push(u64, math.rotr(u64, lhs, rhs % 64)); 2708 | }, 2709 | .fabs_32 => { 2710 | vm.push(f32, @fabs(vm.pop(f32))); 2711 | }, 2712 | .fneg_32 => { 2713 | vm.push(f32, -vm.pop(f32)); 2714 | }, 2715 | .ceil_32 => { 2716 | vm.push(f32, @ceil(vm.pop(f32))); 2717 | }, 2718 | .floor_32 => { 2719 | vm.push(f32, @floor(vm.pop(f32))); 2720 | }, 2721 | .trunc_32 => { 2722 | vm.push(f32, @trunc(vm.pop(f32))); 2723 | }, 2724 | .nearest_32 => { 2725 | vm.push(f32, @round(vm.pop(f32))); 2726 | }, 2727 | .sqrt_32 => { 2728 | vm.push(f32, @sqrt(vm.pop(f32))); 2729 | }, 2730 | .fadd_32 => { 2731 | const rhs = vm.pop(f32); 2732 | const lhs = vm.pop(f32); 2733 | vm.push(f32, lhs + rhs); 2734 | }, 2735 | .fsub_32 => { 2736 | const rhs = vm.pop(f32); 2737 | const lhs = vm.pop(f32); 2738 | vm.push(f32, lhs - rhs); 2739 | }, 2740 | .fmul_32 => { 2741 | const rhs = vm.pop(f32); 2742 | const lhs = vm.pop(f32); 2743 | vm.push(f32, lhs * rhs); 2744 | }, 2745 | .fdiv_32 => { 2746 | const rhs = vm.pop(f32); 2747 | const lhs = vm.pop(f32); 2748 | vm.push(f32, lhs / rhs); 2749 | }, 2750 | .fmin_32 => { 2751 | const rhs = vm.pop(f32); 2752 | const lhs = vm.pop(f32); 2753 | vm.push(f32, @min(lhs, rhs)); 2754 | }, 2755 | .fmax_32 => { 2756 | const rhs = vm.pop(f32); 2757 | const lhs = vm.pop(f32); 2758 | vm.push(f32, @max(lhs, rhs)); 2759 | }, 2760 | .copysign_32 => { 2761 | const rhs = vm.pop(f32); 2762 | const lhs = vm.pop(f32); 2763 | vm.push(f32, math.copysign(lhs, rhs)); 2764 | }, 2765 | .fabs_64 => { 2766 | vm.push(f64, @fabs(vm.pop(f64))); 2767 | }, 2768 | .fneg_64 => { 2769 | vm.push(f64, -vm.pop(f64)); 2770 | }, 2771 | .ceil_64 => { 2772 | vm.push(f64, @ceil(vm.pop(f64))); 2773 | }, 2774 | .floor_64 => { 2775 | vm.push(f64, @floor(vm.pop(f64))); 2776 | }, 2777 | .trunc_64 => { 2778 | vm.push(f64, @trunc(vm.pop(f64))); 2779 | }, 2780 | .nearest_64 => { 2781 | vm.push(f64, @round(vm.pop(f64))); 2782 | }, 2783 | .sqrt_64 => { 2784 | vm.push(f64, @sqrt(vm.pop(f64))); 2785 | }, 2786 | .fadd_64 => { 2787 | const rhs = vm.pop(f64); 2788 | const lhs = vm.pop(f64); 2789 | vm.push(f64, lhs + rhs); 2790 | }, 2791 | .fsub_64 => { 2792 | const rhs = vm.pop(f64); 2793 | const lhs = vm.pop(f64); 2794 | vm.push(f64, lhs - rhs); 2795 | }, 2796 | .fmul_64 => { 2797 | const rhs = vm.pop(f64); 2798 | const lhs = vm.pop(f64); 2799 | vm.push(f64, lhs * rhs); 2800 | }, 2801 | .fdiv_64 => { 2802 | const rhs = vm.pop(f64); 2803 | const lhs = vm.pop(f64); 2804 | vm.push(f64, lhs / rhs); 2805 | }, 2806 | .fmin_64 => { 2807 | const rhs = vm.pop(f64); 2808 | const lhs = vm.pop(f64); 2809 | vm.push(f64, @min(lhs, rhs)); 2810 | }, 2811 | .fmax_64 => { 2812 | const rhs = vm.pop(f64); 2813 | const lhs = vm.pop(f64); 2814 | vm.push(f64, @max(lhs, rhs)); 2815 | }, 2816 | .copysign_64 => { 2817 | const rhs = vm.pop(f64); 2818 | const lhs = vm.pop(f64); 2819 | vm.push(f64, math.copysign(lhs, rhs)); 2820 | }, 2821 | .ftos_32_32 => { 2822 | vm.push(i32, @floatToInt(i32, vm.pop(f32))); 2823 | }, 2824 | .ftou_32_32 => { 2825 | vm.push(u32, @floatToInt(u32, vm.pop(f32))); 2826 | }, 2827 | .ftos_32_64 => { 2828 | vm.push(i32, @floatToInt(i32, vm.pop(f64))); 2829 | }, 2830 | .ftou_32_64 => { 2831 | vm.push(u32, @floatToInt(u32, vm.pop(f64))); 2832 | }, 2833 | .sext_64_32 => { 2834 | vm.push(i32, @bitCast(i32, vm.stack[vm.stack_top - 1]) >> 31); 2835 | }, 2836 | .ftos_64_32 => { 2837 | vm.push(i64, @floatToInt(i64, vm.pop(f32))); 2838 | }, 2839 | .ftou_64_32 => { 2840 | vm.push(u64, @floatToInt(u64, vm.pop(f32))); 2841 | }, 2842 | .ftos_64_64 => { 2843 | vm.push(i64, @floatToInt(i64, vm.pop(f64))); 2844 | }, 2845 | .ftou_64_64 => { 2846 | vm.push(u64, @floatToInt(u64, vm.pop(f64))); 2847 | }, 2848 | .stof_32_32 => { 2849 | vm.push(f32, @intToFloat(f32, vm.pop(i32))); 2850 | }, 2851 | .utof_32_32 => { 2852 | vm.push(f32, @intToFloat(f32, vm.pop(u32))); 2853 | }, 2854 | .stof_32_64 => { 2855 | vm.push(f32, @intToFloat(f32, vm.pop(i64))); 2856 | }, 2857 | .utof_32_64 => { 2858 | vm.push(f32, @intToFloat(f32, vm.pop(u64))); 2859 | }, 2860 | .ftof_32_64 => { 2861 | vm.push(f32, @floatCast(f32, vm.pop(f64))); 2862 | }, 2863 | .stof_64_32 => { 2864 | vm.push(f64, @intToFloat(f64, vm.pop(i32))); 2865 | }, 2866 | .utof_64_32 => { 2867 | vm.push(f64, @intToFloat(f64, vm.pop(u32))); 2868 | }, 2869 | .stof_64_64 => { 2870 | vm.push(f64, @intToFloat(f64, vm.pop(i64))); 2871 | }, 2872 | .utof_64_64 => { 2873 | vm.push(f64, @intToFloat(f64, vm.pop(u64))); 2874 | }, 2875 | .ftof_64_32 => { 2876 | vm.push(f64, @floatCast(f64, vm.pop(f32))); 2877 | }, 2878 | .sext8_32 => { 2879 | vm.push(i32, @truncate(i8, vm.pop(i32))); 2880 | }, 2881 | .sext16_32 => { 2882 | vm.push(i32, @truncate(i16, vm.pop(i32))); 2883 | }, 2884 | .sext8_64 => { 2885 | vm.push(i64, @truncate(i8, vm.pop(i64))); 2886 | }, 2887 | .sext16_64 => { 2888 | vm.push(i64, @truncate(i16, vm.pop(i64))); 2889 | }, 2890 | .sext32_64 => { 2891 | vm.push(i64, @truncate(i32, vm.pop(i64))); 2892 | }, 2893 | .memcpy => { 2894 | const n = vm.pop(u32); 2895 | const src = vm.pop(u32); 2896 | const dest = vm.pop(u32); 2897 | assert(dest + n <= vm.memory_len); 2898 | assert(src + n <= vm.memory_len); 2899 | assert(src + n <= dest or dest + n <= src); // overlapping 2900 | @memcpy(vm.memory.ptr + dest, vm.memory.ptr + src, n); 2901 | }, 2902 | .memset => { 2903 | const n = vm.pop(u32); 2904 | const value = @truncate(u8, vm.pop(u32)); 2905 | const dest = vm.pop(u32); 2906 | assert(dest + n <= vm.memory_len); 2907 | @memset(vm.memory.ptr + dest, value, n); 2908 | }, 2909 | } 2910 | } 2911 | } 2912 | }; 2913 | 2914 | /// fn args_sizes_get(argc: *usize, argv_buf_size: *usize) errno_t; 2915 | fn wasi_args_sizes_get(vm: *VirtualMachine, argc: u32, argv_buf_size: u32) wasi.errno_t { 2916 | trace_log.debug("wasi_args_sizes_get argc={d} argv_buf_size={d}", .{ argc, argv_buf_size }); 2917 | mem.writeIntLittle(u32, vm.memory[argc..][0..4], @intCast(u32, vm.args.len)); 2918 | var buf_size: usize = 0; 2919 | for (vm.args) |arg| { 2920 | buf_size += mem.span(arg).len + 1; 2921 | } 2922 | mem.writeIntLittle(u32, vm.memory[argv_buf_size..][0..4], @intCast(u32, buf_size)); 2923 | return .SUCCESS; 2924 | } 2925 | 2926 | /// extern fn args_get(argv: [*][*:0]u8, argv_buf: [*]u8) errno_t; 2927 | fn wasi_args_get(vm: *VirtualMachine, argv: u32, argv_buf: u32) wasi.errno_t { 2928 | trace_log.debug("wasi_args_get argv={d} argv_buf={d}", .{ argv, argv_buf }); 2929 | var argv_buf_i: usize = 0; 2930 | for (vm.args) |arg, arg_i| { 2931 | // Write the arg to the buffer. 2932 | const argv_ptr = argv_buf + argv_buf_i; 2933 | const arg_len = mem.span(arg).len + 1; 2934 | mem.copy(u8, vm.memory[argv_buf + argv_buf_i ..], arg[0..arg_len]); 2935 | argv_buf_i += arg_len; 2936 | 2937 | mem.writeIntLittle(u32, vm.memory[argv + 4 * arg_i ..][0..4], @intCast(u32, argv_ptr)); 2938 | } 2939 | return .SUCCESS; 2940 | } 2941 | 2942 | /// extern fn random_get(buf: [*]u8, buf_len: usize) errno_t; 2943 | fn wasi_random_get(vm: *VirtualMachine, buf: u32, buf_len: u32) wasi.errno_t { 2944 | const host_buf = vm.memory[buf..][0..buf_len]; 2945 | std.crypto.random.bytes(host_buf); 2946 | trace_log.debug("random_get {x}", .{std.fmt.fmtSliceHexLower(host_buf)}); 2947 | return .SUCCESS; 2948 | } 2949 | 2950 | var preopens_buffer: [10]Preopen = undefined; 2951 | var preopens_len: usize = 0; 2952 | 2953 | const Preopen = struct { 2954 | wasi_fd: wasi.fd_t, 2955 | name: []const u8, 2956 | host_fd: os.fd_t, 2957 | }; 2958 | 2959 | fn addPreopen(wasi_fd: wasi.fd_t, name: []const u8, host_fd: os.fd_t) void { 2960 | preopens_buffer[preopens_len] = .{ 2961 | .wasi_fd = wasi_fd, 2962 | .name = name, 2963 | .host_fd = host_fd, 2964 | }; 2965 | preopens_len += 1; 2966 | } 2967 | 2968 | fn findPreopen(wasi_fd: wasi.fd_t) ?Preopen { 2969 | for (preopens_buffer[0..preopens_len]) |preopen| { 2970 | if (preopen.wasi_fd == wasi_fd) { 2971 | return preopen; 2972 | } 2973 | } 2974 | return null; 2975 | } 2976 | 2977 | fn toHostFd(wasi_fd: wasi.fd_t) os.fd_t { 2978 | const preopen = findPreopen(wasi_fd) orelse return wasi_fd; 2979 | return preopen.host_fd; 2980 | } 2981 | 2982 | /// fn fd_prestat_get(fd: fd_t, buf: *prestat_t) errno_t; 2983 | /// const prestat_t = extern struct { 2984 | /// pr_type: u8, 2985 | /// u: usize, 2986 | /// }; 2987 | fn wasi_fd_prestat_get(vm: *VirtualMachine, fd: wasi.fd_t, buf: u32) wasi.errno_t { 2988 | trace_log.debug("wasi_fd_prestat_get fd={d} buf={d}", .{ fd, buf }); 2989 | const preopen = findPreopen(fd) orelse return .BADF; 2990 | mem.writeIntLittle(u32, vm.memory[buf + 0 ..][0..4], 0); 2991 | mem.writeIntLittle(u32, vm.memory[buf + 4 ..][0..4], @intCast(u32, preopen.name.len)); 2992 | return .SUCCESS; 2993 | } 2994 | 2995 | /// fn fd_prestat_dir_name(fd: fd_t, path: [*]u8, path_len: usize) errno_t; 2996 | fn wasi_fd_prestat_dir_name(vm: *VirtualMachine, fd: wasi.fd_t, path: u32, path_len: u32) wasi.errno_t { 2997 | trace_log.debug("wasi_fd_prestat_dir_name fd={d} path={d} path_len={d}", .{ fd, path, path_len }); 2998 | const preopen = findPreopen(fd) orelse return .BADF; 2999 | assert(path_len == preopen.name.len); 3000 | mem.copy(u8, vm.memory[path..], preopen.name); 3001 | return .SUCCESS; 3002 | } 3003 | 3004 | /// extern fn fd_close(fd: fd_t) errno_t; 3005 | fn wasi_fd_close(vm: *VirtualMachine, fd: wasi.fd_t) wasi.errno_t { 3006 | trace_log.debug("wasi_fd_close fd={d}", .{fd}); 3007 | _ = vm; 3008 | const host_fd = toHostFd(fd); 3009 | os.close(host_fd); 3010 | return .SUCCESS; 3011 | } 3012 | 3013 | fn wasi_fd_read( 3014 | vm: *VirtualMachine, 3015 | fd: wasi.fd_t, 3016 | iovs: u32, // [*]const iovec_t 3017 | iovs_len: u32, // usize 3018 | nread: u32, // *usize 3019 | ) wasi.errno_t { 3020 | trace_log.debug("wasi_fd_read fd={d} iovs={d} iovs_len={d} nread={d}", .{ 3021 | fd, iovs, iovs_len, nread, 3022 | }); 3023 | const host_fd = toHostFd(fd); 3024 | var i: u32 = 0; 3025 | var total_read: usize = 0; 3026 | while (i < iovs_len) : (i += 1) { 3027 | const ptr = mem.readIntLittle(u32, vm.memory[iovs + i * 8 + 0 ..][0..4]); 3028 | const len = mem.readIntLittle(u32, vm.memory[iovs + i * 8 + 4 ..][0..4]); 3029 | const buf = vm.memory[ptr..][0..len]; 3030 | const read = os.read(host_fd, buf) catch |err| return toWasiError(err); 3031 | trace_log.debug("read {d} bytes out of {d}", .{ read, buf.len }); 3032 | total_read += read; 3033 | if (read != buf.len) break; 3034 | } 3035 | mem.writeIntLittle(u32, vm.memory[nread..][0..4], @intCast(u32, total_read)); 3036 | return .SUCCESS; 3037 | } 3038 | 3039 | /// extern fn fd_write(fd: fd_t, iovs: [*]const ciovec_t, iovs_len: usize, nwritten: *usize) errno_t; 3040 | /// const ciovec_t = extern struct { 3041 | /// base: [*]const u8, 3042 | /// len: usize, 3043 | /// }; 3044 | fn wasi_fd_write(vm: *VirtualMachine, fd: wasi.fd_t, iovs: u32, iovs_len: u32, nwritten: u32) wasi.errno_t { 3045 | trace_log.debug("wasi_fd_write fd={d} iovs={d} iovs_len={d} nwritten={d}", .{ 3046 | fd, iovs, iovs_len, nwritten, 3047 | }); 3048 | const host_fd = toHostFd(fd); 3049 | var i: u32 = 0; 3050 | var total_written: usize = 0; 3051 | while (i < iovs_len) : (i += 1) { 3052 | const ptr = mem.readIntLittle(u32, vm.memory[iovs + i * 8 + 0 ..][0..4]); 3053 | const len = mem.readIntLittle(u32, vm.memory[iovs + i * 8 + 4 ..][0..4]); 3054 | const buf = vm.memory[ptr..][0..len]; 3055 | const written = os.write(host_fd, buf) catch |err| return toWasiError(err); 3056 | total_written += written; 3057 | if (written != buf.len) break; 3058 | } 3059 | mem.writeIntLittle(u32, vm.memory[nwritten..][0..4], @intCast(u32, total_written)); 3060 | return .SUCCESS; 3061 | } 3062 | 3063 | fn wasi_fd_pwrite( 3064 | vm: *VirtualMachine, 3065 | fd: wasi.fd_t, 3066 | iovs: u32, // [*]const ciovec_t 3067 | iovs_len: u32, // usize 3068 | offset: wasi.filesize_t, 3069 | written_ptr: u32, // *usize 3070 | ) wasi.errno_t { 3071 | trace_log.debug("wasi_fd_write fd={d} iovs={d} iovs_len={d} offset={d} written_ptr={d}", .{ 3072 | fd, iovs, iovs_len, offset, written_ptr, 3073 | }); 3074 | const host_fd = toHostFd(fd); 3075 | var i: u32 = 0; 3076 | var written: usize = 0; 3077 | while (i < iovs_len) : (i += 1) { 3078 | const ptr = mem.readIntLittle(u32, vm.memory[iovs + i * 8 + 0 ..][0..4]); 3079 | const len = mem.readIntLittle(u32, vm.memory[iovs + i * 8 + 4 ..][0..4]); 3080 | const buf = vm.memory[ptr..][0..len]; 3081 | const w = os.pwrite(host_fd, buf, offset + written) catch |err| return toWasiError(err); 3082 | written += w; 3083 | if (w != buf.len) break; 3084 | } 3085 | mem.writeIntLittle(u32, vm.memory[written_ptr..][0..4], @intCast(u32, written)); 3086 | return .SUCCESS; 3087 | } 3088 | 3089 | ///extern fn path_open( 3090 | /// dirfd: fd_t, 3091 | /// dirflags: lookupflags_t, 3092 | /// path: [*]const u8, 3093 | /// path_len: usize, 3094 | /// oflags: oflags_t, 3095 | /// fs_rights_base: rights_t, 3096 | /// fs_rights_inheriting: rights_t, 3097 | /// fs_flags: fdflags_t, 3098 | /// fd: *fd_t, 3099 | ///) errno_t; 3100 | fn wasi_path_open( 3101 | vm: *VirtualMachine, 3102 | dirfd: wasi.fd_t, 3103 | dirflags: wasi.lookupflags_t, 3104 | path: u32, 3105 | path_len: u32, 3106 | oflags: wasi.oflags_t, 3107 | fs_rights_base: wasi.rights_t, 3108 | fs_rights_inheriting: wasi.rights_t, 3109 | fs_flags: wasi.fdflags_t, 3110 | fd: u32, 3111 | ) wasi.errno_t { 3112 | const sub_path = vm.memory[path..][0..path_len]; 3113 | trace_log.debug("wasi_path_open dirfd={d} dirflags={d} path={s} oflags={d} fs_rights_base={d} fs_rights_inheriting={d} fs_flags={d} fd={d}", .{ 3114 | dirfd, dirflags, sub_path, oflags, fs_rights_base, fs_rights_inheriting, fs_flags, fd, 3115 | }); 3116 | const host_fd = toHostFd(dirfd); 3117 | var flags: u32 = @as(u32, if (oflags & wasi.O.CREAT != 0) os.O.CREAT else 0) | 3118 | @as(u32, if (oflags & wasi.O.DIRECTORY != 0) os.O.DIRECTORY else 0) | 3119 | @as(u32, if (oflags & wasi.O.EXCL != 0) os.O.EXCL else 0) | 3120 | @as(u32, if (oflags & wasi.O.TRUNC != 0) os.O.TRUNC else 0) | 3121 | @as(u32, if (fs_flags & wasi.FDFLAG.APPEND != 0) os.O.APPEND else 0) | 3122 | @as(u32, if (fs_flags & wasi.FDFLAG.DSYNC != 0) os.O.DSYNC else 0) | 3123 | @as(u32, if (fs_flags & wasi.FDFLAG.NONBLOCK != 0) os.O.NONBLOCK else 0) | 3124 | @as(u32, if (fs_flags & wasi.FDFLAG.SYNC != 0) os.O.SYNC else 0); 3125 | if ((fs_rights_base & wasi.RIGHT.FD_READ != 0) and 3126 | (fs_rights_base & wasi.RIGHT.FD_WRITE != 0)) 3127 | { 3128 | flags |= os.O.RDWR; 3129 | } else if (fs_rights_base & wasi.RIGHT.FD_WRITE != 0) { 3130 | flags |= os.O.WRONLY; 3131 | } else if (fs_rights_base & wasi.RIGHT.FD_READ != 0) { 3132 | flags |= os.O.RDONLY; // no-op because O_RDONLY is 0 3133 | } 3134 | const mode = 0o644; 3135 | const res_fd = os.openat(host_fd, sub_path, flags, mode) catch |err| return toWasiError(err); 3136 | mem.writeIntLittle(i32, vm.memory[fd..][0..4], res_fd); 3137 | return .SUCCESS; 3138 | } 3139 | 3140 | fn wasi_path_filestat_get( 3141 | vm: *VirtualMachine, 3142 | fd: wasi.fd_t, 3143 | flags: wasi.lookupflags_t, 3144 | path: u32, // [*]const u8 3145 | path_len: u32, // usize 3146 | buf: u32, // *filestat_t 3147 | ) wasi.errno_t { 3148 | const sub_path = vm.memory[path..][0..path_len]; 3149 | trace_log.debug("wasi_path_filestat_get fd={d} flags={d} path={s} buf={d}", .{ 3150 | fd, flags, sub_path, buf, 3151 | }); 3152 | const host_fd = toHostFd(fd); 3153 | const dir: fs.Dir = .{ .fd = host_fd }; 3154 | const stat = dir.statFile(sub_path) catch |err| return toWasiError(err); 3155 | return finishWasiStat(vm, buf, stat); 3156 | } 3157 | 3158 | /// extern fn path_create_directory(fd: fd_t, path: [*]const u8, path_len: usize) errno_t; 3159 | fn wasi_path_create_directory(vm: *VirtualMachine, fd: wasi.fd_t, path: u32, path_len: u32) wasi.errno_t { 3160 | const sub_path = vm.memory[path..][0..path_len]; 3161 | trace_log.debug("wasi_path_create_directory fd={d} path={s}", .{ fd, sub_path }); 3162 | const host_fd = toHostFd(fd); 3163 | const dir: fs.Dir = .{ .fd = host_fd }; 3164 | dir.makeDir(sub_path) catch |err| return toWasiError(err); 3165 | return .SUCCESS; 3166 | } 3167 | 3168 | fn wasi_path_rename( 3169 | vm: *VirtualMachine, 3170 | old_fd: wasi.fd_t, 3171 | old_path_ptr: u32, // [*]const u8 3172 | old_path_len: u32, // usize 3173 | new_fd: wasi.fd_t, 3174 | new_path_ptr: u32, // [*]const u8 3175 | new_path_len: u32, // usize 3176 | ) wasi.errno_t { 3177 | const old_path = vm.memory[old_path_ptr..][0..old_path_len]; 3178 | const new_path = vm.memory[new_path_ptr..][0..new_path_len]; 3179 | trace_log.debug("wasi_path_rename old_fd={d} old_path={s} new_fd={d} new_path={s}", .{ 3180 | old_fd, old_path, new_fd, new_path, 3181 | }); 3182 | const old_host_fd = toHostFd(old_fd); 3183 | const new_host_fd = toHostFd(new_fd); 3184 | os.renameat(old_host_fd, old_path, new_host_fd, new_path) catch |err| return toWasiError(err); 3185 | return .SUCCESS; 3186 | } 3187 | 3188 | /// extern fn fd_filestat_get(fd: fd_t, buf: *filestat_t) errno_t; 3189 | fn wasi_fd_filestat_get(vm: *VirtualMachine, fd: wasi.fd_t, buf: u32) wasi.errno_t { 3190 | trace_log.debug("wasi_fd_filestat_get fd={d} buf={d}", .{ fd, buf }); 3191 | const host_fd = toHostFd(fd); 3192 | const file = fs.File{ .handle = host_fd }; 3193 | const stat = file.stat() catch |err| return toWasiError(err); 3194 | return finishWasiStat(vm, buf, stat); 3195 | } 3196 | 3197 | fn wasi_fd_filestat_set_size(vm: *VirtualMachine, fd: wasi.fd_t, size: wasi.filesize_t) wasi.errno_t { 3198 | _ = vm; 3199 | trace_log.debug("wasi_fd_filestat_set_size fd={d} size={d}", .{ fd, size }); 3200 | const host_fd = toHostFd(fd); 3201 | os.ftruncate(host_fd, size) catch |err| return toWasiError(err); 3202 | return .SUCCESS; 3203 | } 3204 | 3205 | /// pub extern "wasi_snapshot_preview1" fn fd_fdstat_get(fd: fd_t, buf: *fdstat_t) errno_t; 3206 | /// pub const fdstat_t = extern struct { 3207 | /// fs_filetype: filetype_t, u8 3208 | /// fs_flags: fdflags_t, u16 3209 | /// fs_rights_base: rights_t, u64 3210 | /// fs_rights_inheriting: rights_t, u64 3211 | /// }; 3212 | fn wasi_fd_fdstat_get(vm: *VirtualMachine, fd: wasi.fd_t, buf: u32) wasi.errno_t { 3213 | trace_log.debug("wasi_fd_fdstat_get fd={d} buf={d}", .{ fd, buf }); 3214 | const host_fd = toHostFd(fd); 3215 | const file = fs.File{ .handle = host_fd }; 3216 | const stat = file.stat() catch |err| return toWasiError(err); 3217 | mem.writeIntLittle(u16, vm.memory[buf + 0x00 ..][0..2], @enumToInt(toWasiFileType(stat.kind))); 3218 | mem.writeIntLittle(u16, vm.memory[buf + 0x02 ..][0..2], 0); // flags 3219 | mem.writeIntLittle(u64, vm.memory[buf + 0x08 ..][0..8], math.maxInt(u64)); // rights_base 3220 | mem.writeIntLittle(u64, vm.memory[buf + 0x10 ..][0..8], math.maxInt(u64)); // rights_inheriting 3221 | return .SUCCESS; 3222 | } 3223 | 3224 | /// extern fn clock_time_get(clock_id: clockid_t, precision: timestamp_t, timestamp: *timestamp_t) errno_t; 3225 | fn wasi_clock_time_get(vm: *VirtualMachine, clock_id: wasi.clockid_t, precision: wasi.timestamp_t, timestamp: u32) wasi.errno_t { 3226 | //const host_clock_id = toHostClockId(clock_id); 3227 | _ = precision; 3228 | _ = clock_id; 3229 | const wasi_ts = toWasiTimestamp(std.time.nanoTimestamp()); 3230 | mem.writeIntLittle(u64, vm.memory[timestamp..][0..8], wasi_ts); 3231 | return .SUCCESS; 3232 | } 3233 | 3234 | ///pub extern "wasi_snapshot_preview1" fn debug(string: [*:0]const u8, x: u64) void; 3235 | fn wasi_debug(vm: *VirtualMachine, text: u32, n: u64) void { 3236 | const s = mem.sliceTo(vm.memory[text..], 0); 3237 | trace_log.debug("wasi_debug: '{s}' number={d} {x}", .{ s, n, n }); 3238 | } 3239 | 3240 | /// pub extern "wasi_snapshot_preview1" fn debug_slice(ptr: [*]const u8, len: usize) void; 3241 | fn wasi_debug_slice(vm: *VirtualMachine, ptr: u32, len: u32) void { 3242 | const s = vm.memory[ptr..][0..len]; 3243 | trace_log.debug("wasi_debug_slice: '{s}'", .{s}); 3244 | } 3245 | 3246 | fn toWasiTimestamp(ns: i128) u64 { 3247 | return @intCast(u64, ns); 3248 | } 3249 | 3250 | fn toWasiError(err: anyerror) wasi.errno_t { 3251 | trace_log.warn("wasi error: {s}", .{@errorName(err)}); 3252 | return switch (err) { 3253 | error.AccessDenied => .ACCES, 3254 | error.DiskQuota => .DQUOT, 3255 | error.InputOutput => .IO, 3256 | error.FileTooBig => .FBIG, 3257 | error.NoSpaceLeft => .NOSPC, 3258 | error.BrokenPipe => .PIPE, 3259 | error.NotOpenForWriting => .BADF, 3260 | error.SystemResources => .NOMEM, 3261 | error.FileNotFound => .NOENT, 3262 | error.PathAlreadyExists => .EXIST, 3263 | else => std.debug.panic("unexpected error: {s}", .{@errorName(err)}), 3264 | }; 3265 | } 3266 | 3267 | fn toWasiFileType(kind: fs.File.Kind) wasi.filetype_t { 3268 | return switch (kind) { 3269 | .BlockDevice => .BLOCK_DEVICE, 3270 | .CharacterDevice => .CHARACTER_DEVICE, 3271 | .Directory => .DIRECTORY, 3272 | .SymLink => .SYMBOLIC_LINK, 3273 | .File => .REGULAR_FILE, 3274 | .Unknown => .UNKNOWN, 3275 | 3276 | .NamedPipe, 3277 | .UnixDomainSocket, 3278 | .Whiteout, 3279 | .Door, 3280 | .EventPort, 3281 | => .UNKNOWN, 3282 | }; 3283 | } 3284 | 3285 | /// const filestat_t = extern struct { 3286 | /// dev: device_t, u64 3287 | /// ino: inode_t, u64 3288 | /// filetype: filetype_t, u8 3289 | /// nlink: linkcount_t, u64 3290 | /// size: filesize_t, u64 3291 | /// atim: timestamp_t, u64 3292 | /// mtim: timestamp_t, u64 3293 | /// ctim: timestamp_t, u64 3294 | /// }; 3295 | fn finishWasiStat(vm: *VirtualMachine, buf: u32, stat: fs.File.Stat) wasi.errno_t { 3296 | mem.writeIntLittle(u64, vm.memory[buf + 0x00 ..][0..8], 0); // device 3297 | mem.writeIntLittle(u64, vm.memory[buf + 0x08 ..][0..8], stat.inode); 3298 | mem.writeIntLittle(u64, vm.memory[buf + 0x10 ..][0..8], @enumToInt(toWasiFileType(stat.kind))); 3299 | mem.writeIntLittle(u64, vm.memory[buf + 0x18 ..][0..8], 1); // nlink 3300 | mem.writeIntLittle(u64, vm.memory[buf + 0x20 ..][0..8], stat.size); 3301 | mem.writeIntLittle(u64, vm.memory[buf + 0x28 ..][0..8], toWasiTimestamp(stat.atime)); 3302 | mem.writeIntLittle(u64, vm.memory[buf + 0x30 ..][0..8], toWasiTimestamp(stat.mtime)); 3303 | mem.writeIntLittle(u64, vm.memory[buf + 0x38 ..][0..8], toWasiTimestamp(stat.ctime)); 3304 | return .SUCCESS; 3305 | } 3306 | --------------------------------------------------------------------------------