authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-01-07 17:07:24+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-11 11:37:16+00:00
log00d4f3c00188e2b1fcb2669ba6346831787828c2
tree31c34b87c7f22753c6d20929d4eb029a70791721
parent42dea36ce91db4d79711a8005ae9124bfb1364b3
signaturelock-open Commit is signed but in an unrecognized format.

Liveness: improve logging


2 files changed, 43 insertions(+), 35 deletions(-)

src/Air/Liveness.zig+27-24
......@@ -176,7 +176,10 @@ pub fn analyze(zcu: *Zcu, air: Air, intern_pool: *InternPool) Allocator.Error!Li
176176 data.old_extra = a.extra;
177177 a.extra = .{};
178178 try analyzeBody(&a, .main_analysis, &data, main_body);
179 assert(data.live_set.count() == 0);
179 if (std.debug.runtime_safety and data.live_set.count() != 0) {
180 log.debug("instructions still in live set after analysis: {f}", .{fmtInstSet(&data.live_set)});
181 @panic("liveness analysis failed");
182 }
180183 }
181184
182185 return .{
......@@ -825,10 +828,10 @@ fn analyzeOperands(
825828
826829 // This logic must synchronize with `will_die_immediately` in `AnalyzeBigOperands.init`.
827830 const immediate_death = if (data.live_set.remove(inst)) blk: {
828 log.debug("[{}] %{d}: removed from live set", .{ pass, @intFromEnum(inst) });
831 log.debug("[{t}] {f}: removed from live set", .{ pass, inst });
829832 break :blk false;
830833 } else blk: {
831 log.debug("[{}] %{d}: immediate death", .{ pass, @intFromEnum(inst) });
834 log.debug("[{t}] {f}: immediate death", .{ pass, inst });
832835 break :blk true;
833836 };
834837
......@@ -849,7 +852,7 @@ fn analyzeOperands(
849852 const mask = @as(Bpi, 1) << @as(OperandInt, @intCast(i));
850853
851854 if ((try data.live_set.fetchPut(gpa, operand, {})) == null) {
852 log.debug("[{}] %{d}: added %{d} to live set (operand dies here)", .{ pass, @intFromEnum(inst), operand });
855 log.debug("[{t}] {f}: added {f} to live set (operand dies here)", .{ pass, inst, operand });
853856 tomb_bits |= mask;
854857 }
855858 }
......@@ -988,19 +991,19 @@ fn analyzeInstBlock(
988991 },
989992
990993 .main_analysis => {
991 log.debug("[{}] %{f}: block live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });
994 log.debug("[{t}] {f}: block live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });
992995 // We can move the live set because the body should have a noreturn
993996 // instruction which overrides the set.
994997 try data.block_scopes.put(gpa, inst, .{
995998 .live_set = data.live_set.move(),
996999 });
9971000 defer {
998 log.debug("[{}] %{f}: popped block scope", .{ pass, inst });
1001 log.debug("[{t}] {f}: popped block scope", .{ pass, inst });
9991002 var scope = data.block_scopes.fetchRemove(inst).?.value;
10001003 scope.live_set.deinit(gpa);
10011004 }
10021005
1003 log.debug("[{}] %{f}: pushed new block scope", .{ pass, inst });
1006 log.debug("[{t}] {f}: pushed new block scope", .{ pass, inst });
10041007 try analyzeBody(a, pass, data, body);
10051008
10061009 // If the block is noreturn, block deaths not only aren't useful, they're impossible to
......@@ -1027,7 +1030,7 @@ fn analyzeInstBlock(
10271030 }
10281031 assert(measured_num == num_deaths); // post-live-set should be a subset of pre-live-set
10291032 try a.special.put(gpa, inst, extra_index);
1030 log.debug("[{}] %{f}: block deaths are {f}", .{
1033 log.debug("[{t}] {f}: block deaths are {f}", .{
10311034 pass,
10321035 inst,
10331036 fmtInstList(@ptrCast(a.extra.items[extra_index + 1 ..][0..num_deaths])),
......@@ -1064,7 +1067,7 @@ fn writeLoopInfo(
10641067 const block_inst = key.*;
10651068 a.extra.appendAssumeCapacity(@intFromEnum(block_inst));
10661069 }
1067 log.debug("[{}] %{f}: includes breaks to {f}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.breaks) });
1070 log.debug("[{t}] {f}: includes breaks to {f}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.breaks) });
10681071
10691072 // Now we put the live operands from the loop body in too
10701073 const num_live = data.live_set.count();
......@@ -1076,7 +1079,7 @@ fn writeLoopInfo(
10761079 const alive = key.*;
10771080 a.extra.appendAssumeCapacity(@intFromEnum(alive));
10781081 }
1079 log.debug("[{}] %{f}: maintain liveness of {f}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.live_set) });
1082 log.debug("[{t}] {f}: maintain liveness of {f}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.live_set) });
10801083
10811084 try a.special.put(gpa, inst, extra_index);
10821085
......@@ -1117,7 +1120,7 @@ fn resolveLoopLiveSet(
11171120 try data.live_set.ensureUnusedCapacity(gpa, @intCast(loop_live.len));
11181121 for (loop_live) |alive| data.live_set.putAssumeCapacity(alive, {});
11191122
1120 log.debug("[{}] %{f}: block live set is {f}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });
1123 log.debug("[{t}] {f}: block live set is {f}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });
11211124
11221125 for (breaks) |block_inst| {
11231126 // We might break to this block, so include every operand that the block needs alive
......@@ -1130,7 +1133,7 @@ fn resolveLoopLiveSet(
11301133 }
11311134 }
11321135
1133 log.debug("[{}] %{f}: loop live set is {f}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });
1136 log.debug("[{t}] {f}: loop live set is {f}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });
11341137}
11351138
11361139fn analyzeInstLoop(
......@@ -1168,7 +1171,7 @@ fn analyzeInstLoop(
11681171 .live_set = data.live_set.move(),
11691172 });
11701173 defer {
1171 log.debug("[{}] %{f}: popped loop block scop", .{ pass, inst });
1174 log.debug("[{t}] {f}: popped loop block scop", .{ pass, inst });
11721175 var scope = data.block_scopes.fetchRemove(inst).?.value;
11731176 scope.live_set.deinit(gpa);
11741177 }
......@@ -1269,13 +1272,13 @@ fn analyzeInstCondBr(
12691272 }
12701273 }
12711274
1272 log.debug("[{}] %{f}: 'then' branch mirrored deaths are {f}", .{ pass, inst, fmtInstList(then_mirrored_deaths.items) });
1273 log.debug("[{}] %{f}: 'else' branch mirrored deaths are {f}", .{ pass, inst, fmtInstList(else_mirrored_deaths.items) });
1275 log.debug("[{t}] {f}: 'then' branch mirrored deaths are {f}", .{ pass, inst, fmtInstList(then_mirrored_deaths.items) });
1276 log.debug("[{t}] {f}: 'else' branch mirrored deaths are {f}", .{ pass, inst, fmtInstList(else_mirrored_deaths.items) });
12741277
12751278 data.live_set.deinit(gpa);
12761279 data.live_set = then_live.move(); // Really the union of both live sets
12771280
1278 log.debug("[{}] %{f}: new live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });
1281 log.debug("[{t}] {f}: new live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });
12791282
12801283 // Write the mirrored deaths to `extra`
12811284 const then_death_count = @as(u32, @intCast(then_mirrored_deaths.items.len));
......@@ -1343,7 +1346,7 @@ fn analyzeInstSwitchBr(
13431346 });
13441347 }
13451348 defer if (is_dispatch_loop) {
1346 log.debug("[{}] %{f}: popped loop block scop", .{ pass, inst });
1349 log.debug("[{t}] {f}: popped loop block scope", .{ pass, inst });
13471350 var scope = data.block_scopes.fetchRemove(inst).?.value;
13481351 scope.live_set.deinit(gpa);
13491352 };
......@@ -1401,13 +1404,13 @@ fn analyzeInstSwitchBr(
14011404 }
14021405
14031406 for (mirrored_deaths, 0..) |mirrored, i| {
1404 log.debug("[{}] %{f}: case {} mirrored deaths are {f}", .{ pass, inst, i, fmtInstList(mirrored.items) });
1407 log.debug("[{t}] {f}: case {} mirrored deaths are {f}", .{ pass, inst, i, fmtInstList(mirrored.items) });
14051408 }
14061409
14071410 data.live_set.deinit(gpa);
14081411 data.live_set = all_alive.move();
14091412
1410 log.debug("[{}] %{f}: new live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });
1413 log.debug("[{t}] {f}: new live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });
14111414 }
14121415
14131416 const else_death_count = @as(u32, @intCast(mirrored_deaths[ncases].items.len));
......@@ -1506,7 +1509,7 @@ fn AnalyzeBigOperands(comptime pass: LivenessPass) type {
15061509
15071510 .main_analysis => {
15081511 if ((try big.data.live_set.fetchPut(gpa, operand, {})) == null) {
1509 log.debug("[{}] %{f}: added %{f} to live set (operand dies here)", .{ pass, big.inst, operand });
1512 log.debug("[{t}] {f}: added {f} to live set (operand dies here)", .{ pass, big.inst, operand });
15101513 big.extra_tombs[extra_byte] |= @as(u32, 1) << extra_bit;
15111514 }
15121515 },
......@@ -1568,9 +1571,9 @@ const FmtInstSet = struct {
15681571 return;
15691572 }
15701573 var it = val.set.keyIterator();
1571 try w.print("%{f}", .{it.next().?.*});
1574 try w.print("{f}", .{it.next().?.*});
15721575 while (it.next()) |key| {
1573 try w.print(" %{f}", .{key.*});
1576 try w.print(" {f}", .{key.*});
15741577 }
15751578 }
15761579};
......@@ -1587,9 +1590,9 @@ const FmtInstList = struct {
15871590 try w.writeAll("[no instructions]");
15881591 return;
15891592 }
1590 try w.print("%{f}", .{val.list[0]});
1593 try w.print("{f}", .{val.list[0]});
15911594 for (val.list[1..]) |inst| {
1592 try w.print(" %{f}", .{inst});
1595 try w.print(" {f}", .{inst});
15931596 }
15941597 }
15951598};
src/Air/Liveness/Verify.zig+16-11
......@@ -73,7 +73,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
7373 .trap, .unreach => {
7474 try self.verifyInstOperands(inst, .{ .none, .none, .none });
7575 // This instruction terminates the function, so everything should be dead
76 if (self.live.count() > 0) return invalid("%{f}: instructions still alive", .{inst});
76 if (self.live.count() > 0) return invalid("{f}: instructions still alive", .{inst});
7777 },
7878
7979 // unary
......@@ -166,7 +166,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
166166 const un_op = data[@intFromEnum(inst)].un_op;
167167 try self.verifyInstOperands(inst, .{ un_op, .none, .none });
168168 // This instruction terminates the function, so everything should be dead
169 if (self.live.count() > 0) return invalid("%{f}: instructions still alive", .{inst});
169 if (self.live.count() > 0) return invalid("{f}: instructions still alive", .{inst});
170170 },
171171 .dbg_var_ptr,
172172 .dbg_var_val,
......@@ -441,7 +441,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
441441 .repeat => {
442442 const repeat = data[@intFromEnum(inst)].repeat;
443443 const expected_live = self.loops.get(repeat.loop_inst) orelse
444 return invalid("%{d}: loop %{d} not in scope", .{ @intFromEnum(inst), @intFromEnum(repeat.loop_inst) });
444 return invalid("{f}: loop {f} not in scope", .{ inst, repeat.loop_inst });
445445
446446 try self.verifyMatchingLiveness(repeat.loop_inst, expected_live);
447447 },
......@@ -451,7 +451,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
451451 try self.verifyOperand(inst, br.operand, self.liveness.operandDies(inst, 0));
452452
453453 const expected_live = self.loops.get(br.block_inst) orelse
454 return invalid("%{d}: loop %{d} not in scope", .{ @intFromEnum(inst), @intFromEnum(br.block_inst) });
454 return invalid("{f}: loop {f} not in scope", .{ inst, br.block_inst });
455455
456456 try self.verifyMatchingLiveness(br.block_inst, expected_live);
457457 },
......@@ -487,7 +487,12 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
487487 if (ip.isNoReturn(block_ty.toIntern())) {
488488 assert(!self.blocks.contains(inst));
489489 } else {
490 var live = self.blocks.fetchRemove(inst).?.value;
490 var live = if (self.blocks.fetchRemove(inst)) |kv| kv.value else {
491 return invalid(
492 "{f}: block of type '{f}' not terminated correctly",
493 .{ inst, block_ty.fmtDebug() },
494 );
495 };
491496 defer live.deinit(self.gpa);
492497
493498 try self.verifyMatchingLiveness(inst, live);
......@@ -502,7 +507,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
502507
503508 // The same stuff should be alive after the loop as before it.
504509 const gop = try self.loops.getOrPut(self.gpa, inst);
505 if (gop.found_existing) return invalid("%{d}: loop already exists", .{@intFromEnum(inst)});
510 if (gop.found_existing) return invalid("{f}: loop already exists", .{inst});
506511 defer {
507512 var live = self.loops.fetchRemove(inst).?;
508513 live.value.deinit(self.gpa);
......@@ -551,7 +556,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
551556 // after the loop as before it.
552557 {
553558 const gop = try self.loops.getOrPut(self.gpa, inst);
554 if (gop.found_existing) return invalid("%{d}: loop already exists", .{@intFromEnum(inst)});
559 if (gop.found_existing) return invalid("{f}: loop already exists", .{inst});
555560 gop.value_ptr.* = self.live.move();
556561 }
557562 defer {
......@@ -606,11 +611,11 @@ fn verifyOperand(self: *Verify, inst: Air.Inst.Index, op_ref: Air.Inst.Ref, dies
606611 return;
607612 };
608613 if (dies) {
609 if (!self.live.remove(operand)) return invalid("%{f}: dead operand %{f} reused and killed again", .{
614 if (!self.live.remove(operand)) return invalid("{f}: dead operand {f} reused and killed again", .{
610615 inst, operand,
611616 });
612617 } else {
613 if (!self.live.contains(operand)) return invalid("%{f}: dead operand %{f} reused", .{ inst, operand });
618 if (!self.live.contains(operand)) return invalid("{f}: dead operand {f} reused", .{ inst, operand });
614619 }
615620}
616621
......@@ -635,9 +640,9 @@ fn verifyInst(self: *Verify, inst: Air.Inst.Index) Error!void {
635640}
636641
637642fn verifyMatchingLiveness(self: *Verify, block: Air.Inst.Index, live: LiveMap) Error!void {
638 if (self.live.count() != live.count()) return invalid("%{f}: different deaths across branches", .{block});
643 if (self.live.count() != live.count()) return invalid("{f}: different deaths across branches", .{block});
639644 var live_it = self.live.keyIterator();
640 while (live_it.next()) |live_inst| if (!live.contains(live_inst.*)) return invalid("%{f}: different deaths across branches", .{block});
645 while (live_it.next()) |live_inst| if (!live.contains(live_inst.*)) return invalid("{f}: different deaths across branches", .{block});
641646}
642647
643648fn invalid(comptime fmt: []const u8, args: anytype) error{LivenessInvalid} {