zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

blob 435075a4 (48408B) - Raw


      1 //! For each AIR instruction, we want to know:
      2 //! * Is the instruction unreferenced (e.g. dies immediately)?
      3 //! * For each of its operands, does the operand die with this instruction (e.g. is
      4 //!   this the last reference to it)?
      5 //! Some instructions are special, such as:
      6 //! * Conditional Branches
      7 //! * Switch Branches
      8 const Liveness = @This();
      9 const std = @import("std");
     10 const trace = @import("tracy.zig").trace;
     11 const log = std.log.scoped(.liveness);
     12 const assert = std.debug.assert;
     13 const Allocator = std.mem.Allocator;
     14 const Air = @import("Air.zig");
     15 const Log2Int = std.math.Log2Int;
     16 
     17 /// This array is split into sets of 4 bits per AIR instruction.
     18 /// The MSB (0bX000) is whether the instruction is unreferenced.
     19 /// The LSB (0b000X) is the first operand, and so on, up to 3 operands. A set bit means the
     20 /// operand dies after this instruction.
     21 /// Instructions which need more data to track liveness have special handling via the
     22 /// `special` table.
     23 tomb_bits: []usize,
     24 /// Sparse table of specially handled instructions. The value is an index into the `extra`
     25 /// array. The meaning of the data depends on the AIR tag.
     26 ///  * `cond_br` - points to a `CondBr` in `extra` at this index.
     27 ///  * `switch_br` - points to a `SwitchBr` in `extra` at this index.
     28 ///  * `asm`, `call`, `aggregate_init` - the value is a set of bits which are the extra tomb
     29 ///    bits of operands.
     30 ///    The main tomb bits are still used and the extra ones are starting with the lsb of the
     31 ///    value here.
     32 special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32),
     33 /// Auxiliary data. The way this data is interpreted is determined contextually.
     34 extra: []const u32,
     35 
     36 /// Trailing is the set of instructions whose lifetimes end at the start of the then branch,
     37 /// followed by the set of instructions whose lifetimes end at the start of the else branch.
     38 pub const CondBr = struct {
     39     then_death_count: u32,
     40     else_death_count: u32,
     41 };
     42 
     43 /// Trailing is:
     44 /// * For each case in the same order as in the AIR:
     45 ///   - case_death_count: u32
     46 ///   - Air.Inst.Index for each `case_death_count`: set of instructions whose lifetimes
     47 ///     end at the start of this case.
     48 /// * Air.Inst.Index for each `else_death_count`: set of instructions whose lifetimes
     49 ///   end at the start of the else case.
     50 pub const SwitchBr = struct {
     51     else_death_count: u32,
     52 };
     53 
     54 pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness {
     55     const tracy = trace(@src());
     56     defer tracy.end();
     57 
     58     var a: Analysis = .{
     59         .gpa = gpa,
     60         .air = air,
     61         .table = .{},
     62         .tomb_bits = try gpa.alloc(
     63             usize,
     64             (air.instructions.len * bpi + @bitSizeOf(usize) - 1) / @bitSizeOf(usize),
     65         ),
     66         .extra = .{},
     67         .special = .{},
     68     };
     69     errdefer gpa.free(a.tomb_bits);
     70     errdefer a.special.deinit(gpa);
     71     defer a.extra.deinit(gpa);
     72     defer a.table.deinit(gpa);
     73 
     74     std.mem.set(usize, a.tomb_bits, 0);
     75 
     76     const main_body = air.getMainBody();
     77     try a.table.ensureTotalCapacity(gpa, @intCast(u32, main_body.len));
     78     try analyzeWithContext(&a, null, main_body);
     79     return Liveness{
     80         .tomb_bits = a.tomb_bits,
     81         .special = a.special,
     82         .extra = a.extra.toOwnedSlice(gpa),
     83     };
     84 }
     85 
     86 pub fn getTombBits(l: Liveness, inst: Air.Inst.Index) Bpi {
     87     const usize_index = (inst * bpi) / @bitSizeOf(usize);
     88     return @truncate(Bpi, l.tomb_bits[usize_index] >>
     89         @intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi));
     90 }
     91 
     92 pub fn isUnused(l: Liveness, inst: Air.Inst.Index) bool {
     93     const usize_index = (inst * bpi) / @bitSizeOf(usize);
     94     const mask = @as(usize, 1) <<
     95         @intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi + (bpi - 1));
     96     return (l.tomb_bits[usize_index] & mask) != 0;
     97 }
     98 
     99 pub fn operandDies(l: Liveness, inst: Air.Inst.Index, operand: OperandInt) bool {
    100     assert(operand < bpi - 1);
    101     const usize_index = (inst * bpi) / @bitSizeOf(usize);
    102     const mask = @as(usize, 1) <<
    103         @intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi + operand);
    104     return (l.tomb_bits[usize_index] & mask) != 0;
    105 }
    106 
    107 pub fn clearOperandDeath(l: Liveness, inst: Air.Inst.Index, operand: OperandInt) void {
    108     assert(operand < bpi - 1);
    109     const usize_index = (inst * bpi) / @bitSizeOf(usize);
    110     const mask = @as(usize, 1) <<
    111         @intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi + operand);
    112     l.tomb_bits[usize_index] &= ~mask;
    113 }
    114 
    115 const OperandCategory = enum {
    116     /// The operand lives on, but this instruction cannot possibly mutate memory.
    117     none,
    118     /// The operand lives on and this instruction can mutate memory.
    119     write,
    120     /// The operand dies at this instruction.
    121     tomb,
    122     /// The operand lives on, and this instruction is noreturn.
    123     noret,
    124     /// This instruction is too complicated for analysis, no information is available.
    125     complex,
    126 };
    127 
    128 /// Given an instruction that we are examining, and an operand that we are looking for,
    129 /// returns a classification.
    130 pub fn categorizeOperand(
    131     l: Liveness,
    132     air: Air,
    133     inst: Air.Inst.Index,
    134     operand: Air.Inst.Index,
    135 ) OperandCategory {
    136     const air_tags = air.instructions.items(.tag);
    137     const air_datas = air.instructions.items(.data);
    138     const operand_ref = Air.indexToRef(operand);
    139     switch (air_tags[inst]) {
    140         .add,
    141         .addwrap,
    142         .add_sat,
    143         .sub,
    144         .subwrap,
    145         .sub_sat,
    146         .mul,
    147         .mulwrap,
    148         .mul_sat,
    149         .div_float,
    150         .div_trunc,
    151         .div_floor,
    152         .div_exact,
    153         .rem,
    154         .mod,
    155         .bit_and,
    156         .bit_or,
    157         .xor,
    158         .cmp_lt,
    159         .cmp_lte,
    160         .cmp_eq,
    161         .cmp_gte,
    162         .cmp_gt,
    163         .cmp_neq,
    164         .bool_and,
    165         .bool_or,
    166         .array_elem_val,
    167         .slice_elem_val,
    168         .ptr_elem_val,
    169         .shl,
    170         .shl_exact,
    171         .shl_sat,
    172         .shr,
    173         .shr_exact,
    174         .min,
    175         .max,
    176         .add_optimized,
    177         .addwrap_optimized,
    178         .sub_optimized,
    179         .subwrap_optimized,
    180         .mul_optimized,
    181         .mulwrap_optimized,
    182         .div_float_optimized,
    183         .div_trunc_optimized,
    184         .div_floor_optimized,
    185         .div_exact_optimized,
    186         .rem_optimized,
    187         .mod_optimized,
    188         .neg_optimized,
    189         .cmp_lt_optimized,
    190         .cmp_lte_optimized,
    191         .cmp_eq_optimized,
    192         .cmp_gte_optimized,
    193         .cmp_gt_optimized,
    194         .cmp_neq_optimized,
    195         => {
    196             const o = air_datas[inst].bin_op;
    197             if (o.lhs == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    198             if (o.rhs == operand_ref) return matchOperandSmallIndex(l, inst, 1, .none);
    199             return .none;
    200         },
    201 
    202         .store,
    203         .atomic_store_unordered,
    204         .atomic_store_monotonic,
    205         .atomic_store_release,
    206         .atomic_store_seq_cst,
    207         .set_union_tag,
    208         => {
    209             const o = air_datas[inst].bin_op;
    210             if (o.lhs == operand_ref) return matchOperandSmallIndex(l, inst, 0, .write);
    211             if (o.rhs == operand_ref) return matchOperandSmallIndex(l, inst, 1, .write);
    212             return .write;
    213         },
    214 
    215         .arg,
    216         .alloc,
    217         .ret_ptr,
    218         .constant,
    219         .const_ty,
    220         .breakpoint,
    221         .dbg_stmt,
    222         .dbg_inline_begin,
    223         .dbg_inline_end,
    224         .dbg_block_begin,
    225         .dbg_block_end,
    226         .unreach,
    227         .ret_addr,
    228         .frame_addr,
    229         .wasm_memory_size,
    230         .err_return_trace,
    231         => return .none,
    232 
    233         .fence => return .write,
    234 
    235         .not,
    236         .bitcast,
    237         .load,
    238         .fpext,
    239         .fptrunc,
    240         .intcast,
    241         .trunc,
    242         .optional_payload,
    243         .optional_payload_ptr,
    244         .wrap_optional,
    245         .unwrap_errunion_payload,
    246         .unwrap_errunion_err,
    247         .unwrap_errunion_payload_ptr,
    248         .unwrap_errunion_err_ptr,
    249         .wrap_errunion_payload,
    250         .wrap_errunion_err,
    251         .slice_ptr,
    252         .slice_len,
    253         .ptr_slice_len_ptr,
    254         .ptr_slice_ptr_ptr,
    255         .struct_field_ptr_index_0,
    256         .struct_field_ptr_index_1,
    257         .struct_field_ptr_index_2,
    258         .struct_field_ptr_index_3,
    259         .array_to_slice,
    260         .float_to_int,
    261         .float_to_int_optimized,
    262         .int_to_float,
    263         .get_union_tag,
    264         .clz,
    265         .ctz,
    266         .popcount,
    267         .byte_swap,
    268         .bit_reverse,
    269         .splat,
    270         => {
    271             const o = air_datas[inst].ty_op;
    272             if (o.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    273             return .none;
    274         },
    275 
    276         .optional_payload_ptr_set,
    277         .errunion_payload_ptr_set,
    278         => {
    279             const o = air_datas[inst].ty_op;
    280             if (o.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .write);
    281             return .write;
    282         },
    283 
    284         .is_null,
    285         .is_non_null,
    286         .is_null_ptr,
    287         .is_non_null_ptr,
    288         .is_err,
    289         .is_non_err,
    290         .is_err_ptr,
    291         .is_non_err_ptr,
    292         .ptrtoint,
    293         .bool_to_int,
    294         .tag_name,
    295         .error_name,
    296         .sqrt,
    297         .sin,
    298         .cos,
    299         .tan,
    300         .exp,
    301         .exp2,
    302         .log,
    303         .log2,
    304         .log10,
    305         .fabs,
    306         .floor,
    307         .ceil,
    308         .round,
    309         .trunc_float,
    310         .neg,
    311         .cmp_lt_errors_len,
    312         => {
    313             const o = air_datas[inst].un_op;
    314             if (o == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    315             return .none;
    316         },
    317 
    318         .ret,
    319         .ret_load,
    320         => {
    321             const o = air_datas[inst].un_op;
    322             if (o == operand_ref) return matchOperandSmallIndex(l, inst, 0, .noret);
    323             return .noret;
    324         },
    325 
    326         .set_err_return_trace => {
    327             const o = air_datas[inst].un_op;
    328             if (o == operand_ref) return matchOperandSmallIndex(l, inst, 0, .write);
    329             return .write;
    330         },
    331 
    332         .add_with_overflow,
    333         .sub_with_overflow,
    334         .mul_with_overflow,
    335         .shl_with_overflow,
    336         .ptr_add,
    337         .ptr_sub,
    338         .ptr_elem_ptr,
    339         .slice_elem_ptr,
    340         .slice,
    341         => {
    342             const ty_pl = air_datas[inst].ty_pl;
    343             const extra = air.extraData(Air.Bin, ty_pl.payload).data;
    344             if (extra.lhs == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    345             if (extra.rhs == operand_ref) return matchOperandSmallIndex(l, inst, 1, .none);
    346             return .none;
    347         },
    348 
    349         .dbg_var_ptr,
    350         .dbg_var_val,
    351         => {
    352             const o = air_datas[inst].pl_op.operand;
    353             if (o == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    354             return .none;
    355         },
    356 
    357         .prefetch => {
    358             const prefetch = air_datas[inst].prefetch;
    359             if (prefetch.ptr == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    360             return .none;
    361         },
    362 
    363         .call, .call_always_tail, .call_never_tail, .call_never_inline => {
    364             const inst_data = air_datas[inst].pl_op;
    365             const callee = inst_data.operand;
    366             const extra = air.extraData(Air.Call, inst_data.payload);
    367             const args = @ptrCast([]const Air.Inst.Ref, air.extra[extra.end..][0..extra.data.args_len]);
    368             if (args.len + 1 <= bpi - 1) {
    369                 if (callee == operand_ref) return matchOperandSmallIndex(l, inst, 0, .write);
    370                 for (args) |arg, i| {
    371                     if (arg == operand_ref) return matchOperandSmallIndex(l, inst, @intCast(OperandInt, i + 1), .write);
    372                 }
    373                 return .write;
    374             }
    375             var bt = l.iterateBigTomb(inst);
    376             if (bt.feed()) {
    377                 if (callee == operand_ref) return .tomb;
    378             } else {
    379                 if (callee == operand_ref) return .write;
    380             }
    381             for (args) |arg| {
    382                 if (bt.feed()) {
    383                     if (arg == operand_ref) return .tomb;
    384                 } else {
    385                     if (arg == operand_ref) return .write;
    386                 }
    387             }
    388             return .write;
    389         },
    390         .select => {
    391             const pl_op = air_datas[inst].pl_op;
    392             const extra = air.extraData(Air.Bin, pl_op.payload).data;
    393             if (pl_op.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    394             if (extra.lhs == operand_ref) return matchOperandSmallIndex(l, inst, 1, .none);
    395             if (extra.rhs == operand_ref) return matchOperandSmallIndex(l, inst, 2, .none);
    396             return .none;
    397         },
    398         .shuffle => {
    399             const extra = air.extraData(Air.Shuffle, air_datas[inst].ty_pl.payload).data;
    400             if (extra.a == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    401             if (extra.b == operand_ref) return matchOperandSmallIndex(l, inst, 1, .none);
    402             return .none;
    403         },
    404         .reduce, .reduce_optimized => {
    405             const reduce = air_datas[inst].reduce;
    406             if (reduce.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    407             return .none;
    408         },
    409         .cmp_vector, .cmp_vector_optimized => {
    410             const extra = air.extraData(Air.VectorCmp, air_datas[inst].ty_pl.payload).data;
    411             if (extra.lhs == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    412             if (extra.rhs == operand_ref) return matchOperandSmallIndex(l, inst, 1, .none);
    413             return .none;
    414         },
    415         .aggregate_init => {
    416             const ty_pl = air_datas[inst].ty_pl;
    417             const aggregate_ty = air.getRefType(ty_pl.ty);
    418             const len = @intCast(usize, aggregate_ty.arrayLen());
    419             const elements = @ptrCast([]const Air.Inst.Ref, air.extra[ty_pl.payload..][0..len]);
    420 
    421             if (elements.len <= bpi - 1) {
    422                 for (elements) |elem, i| {
    423                     if (elem == operand_ref) return matchOperandSmallIndex(l, inst, @intCast(OperandInt, i), .none);
    424                 }
    425                 return .none;
    426             }
    427 
    428             var bt = l.iterateBigTomb(inst);
    429             for (elements) |elem| {
    430                 if (bt.feed()) {
    431                     if (elem == operand_ref) return .tomb;
    432                 } else {
    433                     if (elem == operand_ref) return .write;
    434                 }
    435             }
    436             return .write;
    437         },
    438         .union_init => {
    439             const extra = air.extraData(Air.UnionInit, air_datas[inst].ty_pl.payload).data;
    440             if (extra.init == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    441             return .none;
    442         },
    443         .struct_field_ptr, .struct_field_val => {
    444             const extra = air.extraData(Air.StructField, air_datas[inst].ty_pl.payload).data;
    445             if (extra.struct_operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    446             return .none;
    447         },
    448         .field_parent_ptr => {
    449             const extra = air.extraData(Air.FieldParentPtr, air_datas[inst].ty_pl.payload).data;
    450             if (extra.field_ptr == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    451             return .none;
    452         },
    453         .cmpxchg_strong, .cmpxchg_weak => {
    454             const extra = air.extraData(Air.Cmpxchg, air_datas[inst].ty_pl.payload).data;
    455             if (extra.ptr == operand_ref) return matchOperandSmallIndex(l, inst, 0, .write);
    456             if (extra.expected_value == operand_ref) return matchOperandSmallIndex(l, inst, 1, .write);
    457             if (extra.new_value == operand_ref) return matchOperandSmallIndex(l, inst, 2, .write);
    458             return .write;
    459         },
    460         .mul_add => {
    461             const pl_op = air_datas[inst].pl_op;
    462             const extra = air.extraData(Air.Bin, pl_op.payload).data;
    463             if (extra.lhs == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    464             if (extra.rhs == operand_ref) return matchOperandSmallIndex(l, inst, 1, .none);
    465             if (pl_op.operand == operand_ref) return matchOperandSmallIndex(l, inst, 2, .none);
    466             return .none;
    467         },
    468         .atomic_load => {
    469             const ptr = air_datas[inst].atomic_load.ptr;
    470             if (ptr == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    471             return .none;
    472         },
    473         .atomic_rmw => {
    474             const pl_op = air_datas[inst].pl_op;
    475             const extra = air.extraData(Air.AtomicRmw, pl_op.payload).data;
    476             if (pl_op.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .write);
    477             if (extra.operand == operand_ref) return matchOperandSmallIndex(l, inst, 1, .write);
    478             return .write;
    479         },
    480         .memset,
    481         .memcpy,
    482         => {
    483             const pl_op = air_datas[inst].pl_op;
    484             const extra = air.extraData(Air.Bin, pl_op.payload).data;
    485             if (pl_op.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .write);
    486             if (extra.lhs == operand_ref) return matchOperandSmallIndex(l, inst, 1, .write);
    487             if (extra.rhs == operand_ref) return matchOperandSmallIndex(l, inst, 2, .write);
    488             return .write;
    489         },
    490 
    491         .br => {
    492             const br = air_datas[inst].br;
    493             if (br.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .noret);
    494             return .noret;
    495         },
    496         .assembly => {
    497             return .complex;
    498         },
    499         .block => {
    500             return .complex;
    501         },
    502         .@"try" => {
    503             return .complex;
    504         },
    505         .try_ptr => {
    506             return .complex;
    507         },
    508         .loop => {
    509             return .complex;
    510         },
    511         .cond_br => {
    512             return .complex;
    513         },
    514         .switch_br => {
    515             return .complex;
    516         },
    517         .wasm_memory_grow => {
    518             const pl_op = air_datas[inst].pl_op;
    519             if (pl_op.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
    520             return .none;
    521         },
    522     }
    523 }
    524 
    525 fn matchOperandSmallIndex(
    526     l: Liveness,
    527     inst: Air.Inst.Index,
    528     operand: OperandInt,
    529     default: OperandCategory,
    530 ) OperandCategory {
    531     if (operandDies(l, inst, operand)) {
    532         return .tomb;
    533     } else {
    534         return default;
    535     }
    536 }
    537 
    538 /// Higher level API.
    539 pub const CondBrSlices = struct {
    540     then_deaths: []const Air.Inst.Index,
    541     else_deaths: []const Air.Inst.Index,
    542 };
    543 
    544 pub fn getCondBr(l: Liveness, inst: Air.Inst.Index) CondBrSlices {
    545     var index: usize = l.special.get(inst) orelse return .{
    546         .then_deaths = &.{},
    547         .else_deaths = &.{},
    548     };
    549     const then_death_count = l.extra[index];
    550     index += 1;
    551     const else_death_count = l.extra[index];
    552     index += 1;
    553     const then_deaths = l.extra[index..][0..then_death_count];
    554     index += then_death_count;
    555     return .{
    556         .then_deaths = then_deaths,
    557         .else_deaths = l.extra[index..][0..else_death_count],
    558     };
    559 }
    560 
    561 /// Indexed by case number as they appear in AIR.
    562 /// Else is the last element.
    563 pub const SwitchBrTable = struct {
    564     deaths: []const []const Air.Inst.Index,
    565 };
    566 
    567 /// Caller owns the memory.
    568 pub fn getSwitchBr(l: Liveness, gpa: Allocator, inst: Air.Inst.Index, cases_len: u32) Allocator.Error!SwitchBrTable {
    569     var index: usize = l.special.get(inst) orelse return SwitchBrTable{
    570         .deaths = &.{},
    571     };
    572     const else_death_count = l.extra[index];
    573     index += 1;
    574 
    575     var deaths = std.ArrayList([]const Air.Inst.Index).init(gpa);
    576     defer deaths.deinit();
    577     try deaths.ensureTotalCapacity(cases_len + 1);
    578 
    579     var case_i: u32 = 0;
    580     while (case_i < cases_len - 1) : (case_i += 1) {
    581         const case_death_count: u32 = l.extra[index];
    582         index += 1;
    583         const case_deaths = l.extra[index..][0..case_death_count];
    584         index += case_death_count;
    585         deaths.appendAssumeCapacity(case_deaths);
    586     }
    587     {
    588         // Else
    589         const else_deaths = l.extra[index..][0..else_death_count];
    590         deaths.appendAssumeCapacity(else_deaths);
    591     }
    592     return SwitchBrTable{
    593         .deaths = deaths.toOwnedSlice(),
    594     };
    595 }
    596 
    597 pub fn deinit(l: *Liveness, gpa: Allocator) void {
    598     gpa.free(l.tomb_bits);
    599     gpa.free(l.extra);
    600     l.special.deinit(gpa);
    601     l.* = undefined;
    602 }
    603 
    604 pub fn iterateBigTomb(l: Liveness, inst: Air.Inst.Index) BigTomb {
    605     return .{
    606         .tomb_bits = l.getTombBits(inst),
    607         .extra_start = l.special.get(inst) orelse 0,
    608         .extra_offset = 0,
    609         .extra = l.extra,
    610         .bit_index = 0,
    611     };
    612 }
    613 
    614 /// How many tomb bits per AIR instruction.
    615 pub const bpi = 4;
    616 pub const Bpi = std.meta.Int(.unsigned, bpi);
    617 pub const OperandInt = std.math.Log2Int(Bpi);
    618 
    619 /// Useful for decoders of Liveness information.
    620 pub const BigTomb = struct {
    621     tomb_bits: Liveness.Bpi,
    622     bit_index: u32,
    623     extra_start: u32,
    624     extra_offset: u32,
    625     extra: []const u32,
    626 
    627     /// Returns whether the next operand dies.
    628     pub fn feed(bt: *BigTomb) bool {
    629         const this_bit_index = bt.bit_index;
    630         bt.bit_index += 1;
    631 
    632         const small_tombs = Liveness.bpi - 1;
    633         if (this_bit_index < small_tombs) {
    634             const dies = @truncate(u1, bt.tomb_bits >> @intCast(Liveness.OperandInt, this_bit_index)) != 0;
    635             return dies;
    636         }
    637 
    638         const big_bit_index = this_bit_index - small_tombs;
    639         while (big_bit_index - bt.extra_offset * 31 >= 31) {
    640             bt.extra_offset += 1;
    641         }
    642         const dies = @truncate(u1, bt.extra[bt.extra_start + bt.extra_offset] >>
    643             @intCast(u5, big_bit_index - bt.extra_offset * 31)) != 0;
    644         return dies;
    645     }
    646 };
    647 
    648 /// In-progress data; on successful analysis converted into `Liveness`.
    649 const Analysis = struct {
    650     gpa: Allocator,
    651     air: Air,
    652     table: std.AutoHashMapUnmanaged(Air.Inst.Index, void),
    653     tomb_bits: []usize,
    654     special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32),
    655     extra: std.ArrayListUnmanaged(u32),
    656 
    657     fn storeTombBits(a: *Analysis, inst: Air.Inst.Index, tomb_bits: Bpi) void {
    658         const usize_index = (inst * bpi) / @bitSizeOf(usize);
    659         a.tomb_bits[usize_index] |= @as(usize, tomb_bits) <<
    660             @intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi);
    661     }
    662 
    663     fn addExtra(a: *Analysis, extra: anytype) Allocator.Error!u32 {
    664         const fields = std.meta.fields(@TypeOf(extra));
    665         try a.extra.ensureUnusedCapacity(a.gpa, fields.len);
    666         return addExtraAssumeCapacity(a, extra);
    667     }
    668 
    669     fn addExtraAssumeCapacity(a: *Analysis, extra: anytype) u32 {
    670         const fields = std.meta.fields(@TypeOf(extra));
    671         const result = @intCast(u32, a.extra.items.len);
    672         inline for (fields) |field| {
    673             a.extra.appendAssumeCapacity(switch (field.field_type) {
    674                 u32 => @field(extra, field.name),
    675                 else => @compileError("bad field type"),
    676             });
    677         }
    678         return result;
    679     }
    680 };
    681 
    682 fn analyzeWithContext(
    683     a: *Analysis,
    684     new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
    685     body: []const Air.Inst.Index,
    686 ) Allocator.Error!void {
    687     var i: usize = body.len;
    688 
    689     if (new_set) |ns| {
    690         // We are only interested in doing this for instructions which are born
    691         // before a conditional branch, so after obtaining the new set for
    692         // each branch we prune the instructions which were born within.
    693         while (i != 0) {
    694             i -= 1;
    695             const inst = body[i];
    696             _ = ns.remove(inst);
    697             try analyzeInst(a, new_set, inst);
    698         }
    699     } else {
    700         while (i != 0) {
    701             i -= 1;
    702             const inst = body[i];
    703             try analyzeInst(a, new_set, inst);
    704         }
    705     }
    706 }
    707 
    708 fn analyzeInst(
    709     a: *Analysis,
    710     new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
    711     inst: Air.Inst.Index,
    712 ) Allocator.Error!void {
    713     const gpa = a.gpa;
    714     const table = &a.table;
    715     const inst_tags = a.air.instructions.items(.tag);
    716     const inst_datas = a.air.instructions.items(.data);
    717 
    718     // No tombstone for this instruction means it is never referenced,
    719     // and its birth marks its own death. Very metal 🤘
    720     const main_tomb = !table.contains(inst);
    721 
    722     switch (inst_tags[inst]) {
    723         .add,
    724         .add_optimized,
    725         .addwrap,
    726         .addwrap_optimized,
    727         .add_sat,
    728         .sub,
    729         .sub_optimized,
    730         .subwrap,
    731         .subwrap_optimized,
    732         .sub_sat,
    733         .mul,
    734         .mul_optimized,
    735         .mulwrap,
    736         .mulwrap_optimized,
    737         .mul_sat,
    738         .div_float,
    739         .div_float_optimized,
    740         .div_trunc,
    741         .div_trunc_optimized,
    742         .div_floor,
    743         .div_floor_optimized,
    744         .div_exact,
    745         .div_exact_optimized,
    746         .rem,
    747         .rem_optimized,
    748         .mod,
    749         .mod_optimized,
    750         .bit_and,
    751         .bit_or,
    752         .xor,
    753         .cmp_lt,
    754         .cmp_lt_optimized,
    755         .cmp_lte,
    756         .cmp_lte_optimized,
    757         .cmp_eq,
    758         .cmp_eq_optimized,
    759         .cmp_gte,
    760         .cmp_gte_optimized,
    761         .cmp_gt,
    762         .cmp_gt_optimized,
    763         .cmp_neq,
    764         .cmp_neq_optimized,
    765         .bool_and,
    766         .bool_or,
    767         .store,
    768         .array_elem_val,
    769         .slice_elem_val,
    770         .ptr_elem_val,
    771         .shl,
    772         .shl_exact,
    773         .shl_sat,
    774         .shr,
    775         .shr_exact,
    776         .atomic_store_unordered,
    777         .atomic_store_monotonic,
    778         .atomic_store_release,
    779         .atomic_store_seq_cst,
    780         .set_union_tag,
    781         .min,
    782         .max,
    783         => {
    784             const o = inst_datas[inst].bin_op;
    785             return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none });
    786         },
    787 
    788         .arg,
    789         .alloc,
    790         .ret_ptr,
    791         .constant,
    792         .const_ty,
    793         .breakpoint,
    794         .dbg_stmt,
    795         .dbg_inline_begin,
    796         .dbg_inline_end,
    797         .dbg_block_begin,
    798         .dbg_block_end,
    799         .unreach,
    800         .fence,
    801         .ret_addr,
    802         .frame_addr,
    803         .wasm_memory_size,
    804         .err_return_trace,
    805         => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }),
    806 
    807         .not,
    808         .bitcast,
    809         .load,
    810         .fpext,
    811         .fptrunc,
    812         .intcast,
    813         .trunc,
    814         .optional_payload,
    815         .optional_payload_ptr,
    816         .optional_payload_ptr_set,
    817         .errunion_payload_ptr_set,
    818         .wrap_optional,
    819         .unwrap_errunion_payload,
    820         .unwrap_errunion_err,
    821         .unwrap_errunion_payload_ptr,
    822         .unwrap_errunion_err_ptr,
    823         .wrap_errunion_payload,
    824         .wrap_errunion_err,
    825         .slice_ptr,
    826         .slice_len,
    827         .ptr_slice_len_ptr,
    828         .ptr_slice_ptr_ptr,
    829         .struct_field_ptr_index_0,
    830         .struct_field_ptr_index_1,
    831         .struct_field_ptr_index_2,
    832         .struct_field_ptr_index_3,
    833         .array_to_slice,
    834         .float_to_int,
    835         .float_to_int_optimized,
    836         .int_to_float,
    837         .get_union_tag,
    838         .clz,
    839         .ctz,
    840         .popcount,
    841         .byte_swap,
    842         .bit_reverse,
    843         .splat,
    844         => {
    845             const o = inst_datas[inst].ty_op;
    846             return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });
    847         },
    848 
    849         .is_null,
    850         .is_non_null,
    851         .is_null_ptr,
    852         .is_non_null_ptr,
    853         .is_err,
    854         .is_non_err,
    855         .is_err_ptr,
    856         .is_non_err_ptr,
    857         .ptrtoint,
    858         .bool_to_int,
    859         .ret,
    860         .ret_load,
    861         .tag_name,
    862         .error_name,
    863         .sqrt,
    864         .sin,
    865         .cos,
    866         .tan,
    867         .exp,
    868         .exp2,
    869         .log,
    870         .log2,
    871         .log10,
    872         .fabs,
    873         .floor,
    874         .ceil,
    875         .round,
    876         .trunc_float,
    877         .neg,
    878         .neg_optimized,
    879         .cmp_lt_errors_len,
    880         .set_err_return_trace,
    881         => {
    882             const operand = inst_datas[inst].un_op;
    883             return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none });
    884         },
    885 
    886         .add_with_overflow,
    887         .sub_with_overflow,
    888         .mul_with_overflow,
    889         .shl_with_overflow,
    890         .ptr_add,
    891         .ptr_sub,
    892         .ptr_elem_ptr,
    893         .slice_elem_ptr,
    894         .slice,
    895         => {
    896             const ty_pl = inst_datas[inst].ty_pl;
    897             const extra = a.air.extraData(Air.Bin, ty_pl.payload).data;
    898             return trackOperands(a, new_set, inst, main_tomb, .{ extra.lhs, extra.rhs, .none });
    899         },
    900 
    901         .dbg_var_ptr,
    902         .dbg_var_val,
    903         => {
    904             const operand = inst_datas[inst].pl_op.operand;
    905             return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none });
    906         },
    907 
    908         .prefetch => {
    909             const prefetch = inst_datas[inst].prefetch;
    910             return trackOperands(a, new_set, inst, main_tomb, .{ prefetch.ptr, .none, .none });
    911         },
    912 
    913         .call, .call_always_tail, .call_never_tail, .call_never_inline => {
    914             const inst_data = inst_datas[inst].pl_op;
    915             const callee = inst_data.operand;
    916             const extra = a.air.extraData(Air.Call, inst_data.payload);
    917             const args = @ptrCast([]const Air.Inst.Ref, a.air.extra[extra.end..][0..extra.data.args_len]);
    918             if (args.len + 1 <= bpi - 1) {
    919                 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
    920                 buf[0] = callee;
    921                 std.mem.copy(Air.Inst.Ref, buf[1..], args);
    922                 return trackOperands(a, new_set, inst, main_tomb, buf);
    923             }
    924             var extra_tombs: ExtraTombs = .{
    925                 .analysis = a,
    926                 .new_set = new_set,
    927                 .inst = inst,
    928                 .main_tomb = main_tomb,
    929             };
    930             defer extra_tombs.deinit();
    931             try extra_tombs.feed(callee);
    932             for (args) |arg| {
    933                 try extra_tombs.feed(arg);
    934             }
    935             return extra_tombs.finish();
    936         },
    937         .select => {
    938             const pl_op = inst_datas[inst].pl_op;
    939             const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
    940             return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.lhs, extra.rhs });
    941         },
    942         .shuffle => {
    943             const extra = a.air.extraData(Air.Shuffle, inst_datas[inst].ty_pl.payload).data;
    944             return trackOperands(a, new_set, inst, main_tomb, .{ extra.a, extra.b, .none });
    945         },
    946         .reduce, .reduce_optimized => {
    947             const reduce = inst_datas[inst].reduce;
    948             return trackOperands(a, new_set, inst, main_tomb, .{ reduce.operand, .none, .none });
    949         },
    950         .cmp_vector, .cmp_vector_optimized => {
    951             const extra = a.air.extraData(Air.VectorCmp, inst_datas[inst].ty_pl.payload).data;
    952             return trackOperands(a, new_set, inst, main_tomb, .{ extra.lhs, extra.rhs, .none });
    953         },
    954         .aggregate_init => {
    955             const ty_pl = inst_datas[inst].ty_pl;
    956             const aggregate_ty = a.air.getRefType(ty_pl.ty);
    957             const len = @intCast(usize, aggregate_ty.arrayLen());
    958             const elements = @ptrCast([]const Air.Inst.Ref, a.air.extra[ty_pl.payload..][0..len]);
    959 
    960             if (elements.len <= bpi - 1) {
    961                 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
    962                 std.mem.copy(Air.Inst.Ref, &buf, elements);
    963                 return trackOperands(a, new_set, inst, main_tomb, buf);
    964             }
    965             var extra_tombs: ExtraTombs = .{
    966                 .analysis = a,
    967                 .new_set = new_set,
    968                 .inst = inst,
    969                 .main_tomb = main_tomb,
    970             };
    971             defer extra_tombs.deinit();
    972             for (elements) |elem| {
    973                 try extra_tombs.feed(elem);
    974             }
    975             return extra_tombs.finish();
    976         },
    977         .union_init => {
    978             const extra = a.air.extraData(Air.UnionInit, inst_datas[inst].ty_pl.payload).data;
    979             return trackOperands(a, new_set, inst, main_tomb, .{ extra.init, .none, .none });
    980         },
    981         .struct_field_ptr, .struct_field_val => {
    982             const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data;
    983             return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_operand, .none, .none });
    984         },
    985         .field_parent_ptr => {
    986             const extra = a.air.extraData(Air.FieldParentPtr, inst_datas[inst].ty_pl.payload).data;
    987             return trackOperands(a, new_set, inst, main_tomb, .{ extra.field_ptr, .none, .none });
    988         },
    989         .cmpxchg_strong, .cmpxchg_weak => {
    990             const extra = a.air.extraData(Air.Cmpxchg, inst_datas[inst].ty_pl.payload).data;
    991             return trackOperands(a, new_set, inst, main_tomb, .{ extra.ptr, extra.expected_value, extra.new_value });
    992         },
    993         .mul_add => {
    994             const pl_op = inst_datas[inst].pl_op;
    995             const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
    996             return trackOperands(a, new_set, inst, main_tomb, .{ extra.lhs, extra.rhs, pl_op.operand });
    997         },
    998         .atomic_load => {
    999             const ptr = inst_datas[inst].atomic_load.ptr;
   1000             return trackOperands(a, new_set, inst, main_tomb, .{ ptr, .none, .none });
   1001         },
   1002         .atomic_rmw => {
   1003             const pl_op = inst_datas[inst].pl_op;
   1004             const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data;
   1005             return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.operand, .none });
   1006         },
   1007         .memset,
   1008         .memcpy,
   1009         => {
   1010             const pl_op = inst_datas[inst].pl_op;
   1011             const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
   1012             return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.lhs, extra.rhs });
   1013         },
   1014 
   1015         .br => {
   1016             const br = inst_datas[inst].br;
   1017             return trackOperands(a, new_set, inst, main_tomb, .{ br.operand, .none, .none });
   1018         },
   1019         .assembly => {
   1020             const extra = a.air.extraData(Air.Asm, inst_datas[inst].ty_pl.payload);
   1021             var extra_i: usize = extra.end;
   1022             const outputs = @ptrCast([]const Air.Inst.Ref, a.air.extra[extra_i..][0..extra.data.outputs_len]);
   1023             extra_i += outputs.len;
   1024             const inputs = @ptrCast([]const Air.Inst.Ref, a.air.extra[extra_i..][0..extra.data.inputs_len]);
   1025             extra_i += inputs.len;
   1026 
   1027             simple: {
   1028                 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
   1029                 var buf_index: usize = 0;
   1030                 for (outputs) |output| {
   1031                     if (output != .none) {
   1032                         if (buf_index >= buf.len) break :simple;
   1033                         buf[buf_index] = output;
   1034                         buf_index += 1;
   1035                     }
   1036                 }
   1037                 if (buf_index + inputs.len > buf.len) break :simple;
   1038                 std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs);
   1039                 return trackOperands(a, new_set, inst, main_tomb, buf);
   1040             }
   1041             var extra_tombs: ExtraTombs = .{
   1042                 .analysis = a,
   1043                 .new_set = new_set,
   1044                 .inst = inst,
   1045                 .main_tomb = main_tomb,
   1046             };
   1047             defer extra_tombs.deinit();
   1048             for (outputs) |output| {
   1049                 if (output != .none) {
   1050                     try extra_tombs.feed(output);
   1051                 }
   1052             }
   1053             for (inputs) |input| {
   1054                 try extra_tombs.feed(input);
   1055             }
   1056             return extra_tombs.finish();
   1057         },
   1058         .block => {
   1059             const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
   1060             const body = a.air.extra[extra.end..][0..extra.data.body_len];
   1061             try analyzeWithContext(a, new_set, body);
   1062             return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none });
   1063         },
   1064         .loop => {
   1065             const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
   1066             const body = a.air.extra[extra.end..][0..extra.data.body_len];
   1067             try analyzeWithContext(a, new_set, body);
   1068             return; // Loop has no operands and it is always unreferenced.
   1069         },
   1070         .@"try" => {
   1071             const pl_op = inst_datas[inst].pl_op;
   1072             const extra = a.air.extraData(Air.Try, pl_op.payload);
   1073             const body = a.air.extra[extra.end..][0..extra.data.body_len];
   1074             try analyzeWithContext(a, new_set, body);
   1075             return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, .none, .none });
   1076         },
   1077         .try_ptr => {
   1078             const extra = a.air.extraData(Air.TryPtr, inst_datas[inst].ty_pl.payload);
   1079             const body = a.air.extra[extra.end..][0..extra.data.body_len];
   1080             try analyzeWithContext(a, new_set, body);
   1081             return trackOperands(a, new_set, inst, main_tomb, .{ extra.data.ptr, .none, .none });
   1082         },
   1083         .cond_br => {
   1084             // Each death that occurs inside one branch, but not the other, needs
   1085             // to be added as a death immediately upon entering the other branch.
   1086             const inst_data = inst_datas[inst].pl_op;
   1087             const condition = inst_data.operand;
   1088             const extra = a.air.extraData(Air.CondBr, inst_data.payload);
   1089             const then_body = a.air.extra[extra.end..][0..extra.data.then_body_len];
   1090             const else_body = a.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
   1091 
   1092             var then_table: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};
   1093             defer then_table.deinit(gpa);
   1094             try analyzeWithContext(a, &then_table, then_body);
   1095 
   1096             // Reset the table back to its state from before the branch.
   1097             {
   1098                 var it = then_table.keyIterator();
   1099                 while (it.next()) |key| {
   1100                     assert(table.remove(key.*));
   1101                 }
   1102             }
   1103 
   1104             var else_table: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};
   1105             defer else_table.deinit(gpa);
   1106             try analyzeWithContext(a, &else_table, else_body);
   1107 
   1108             var then_entry_deaths = std.ArrayList(Air.Inst.Index).init(gpa);
   1109             defer then_entry_deaths.deinit();
   1110             var else_entry_deaths = std.ArrayList(Air.Inst.Index).init(gpa);
   1111             defer else_entry_deaths.deinit();
   1112 
   1113             {
   1114                 var it = else_table.keyIterator();
   1115                 while (it.next()) |key| {
   1116                     const else_death = key.*;
   1117                     if (!then_table.contains(else_death)) {
   1118                         try then_entry_deaths.append(else_death);
   1119                     }
   1120                 }
   1121             }
   1122             // This loop is the same, except it's for the then branch, and it additionally
   1123             // has to put its items back into the table to undo the reset.
   1124             {
   1125                 var it = then_table.keyIterator();
   1126                 while (it.next()) |key| {
   1127                     const then_death = key.*;
   1128                     if (!else_table.contains(then_death)) {
   1129                         try else_entry_deaths.append(then_death);
   1130                     }
   1131                     try table.put(gpa, then_death, {});
   1132                 }
   1133             }
   1134             // Now we have to correctly populate new_set.
   1135             if (new_set) |ns| {
   1136                 try ns.ensureUnusedCapacity(gpa, @intCast(u32, then_table.count() + else_table.count()));
   1137                 var it = then_table.keyIterator();
   1138                 while (it.next()) |key| {
   1139                     _ = ns.putAssumeCapacity(key.*, {});
   1140                 }
   1141                 it = else_table.keyIterator();
   1142                 while (it.next()) |key| {
   1143                     _ = ns.putAssumeCapacity(key.*, {});
   1144                 }
   1145             }
   1146             const then_death_count = @intCast(u32, then_entry_deaths.items.len);
   1147             const else_death_count = @intCast(u32, else_entry_deaths.items.len);
   1148 
   1149             try a.extra.ensureUnusedCapacity(gpa, std.meta.fields(Air.CondBr).len +
   1150                 then_death_count + else_death_count);
   1151             const extra_index = a.addExtraAssumeCapacity(CondBr{
   1152                 .then_death_count = then_death_count,
   1153                 .else_death_count = else_death_count,
   1154             });
   1155             a.extra.appendSliceAssumeCapacity(then_entry_deaths.items);
   1156             a.extra.appendSliceAssumeCapacity(else_entry_deaths.items);
   1157             try a.special.put(gpa, inst, extra_index);
   1158 
   1159             // Continue on with the instruction analysis. The following code will find the condition
   1160             // instruction, and the deaths flag for the CondBr instruction will indicate whether the
   1161             // condition's lifetime ends immediately before entering any branch.
   1162             return trackOperands(a, new_set, inst, main_tomb, .{ condition, .none, .none });
   1163         },
   1164         .switch_br => {
   1165             const pl_op = inst_datas[inst].pl_op;
   1166             const condition = pl_op.operand;
   1167             const switch_br = a.air.extraData(Air.SwitchBr, pl_op.payload);
   1168 
   1169             const Table = std.AutoHashMapUnmanaged(Air.Inst.Index, void);
   1170             const case_tables = try gpa.alloc(Table, switch_br.data.cases_len + 1); // +1 for else
   1171             defer gpa.free(case_tables);
   1172 
   1173             std.mem.set(Table, case_tables, .{});
   1174             defer for (case_tables) |*ct| ct.deinit(gpa);
   1175 
   1176             var air_extra_index: usize = switch_br.end;
   1177             for (case_tables[0..switch_br.data.cases_len]) |*case_table| {
   1178                 const case = a.air.extraData(Air.SwitchBr.Case, air_extra_index);
   1179                 const case_body = a.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];
   1180                 air_extra_index = case.end + case.data.items_len + case_body.len;
   1181                 try analyzeWithContext(a, case_table, case_body);
   1182 
   1183                 // Reset the table back to its state from before the case.
   1184                 var it = case_table.keyIterator();
   1185                 while (it.next()) |key| {
   1186                     assert(table.remove(key.*));
   1187                 }
   1188             }
   1189             { // else
   1190                 const else_table = &case_tables[case_tables.len - 1];
   1191                 const else_body = a.air.extra[air_extra_index..][0..switch_br.data.else_body_len];
   1192                 try analyzeWithContext(a, else_table, else_body);
   1193 
   1194                 // Reset the table back to its state from before the case.
   1195                 var it = else_table.keyIterator();
   1196                 while (it.next()) |key| {
   1197                     assert(table.remove(key.*));
   1198                 }
   1199             }
   1200 
   1201             const List = std.ArrayListUnmanaged(Air.Inst.Index);
   1202             const case_deaths = try gpa.alloc(List, case_tables.len); // includes else
   1203             defer gpa.free(case_deaths);
   1204 
   1205             std.mem.set(List, case_deaths, .{});
   1206             defer for (case_deaths) |*cd| cd.deinit(gpa);
   1207 
   1208             var total_deaths: u32 = 0;
   1209             for (case_tables) |*ct, i| {
   1210                 total_deaths += ct.count();
   1211                 var it = ct.keyIterator();
   1212                 while (it.next()) |key| {
   1213                     const case_death = key.*;
   1214                     for (case_tables) |*ct_inner, j| {
   1215                         if (i == j) continue;
   1216                         if (!ct_inner.contains(case_death)) {
   1217                             // instruction is not referenced in this case
   1218                             try case_deaths[j].append(gpa, case_death);
   1219                         }
   1220                     }
   1221                     // undo resetting the table
   1222                     try table.put(gpa, case_death, {});
   1223                 }
   1224             }
   1225 
   1226             // Now we have to correctly populate new_set.
   1227             if (new_set) |ns| {
   1228                 try ns.ensureUnusedCapacity(gpa, total_deaths);
   1229                 for (case_tables) |*ct| {
   1230                     var it = ct.keyIterator();
   1231                     while (it.next()) |key| {
   1232                         _ = ns.putAssumeCapacity(key.*, {});
   1233                     }
   1234                 }
   1235             }
   1236 
   1237             const else_death_count = @intCast(u32, case_deaths[case_deaths.len - 1].items.len);
   1238             const extra_index = try a.addExtra(SwitchBr{
   1239                 .else_death_count = else_death_count,
   1240             });
   1241             for (case_deaths[0 .. case_deaths.len - 1]) |*cd| {
   1242                 const case_death_count = @intCast(u32, cd.items.len);
   1243                 try a.extra.ensureUnusedCapacity(gpa, 1 + case_death_count + else_death_count);
   1244                 a.extra.appendAssumeCapacity(case_death_count);
   1245                 a.extra.appendSliceAssumeCapacity(cd.items);
   1246             }
   1247             a.extra.appendSliceAssumeCapacity(case_deaths[case_deaths.len - 1].items);
   1248             try a.special.put(gpa, inst, extra_index);
   1249 
   1250             return trackOperands(a, new_set, inst, main_tomb, .{ condition, .none, .none });
   1251         },
   1252         .wasm_memory_grow => {
   1253             const pl_op = inst_datas[inst].pl_op;
   1254             return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, .none, .none });
   1255         },
   1256     }
   1257 }
   1258 
   1259 fn trackOperands(
   1260     a: *Analysis,
   1261     new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
   1262     inst: Air.Inst.Index,
   1263     main_tomb: bool,
   1264     operands: [bpi - 1]Air.Inst.Ref,
   1265 ) Allocator.Error!void {
   1266     const table = &a.table;
   1267     const gpa = a.gpa;
   1268 
   1269     var tomb_bits: Bpi = @boolToInt(main_tomb);
   1270     var i = operands.len;
   1271 
   1272     while (i > 0) {
   1273         i -= 1;
   1274         tomb_bits <<= 1;
   1275         const op_int = @enumToInt(operands[i]);
   1276         if (op_int < Air.Inst.Ref.typed_value_map.len) continue;
   1277         const operand: Air.Inst.Index = op_int - @intCast(u32, Air.Inst.Ref.typed_value_map.len);
   1278         const prev = try table.fetchPut(gpa, operand, {});
   1279         if (prev == null) {
   1280             // Death.
   1281             tomb_bits |= 1;
   1282             if (new_set) |ns| try ns.putNoClobber(gpa, operand, {});
   1283         }
   1284     }
   1285     a.storeTombBits(inst, tomb_bits);
   1286 }
   1287 
   1288 const ExtraTombs = struct {
   1289     analysis: *Analysis,
   1290     new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
   1291     inst: Air.Inst.Index,
   1292     main_tomb: bool,
   1293     bit_index: usize = 0,
   1294     tomb_bits: Bpi = 0,
   1295     big_tomb_bits: u32 = 0,
   1296     big_tomb_bits_extra: std.ArrayListUnmanaged(u32) = .{},
   1297 
   1298     fn feed(et: *ExtraTombs, op_ref: Air.Inst.Ref) !void {
   1299         const this_bit_index = et.bit_index;
   1300         et.bit_index += 1;
   1301         const gpa = et.analysis.gpa;
   1302         const op_index = Air.refToIndex(op_ref) orelse return;
   1303         const prev = try et.analysis.table.fetchPut(gpa, op_index, {});
   1304         if (prev == null) {
   1305             // Death.
   1306             if (et.new_set) |ns| try ns.putNoClobber(gpa, op_index, {});
   1307             const available_tomb_bits = bpi - 1;
   1308             if (this_bit_index < available_tomb_bits) {
   1309                 et.tomb_bits |= @as(Bpi, 1) << @intCast(OperandInt, this_bit_index);
   1310             } else {
   1311                 const big_bit_index = this_bit_index - available_tomb_bits;
   1312                 while (big_bit_index >= (et.big_tomb_bits_extra.items.len + 1) * 31) {
   1313                     // We need another element in the extra array.
   1314                     try et.big_tomb_bits_extra.append(gpa, et.big_tomb_bits);
   1315                     et.big_tomb_bits = 0;
   1316                 } else {
   1317                     const final_bit_index = big_bit_index - et.big_tomb_bits_extra.items.len * 31;
   1318                     et.big_tomb_bits |= @as(u32, 1) << @intCast(u5, final_bit_index);
   1319                 }
   1320             }
   1321         }
   1322     }
   1323 
   1324     fn finish(et: *ExtraTombs) !void {
   1325         et.tomb_bits |= @as(Bpi, @boolToInt(et.main_tomb)) << (bpi - 1);
   1326         // Signal the terminal big_tomb_bits element.
   1327         et.big_tomb_bits |= @as(u32, 1) << 31;
   1328 
   1329         et.analysis.storeTombBits(et.inst, et.tomb_bits);
   1330         const extra_index = @intCast(u32, et.analysis.extra.items.len);
   1331         try et.analysis.extra.ensureUnusedCapacity(et.analysis.gpa, et.big_tomb_bits_extra.items.len + 1);
   1332         try et.analysis.special.put(et.analysis.gpa, et.inst, extra_index);
   1333         et.analysis.extra.appendSliceAssumeCapacity(et.big_tomb_bits_extra.items);
   1334         et.analysis.extra.appendAssumeCapacity(et.big_tomb_bits);
   1335     }
   1336 
   1337     fn deinit(et: *ExtraTombs) void {
   1338         et.big_tomb_bits_extra.deinit(et.analysis.gpa);
   1339     }
   1340 };