authorgravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2026-03-17 06:59:17+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-04 15:21:35+02:00
logad7a028228eabffd19b2d831bebe87c67723347c
treeac3617c97d2bb0123c2148434db9589155e74ff0
parent65fe99e18a89e92b6b82f798411e1e13e21df511

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.

14 files changed, 44 insertions(+), 73 deletions(-)

src/codegen/wasm/CodeGen.zig+31-64
......@@ -345,7 +345,10 @@ fn finishAir(cg: *CodeGen, inst: Air.Inst.Index, result: WValue, operands: []con
345345 if (!dies) continue;
346346 processDeath(cg, operand);
347347 }
348 try cg.finishAirResult(inst, result);
349}
348350
351fn finishAirResult(cg: *CodeGen, inst: Air.Inst.Index, result: WValue) InnerError!void {
349352 // results of `none` can never be referenced.
350353 if (result != .none) {
351354 const trackable_result = if (result != .stack)
......@@ -374,37 +377,10 @@ inline fn currentBranch(cg: *CodeGen) *Branch {
374377 return &cg.branches.items[cg.branches.items.len - 1];
375378}
376379
377const BigTomb = struct {
378 gen: *CodeGen,
379 inst: Air.Inst.Index,
380 lbt: Air.Liveness.BigTomb,
381
382 fn feed(bt: *BigTomb, op_ref: Air.Inst.Ref) void {
383 const dies = bt.lbt.feed();
384 if (!dies) return;
385 // This will be a nop for interned constants.
386 processDeath(bt.gen, op_ref);
380fn feed(cg: *CodeGen, bt: *Air.Liveness.BigTomb, operand: Air.Inst.Ref) void {
381 if (bt.feed()) {
382 cg.processDeath(operand);
387383 }
388
389 fn finishAir(bt: *BigTomb, result: WValue) void {
390 assert(result != .stack);
391 if (result != .none) {
392 bt.gen.currentBranch().values.putAssumeCapacityNoClobber(bt.inst.toRef(), result);
393 }
394
395 if (std.debug.runtime_safety) {
396 bt.gen.air_bookkeeping += 1;
397 }
398 }
399};
400
401fn iterateBigTomb(cg: *CodeGen, inst: Air.Inst.Index, operand_count: usize) !BigTomb {
402 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, operand_count + 1);
403 return BigTomb{
404 .gen = cg,
405 .inst = inst,
406 .lbt = cg.liveness.iterateBigTomb(inst),
407 };
408384}
409385
410386fn processDeath(cg: *CodeGen, ref: Air.Inst.Ref) void {
......@@ -766,11 +742,15 @@ pub fn generate(
766742
767743fn generateInner(cg: *CodeGen, any_returns: bool) InnerError!Mir {
768744 const zcu = cg.pt.zcu;
745 // branch used for const values
746 try cg.branches.append(cg.gpa, .{});
747 // func scope branch
769748 try cg.branches.append(cg.gpa, .{});
770 // clean up outer branch
771749 defer {
772 var outer_branch = cg.branches.pop().?;
773 outer_branch.deinit(cg.gpa);
750 var func_branch = cg.branches.pop().?;
751 func_branch.deinit(cg.gpa);
752 var const_branch = cg.branches.pop().?;
753 const_branch.deinit(cg.gpa);
774754 assert(cg.branches.items.len == 0); // missing branch merge
775755 }
776756 // Generate MIR for function body
......@@ -1920,7 +1900,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
19201900 continue;
19211901 }
19221902 const old_bookkeeping_value = cg.air_bookkeeping;
1923 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, Air.Liveness.bpi);
1903 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, 1);
19241904 try cg.genInst(inst);
19251905
19261906 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
21022082 }
21032083 };
21042084
2105 var bt = try cg.iterateBigTomb(inst, 1 + args.len);
2106 bt.feed(call.callee);
2107 for (args) |arg| bt.feed(arg);
2108 return bt.finishAir(result_value);
2085 var bt = cg.liveness.iterateBigTomb(inst);
2086 cg.feed(&bt, call.callee);
2087 for (args) |arg| cg.feed(&bt, arg);
2088 return cg.finishAirResult(inst, result_value);
21092089}
21102090
21112091fn 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
46084588 .value = block_result,
46094589 });
46104590
4611 try cg.genBody(body);
4612 try cg.endBlock();
4613
4614 const liveness = cg.liveness.getBlock(inst);
4615 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths.len);
4591 {
4592 try cg.branches.append(cg.gpa, .{});
4593 defer {
4594 var branch = cg.branches.pop().?;
4595 branch.deinit(cg.gpa);
4596 }
4597 try cg.genBody(body);
4598 try cg.endBlock();
4599 }
46164600
46174601 return cg.finishAir(inst, block_result, &.{});
46184602}
......@@ -4653,7 +4637,6 @@ fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
46534637 const condition = try cg.resolveInst(cond_br.condition);
46544638 const then_body = cond_br.then_body;
46554639 const else_body = cond_br.else_body;
4656 const liveness_condbr = cg.liveness.getCondBr(inst);
46574640
46584641 // result type is always noreturn, so use `block_empty` as type.
46594642 try cg.startBlock(.block, .empty);
......@@ -4668,7 +4651,6 @@ fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
46684651 try cg.branches.ensureUnusedCapacity(cg.gpa, 2);
46694652 {
46704653 cg.branches.appendAssumeCapacity(.{});
4671 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, @as(u32, @intCast(liveness_condbr.else_deaths.len)));
46724654 defer {
46734655 var else_stack = cg.branches.pop().?;
46744656 else_stack.deinit(cg.gpa);
......@@ -4680,7 +4662,6 @@ fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
46804662 // Outer block that matches the condition
46814663 {
46824664 cg.branches.appendAssumeCapacity(.{});
4683 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, @as(u32, @intCast(liveness_condbr.then_deaths.len)));
46844665 defer {
46854666 var then_stack = cg.branches.pop().?;
46864667 then_stack.deinit(cg.gpa);
......@@ -5172,9 +5153,6 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index, is_dispatch_loop: bool) Inner
51725153 break :target target;
51735154 } else try cg.resolveInst(switch_br.operand);
51745155
5175 const liveness = try cg.liveness.getSwitchBr(cg.gpa, inst, switch_br.cases_len + 1);
5176 defer cg.gpa.free(liveness.deaths);
5177
51785156 const has_else_body = switch_br.else_body_len != 0;
51795157 const branch_count = switch_br.cases_len + 1; // if else branch is missing, we trap when failing all conditions
51805158 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
51865164 const else_body = it.elseBody();
51875165
51885166 cg.branches.appendAssumeCapacity(.{});
5189 const else_deaths = liveness.deaths.len - 1;
5190 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[else_deaths].len);
51915167 defer {
51925168 var else_branch = cg.branches.pop().?;
51935169 else_branch.deinit(cg.gpa);
......@@ -5321,7 +5297,6 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index, is_dispatch_loop: bool) Inner
53215297 try cg.endBlock();
53225298
53235299 cg.branches.appendAssumeCapacity(.{});
5324 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[case.idx].len);
53255300 defer {
53265301 var case_branch = cg.branches.pop().?;
53275302 case_branch.deinit(cg.gpa);
......@@ -5336,8 +5311,6 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index, is_dispatch_loop: bool) Inner
53365311 const else_body = cases_it.elseBody();
53375312
53385313 cg.branches.appendAssumeCapacity(.{});
5339 const else_deaths = liveness.deaths.len - 1;
5340 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.deaths[else_deaths].len);
53415314 defer {
53425315 var else_branch = cg.branches.pop().?;
53435316 else_branch.deinit(cg.gpa);
......@@ -6329,14 +6302,9 @@ fn airAggregateInit(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
63296302 }
63306303 };
63316304
6332 if (elements.len <= Air.Liveness.bpi - 1) {
6333 var buf = [1]Air.Inst.Ref{.none} ** (Air.Liveness.bpi - 1);
6334 @memcpy(buf[0..elements.len], elements);
6335 return cg.finishAir(inst, result, &buf);
6336 }
6337 var bt = try cg.iterateBigTomb(inst, elements.len);
6338 for (elements) |arg| bt.feed(arg);
6339 return bt.finishAir(result);
6305 var bt = cg.liveness.iterateBigTomb(inst);
6306 for (elements) |arg| cg.feed(&bt, arg);
6307 return cg.finishAirResult(inst, result);
63406308}
63416309
63426310fn airUnionInit(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
......@@ -6671,6 +6639,7 @@ fn lowerTry(
66716639 err_union_ty: Type,
66726640 operand_is_ptr: bool,
66736641) InnerError!WValue {
6642 _ = inst;
66746643 const zcu = cg.pt.zcu;
66756644
66766645 const pl_ty = err_union_ty.errorUnionPayload(zcu);
......@@ -6692,9 +6661,7 @@ fn lowerTry(
66926661 try cg.addTag(.i32_eqz);
66936662 try cg.addLabel(.br_if, 0); // jump out of block when error is '0'
66946663
6695 const liveness = cg.liveness.getCondBr(inst);
66966664 try cg.branches.append(cg.gpa, .{});
6697 try cg.currentBranch().values.ensureUnusedCapacity(cg.gpa, liveness.else_deaths.len + liveness.then_deaths.len);
66986665 defer {
66996666 var branch = cg.branches.pop().?;
67006667 branch.deinit(cg.gpa);
src/link/Wasm.zig+1-2
......@@ -931,8 +931,7 @@ pub const ZcuFunc = union {
931931 const ip = &zcu.intern_pool;
932932 switch (ip.indexToKey(i.key(wasm).*)) {
933933 .func => |func| {
934 const fn_ty = zcu.navValue(func.owner_nav).typeOf(zcu);
935 const fn_info = zcu.typeToFunc(fn_ty).?;
934 const fn_info = zcu.typeToFunc(.fromInterned(func.ty)).?;
936935 return wasm.getExistingFunctionType(fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), target).?;
937936 },
938937 .enum_type => {
src/target.zig+1
......@@ -910,6 +910,7 @@ pub inline fn backendSupportsFeature(backend: std.builtin.CompilerBackend, compt
910910 .stage2_llvm,
911911 .stage2_x86_64,
912912 .stage2_riscv64,
913 .stage2_wasm,
913914 => true,
914915 else => false,
915916 },
test/incremental/add_decl+1-1
......@@ -3,7 +3,7 @@
33#target=x86_64-linux-cbe
44#target=x86_64-windows-cbe
55#target=x86_64-linux-llvm
6//#target=wasm32-wasi-selfhosted
6#target=wasm32-wasi-selfhosted
77#update=initial version
88#file=main.zig
99const std = @import("std");
test/incremental/add_decl_namespaced+1-1
......@@ -3,7 +3,7 @@
33#target=x86_64-linux-cbe
44#target=x86_64-windows-cbe
55#target=x86_64-linux-llvm
6//#target=wasm32-wasi-selfhosted
6#target=wasm32-wasi-selfhosted
77#update=initial version
88#file=main.zig
99const std = @import("std");
test/incremental/add_remove_struct_fields+1-1
......@@ -3,7 +3,7 @@
33#target=x86_64-linux-cbe
44#target=x86_64-windows-cbe
55#target=x86_64-linux-llvm
6//#target=wasm32-wasi-selfhosted
6#target=wasm32-wasi-selfhosted
77#update=initial version
88#file=main.zig
99const S = struct { x: u8 };
test/incremental/add_remove_toplevel_fields+1-1
......@@ -3,7 +3,7 @@
33#target=x86_64-linux-cbe
44#target=x86_64-windows-cbe
55#target=x86_64-linux-llvm
6//#target=wasm32-wasi-selfhosted
6#target=wasm32-wasi-selfhosted
77#update=initial version
88#file=main.zig
99const S = @This();
test/incremental/change_fn_type+1
......@@ -3,6 +3,7 @@
33#target=x86_64-linux-cbe
44#target=x86_64-windows-cbe
55#target=x86_64-linux-llvm
6#target=wasm32-wasi-selfhosted
67#update=initial version
78#file=main.zig
89pub fn main() !void {
test/incremental/change_panic_handler+1
......@@ -3,6 +3,7 @@
33#target=x86_64-linux-cbe
44#target=x86_64-windows-cbe
55#target=x86_64-linux-llvm
6#target=wasm32-wasi-selfhosted
67#update=initial version
78#file=main.zig
89pub fn main() !u8 {
test/incremental/change_panic_handler_explicit+1
......@@ -3,6 +3,7 @@
33#target=x86_64-linux-cbe
44#target=x86_64-windows-cbe
55#target=x86_64-linux-llvm
6#target=wasm32-wasi-selfhosted
67#update=initial version
78#file=main.zig
89pub fn main() !u8 {
test/incremental/change_zon_file+1-1
......@@ -3,7 +3,7 @@
33#target=x86_64-linux-cbe
44#target=x86_64-windows-cbe
55#target=x86_64-linux-llvm
6//#target=wasm32-wasi-selfhosted
6#target=wasm32-wasi-selfhosted
77#update=initial version
88#file=main.zig
99const std = @import("std");
test/incremental/change_zon_file_no_result_type+1-1
......@@ -3,7 +3,7 @@
33#target=x86_64-linux-cbe
44#target=x86_64-windows-cbe
55#target=x86_64-linux-llvm
6//#target=wasm32-wasi-selfhosted
6#target=wasm32-wasi-selfhosted
77#update=initial version
88#file=main.zig
99const std = @import("std");
test/incremental/function_becomes_inline+1
......@@ -3,6 +3,7 @@
33#target=x86_64-linux-cbe
44#target=x86_64-windows-cbe
55#target=x86_64-linux-llvm
6#target=wasm32-wasi-selfhosted
67#update=non-inline version
78#file=main.zig
89pub fn main() !void {
test/incremental/no_change_preserves_tag_names+1-1
......@@ -3,7 +3,7 @@
33#target=x86_64-linux-cbe
44#target=x86_64-windows-cbe
55#target=x86_64-linux-llvm
6//#target=wasm32-wasi-selfhosted
6#target=wasm32-wasi-selfhosted
77#update=initial version
88#file=main.zig
99const std = @import("std");