commit ad7a028228eabffd19b2d831bebe87c67723347c (tree)
parent 65fe99e18a89e92b6b82f798411e1e13e21df511
Author: Pavel Verigo <paul.verigo@gmail.com>
Date: Tue, 17 Mar 2026 06:59:17 +0100
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.
Diffstat:
14 files changed, 44 insertions(+), 73 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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");