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();