master
1//! Verifies that Liveness information is valid.
2
3gpa: std.mem.Allocator,
4zcu: *Zcu,
5air: Air,
6liveness: Liveness,
7live: LiveMap = .{},
8blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, LiveMap) = .empty,
9loops: std.AutoHashMapUnmanaged(Air.Inst.Index, LiveMap) = .empty,
10intern_pool: *const InternPool,
11
12pub const Error = error{ LivenessInvalid, OutOfMemory };
13
14pub fn deinit(self: *Verify) void {
15 self.live.deinit(self.gpa);
16 {
17 var it = self.blocks.valueIterator();
18 while (it.next()) |block| block.deinit(self.gpa);
19 self.blocks.deinit(self.gpa);
20 }
21 {
22 var it = self.loops.valueIterator();
23 while (it.next()) |block| block.deinit(self.gpa);
24 self.loops.deinit(self.gpa);
25 }
26 self.* = undefined;
27}
28
29pub fn verify(self: *Verify) Error!void {
30 self.live.clearRetainingCapacity();
31 self.blocks.clearRetainingCapacity();
32 self.loops.clearRetainingCapacity();
33 try self.verifyBody(self.air.getMainBody());
34 // We don't care about `self.live` now, because the loop body was noreturn - everything being dead was checked on `ret` etc
35 assert(self.blocks.count() == 0);
36 assert(self.loops.count() == 0);
37}
38
39const LiveMap = std.AutoHashMapUnmanaged(Air.Inst.Index, void);
40
41fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
42 const ip = self.intern_pool;
43 const tags = self.air.instructions.items(.tag);
44 const data = self.air.instructions.items(.data);
45 for (body) |inst| {
46 if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip)) {
47 // This instruction will not be lowered and should be ignored.
48 continue;
49 }
50
51 switch (tags[@intFromEnum(inst)]) {
52 // no operands
53 .arg,
54 .alloc,
55 .inferred_alloc,
56 .inferred_alloc_comptime,
57 .ret_ptr,
58 .breakpoint,
59 .dbg_stmt,
60 .dbg_empty_stmt,
61 .ret_addr,
62 .frame_addr,
63 .wasm_memory_size,
64 .err_return_trace,
65 .save_err_return_trace_index,
66 .runtime_nav_ptr,
67 .c_va_start,
68 .work_item_id,
69 .work_group_size,
70 .work_group_id,
71 => try self.verifyInstOperands(inst, .{ .none, .none, .none }),
72
73 .trap, .unreach => {
74 try self.verifyInstOperands(inst, .{ .none, .none, .none });
75 // This instruction terminates the function, so everything should be dead
76 if (self.live.count() > 0) return invalid("%{f}: instructions still alive", .{inst});
77 },
78
79 // unary
80 .not,
81 .bitcast,
82 .load,
83 .fpext,
84 .fptrunc,
85 .intcast,
86 .intcast_safe,
87 .trunc,
88 .optional_payload,
89 .optional_payload_ptr,
90 .optional_payload_ptr_set,
91 .errunion_payload_ptr_set,
92 .wrap_optional,
93 .unwrap_errunion_payload,
94 .unwrap_errunion_err,
95 .unwrap_errunion_payload_ptr,
96 .unwrap_errunion_err_ptr,
97 .wrap_errunion_payload,
98 .wrap_errunion_err,
99 .slice_ptr,
100 .slice_len,
101 .ptr_slice_len_ptr,
102 .ptr_slice_ptr_ptr,
103 .struct_field_ptr_index_0,
104 .struct_field_ptr_index_1,
105 .struct_field_ptr_index_2,
106 .struct_field_ptr_index_3,
107 .array_to_slice,
108 .int_from_float,
109 .int_from_float_optimized,
110 .int_from_float_safe,
111 .int_from_float_optimized_safe,
112 .float_from_int,
113 .get_union_tag,
114 .clz,
115 .ctz,
116 .popcount,
117 .byte_swap,
118 .bit_reverse,
119 .splat,
120 .error_set_has_value,
121 .addrspace_cast,
122 .c_va_arg,
123 .c_va_copy,
124 .abs,
125 => {
126 const ty_op = data[@intFromEnum(inst)].ty_op;
127 try self.verifyInstOperands(inst, .{ ty_op.operand, .none, .none });
128 },
129 .is_null,
130 .is_non_null,
131 .is_null_ptr,
132 .is_non_null_ptr,
133 .is_err,
134 .is_non_err,
135 .is_err_ptr,
136 .is_non_err_ptr,
137 .is_named_enum_value,
138 .tag_name,
139 .error_name,
140 .sqrt,
141 .sin,
142 .cos,
143 .tan,
144 .exp,
145 .exp2,
146 .log,
147 .log2,
148 .log10,
149 .floor,
150 .ceil,
151 .round,
152 .trunc_float,
153 .neg,
154 .neg_optimized,
155 .cmp_lt_errors_len,
156 .set_err_return_trace,
157 .c_va_end,
158 => {
159 const un_op = data[@intFromEnum(inst)].un_op;
160 try self.verifyInstOperands(inst, .{ un_op, .none, .none });
161 },
162 .ret,
163 .ret_safe,
164 .ret_load,
165 => {
166 const un_op = data[@intFromEnum(inst)].un_op;
167 try self.verifyInstOperands(inst, .{ un_op, .none, .none });
168 // This instruction terminates the function, so everything should be dead
169 if (self.live.count() > 0) return invalid("%{f}: instructions still alive", .{inst});
170 },
171 .dbg_var_ptr,
172 .dbg_var_val,
173 .dbg_arg_inline,
174 .wasm_memory_grow,
175 => {
176 const pl_op = data[@intFromEnum(inst)].pl_op;
177 try self.verifyInstOperands(inst, .{ pl_op.operand, .none, .none });
178 },
179 .prefetch => {
180 const prefetch = data[@intFromEnum(inst)].prefetch;
181 try self.verifyInstOperands(inst, .{ prefetch.ptr, .none, .none });
182 },
183 .reduce,
184 .reduce_optimized,
185 => {
186 const reduce = data[@intFromEnum(inst)].reduce;
187 try self.verifyInstOperands(inst, .{ reduce.operand, .none, .none });
188 },
189 .union_init => {
190 const ty_pl = data[@intFromEnum(inst)].ty_pl;
191 const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data;
192 try self.verifyInstOperands(inst, .{ extra.init, .none, .none });
193 },
194 .struct_field_ptr, .struct_field_val => {
195 const ty_pl = data[@intFromEnum(inst)].ty_pl;
196 const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
197 try self.verifyInstOperands(inst, .{ extra.struct_operand, .none, .none });
198 },
199 .field_parent_ptr => {
200 const ty_pl = data[@intFromEnum(inst)].ty_pl;
201 const extra = self.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
202 try self.verifyInstOperands(inst, .{ extra.field_ptr, .none, .none });
203 },
204 .atomic_load => {
205 const atomic_load = data[@intFromEnum(inst)].atomic_load;
206 try self.verifyInstOperands(inst, .{ atomic_load.ptr, .none, .none });
207 },
208
209 // binary
210 .add,
211 .add_safe,
212 .add_optimized,
213 .add_wrap,
214 .add_sat,
215 .sub,
216 .sub_safe,
217 .sub_optimized,
218 .sub_wrap,
219 .sub_sat,
220 .mul,
221 .mul_safe,
222 .mul_optimized,
223 .mul_wrap,
224 .mul_sat,
225 .div_float,
226 .div_float_optimized,
227 .div_trunc,
228 .div_trunc_optimized,
229 .div_floor,
230 .div_floor_optimized,
231 .div_exact,
232 .div_exact_optimized,
233 .rem,
234 .rem_optimized,
235 .mod,
236 .mod_optimized,
237 .bit_and,
238 .bit_or,
239 .xor,
240 .cmp_lt,
241 .cmp_lt_optimized,
242 .cmp_lte,
243 .cmp_lte_optimized,
244 .cmp_eq,
245 .cmp_eq_optimized,
246 .cmp_gte,
247 .cmp_gte_optimized,
248 .cmp_gt,
249 .cmp_gt_optimized,
250 .cmp_neq,
251 .cmp_neq_optimized,
252 .bool_and,
253 .bool_or,
254 .store,
255 .store_safe,
256 .array_elem_val,
257 .slice_elem_val,
258 .ptr_elem_val,
259 .shl,
260 .shl_exact,
261 .shl_sat,
262 .shr,
263 .shr_exact,
264 .atomic_store_unordered,
265 .atomic_store_monotonic,
266 .atomic_store_release,
267 .atomic_store_seq_cst,
268 .set_union_tag,
269 .min,
270 .max,
271 .memset,
272 .memset_safe,
273 .memcpy,
274 .memmove,
275 .legalize_vec_elem_val,
276 => {
277 const bin_op = data[@intFromEnum(inst)].bin_op;
278 try self.verifyInstOperands(inst, .{ bin_op.lhs, bin_op.rhs, .none });
279 },
280 .add_with_overflow,
281 .sub_with_overflow,
282 .mul_with_overflow,
283 .shl_with_overflow,
284 .ptr_add,
285 .ptr_sub,
286 .ptr_elem_ptr,
287 .slice_elem_ptr,
288 .slice,
289 => {
290 const ty_pl = data[@intFromEnum(inst)].ty_pl;
291 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
292 try self.verifyInstOperands(inst, .{ extra.lhs, extra.rhs, .none });
293 },
294 .shuffle_one => {
295 const unwrapped = self.air.unwrapShuffleOne(self.zcu, inst);
296 try self.verifyInstOperands(inst, .{ unwrapped.operand, .none, .none });
297 },
298 .shuffle_two => {
299 const unwrapped = self.air.unwrapShuffleTwo(self.zcu, inst);
300 try self.verifyInstOperands(inst, .{ unwrapped.operand_a, unwrapped.operand_b, .none });
301 },
302 .cmp_vector,
303 .cmp_vector_optimized,
304 => {
305 const ty_pl = data[@intFromEnum(inst)].ty_pl;
306 const extra = self.air.extraData(Air.VectorCmp, ty_pl.payload).data;
307 try self.verifyInstOperands(inst, .{ extra.lhs, extra.rhs, .none });
308 },
309 .atomic_rmw => {
310 const pl_op = data[@intFromEnum(inst)].pl_op;
311 const extra = self.air.extraData(Air.AtomicRmw, pl_op.payload).data;
312 try self.verifyInstOperands(inst, .{ pl_op.operand, extra.operand, .none });
313 },
314
315 // ternary
316 .select => {
317 const pl_op = data[@intFromEnum(inst)].pl_op;
318 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
319 try self.verifyInstOperands(inst, .{ pl_op.operand, extra.lhs, extra.rhs });
320 },
321 .mul_add => {
322 const pl_op = data[@intFromEnum(inst)].pl_op;
323 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
324 try self.verifyInstOperands(inst, .{ extra.lhs, extra.rhs, pl_op.operand });
325 },
326 .cmpxchg_strong,
327 .cmpxchg_weak,
328 => {
329 const ty_pl = data[@intFromEnum(inst)].ty_pl;
330 const extra = self.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
331 try self.verifyInstOperands(inst, .{ extra.ptr, extra.expected_value, extra.new_value });
332 },
333
334 // big tombs
335 .aggregate_init => {
336 const ty_pl = data[@intFromEnum(inst)].ty_pl;
337 const aggregate_ty = ty_pl.ty.toType();
338 const len = @as(usize, @intCast(aggregate_ty.arrayLenIp(ip)));
339 const elements = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra.items[ty_pl.payload..][0..len]));
340
341 var bt = self.liveness.iterateBigTomb(inst);
342 for (elements) |element| {
343 try self.verifyOperand(inst, element, bt.feed());
344 }
345 try self.verifyInst(inst);
346 },
347 .call, .call_always_tail, .call_never_tail, .call_never_inline => {
348 const pl_op = data[@intFromEnum(inst)].pl_op;
349 const extra = self.air.extraData(Air.Call, pl_op.payload);
350 const args = @as(
351 []const Air.Inst.Ref,
352 @ptrCast(self.air.extra.items[extra.end..][0..extra.data.args_len]),
353 );
354
355 var bt = self.liveness.iterateBigTomb(inst);
356 try self.verifyOperand(inst, pl_op.operand, bt.feed());
357 for (args) |arg| {
358 try self.verifyOperand(inst, arg, bt.feed());
359 }
360 try self.verifyInst(inst);
361 },
362 .assembly => {
363 const ty_pl = data[@intFromEnum(inst)].ty_pl;
364 const extra = self.air.extraData(Air.Asm, ty_pl.payload);
365 const outputs_len = extra.data.flags.outputs_len;
366 var extra_i = extra.end;
367 const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]);
368 extra_i += outputs.len;
369 const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]);
370 extra_i += inputs.len;
371
372 var bt = self.liveness.iterateBigTomb(inst);
373 for (outputs) |output| {
374 if (output != .none) {
375 try self.verifyOperand(inst, output, bt.feed());
376 }
377 }
378 for (inputs) |input| {
379 try self.verifyOperand(inst, input, bt.feed());
380 }
381 try self.verifyInst(inst);
382 },
383
384 // control flow
385 .@"try", .try_cold => {
386 const pl_op = data[@intFromEnum(inst)].pl_op;
387 const extra = self.air.extraData(Air.Try, pl_op.payload);
388 const try_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]);
389
390 const cond_br_liveness = self.liveness.getCondBr(inst);
391
392 try self.verifyOperand(inst, pl_op.operand, self.liveness.operandDies(inst, 0));
393
394 var live = try self.live.clone(self.gpa);
395 defer live.deinit(self.gpa);
396
397 for (cond_br_liveness.else_deaths) |death| try self.verifyDeath(inst, death);
398 try self.verifyBody(try_body);
399
400 self.live.deinit(self.gpa);
401 self.live = live.move();
402
403 for (cond_br_liveness.then_deaths) |death| try self.verifyDeath(inst, death);
404
405 try self.verifyInst(inst);
406 },
407 .try_ptr, .try_ptr_cold => {
408 const ty_pl = data[@intFromEnum(inst)].ty_pl;
409 const extra = self.air.extraData(Air.TryPtr, ty_pl.payload);
410 const try_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]);
411
412 const cond_br_liveness = self.liveness.getCondBr(inst);
413
414 try self.verifyOperand(inst, extra.data.ptr, self.liveness.operandDies(inst, 0));
415
416 var live = try self.live.clone(self.gpa);
417 defer live.deinit(self.gpa);
418
419 for (cond_br_liveness.else_deaths) |death| try self.verifyDeath(inst, death);
420 try self.verifyBody(try_body);
421
422 self.live.deinit(self.gpa);
423 self.live = live.move();
424
425 for (cond_br_liveness.then_deaths) |death| try self.verifyDeath(inst, death);
426
427 try self.verifyInst(inst);
428 },
429 .br => {
430 const br = data[@intFromEnum(inst)].br;
431 const gop = try self.blocks.getOrPut(self.gpa, br.block_inst);
432
433 try self.verifyOperand(inst, br.operand, self.liveness.operandDies(inst, 0));
434 if (gop.found_existing) {
435 try self.verifyMatchingLiveness(br.block_inst, gop.value_ptr.*);
436 } else {
437 gop.value_ptr.* = try self.live.clone(self.gpa);
438 }
439 try self.verifyInst(inst);
440 },
441 .repeat => {
442 const repeat = data[@intFromEnum(inst)].repeat;
443 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) });
445
446 try self.verifyMatchingLiveness(repeat.loop_inst, expected_live);
447 },
448 .switch_dispatch => {
449 const br = data[@intFromEnum(inst)].br;
450
451 try self.verifyOperand(inst, br.operand, self.liveness.operandDies(inst, 0));
452
453 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) });
455
456 try self.verifyMatchingLiveness(br.block_inst, expected_live);
457 },
458 .block, .dbg_inline_block => |tag| {
459 const ty_pl = data[@intFromEnum(inst)].ty_pl;
460 const block_ty = ty_pl.ty.toType();
461 const block_body: []const Air.Inst.Index = @ptrCast(switch (tag) {
462 inline .block, .dbg_inline_block => |comptime_tag| body: {
463 const extra = self.air.extraData(switch (comptime_tag) {
464 .block => Air.Block,
465 .dbg_inline_block => Air.DbgInlineBlock,
466 else => unreachable,
467 }, ty_pl.payload);
468 break :body self.air.extra.items[extra.end..][0..extra.data.body_len];
469 },
470 else => unreachable,
471 });
472 const block_liveness = self.liveness.getBlock(inst);
473
474 var orig_live = try self.live.clone(self.gpa);
475 defer orig_live.deinit(self.gpa);
476
477 assert(!self.blocks.contains(inst));
478 try self.verifyBody(block_body);
479
480 // Liveness data after the block body is garbage, but we want to
481 // restore it to verify deaths
482 self.live.deinit(self.gpa);
483 self.live = orig_live.move();
484
485 for (block_liveness.deaths) |death| try self.verifyDeath(inst, death);
486
487 if (ip.isNoReturn(block_ty.toIntern())) {
488 assert(!self.blocks.contains(inst));
489 } else {
490 var live = self.blocks.fetchRemove(inst).?.value;
491 defer live.deinit(self.gpa);
492
493 try self.verifyMatchingLiveness(inst, live);
494 }
495
496 try self.verifyInstOperands(inst, .{ .none, .none, .none });
497 },
498 .loop => {
499 const ty_pl = data[@intFromEnum(inst)].ty_pl;
500 const extra = self.air.extraData(Air.Block, ty_pl.payload);
501 const loop_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]);
502
503 // The same stuff should be alive after the loop as before it.
504 const gop = try self.loops.getOrPut(self.gpa, inst);
505 if (gop.found_existing) return invalid("%{d}: loop already exists", .{@intFromEnum(inst)});
506 defer {
507 var live = self.loops.fetchRemove(inst).?;
508 live.value.deinit(self.gpa);
509 }
510 gop.value_ptr.* = try self.live.clone(self.gpa);
511
512 try self.verifyBody(loop_body);
513
514 try self.verifyInstOperands(inst, .{ .none, .none, .none });
515 },
516 .cond_br => {
517 const pl_op = data[@intFromEnum(inst)].pl_op;
518 const extra = self.air.extraData(Air.CondBr, pl_op.payload);
519 const then_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.then_body_len]);
520 const else_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]);
521 const cond_br_liveness = self.liveness.getCondBr(inst);
522
523 try self.verifyOperand(inst, pl_op.operand, self.liveness.operandDies(inst, 0));
524
525 var live = try self.live.clone(self.gpa);
526 defer live.deinit(self.gpa);
527
528 for (cond_br_liveness.then_deaths) |death| try self.verifyDeath(inst, death);
529 try self.verifyBody(then_body);
530
531 self.live.deinit(self.gpa);
532 self.live = live.move();
533
534 for (cond_br_liveness.else_deaths) |death| try self.verifyDeath(inst, death);
535 try self.verifyBody(else_body);
536
537 try self.verifyInst(inst);
538 },
539 .switch_br, .loop_switch_br => {
540 const switch_br = self.air.unwrapSwitch(inst);
541 const switch_br_liveness = try self.liveness.getSwitchBr(
542 self.gpa,
543 inst,
544 switch_br.cases_len + 1,
545 );
546 defer self.gpa.free(switch_br_liveness.deaths);
547
548 try self.verifyOperand(inst, switch_br.operand, self.liveness.operandDies(inst, 0));
549
550 // Excluding the operand (which we just handled), the same stuff should be alive
551 // after the loop as before it.
552 {
553 const gop = try self.loops.getOrPut(self.gpa, inst);
554 if (gop.found_existing) return invalid("%{d}: loop already exists", .{@intFromEnum(inst)});
555 gop.value_ptr.* = self.live.move();
556 }
557 defer {
558 var live = self.loops.fetchRemove(inst).?;
559 live.value.deinit(self.gpa);
560 }
561
562 var it = switch_br.iterateCases();
563 while (it.next()) |case| {
564 self.live.deinit(self.gpa);
565 self.live = try self.loops.get(inst).?.clone(self.gpa);
566
567 for (switch_br_liveness.deaths[case.idx]) |death| try self.verifyDeath(inst, death);
568 try self.verifyBody(case.body);
569 }
570
571 const else_body = it.elseBody();
572 if (else_body.len > 0) {
573 self.live.deinit(self.gpa);
574 self.live = try self.loops.get(inst).?.clone(self.gpa);
575 for (switch_br_liveness.deaths[switch_br.cases_len]) |death| try self.verifyDeath(inst, death);
576 try self.verifyBody(else_body);
577 }
578
579 try self.verifyInst(inst);
580 },
581 .legalize_vec_store_elem => {
582 const pl_op = data[@intFromEnum(inst)].pl_op;
583 const bin = self.air.extraData(Air.Bin, pl_op.payload).data;
584 try self.verifyInstOperands(inst, .{ pl_op.operand, bin.lhs, bin.rhs });
585 },
586 .legalize_compiler_rt_call => {
587 const extra = self.air.extraData(Air.Call, data[@intFromEnum(inst)].legalize_compiler_rt_call.payload);
588 const args: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.args_len]);
589 var bt = self.liveness.iterateBigTomb(inst);
590 for (args) |arg| {
591 try self.verifyOperand(inst, arg, bt.feed());
592 }
593 try self.verifyInst(inst);
594 },
595 }
596 }
597}
598
599fn verifyDeath(self: *Verify, inst: Air.Inst.Index, operand: Air.Inst.Index) Error!void {
600 try self.verifyOperand(inst, operand.toRef(), true);
601}
602
603fn verifyOperand(self: *Verify, inst: Air.Inst.Index, op_ref: Air.Inst.Ref, dies: bool) Error!void {
604 const operand = op_ref.toIndexAllowNone() orelse {
605 assert(!dies);
606 return;
607 };
608 if (dies) {
609 if (!self.live.remove(operand)) return invalid("%{f}: dead operand %{f} reused and killed again", .{
610 inst, operand,
611 });
612 } else {
613 if (!self.live.contains(operand)) return invalid("%{f}: dead operand %{f} reused", .{ inst, operand });
614 }
615}
616
617fn verifyInstOperands(
618 self: *Verify,
619 inst: Air.Inst.Index,
620 operands: [Liveness.bpi - 1]Air.Inst.Ref,
621) Error!void {
622 for (operands, 0..) |operand, operand_index| {
623 const dies = self.liveness.operandDies(inst, @as(Liveness.OperandInt, @intCast(operand_index)));
624 try self.verifyOperand(inst, operand, dies);
625 }
626 try self.verifyInst(inst);
627}
628
629fn verifyInst(self: *Verify, inst: Air.Inst.Index) Error!void {
630 if (self.liveness.isUnused(inst)) {
631 assert(!self.live.contains(inst));
632 } else {
633 try self.live.putNoClobber(self.gpa, inst, {});
634 }
635}
636
637fn 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});
639 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});
641}
642
643fn invalid(comptime fmt: []const u8, args: anytype) error{LivenessInvalid} {
644 log.err(fmt, args);
645 return error.LivenessInvalid;
646}
647
648const std = @import("std");
649const assert = std.debug.assert;
650const log = std.log.scoped(.liveness_verify);
651
652const Air = @import("../../Air.zig");
653const Liveness = @import("../Liveness.zig");
654const InternPool = @import("../../InternPool.zig");
655const Zcu = @import("../../Zcu.zig");
656const Verify = @This();