From c32f7a451356817091f874a97c78dd9abb9468fe Mon Sep 17 00:00:00 2001 From: Pavel Verigo Date: Wed, 15 Apr 2026 03:30:55 +0200 Subject: [PATCH] stage2-wasm: support assembly --- src/codegen/wasm/CodeGen.zig | 99 ++++- src/codegen/wasm/assembly.zig | 717 ++++++++++++++++++++++++++++++++++ src/dev.zig | 3 +- test/behavior/wasm.zig | 158 +++++++- 4 files changed, 959 insertions(+), 18 deletions(-) create mode 100644 src/codegen/wasm/assembly.zig diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig index 14f2716b3300c297b4dee8fa6803ab9658f5d940..82fd6353241cc11c4dbc4847896e5e95d8a43702 100644 --- a/src/codegen/wasm/CodeGen.zig +++ b/src/codegen/wasm/CodeGen.zig @@ -17,6 +17,7 @@ const Compilation = @import("../../Compilation.zig"); const link = @import("../../link.zig"); const Air = @import("../../Air.zig"); const Mir = @import("Mir.zig"); +const assembly = @import("assembly.zig"); const abi = @import("../../codegen/wasm/abi.zig"); const Alignment = InternPool.Alignment; const errUnionPayloadOffset = codegen.errUnionPayloadOffset; @@ -277,7 +278,7 @@ pub fn deinit(cg: *CodeGen) void { cg.* = undefined; } -fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { +pub fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { const zcu = cg.pt.zcu; const func = zcu.funcInfo(cg.func_index); return zcu.codegenFail(func.owner_nav, fmt, args); @@ -401,45 +402,45 @@ fn processDeath(cg: *CodeGen, ref: Air.Inst.Ref) void { } } -fn addInst(cg: *CodeGen, inst: Mir.Inst) error{OutOfMemory}!void { +pub fn addInst(cg: *CodeGen, inst: Mir.Inst) error{OutOfMemory}!void { try cg.mir_instructions.append(cg.gpa, inst); } -fn addTag(cg: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void { +pub fn addTag(cg: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void { try cg.addInst(.{ .tag = tag, .data = .{ .tag = {} } }); } -fn addExtended(cg: *CodeGen, opcode: std.wasm.MiscOpcode) error{OutOfMemory}!void { +pub fn addExtended(cg: *CodeGen, opcode: std.wasm.MiscOpcode) error{OutOfMemory}!void { const extra_index: u32 = @intCast(cg.mir_extra.items.len); try cg.mir_extra.append(cg.gpa, @intFromEnum(opcode)); try cg.addInst(.{ .tag = .misc_prefix, .data = .{ .payload = extra_index } }); } -fn addLabel(cg: *CodeGen, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void { +pub fn addLabel(cg: *CodeGen, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void { try cg.addInst(.{ .tag = tag, .data = .{ .label = label } }); } -fn addLocal(cg: *CodeGen, tag: Mir.Inst.Tag, local: u32) error{OutOfMemory}!void { +pub fn addLocal(cg: *CodeGen, tag: Mir.Inst.Tag, local: u32) error{OutOfMemory}!void { try cg.addInst(.{ .tag = tag, .data = .{ .local = local } }); } /// Accepts an unsigned 32bit integer rather than a signed integer to /// prevent us from having to bitcast multiple times as most values /// within codegen are represented as unsigned rather than signed. -fn addImm32(cg: *CodeGen, imm: u32) error{OutOfMemory}!void { +pub fn addImm32(cg: *CodeGen, imm: u32) error{OutOfMemory}!void { try cg.addInst(.{ .tag = .i32_const, .data = .{ .imm32 = @bitCast(imm) } }); } /// Accepts an unsigned 64bit integer rather than a signed integer to /// prevent us from having to bitcast multiple times as most values /// within codegen are represented as unsigned rather than signed. -fn addImm64(cg: *CodeGen, imm: u64) error{OutOfMemory}!void { +pub fn addImm64(cg: *CodeGen, imm: u64) error{OutOfMemory}!void { const extra_index = try cg.addExtra(Mir.Imm64.init(imm)); try cg.addInst(.{ .tag = .i64_const, .data = .{ .payload = extra_index } }); } /// Accepts the index into the list of 128bit-immediates -fn addImm128(cg: *CodeGen, index: u32) error{OutOfMemory}!void { +pub fn addImm128(cg: *CodeGen, index: u32) error{OutOfMemory}!void { const simd_values = cg.simd_immediates.items[index]; const extra_index: u32 = @intCast(cg.mir_extra.items.len); // tag + 128bit value @@ -449,27 +450,31 @@ fn addImm128(cg: *CodeGen, index: u32) error{OutOfMemory}!void { try cg.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } }); } -fn addFloat64(cg: *CodeGen, float: f64) error{OutOfMemory}!void { +pub fn addFloat32(cg: *CodeGen, float: f32) error{OutOfMemory}!void { + try cg.addInst(.{ .tag = .f32_const, .data = .{ .float32 = float } }); +} + +pub fn addFloat64(cg: *CodeGen, float: f64) error{OutOfMemory}!void { const extra_index = try cg.addExtra(Mir.Float64.init(float)); try cg.addInst(.{ .tag = .f64_const, .data = .{ .payload = extra_index } }); } /// Inserts an instruction to load/store from/to wasm's linear memory dependent on the given `tag`. -fn addMemArg(cg: *CodeGen, tag: Mir.Inst.Tag, mem_arg: Mir.MemArg) error{OutOfMemory}!void { +pub fn addMemArg(cg: *CodeGen, tag: Mir.Inst.Tag, mem_arg: Mir.MemArg) error{OutOfMemory}!void { const extra_index = try cg.addExtra(mem_arg); try cg.addInst(.{ .tag = tag, .data = .{ .payload = extra_index } }); } /// Inserts an instruction from the 'atomics' feature which accesses wasm's linear memory dependent on the /// given `tag`. -fn addAtomicMemArg(cg: *CodeGen, tag: std.wasm.AtomicsOpcode, mem_arg: Mir.MemArg) error{OutOfMemory}!void { +pub fn addAtomicMemArg(cg: *CodeGen, tag: std.wasm.AtomicsOpcode, mem_arg: Mir.MemArg) error{OutOfMemory}!void { const extra_index = try cg.addExtra(@as(struct { val: u32 }, .{ .val = @intFromEnum(tag) })); _ = try cg.addExtra(mem_arg); try cg.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } }); } /// Helper function to emit atomic mir opcodes. -fn addAtomicTag(cg: *CodeGen, tag: std.wasm.AtomicsOpcode) error{OutOfMemory}!void { +pub fn addAtomicTag(cg: *CodeGen, tag: std.wasm.AtomicsOpcode) error{OutOfMemory}!void { const extra_index = try cg.addExtra(@as(struct { val: u32 }, .{ .val = @intFromEnum(tag) })); try cg.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } }); } @@ -550,7 +555,7 @@ fn emitWValue(cg: *CodeGen, value: WValue) InnerError!void { .imm32 => |val| try cg.addImm32(val), .imm64 => |val| try cg.addImm64(val), .imm128 => |val| try cg.addImm128(val), - .float32 => |val| try cg.addInst(.{ .tag = .f32_const, .data = .{ .float32 = val } }), + .float32 => |val| try cg.addFloat32(val), .float64 => |val| try cg.addFloat64(val), .nav_ref => |nav_ref| { const zcu = cg.pt.zcu; @@ -1832,7 +1837,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .runtime_nav_ptr => cg.airRuntimeNavPtr(inst), - .assembly, + .assembly => cg.airAsm(inst), + .err_return_trace, .set_err_return_trace, .save_err_return_trace_index, @@ -7153,6 +7159,69 @@ fn airRuntimeNavPtr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { return cg.fail("TODO: thread-local variables", .{}); } +fn airAsm(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { + const unwrapped_asm = cg.air.unwrapAsm(inst); + const outputs = unwrapped_asm.outputs; + const inputs = unwrapped_asm.inputs; + + const zcu = cg.pt.zcu; + const output_ty = cg.typeOfIndex(inst); + + const result: WValue = if (output_ty.hasRuntimeBits(zcu)) + try cg.allocLocal(output_ty) + else + .none; + + var local_map: assembly.LocalMap = .empty; + defer local_map.deinit(cg.gpa); + + { + var it = unwrapped_asm.iterateOutputs(); + if (it.next()) |output| { + const constraint = output.constraint; + assert(output.operand == .none); + const name = output.name; + + if (!mem.eql(u8, constraint, "=r")) { + return cg.fail("Self-hosted wasm backend requires output constraint to be equal \"=r\"", .{}); + } + + const gop = try local_map.getOrPutValue(cg.gpa, name, result.local.value); + assert(!gop.found_existing); // first value + + assert(it.next() == null); + } + } + + { + var it = unwrapped_asm.iterateInputs(); + while (it.next()) |input| { + const constraint = input.constraint; + const operand = try cg.resolveInst(input.operand); + const name = input.name; + + if (!mem.eql(u8, constraint, "r")) { + return cg.fail("Self-hosted wasm backend requires input constraint to be equal \"r\"", .{}); + } + + try cg.lowerToStack(operand); + const op_local = try WValue.toLocal(.stack, cg, cg.typeOf(input.operand)); + + const gop = try local_map.getOrPutValue(cg.gpa, name, op_local.local.value); + if (gop.found_existing) { + return cg.fail("Duplicate asm variable name \"{s}\"", .{name}); + } + } + } + + try assembly.assemble(cg, unwrapped_asm.source, &local_map); + + var bt = cg.liveness.iterateBigTomb(inst); + for (outputs) |output| if (output != .none) cg.feed(&bt, output); + for (inputs) |input| cg.feed(&bt, input); + return cg.finishAirResult(inst, result); +} + fn typeOf(cg: *CodeGen, inst: Air.Inst.Ref) Type { const zcu = cg.pt.zcu; return cg.air.typeOf(inst, &zcu.intern_pool); diff --git a/src/codegen/wasm/assembly.zig b/src/codegen/wasm/assembly.zig new file mode 100644 index 0000000000000000000000000000000000000000..d52c83d461a07d252c164d35ea5162e9f973d293 --- /dev/null +++ b/src/codegen/wasm/assembly.zig @@ -0,0 +1,717 @@ +const std = @import("std"); +const mem = std.mem; +const assert = std.debug.assert; +const CodeGen = @import("CodeGen.zig"); +const Mir = @import("Mir.zig"); + +pub const LocalMap = std.array_hash_map.String(u32); + +pub fn assemble( + cg: *CodeGen, + source: [:0]const u8, + local_map: *const LocalMap, +) !void { + var line_it = mem.tokenizeAny(u8, source, "\n\r;"); + next_line: while (line_it.next()) |line| { + var word_it = mem.tokenizeAny(u8, line, " \t"); + const inst = word_it.next() orelse continue :next_line; + + if (mem.eql(u8, inst, "unreachable")) { + try cg.addTag(.@"unreachable"); + } else if (mem.eql(u8, inst, "block")) { + try cg.addTag(.block); + } else if (mem.eql(u8, inst, "loop")) { + try cg.addTag(.loop); + } else if (mem.eql(u8, inst, "end")) { + try cg.addTag(.end); + } else if (mem.eql(u8, inst, "br")) { + const label = try parseLabel(cg, inst, &word_it); + try cg.addLabel(.br, label); + } else if (mem.eql(u8, inst, "br_if")) { + const label = try parseLabel(cg, inst, &word_it); + try cg.addLabel(.br_if, label); + } else if (mem.eql(u8, inst, "return")) { + try cg.addTag(.@"return"); + } else if (mem.eql(u8, inst, "drop")) { + try cg.addTag(.drop); + } else if (mem.eql(u8, inst, "select")) { + try cg.addTag(.select); + } else if (mem.eql(u8, inst, "local.get")) { + const local = try parseLocalArg(cg, inst, &word_it, local_map); + try cg.addLocal(.local_get, local); + } else if (mem.eql(u8, inst, "local.set")) { + const local = try parseLocalArg(cg, inst, &word_it, local_map); + try cg.addLocal(.local_set, local); + } else if (mem.eql(u8, inst, "local.tee")) { + const local = try parseLocalArg(cg, inst, &word_it, local_map); + try cg.addLocal(.local_tee, local); + } else if (mem.eql(u8, inst, "i32.load")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i32_load, mem_arg); + } else if (mem.eql(u8, inst, "i64.load")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i64_load, mem_arg); + } else if (mem.eql(u8, inst, "f32.load")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.f32_load, mem_arg); + } else if (mem.eql(u8, inst, "f64.load")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.f64_load, mem_arg); + } else if (mem.eql(u8, inst, "i32.load8_s")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i32_load8_s, mem_arg); + } else if (mem.eql(u8, inst, "i32.load8_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i32_load8_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.load16_s")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i32_load16_s, mem_arg); + } else if (mem.eql(u8, inst, "i32.load16_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i32_load16_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.load8_s")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i64_load8_s, mem_arg); + } else if (mem.eql(u8, inst, "i64.load8_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i64_load8_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.load16_s")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i64_load16_s, mem_arg); + } else if (mem.eql(u8, inst, "i64.load16_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i64_load16_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.load32_s")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i64_load32_s, mem_arg); + } else if (mem.eql(u8, inst, "i64.load32_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i64_load32_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.store")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i32_store, mem_arg); + } else if (mem.eql(u8, inst, "i64.store")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i64_store, mem_arg); + } else if (mem.eql(u8, inst, "f32.store")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.f32_store, mem_arg); + } else if (mem.eql(u8, inst, "f64.store")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.f64_store, mem_arg); + } else if (mem.eql(u8, inst, "i32.store8")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i32_store8, mem_arg); + } else if (mem.eql(u8, inst, "i32.store16")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i32_store16, mem_arg); + } else if (mem.eql(u8, inst, "i64.store8")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i64_store8, mem_arg); + } else if (mem.eql(u8, inst, "i64.store16")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i64_store16, mem_arg); + } else if (mem.eql(u8, inst, "i64.store32")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addMemArg(.i64_store32, mem_arg); + } else if (mem.eql(u8, inst, "memory.size")) { + try cg.addLabel(.memory_size, 0); + } else if (mem.eql(u8, inst, "memory.grow")) { + try cg.addLabel(.memory_grow, 0); + } else if (mem.eql(u8, inst, "i32.const")) { + const value = try parseInt(i32, cg, inst, &word_it); + try cg.addImm32(@bitCast(value)); + } else if (mem.eql(u8, inst, "i64.const")) { + const value = try parseInt(i64, cg, inst, &word_it); + try cg.addImm64(@bitCast(value)); + } else if (mem.eql(u8, inst, "f32.const")) { + const value = try parseFloat(f32, cg, inst, &word_it); + try cg.addFloat32(value); + } else if (mem.eql(u8, inst, "f64.const")) { + const value = try parseFloat(f64, cg, inst, &word_it); + try cg.addFloat64(value); + } else if (mem.eql(u8, inst, "i32.eqz")) { + try cg.addTag(.i32_eqz); + } else if (mem.eql(u8, inst, "i32.eq")) { + try cg.addTag(.i32_eq); + } else if (mem.eql(u8, inst, "i32.ne")) { + try cg.addTag(.i32_ne); + } else if (mem.eql(u8, inst, "i32.lt_s")) { + try cg.addTag(.i32_lt_s); + } else if (mem.eql(u8, inst, "i32.lt_u")) { + try cg.addTag(.i32_lt_u); + } else if (mem.eql(u8, inst, "i32.gt_s")) { + try cg.addTag(.i32_gt_s); + } else if (mem.eql(u8, inst, "i32.gt_u")) { + try cg.addTag(.i32_gt_u); + } else if (mem.eql(u8, inst, "i32.le_s")) { + try cg.addTag(.i32_le_s); + } else if (mem.eql(u8, inst, "i32.le_u")) { + try cg.addTag(.i32_le_u); + } else if (mem.eql(u8, inst, "i32.ge_s")) { + try cg.addTag(.i32_ge_s); + } else if (mem.eql(u8, inst, "i32.ge_u")) { + try cg.addTag(.i32_ge_u); + } else if (mem.eql(u8, inst, "i64.eqz")) { + try cg.addTag(.i64_eqz); + } else if (mem.eql(u8, inst, "i64.eq")) { + try cg.addTag(.i64_eq); + } else if (mem.eql(u8, inst, "i64.ne")) { + try cg.addTag(.i64_ne); + } else if (mem.eql(u8, inst, "i64.lt_s")) { + try cg.addTag(.i64_lt_s); + } else if (mem.eql(u8, inst, "i64.lt_u")) { + try cg.addTag(.i64_lt_u); + } else if (mem.eql(u8, inst, "i64.gt_s")) { + try cg.addTag(.i64_gt_s); + } else if (mem.eql(u8, inst, "i64.gt_u")) { + try cg.addTag(.i64_gt_u); + } else if (mem.eql(u8, inst, "i64.le_s")) { + try cg.addTag(.i64_le_s); + } else if (mem.eql(u8, inst, "i64.le_u")) { + try cg.addTag(.i64_le_u); + } else if (mem.eql(u8, inst, "i64.ge_s")) { + try cg.addTag(.i64_ge_s); + } else if (mem.eql(u8, inst, "i64.ge_u")) { + try cg.addTag(.i64_ge_u); + } else if (mem.eql(u8, inst, "f32.eq")) { + try cg.addTag(.f32_eq); + } else if (mem.eql(u8, inst, "f32.ne")) { + try cg.addTag(.f32_ne); + } else if (mem.eql(u8, inst, "f32.lt")) { + try cg.addTag(.f32_lt); + } else if (mem.eql(u8, inst, "f32.gt")) { + try cg.addTag(.f32_gt); + } else if (mem.eql(u8, inst, "f32.le")) { + try cg.addTag(.f32_le); + } else if (mem.eql(u8, inst, "f32.ge")) { + try cg.addTag(.f32_ge); + } else if (mem.eql(u8, inst, "f64.eq")) { + try cg.addTag(.f64_eq); + } else if (mem.eql(u8, inst, "f64.ne")) { + try cg.addTag(.f64_ne); + } else if (mem.eql(u8, inst, "f64.lt")) { + try cg.addTag(.f64_lt); + } else if (mem.eql(u8, inst, "f64.gt")) { + try cg.addTag(.f64_gt); + } else if (mem.eql(u8, inst, "f64.le")) { + try cg.addTag(.f64_le); + } else if (mem.eql(u8, inst, "f64.ge")) { + try cg.addTag(.f64_ge); + } else if (mem.eql(u8, inst, "i32.clz")) { + try cg.addTag(.i32_clz); + } else if (mem.eql(u8, inst, "i32.ctz")) { + try cg.addTag(.i32_ctz); + } else if (mem.eql(u8, inst, "i32.popcnt")) { + try cg.addTag(.i32_popcnt); + } else if (mem.eql(u8, inst, "i32.add")) { + try cg.addTag(.i32_add); + } else if (mem.eql(u8, inst, "i32.sub")) { + try cg.addTag(.i32_sub); + } else if (mem.eql(u8, inst, "i32.mul")) { + try cg.addTag(.i32_mul); + } else if (mem.eql(u8, inst, "i32.div_s")) { + try cg.addTag(.i32_div_s); + } else if (mem.eql(u8, inst, "i32.div_u")) { + try cg.addTag(.i32_div_u); + } else if (mem.eql(u8, inst, "i32.rem_s")) { + try cg.addTag(.i32_rem_s); + } else if (mem.eql(u8, inst, "i32.rem_u")) { + try cg.addTag(.i32_rem_u); + } else if (mem.eql(u8, inst, "i32.and")) { + try cg.addTag(.i32_and); + } else if (mem.eql(u8, inst, "i32.or")) { + try cg.addTag(.i32_or); + } else if (mem.eql(u8, inst, "i32.xor")) { + try cg.addTag(.i32_xor); + } else if (mem.eql(u8, inst, "i32.shl")) { + try cg.addTag(.i32_shl); + } else if (mem.eql(u8, inst, "i32.shr_s")) { + try cg.addTag(.i32_shr_s); + } else if (mem.eql(u8, inst, "i32.shr_u")) { + try cg.addTag(.i32_shr_u); + } else if (mem.eql(u8, inst, "i64.clz")) { + try cg.addTag(.i64_clz); + } else if (mem.eql(u8, inst, "i64.ctz")) { + try cg.addTag(.i64_ctz); + } else if (mem.eql(u8, inst, "i64.popcnt")) { + try cg.addTag(.i64_popcnt); + } else if (mem.eql(u8, inst, "i64.add")) { + try cg.addTag(.i64_add); + } else if (mem.eql(u8, inst, "i64.sub")) { + try cg.addTag(.i64_sub); + } else if (mem.eql(u8, inst, "i64.mul")) { + try cg.addTag(.i64_mul); + } else if (mem.eql(u8, inst, "i64.div_s")) { + try cg.addTag(.i64_div_s); + } else if (mem.eql(u8, inst, "i64.div_u")) { + try cg.addTag(.i64_div_u); + } else if (mem.eql(u8, inst, "i64.rem_s")) { + try cg.addTag(.i64_rem_s); + } else if (mem.eql(u8, inst, "i64.rem_u")) { + try cg.addTag(.i64_rem_u); + } else if (mem.eql(u8, inst, "i64.and")) { + try cg.addTag(.i64_and); + } else if (mem.eql(u8, inst, "i64.or")) { + try cg.addTag(.i64_or); + } else if (mem.eql(u8, inst, "i64.xor")) { + try cg.addTag(.i64_xor); + } else if (mem.eql(u8, inst, "i64.shl")) { + try cg.addTag(.i64_shl); + } else if (mem.eql(u8, inst, "i64.shr_s")) { + try cg.addTag(.i64_shr_s); + } else if (mem.eql(u8, inst, "i64.shr_u")) { + try cg.addTag(.i64_shr_u); + } else if (mem.eql(u8, inst, "f32.abs")) { + try cg.addTag(.f32_abs); + } else if (mem.eql(u8, inst, "f32.neg")) { + try cg.addTag(.f32_neg); + } else if (mem.eql(u8, inst, "f32.ceil")) { + try cg.addTag(.f32_ceil); + } else if (mem.eql(u8, inst, "f32.floor")) { + try cg.addTag(.f32_floor); + } else if (mem.eql(u8, inst, "f32.trunc")) { + try cg.addTag(.f32_trunc); + } else if (mem.eql(u8, inst, "f32.nearest")) { + try cg.addTag(.f32_nearest); + } else if (mem.eql(u8, inst, "f32.sqrt")) { + try cg.addTag(.f32_sqrt); + } else if (mem.eql(u8, inst, "f32.add")) { + try cg.addTag(.f32_add); + } else if (mem.eql(u8, inst, "f32.sub")) { + try cg.addTag(.f32_sub); + } else if (mem.eql(u8, inst, "f32.mul")) { + try cg.addTag(.f32_mul); + } else if (mem.eql(u8, inst, "f32.div")) { + try cg.addTag(.f32_div); + } else if (mem.eql(u8, inst, "f32.min")) { + try cg.addTag(.f32_min); + } else if (mem.eql(u8, inst, "f32.max")) { + try cg.addTag(.f32_max); + } else if (mem.eql(u8, inst, "f32.copysign")) { + try cg.addTag(.f32_copysign); + } else if (mem.eql(u8, inst, "f64.abs")) { + try cg.addTag(.f64_abs); + } else if (mem.eql(u8, inst, "f64.neg")) { + try cg.addTag(.f64_neg); + } else if (mem.eql(u8, inst, "f64.ceil")) { + try cg.addTag(.f64_ceil); + } else if (mem.eql(u8, inst, "f64.floor")) { + try cg.addTag(.f64_floor); + } else if (mem.eql(u8, inst, "f64.trunc")) { + try cg.addTag(.f64_trunc); + } else if (mem.eql(u8, inst, "f64.nearest")) { + try cg.addTag(.f64_nearest); + } else if (mem.eql(u8, inst, "f64.sqrt")) { + try cg.addTag(.f64_sqrt); + } else if (mem.eql(u8, inst, "f64.add")) { + try cg.addTag(.f64_add); + } else if (mem.eql(u8, inst, "f64.sub")) { + try cg.addTag(.f64_sub); + } else if (mem.eql(u8, inst, "f64.mul")) { + try cg.addTag(.f64_mul); + } else if (mem.eql(u8, inst, "f64.div")) { + try cg.addTag(.f64_div); + } else if (mem.eql(u8, inst, "f64.min")) { + try cg.addTag(.f64_min); + } else if (mem.eql(u8, inst, "f64.max")) { + try cg.addTag(.f64_max); + } else if (mem.eql(u8, inst, "f64.copysign")) { + try cg.addTag(.f64_copysign); + } else if (mem.eql(u8, inst, "i32.wrap_i64")) { + try cg.addTag(.i32_wrap_i64); + } else if (mem.eql(u8, inst, "i32.trunc_f32_s")) { + try cg.addTag(.i32_trunc_f32_s); + } else if (mem.eql(u8, inst, "i32.trunc_f32_u")) { + try cg.addTag(.i32_trunc_f32_u); + } else if (mem.eql(u8, inst, "i32.trunc_f64_s")) { + try cg.addTag(.i32_trunc_f64_s); + } else if (mem.eql(u8, inst, "i32.trunc_f64_u")) { + try cg.addTag(.i32_trunc_f64_u); + } else if (mem.eql(u8, inst, "i64.extend_i32_s")) { + try cg.addTag(.i64_extend_i32_s); + } else if (mem.eql(u8, inst, "i64.extend_i32_u")) { + try cg.addTag(.i64_extend_i32_u); + } else if (mem.eql(u8, inst, "i64.trunc_f32_s")) { + try cg.addTag(.i64_trunc_f32_s); + } else if (mem.eql(u8, inst, "i64.trunc_f32_u")) { + try cg.addTag(.i64_trunc_f32_u); + } else if (mem.eql(u8, inst, "i64.trunc_f64_s")) { + try cg.addTag(.i64_trunc_f64_s); + } else if (mem.eql(u8, inst, "i64.trunc_f64_u")) { + try cg.addTag(.i64_trunc_f64_u); + } else if (mem.eql(u8, inst, "f32.convert_i32_s")) { + try cg.addTag(.f32_convert_i32_s); + } else if (mem.eql(u8, inst, "f32.convert_i32_u")) { + try cg.addTag(.f32_convert_i32_u); + } else if (mem.eql(u8, inst, "f32.convert_i64_s")) { + try cg.addTag(.f32_convert_i64_s); + } else if (mem.eql(u8, inst, "f32.convert_i64_u")) { + try cg.addTag(.f32_convert_i64_u); + } else if (mem.eql(u8, inst, "f32.demote_f64")) { + try cg.addTag(.f32_demote_f64); + } else if (mem.eql(u8, inst, "f64.convert_i32_s")) { + try cg.addTag(.f64_convert_i32_s); + } else if (mem.eql(u8, inst, "f64.convert_i32_u")) { + try cg.addTag(.f64_convert_i32_u); + } else if (mem.eql(u8, inst, "f64.convert_i64_s")) { + try cg.addTag(.f64_convert_i64_s); + } else if (mem.eql(u8, inst, "f64.convert_i64_u")) { + try cg.addTag(.f64_convert_i64_u); + } else if (mem.eql(u8, inst, "f64.promote_f32")) { + try cg.addTag(.f64_promote_f32); + } else if (mem.eql(u8, inst, "i32.reinterpret_f32")) { + try cg.addTag(.i32_reinterpret_f32); + } else if (mem.eql(u8, inst, "i64.reinterpret_f64")) { + try cg.addTag(.i64_reinterpret_f64); + } else if (mem.eql(u8, inst, "f32.reinterpret_i32")) { + try cg.addTag(.f32_reinterpret_i32); + } else if (mem.eql(u8, inst, "f64.reinterpret_i64")) { + try cg.addTag(.f64_reinterpret_i64); + } else if (mem.eql(u8, inst, "i32.extend8_s")) { + try cg.addTag(.i32_extend8_s); + } else if (mem.eql(u8, inst, "i32.extend16_s")) { + try cg.addTag(.i32_extend16_s); + } else if (mem.eql(u8, inst, "i64.extend8_s")) { + try cg.addTag(.i64_extend8_s); + } else if (mem.eql(u8, inst, "i64.extend16_s")) { + try cg.addTag(.i64_extend16_s); + } else if (mem.eql(u8, inst, "i64.extend32_s")) { + try cg.addTag(.i64_extend32_s); + } else if (mem.eql(u8, inst, "i32.trunc_sat_f32_s")) { + try cg.addExtended(.i32_trunc_sat_f32_s); + } else if (mem.eql(u8, inst, "i32.trunc_sat_f32_u")) { + try cg.addExtended(.i32_trunc_sat_f32_u); + } else if (mem.eql(u8, inst, "i32.trunc_sat_f64_s")) { + try cg.addExtended(.i32_trunc_sat_f64_s); + } else if (mem.eql(u8, inst, "i32.trunc_sat_f64_u")) { + try cg.addExtended(.i32_trunc_sat_f64_u); + } else if (mem.eql(u8, inst, "i64.trunc_sat_f32_s")) { + try cg.addExtended(.i64_trunc_sat_f32_s); + } else if (mem.eql(u8, inst, "i64.trunc_sat_f32_u")) { + try cg.addExtended(.i64_trunc_sat_f32_u); + } else if (mem.eql(u8, inst, "i64.trunc_sat_f64_s")) { + try cg.addExtended(.i64_trunc_sat_f64_s); + } else if (mem.eql(u8, inst, "i64.trunc_sat_f64_u")) { + try cg.addExtended(.i64_trunc_sat_f64_u); + } else if (mem.eql(u8, inst, "memory.init")) { + try cg.addExtended(.memory_init); + } else if (mem.eql(u8, inst, "data.drop")) { + try cg.addExtended(.data_drop); + } else if (mem.eql(u8, inst, "memory.copy")) { + const str1 = word_it.next(); + if (str1 == null or !mem.eql(u8, str1.?, "0,")) { + return cg.fail("Self-hosted backend requires memory.copy be in form of \"memory.copy 0, 0\"", .{}); + } + const str2 = word_it.next(); + if (str2 == null or !mem.eql(u8, str2.?, "0")) { + return cg.fail("Self-hosted backend requires memory.copy be in form of \"memory.copy 0, 0\"", .{}); + } + try cg.addExtended(.memory_copy); + } else if (mem.eql(u8, inst, "memory.fill")) { + const str = word_it.next(); + if (str == null or !mem.eql(u8, str.?, "0")) { + return cg.fail("Self-hosted backend requires memory.fill be in form of \"memory.fill 0\"", .{}); + } + try cg.addExtended(.memory_fill); + } else if (mem.eql(u8, inst, "table.init")) { + try cg.addExtended(.table_init); + } else if (mem.eql(u8, inst, "elem.drop")) { + try cg.addExtended(.elem_drop); + } else if (mem.eql(u8, inst, "table.copy")) { + try cg.addExtended(.table_copy); + } else if (mem.eql(u8, inst, "table.grow")) { + try cg.addExtended(.table_grow); + } else if (mem.eql(u8, inst, "table.size")) { + try cg.addExtended(.table_size); + } else if (mem.eql(u8, inst, "table.fill")) { + try cg.addExtended(.table_fill); + } else if (mem.eql(u8, inst, "memory.atomic.notify")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.memory_atomic_notify, mem_arg); + } else if (mem.eql(u8, inst, "memory.atomic.wait32")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.memory_atomic_wait32, mem_arg); + } else if (mem.eql(u8, inst, "memory.atomic.wait64")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.memory_atomic_wait64, mem_arg); + } else if (mem.eql(u8, inst, "atomic.fence")) { + try cg.addAtomicTag(.atomic_fence); + } else if (mem.eql(u8, inst, "i32.atomic.load")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_load, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.load")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_load, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.load8_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_load8_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.load16_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_load16_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.load8_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_load8_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.load16_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_load16_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.load32_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_load32_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.store")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_store, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.store")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_store, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.store8")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_store8, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.store16")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_store16, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.store8")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_store8, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.store16")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_store16, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.store32")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_store32, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw.add")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw_add, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw.add")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw_add, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw8.add_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw8_add_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw16.add_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw16_add_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw8.add_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw8_add_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw16.add_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw16_add_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw32.add_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw32_add_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw.sub")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw_sub, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw.sub")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw_sub, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw8.sub_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw8_sub_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw16.sub_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw16_sub_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw8.sub_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw8_sub_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw16.sub_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw16_sub_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw32.sub_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw32_sub_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw.and")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw_and, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw.and")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw_and, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw8.and_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw8_and_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw16.and_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw16_and_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw8.and_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw8_and_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw16.and_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw16_and_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw32.and_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw32_and_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw.or")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw_or, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw.or")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw_or, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw8.or_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw8_or_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw16.or_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw16_or_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw8.or_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw8_or_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw16.or_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw16_or_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw32.or_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw32_or_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw.xor")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw_xor, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw.xor")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw_xor, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw8.xor_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw8_xor_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw16.xor_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw16_xor_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw8.xor_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw8_xor_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw16.xor_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw16_xor_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw32.xor_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw32_xor_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw.xchg")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw_xchg, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw.xchg")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw_xchg, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw8.xchg_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw8_xchg_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw16.xchg_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw16_xchg_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw8.xchg_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw8_xchg_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw16.xchg_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw16_xchg_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw32.xchg_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw32_xchg_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw.cmpxchg")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw_cmpxchg, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw.cmpxchg")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw_cmpxchg, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw8.cmpxchg_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw8_cmpxchg_u, mem_arg); + } else if (mem.eql(u8, inst, "i32.atomic.rmw16.cmpxchg_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i32_atomic_rmw16_cmpxchg_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw8.cmpxchg_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw8_cmpxchg_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw16.cmpxchg_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw16_cmpxchg_u, mem_arg); + } else if (mem.eql(u8, inst, "i64.atomic.rmw32.cmpxchg_u")) { + const mem_arg = try parseMemArg(cg, inst, &word_it); + try cg.addAtomicMemArg(.i64_atomic_rmw32_cmpxchg_u, mem_arg); + } else { + if (mem.startsWith(u8, inst, "#")) continue :next_line; + if (mem.startsWith(u8, inst, "//")) continue :next_line; + + return cg.fail("Malformed assembly, unknown instruction \"{s}\"", .{inst}); + } + + const remaining = word_it.next() orelse continue :next_line; + if (mem.startsWith(u8, inst, "#")) continue :next_line; + if (mem.startsWith(u8, inst, "//")) continue :next_line; + return cg.fail("Malformed assembly, unknown string after instruction \"{s}\"", .{remaining}); + } +} + +fn parseLocalArg( + cg: *CodeGen, + inst: []const u8, + word_it: *mem.TokenIterator(u8, .any), + local_map: *const LocalMap, +) !u32 { + const arg = word_it.next() orelse { + return cg.fail("Malformed assembly, argument not found for \"{s}\"", .{inst}); + }; + if (arg.len < 3 or arg[0] != '%' or arg[1] != '[' or arg[arg.len - 1] != ']') { + const local = std.fmt.parseInt(u32, arg, 0) catch { + return cg.fail("Malformed assembly, malformed argument \"{s}\"", .{arg}); + }; + return local; + } + const name = arg[2 .. arg.len - 1]; + return local_map.get(name) orelse { + return cg.fail("Malformed assembly, unknown name \"{s}\"", .{name}); + }; +} + +fn parseLabel(cg: *CodeGen, inst: []const u8, word_it: *mem.TokenIterator(u8, .any)) !u32 { + const label_str = word_it.next() orelse { + return cg.fail("Malformed assembly, label not found for \"{s}\"", .{inst}); + }; + const label = std.fmt.parseInt(u32, label_str, 0) catch { + return cg.fail("Malformed assembly, malformed label \"{s}\"", .{label_str}); + }; + return label; +} + +fn parseMemArg(cg: *CodeGen, inst: []const u8, word_it: *mem.TokenIterator(u8, .any)) !Mir.MemArg { + const mem_arg_str = word_it.next() orelse { + return cg.fail("Malformed assembly, memory argument not found for \"{s}\"", .{inst}); + }; + if (mem.cut(u8, mem_arg_str, ":p2align=")) |oa| { + const offset_str = oa[0]; + const alignment_str = oa[1]; + + const offset = std.fmt.parseInt(i32, offset_str, 0) catch { + return cg.fail("Malformed assembly, malformed offset \"{s}\"", .{offset_str}); + }; + const alignment_p2 = std.fmt.parseInt(u5, alignment_str, 0) catch { + return cg.fail("Malformed assembly, malformed offset \"{s}\"", .{alignment_str}); + }; + + return .{ .offset = @bitCast(offset), .alignment = @as(u32, 1) << alignment_p2 }; + } else { + const offset_str = mem_arg_str; + const offset = std.fmt.parseInt(i32, offset_str, 0) catch { + return cg.fail("Malformed assembly, malformed offset \"{s}\"", .{offset_str}); + }; + return .{ .offset = @bitCast(offset), .alignment = 1 }; + } +} + +fn parseInt(comptime T: type, cg: *CodeGen, inst: []const u8, word_it: *mem.TokenIterator(u8, .any)) !T { + const value_str = word_it.next() orelse { + return cg.fail("Malformed assembly, value not found for \"{s}\"", .{inst}); + }; + const value = std.fmt.parseInt(T, value_str, 0) catch { + return cg.fail("Malformed assembly, malformed integer value \"{s}\"", .{value_str}); + }; + return value; +} + +fn parseFloat(comptime T: type, cg: *CodeGen, inst: []const u8, word_it: *mem.TokenIterator(u8, .any)) !T { + const value_str = word_it.next() orelse { + return cg.fail("Malformed assembly, value not found for \"{s}\"", .{inst}); + }; + const value = std.fmt.parseFloat(T, value_str) catch { + return cg.fail("Malformed assembly, malformed float value \"{s}\"", .{value_str}); + }; + return value; +} diff --git a/src/dev.zig b/src/dev.zig index 1deb3260accfaab12c6158ca6c059f9ef7856ded..f3c4016faf2338e1f80b1e96953d207e7fed866f 100644 --- a/src/dev.zig +++ b/src/dev.zig @@ -197,8 +197,9 @@ pub const Env = enum { else => Env.sema.supports(feature), }, .wasm => switch (feature) { - .stdio_listen, .incremental, + .legalize, + .stdio_listen, .wasm_backend, .wasm_linker, => true, diff --git a/test/behavior/wasm.zig b/test/behavior/wasm.zig index 2db3847f871310b5b39b085a400095b6bf1665ba..d7bd8e07e5d32701ee8eae5319f33199ee30ad80 100644 --- a/test/behavior/wasm.zig +++ b/test/behavior/wasm.zig @@ -3,10 +3,164 @@ const expect = std.testing.expect; const builtin = @import("builtin"); test "memory size and grow" { - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - var prev = @wasmMemorySize(0); _ = &prev; try expect(prev == @wasmMemoryGrow(0, 1)); try expect(prev + 1 == @wasmMemorySize(0)); } + +test "asm .i32.add" { + const a: u32 = std.math.maxInt(u32); + const b: u32 = 3; + const result = asm ( + \\ local.get %[a] + \\ local.get %[b] + \\ i32.add + \\ local.set %[ret] + : [ret] "=r" (-> u32), + : [a] "r" (a), + [b] "r" (b), + ); + + try expect(result == 2); +} + +test "asm .i64.clz" { + const a: u64 = 1; + const result = asm ( + \\ local.get %[a] + \\ i64.clz + \\ local.set %[ret] + : [ret] "=r" (-> u64), + : [a] "r" (a), + ); + + try expect(result == 63); +} + +test "asm .i32.const" { + const result = asm ( + \\ i32.const 12 + \\ local.set %[ret] + : [ret] "=r" (-> u32), + ); + + try expect(result == 12); +} + +test "asm .i64.const" { + const result = asm ( + \\ i64.const 42 + \\ local.set %[ret] + : [ret] "=r" (-> u64), + ); + + try expect(result == 42); +} + +test "asm .f32.const" { + const result = asm ( + \\ f32.const 1.5 + \\ local.set %[ret] + : [ret] "=r" (-> f32), + ); + + try expect(result == 1.5); +} + +test "asm .f64.const" { + const result = asm ( + \\ f64.const 2.25 + \\ local.set %[ret] + : [ret] "=r" (-> f64), + ); + + try expect(result == 2.25); +} + +test "asm .local.get" { + const a: u32 = 77; + const result = asm ( + \\ local.get %[a] + \\ local.set %[ret] + : [ret] "=r" (-> u32), + : [a] "r" (a), + ); + + try expect(result == 77); +} + +test "asm .local.set" { + const result = asm ( + \\ i32.const 55 + \\ local.set %[ret] + : [ret] "=r" (-> u32), + ); + + try expect(result == 55); +} + +test "asm .local.tee" { + const a: u32 = 3; + const result = asm ( + \\ local.get %[a] + \\ local.tee %[ret] + \\ drop + : [ret] "=r" (-> u32), + : [a] "r" (a), + ); + + try expect(result == 3); +} + +test "asm .memory.copy" { + var src: [8]u8 = .{ 1, 2, 3, 4, 5, 6, 7, 8 }; + var dst: [8]u8 = .{ 0, 0, 0, 0, 0, 0, 0, 0 }; + + asm volatile ( + \\ local.get %[dst] + \\ local.get %[src] + \\ i32.const 8 + \\ memory.copy 0, 0 + : + : [dst] "r" (@intFromPtr(&dst)), + [src] "r" (@intFromPtr(&src)), + ); + + try std.testing.expectEqualSlices(u8, &src, &dst); +} + +test "asm .memory.fill" { + var buf: [8]u8 = .{ 0, 0, 0, 0, 0, 0, 0, 0 }; + + asm volatile ( + \\ local.get %[dst] + \\ i32.const 2 + \\ i32.const 8 + \\ memory.fill 0 + : + : [dst] "r" (@intFromPtr(&buf)), + ); + + try std.testing.expectEqualSlices(u8, &.{ 2, 2, 2, 2, 2, 2, 2, 2 }, &buf); +} + +test "asm .i64.load and .i64.store" { + var slot: u64 = 0; + const value: u64 = 0x4444; + + const result = asm ( + \\ local.get %[ptr] + \\ local.get %[value] + \\ i64.store 0:p2align=0 + \\ local.get %[ptr] + \\ i64.load 0 + \\ local.set %[ret] + : [ret] "=r" (-> u64), + : [ptr] "r" (@intFromPtr(&slot)), + [value] "r" (value), + ); + + try expect(result == value); + try expect(slot == value); +} -- 2.54.0