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 };