From ad7a028228eabffd19b2d831bebe87c67723347c Mon Sep 17 00:00:00 2001 From: Pavel Verigo Date: Tue, 17 Mar 2026 06:59:17 +0100 Subject: [PATCH] stage2-wasm: pass incremental tests This PR enables all incremental tests under the `test/incremental` directory, except one: `change_exports`, which is currently ignored as it requires a non-trivial amount of work on the linker, since we do not currently support exporting data symbols. To enable the other tests, the following fixes were needed: 1. `src/link/Wasm.zig`: instead of chasing function type through Nav, get it directly. 2. `src/target.zig`: `.panic_fn` appears to work fine with the wasm backend. 3. `src/codegen/wasm/CodeGen.zig`: there was a liveness related bug that caused some `ArenaAllocator` code to crash the backend. More info on (3), the liveness and local reuse code in the backend for years in unfinished state. For example there is currently no branch merging and reuse happens only when inst die in same block level. I initially considered doing a large refactor to implement everything correctly, but aborted due to its sheer size and currently! no clear idea about how to do this efficiently. Instead, I fixed the bug with minimal changes and removed useless code, keeping the old solution otherwise intact. --- src/codegen/wasm/CodeGen.zig | 95 ++++++------------- src/link/Wasm.zig | 3 +- src/target.zig | 1 + test/incremental/add_decl | 2 +- test/incremental/add_decl_namespaced | 2 +- test/incremental/add_remove_struct_fields | 2 +- test/incremental/add_remove_toplevel_fields | 2 +- test/incremental/change_fn_type | 1 + test/incremental/change_panic_handler | 1 + .../incremental/change_panic_handler_explicit | 1 + test/incremental/change_zon_file | 2 +- .../change_zon_file_no_result_type | 2 +- test/incremental/function_becomes_inline | 1 + .../incremental/no_change_preserves_tag_names | 2 +- 14 files changed, 44 insertions(+), 73 deletions(-) diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig index a3a6ab0c740c100bf9b1ed754587a9b719b779a1..14f2716b3300c297b4dee8fa6803ab9658f5d940 100644 --- a/src/codegen/wasm/CodeGen.zig +++ b/src/codegen/wasm/CodeGen.zig @@ -345,7 +345,10 @@ fn finishAir(cg: *CodeGen, inst: Air.Inst.Index, result: WValue, operands: []con if (!dies) continue; processDeath(cg, operand); } + try cg.finishAirResult(inst, result); +} +fn finishAirResult(cg: *CodeGen, inst: Air.Inst.Index, result: WValue) InnerError!void { // results of `none` can never be referenced. if (result != .none) { const trackable_result = if (result != .stack) @@ -374,37 +377,10 @@ inline fn currentBranch(cg: *CodeGen) *Branch { return &cg.branches.items[cg.branches.items.len - 1]; } -const BigTomb = struct { - gen: *CodeGen, - inst: Air.Inst.Index, - lbt: Air.Liveness.BigTomb, - - fn feed(bt: *BigTomb, op_ref: Air.Inst.Ref) void { - const dies = bt.lbt.feed(); - if (!dies) return; - // This will be a nop for interned constants. - processDeath(bt.gen, op_ref); +fn feed(cg: *CodeGen, bt: *Air.Liveness.BigTomb, operand: Air.Inst.Ref) void { + if (bt.feed()) { + cg.processDeath(operand); } - - fn finishAir(bt: *BigTomb, result: WValue) void { - assert(result != .stack); - if (result != .none) { - bt.gen.currentBranch().values.putAssumeCapacityNoClobber(bt.inst.toRef(), result); - } - - if (std.debug.runtime_safety) { - bt.gen.air_bookkeeping += 1; - } - } -}; - -fn iterateBigTomb(cg: *CodeGen, inst: Air.Inst.Index, operand_count: usize) !BigTomb { - try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, operand_count + 1); - return BigTomb{ - .gen = cg, - .inst = inst, - .lbt = cg.liveness.iterateBigTomb(inst), - }; } fn processDeath(cg: *CodeGen, ref: Air.Inst.Ref) void { @@ -766,11 +742,15 @@ pub fn generate( fn generateInner(cg: *CodeGen, any_returns: bool) InnerError!Mir { const zcu = cg.pt.zcu; + // branch used for const values + try cg.branches.append(cg.gpa, .{}); + // func scope branch try cg.branches.append(cg.gpa, .{}); - // clean up outer branch defer { - var outer_branch = cg.branches.pop().?; - outer_branch.deinit(cg.gpa); + var func_branch = cg.branches.pop().?; + func_branch.deinit(cg.gpa); + var const_branch = cg.branches.pop().?; + const_branch.deinit(cg.gpa); assert(cg.branches.items.len == 0); // missing branch merge } // Generate MIR for function body @@ -1920,7 +1900,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { continue; } const old_bookkeeping_value = cg.air_bookkeeping; - try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, Air.Liveness.bpi); + try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, 1); try cg.genInst(inst); if (std.debug.runtime_safety and cg.air_bookkeeping < old_bookkeeping_value + 1) { @@ -2102,10 +2082,10 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie } }; - var bt = try cg.iterateBigTomb(inst, 1 + args.len); - bt.feed(call.callee); - for (args) |arg| bt.feed(arg); - return bt.finishAir(result_value); + var bt = cg.liveness.iterateBigTomb(inst); + cg.feed(&bt, call.callee); + for (args) |arg| cg.feed(&bt, arg); + return cg.finishAirResult(inst, result_value); } fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { @@ -4608,11 +4588,15 @@ fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []const .value = block_result, }); - try cg.genBody(body); - try cg.endBlock(); - - const liveness = cg.liveness.getBlock(inst); - try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths.len); + { + try cg.branches.append(cg.gpa, .{}); + defer { + var branch = cg.branches.pop().?; + branch.deinit(cg.gpa); + } + try cg.genBody(body); + try cg.endBlock(); + } return cg.finishAir(inst, block_result, &.{}); } @@ -4653,7 +4637,6 @@ fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { const condition = try cg.resolveInst(cond_br.condition); const then_body = cond_br.then_body; const else_body = cond_br.else_body; - const liveness_condbr = cg.liveness.getCondBr(inst); // result type is always noreturn, so use `block_empty` as type. try cg.startBlock(.block, .empty); @@ -4668,7 +4651,6 @@ fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { try cg.branches.ensureUnusedCapacity(cg.gpa, 2); { cg.branches.appendAssumeCapacity(.{}); - try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, @as(u32, @intCast(liveness_condbr.else_deaths.len))); defer { var else_stack = cg.branches.pop().?; else_stack.deinit(cg.gpa); @@ -4680,7 +4662,6 @@ fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { // Outer block that matches the condition { cg.branches.appendAssumeCapacity(.{}); - try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, @as(u32, @intCast(liveness_condbr.then_deaths.len))); defer { var then_stack = cg.branches.pop().?; then_stack.deinit(cg.gpa); @@ -5172,9 +5153,6 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index, is_dispatch_loop: bool) Inner break :target target; } else try cg.resolveInst(switch_br.operand); - const liveness = try cg.liveness.getSwitchBr(cg.gpa, inst, switch_br.cases_len + 1); - defer cg.gpa.free(liveness.deaths); - const has_else_body = switch_br.else_body_len != 0; const branch_count = switch_br.cases_len + 1; // if else branch is missing, we trap when failing all conditions try cg.branches.ensureUnusedCapacity(cg.gpa, switch_br.cases_len + @intFromBool(has_else_body)); @@ -5186,8 +5164,6 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index, is_dispatch_loop: bool) Inner const else_body = it.elseBody(); cg.branches.appendAssumeCapacity(.{}); - const else_deaths = liveness.deaths.len - 1; - try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[else_deaths].len); defer { var else_branch = cg.branches.pop().?; else_branch.deinit(cg.gpa); @@ -5321,7 +5297,6 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index, is_dispatch_loop: bool) Inner try cg.endBlock(); cg.branches.appendAssumeCapacity(.{}); - try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[case.idx].len); defer { var case_branch = cg.branches.pop().?; case_branch.deinit(cg.gpa); @@ -5336,8 +5311,6 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index, is_dispatch_loop: bool) Inner const else_body = cases_it.elseBody(); cg.branches.appendAssumeCapacity(.{}); - const else_deaths = liveness.deaths.len - 1; - try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[else_deaths].len); defer { var else_branch = cg.branches.pop().?; else_branch.deinit(cg.gpa); @@ -6329,14 +6302,9 @@ fn airAggregateInit(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { } }; - if (elements.len <= Air.Liveness.bpi - 1) { - var buf = [1]Air.Inst.Ref{.none} ** (Air.Liveness.bpi - 1); - @memcpy(buf[0..elements.len], elements); - return cg.finishAir(inst, result, &buf); - } - var bt = try cg.iterateBigTomb(inst, elements.len); - for (elements) |arg| bt.feed(arg); - return bt.finishAir(result); + var bt = cg.liveness.iterateBigTomb(inst); + for (elements) |arg| cg.feed(&bt, arg); + return cg.finishAirResult(inst, result); } fn airUnionInit(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { @@ -6671,6 +6639,7 @@ fn lowerTry( err_union_ty: Type, operand_is_ptr: bool, ) InnerError!WValue { + _ = inst; const zcu = cg.pt.zcu; const pl_ty = err_union_ty.errorUnionPayload(zcu); @@ -6692,9 +6661,7 @@ fn lowerTry( try cg.addTag(.i32_eqz); try cg.addLabel(.br_if, 0); // jump out of block when error is '0' - const liveness = cg.liveness.getCondBr(inst); try cg.branches.append(cg.gpa, .{}); - try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.else_deaths.len + liveness.then_deaths.len); defer { var branch = cg.branches.pop().?; branch.deinit(cg.gpa); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 041a8c6d27bb03507f334fcbae865651e2161866..b4760bce4d676f6ec591180af3c4ffa4075352d2 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -931,8 +931,7 @@ pub const ZcuFunc = union { const ip = &zcu.intern_pool; switch (ip.indexToKey(i.key(wasm).*)) { .func => |func| { - const fn_ty = zcu.navValue(func.owner_nav).typeOf(zcu); - const fn_info = zcu.typeToFunc(fn_ty).?; + const fn_info = zcu.typeToFunc(.fromInterned(func.ty)).?; return wasm.getExistingFunctionType(fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), target).?; }, .enum_type => { diff --git a/src/target.zig b/src/target.zig index 43858ea5f200b4f0b4a2271b158e22d2313d62ca..3d04c06f5e5d1da0fe5e61ed693cf32651055e85 100644 --- a/src/target.zig +++ b/src/target.zig @@ -910,6 +910,7 @@ pub inline fn backendSupportsFeature(backend: std.builtin.CompilerBackend, compt .stage2_llvm, .stage2_x86_64, .stage2_riscv64, + .stage2_wasm, => true, else => false, }, diff --git a/test/incremental/add_decl b/test/incremental/add_decl index e91eda1930a5c90485112ef01c08f9ea8fab18e9..4716c7787bba5f9a222c0fea7ae991b41874aedd 100644 --- a/test/incremental/add_decl +++ b/test/incremental/add_decl @@ -3,7 +3,7 @@ #target=x86_64-linux-cbe #target=x86_64-windows-cbe #target=x86_64-linux-llvm -//#target=wasm32-wasi-selfhosted +#target=wasm32-wasi-selfhosted #update=initial version #file=main.zig const std = @import("std"); diff --git a/test/incremental/add_decl_namespaced b/test/incremental/add_decl_namespaced index 389aef6fb8d289a480260811b7d51a0c6a32858d..96872d737250163e14d2e066c6bb88c0f5c5631a 100644 --- a/test/incremental/add_decl_namespaced +++ b/test/incremental/add_decl_namespaced @@ -3,7 +3,7 @@ #target=x86_64-linux-cbe #target=x86_64-windows-cbe #target=x86_64-linux-llvm -//#target=wasm32-wasi-selfhosted +#target=wasm32-wasi-selfhosted #update=initial version #file=main.zig const std = @import("std"); diff --git a/test/incremental/add_remove_struct_fields b/test/incremental/add_remove_struct_fields index 34ad260ae1aa479686eec3fd68d4061e2afa8cc5..bf19be99de72fb45ced938e73a249c4dbf63c6d8 100644 --- a/test/incremental/add_remove_struct_fields +++ b/test/incremental/add_remove_struct_fields @@ -3,7 +3,7 @@ #target=x86_64-linux-cbe #target=x86_64-windows-cbe #target=x86_64-linux-llvm -//#target=wasm32-wasi-selfhosted +#target=wasm32-wasi-selfhosted #update=initial version #file=main.zig const S = struct { x: u8 }; diff --git a/test/incremental/add_remove_toplevel_fields b/test/incremental/add_remove_toplevel_fields index 83775ae3e3cafa535ebfd79ec9d0b21b250fed66..c404827958b703b9bd6500b9c0d1cab9c57e24dd 100644 --- a/test/incremental/add_remove_toplevel_fields +++ b/test/incremental/add_remove_toplevel_fields @@ -3,7 +3,7 @@ #target=x86_64-linux-cbe #target=x86_64-windows-cbe #target=x86_64-linux-llvm -//#target=wasm32-wasi-selfhosted +#target=wasm32-wasi-selfhosted #update=initial version #file=main.zig const S = @This(); diff --git a/test/incremental/change_fn_type b/test/incremental/change_fn_type index 69e25a5a4aa7e896e44583867873bc34f5da07b7..fc2e5bb8a2af08f6777c73edfceb1cbcec3b5048 100644 --- a/test/incremental/change_fn_type +++ b/test/incremental/change_fn_type @@ -3,6 +3,7 @@ #target=x86_64-linux-cbe #target=x86_64-windows-cbe #target=x86_64-linux-llvm +#target=wasm32-wasi-selfhosted #update=initial version #file=main.zig pub fn main() !void { diff --git a/test/incremental/change_panic_handler b/test/incremental/change_panic_handler index 13685406dff01786fe8ff1fe17ad0133a162f448..6ce1409eb7d535069df60c9e183712f3c10fa0c1 100644 --- a/test/incremental/change_panic_handler +++ b/test/incremental/change_panic_handler @@ -3,6 +3,7 @@ #target=x86_64-linux-cbe #target=x86_64-windows-cbe #target=x86_64-linux-llvm +#target=wasm32-wasi-selfhosted #update=initial version #file=main.zig pub fn main() !u8 { diff --git a/test/incremental/change_panic_handler_explicit b/test/incremental/change_panic_handler_explicit index ed4cfcfca44cad7f2c7085b6855a3312bdf7542c..e165ec4eda159f31da234b9081cae31bfcc0dc78 100644 --- a/test/incremental/change_panic_handler_explicit +++ b/test/incremental/change_panic_handler_explicit @@ -3,6 +3,7 @@ #target=x86_64-linux-cbe #target=x86_64-windows-cbe #target=x86_64-linux-llvm +#target=wasm32-wasi-selfhosted #update=initial version #file=main.zig pub fn main() !u8 { diff --git a/test/incremental/change_zon_file b/test/incremental/change_zon_file index 822996ac74966971b237718461d555ce461d194f..77eef7c7f1e7d7ac3b0eccf916e4862e6808648b 100644 --- a/test/incremental/change_zon_file +++ b/test/incremental/change_zon_file @@ -3,7 +3,7 @@ #target=x86_64-linux-cbe #target=x86_64-windows-cbe #target=x86_64-linux-llvm -//#target=wasm32-wasi-selfhosted +#target=wasm32-wasi-selfhosted #update=initial version #file=main.zig const std = @import("std"); diff --git a/test/incremental/change_zon_file_no_result_type b/test/incremental/change_zon_file_no_result_type index f9a3c3006577c64a6b8725e90fef02e67364130f..8e4500d0ca0a3c5732ae185aef51be633f634e71 100644 --- a/test/incremental/change_zon_file_no_result_type +++ b/test/incremental/change_zon_file_no_result_type @@ -3,7 +3,7 @@ #target=x86_64-linux-cbe #target=x86_64-windows-cbe #target=x86_64-linux-llvm -//#target=wasm32-wasi-selfhosted +#target=wasm32-wasi-selfhosted #update=initial version #file=main.zig const std = @import("std"); diff --git a/test/incremental/function_becomes_inline b/test/incremental/function_becomes_inline index b63aa3e5697a600d2463dfb3af9282ea62b223a4..4c211d865a4c273aa50af57c0eae57033b9d4a07 100644 --- a/test/incremental/function_becomes_inline +++ b/test/incremental/function_becomes_inline @@ -3,6 +3,7 @@ #target=x86_64-linux-cbe #target=x86_64-windows-cbe #target=x86_64-linux-llvm +#target=wasm32-wasi-selfhosted #update=non-inline version #file=main.zig pub fn main() !void { diff --git a/test/incremental/no_change_preserves_tag_names b/test/incremental/no_change_preserves_tag_names index 7d7e0b20ea4d0756258815d477368ec68d715664..659cefade6cb2935eb72c296a00f4f9756036d98 100644 --- a/test/incremental/no_change_preserves_tag_names +++ b/test/incremental/no_change_preserves_tag_names @@ -3,7 +3,7 @@ #target=x86_64-linux-cbe #target=x86_64-windows-cbe #target=x86_64-linux-llvm -//#target=wasm32-wasi-selfhosted +#target=wasm32-wasi-selfhosted #update=initial version #file=main.zig const std = @import("std"); -- 2.54.0