authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-04-09 01:32:53+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-04-20 20:28:04+01:00
log8258530c39c347ca83fdbe7575460712960a83f3
treebb7ddf85cdb42341f09b6d1812e32938165645b2
parentfac120bc3ad58a10ab80952e42becd0084aec059
signature Commit is signed but in an unrecognized format.

Liveness: control flow analysis

This is a partial rewrite of Liveness, so has some other notable changes: - A proper multi-pass system to prevent code duplication - Better logging - Minor bugfixes

8 files changed, 956 insertions(+), 908 deletions(-)

src/Liveness.zig+956-864
......@@ -24,8 +24,10 @@ tomb_bits: []usize,
2424/// Sparse table of specially handled instructions. The value is an index into the `extra`
2525/// array. The meaning of the data depends on the AIR tag.
2626/// * `cond_br` - points to a `CondBr` in `extra` at this index.
27/// * `try`, `try_ptr` - points to a `CondBr` in `extra` at this index. The error path (the block
28/// in the instruction) is considered the "else" path, and the rest of the block the "then".
2729/// * `switch_br` - points to a `SwitchBr` in `extra` at this index.
28/// * `loop` - points to a `Loop` in `extra` at this index.
30/// * `block` - points to a `Block` in `extra` at this index.
2931/// * `asm`, `call`, `aggregate_init` - the value is a set of bits which are the extra tomb
3032/// bits of operands.
3133/// The main tomb bits are still used and the extra ones are starting with the lsb of the
......@@ -52,11 +54,88 @@ pub const SwitchBr = struct {
5254 else_death_count: u32,
5355};
5456
55/// Trailing is the set of instructions whose lifetimes end at the end of the loop body.
56pub const Loop = struct {
57/// Trailing is the set of instructions which die in the block. Note that these are not additional
58/// deaths (they are all recorded as normal within the block), but backends may use this information
59/// as a more efficient way to track which instructions are still alive after a block.
60pub const Block = struct {
5761 death_count: u32,
5862};
5963
64/// Liveness analysis runs in several passes. Each pass iterates backwards over instructions in
65/// bodies, and recurses into bodies.
66const LivenessPass = enum {
67 /// In this pass, we perform some basic analysis of loops to gain information the main pass
68 /// needs. In particular, for every `loop`, we track the following information:
69 /// * Every block which the loop body contains a `br` to.
70 /// * Every operand referenced within the loop body but created outside the loop.
71 /// This gives the main analysis pass enough information to determine the full set of
72 /// instructions which need to be alive when a loop repeats. This data is TEMPORARILY stored in
73 /// `a.extra`. It is not re-added to `extra` by the main pass, since it is not useful to
74 /// backends.
75 loop_analysis,
76
77 /// This pass performs the main liveness analysis, setting up tombs and extra data while
78 /// considering control flow etc.
79 main_analysis,
80};
81
82/// Each analysis pass may wish to pass data through calls. A pointer to a `LivenessPassData(pass)`
83/// stored on the stack is passed through calls to `analyzeInst` etc.
84fn LivenessPassData(comptime pass: LivenessPass) type {
85 return switch (pass) {
86 .loop_analysis => struct {
87 /// The set of blocks which are exited with a `br` instruction at some point within this
88 /// body and which we are currently within.
89 breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
90
91 /// The set of operands for which we have seen at least one usage but not their birth.
92 live_set: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
93
94 fn deinit(self: *@This(), gpa: Allocator) void {
95 self.breaks.deinit(gpa);
96 self.live_set.deinit(gpa);
97 }
98 },
99
100 .main_analysis => struct {
101 /// Every `block` currently under analysis.
102 block_scopes: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockScope) = .{},
103
104 /// The set of deaths which should be made to occur at the earliest possible point in
105 /// this control flow branch. These instructions die when they are last referenced in
106 /// the current branch; if unreferenced, they die at the start of the branch. Populated
107 /// when a `br` instruction is reached. If deaths are common to all branches of control
108 /// flow, they may be bubbled up to the parent branch.
109 branch_deaths: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
110
111 /// The set of instructions currently alive. Instructions which must die in this branch
112 /// (i.e. those in `branch_deaths`) are not in this set, because they must die before
113 /// this point.
114 live_set: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
115
116 /// The extra data initialized by the `loop_analysis` pass for this pass to consume.
117 /// Owned by this struct during this pass.
118 old_extra: std.ArrayListUnmanaged(u32) = .{},
119
120 const BlockScope = struct {
121 /// The set of instructions which are alive upon a `br` to this block.
122 live_set: std.AutoHashMapUnmanaged(Air.Inst.Index, void),
123 };
124
125 fn deinit(self: *@This(), gpa: Allocator) void {
126 var it = self.block_scopes.valueIterator();
127 while (it.next()) |block| {
128 block.live_set.deinit(gpa);
129 }
130 self.block_scopes.deinit(gpa);
131 self.branch_deaths.deinit(gpa);
132 self.live_set.deinit(gpa);
133 self.old_extra.deinit(gpa);
134 }
135 },
136 };
137}
138
60139pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness {
61140 const tracy = trace(@src());
62141 defer tracy.end();
......@@ -64,7 +143,6 @@ pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness {
64143 var a: Analysis = .{
65144 .gpa = gpa,
66145 .air = air,
67 .table = .{},
68146 .tomb_bits = try gpa.alloc(
69147 usize,
70148 (air.instructions.len * bpi + @bitSizeOf(usize) - 1) / @bitSizeOf(usize),
......@@ -75,19 +153,27 @@ pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness {
75153 errdefer gpa.free(a.tomb_bits);
76154 errdefer a.special.deinit(gpa);
77155 defer a.extra.deinit(gpa);
78 defer a.table.deinit(gpa);
79156
80157 std.mem.set(usize, a.tomb_bits, 0);
81158
82159 const main_body = air.getMainBody();
83 try a.table.ensureTotalCapacity(gpa, @intCast(u32, main_body.len));
84 try analyzeWithContext(&a, null, main_body);
160
161 {
162 var data: LivenessPassData(.loop_analysis) = .{};
163 defer data.deinit(gpa);
164 try analyzeBody(&a, .loop_analysis, &data, main_body);
165 }
166
85167 {
86 var to_remove: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};
87 defer to_remove.deinit(gpa);
88 try removeDeaths(&a, &to_remove, main_body);
168 var data: LivenessPassData(.main_analysis) = .{};
169 defer data.deinit(gpa);
170 data.old_extra = a.extra;
171 a.extra = .{};
172 try analyzeBody(&a, .main_analysis, &data, main_body);
173 assert(data.branch_deaths.count() == 0);
89174 }
90 return Liveness{
175
176 return .{
91177 .tomb_bits = a.tomb_bits,
92178 .special = a.special,
93179 .extra = try a.extra.toOwnedSlice(gpa),
......@@ -661,18 +747,27 @@ pub fn getSwitchBr(l: Liveness, gpa: Allocator, inst: Air.Inst.Index, cases_len:
661747 };
662748}
663749
664pub const LoopSlice = struct {
750/// Note that this information is technically redundant, but is useful for
751/// backends nonetheless: see `Block`.
752pub const BlockSlices = struct {
665753 deaths: []const Air.Inst.Index,
666754};
667755
668pub fn getLoop(l: Liveness, inst: Air.Inst.Index) LoopSlice {
756pub fn getBlock(l: Liveness, inst: Air.Inst.Index) BlockSlices {
669757 const index: usize = l.special.get(inst) orelse return .{
670758 .deaths = &.{},
671759 };
672760 const death_count = l.extra[index];
673 return .{ .deaths = l.extra[index + 1 ..][0..death_count] };
761 const deaths = l.extra[index + 1 ..][0..death_count];
762 return .{
763 .deaths = deaths,
764 };
674765}
675766
767pub const LoopSlice = struct {
768 deaths: []const Air.Inst.Index,
769};
770
676771pub fn deinit(l: *Liveness, gpa: Allocator) void {
677772 gpa.free(l.tomb_bits);
678773 gpa.free(l.extra);
......@@ -687,6 +782,7 @@ pub fn iterateBigTomb(l: Liveness, inst: Air.Inst.Index) BigTomb {
687782 .extra_offset = 0,
688783 .extra = l.extra,
689784 .bit_index = 0,
785 .reached_end = false,
690786 };
691787}
692788
......@@ -702,13 +798,16 @@ pub const BigTomb = struct {
702798 extra_start: u32,
703799 extra_offset: u32,
704800 extra: []const u32,
801 reached_end: bool,
705802
706803 /// Returns whether the next operand dies.
707804 pub fn feed(bt: *BigTomb) bool {
805 if (bt.reached_end) return false;
806
708807 const this_bit_index = bt.bit_index;
709808 bt.bit_index += 1;
710809
711 const small_tombs = Liveness.bpi - 1;
810 const small_tombs = bpi - 1;
712811 if (this_bit_index < small_tombs) {
713812 const dies = @truncate(u1, bt.tomb_bits >> @intCast(Liveness.OperandInt, this_bit_index)) != 0;
714813 return dies;
......@@ -716,6 +815,10 @@ pub const BigTomb = struct {
716815
717816 const big_bit_index = this_bit_index - small_tombs;
718817 while (big_bit_index - bt.extra_offset * 31 >= 31) {
818 if (@truncate(u1, bt.extra[bt.extra_start + bt.extra_offset] >> 31) != 0) {
819 bt.reached_end = true;
820 return false;
821 }
719822 bt.extra_offset += 1;
720823 }
721824 const dies = @truncate(u1, bt.extra[bt.extra_start + bt.extra_offset] >>
......@@ -728,7 +831,6 @@ pub const BigTomb = struct {
728831const Analysis = struct {
729832 gpa: Allocator,
730833 air: Air,
731 table: std.AutoHashMapUnmanaged(Air.Inst.Index, void),
732834 tomb_bits: []usize,
733835 special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32),
734836 extra: std.ArrayListUnmanaged(u32),
......@@ -758,46 +860,70 @@ const Analysis = struct {
758860 }
759861};
760862
761fn analyzeWithContext(
863fn analyzeBody(
762864 a: *Analysis,
763 new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
865 comptime pass: LivenessPass,
866 data: *LivenessPassData(pass),
764867 body: []const Air.Inst.Index,
765868) Allocator.Error!void {
766869 var i: usize = body.len;
870 while (i != 0) {
871 i -= 1;
872 const inst = body[i];
873 try analyzeInst(a, pass, data, inst);
874 }
875}
767876
768 if (new_set) |ns| {
769 // We are only interested in doing this for instructions which are born
770 // before a conditional branch, so after obtaining the new set for
771 // each branch we prune the instructions which were born within.
772 while (i != 0) {
773 i -= 1;
774 const inst = body[i];
775 _ = ns.remove(inst);
776 try analyzeInst(a, new_set, inst);
777 }
778 } else {
779 while (i != 0) {
780 i -= 1;
781 const inst = body[i];
782 try analyzeInst(a, new_set, inst);
783 }
877const ControlBranchInfo = struct {
878 branch_deaths: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
879 live_set: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
880};
881
882/// Helper function for running `analyzeBody`, but resetting `branch_deaths` and `live_set` to their
883/// original states before returning, returning the modified versions of them. Only makes sense in
884/// the `main_analysis` pass.
885fn analyzeBodyResetBranch(
886 a: *Analysis,
887 comptime pass: LivenessPass,
888 data: *LivenessPassData(pass),
889 body: []const Air.Inst.Index,
890) !ControlBranchInfo {
891 switch (pass) {
892 .main_analysis => {},
893 else => @compileError("Liveness.analyzeBodyResetBranch only makes sense in LivenessPass.main_analysis"),
894 }
895
896 const gpa = a.gpa;
897
898 const old_branch_deaths = try data.branch_deaths.clone(a.gpa);
899 defer {
900 data.branch_deaths.deinit(gpa);
901 data.branch_deaths = old_branch_deaths;
902 }
903
904 const old_live_set = try data.live_set.clone(a.gpa);
905 defer {
906 data.live_set.deinit(gpa);
907 data.live_set = old_live_set;
784908 }
909
910 try analyzeBody(a, pass, data, body);
911
912 return .{
913 .branch_deaths = data.branch_deaths.move(),
914 .live_set = data.live_set.move(),
915 };
785916}
786917
787918fn analyzeInst(
788919 a: *Analysis,
789 new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
920 comptime pass: LivenessPass,
921 data: *LivenessPassData(pass),
790922 inst: Air.Inst.Index,
791923) Allocator.Error!void {
792 const gpa = a.gpa;
793 const table = &a.table;
794924 const inst_tags = a.air.instructions.items(.tag);
795925 const inst_datas = a.air.instructions.items(.data);
796926
797 // No tombstone for this instruction means it is never referenced,
798 // and its birth marks its own death. Very metal 🤘
799 const main_tomb = !table.contains(inst);
800
801927 switch (inst_tags[inst]) {
802928 .add,
803929 .add_optimized,
......@@ -861,28 +987,24 @@ fn analyzeInst(
861987 .max,
862988 => {
863989 const o = inst_datas[inst].bin_op;
864 return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none });
990 return analyzeOperands(a, pass, data, inst, .{ o.lhs, o.rhs, .none });
865991 },
866992
867993 .vector_store_elem => {
868994 const o = inst_datas[inst].vector_store_elem;
869995 const extra = a.air.extraData(Air.Bin, o.payload).data;
870 return trackOperands(a, new_set, inst, main_tomb, .{ o.vector_ptr, extra.lhs, extra.rhs });
996 return analyzeOperands(a, pass, data, inst, .{ o.vector_ptr, extra.lhs, extra.rhs });
871997 },
872998
873999 .arg,
8741000 .alloc,
8751001 .ret_ptr,
876 .constant,
877 .const_ty,
878 .trap,
8791002 .breakpoint,
8801003 .dbg_stmt,
8811004 .dbg_inline_begin,
8821005 .dbg_inline_end,
8831006 .dbg_block_begin,
8841007 .dbg_block_end,
885 .unreach,
8861008 .fence,
8871009 .ret_addr,
8881010 .frame_addr,
......@@ -893,7 +1015,15 @@ fn analyzeInst(
8931015 .work_item_id,
8941016 .work_group_size,
8951017 .work_group_id,
896 => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }),
1018 => return analyzeOperands(a, pass, data, inst, .{ .none, .none, .none }),
1019
1020 .constant,
1021 .const_ty,
1022 => unreachable,
1023
1024 .trap,
1025 .unreach,
1026 => return analyzeFuncEnd(a, pass, data, inst, .{ .none, .none, .none }),
8971027
8981028 .not,
8991029 .bitcast,
......@@ -938,7 +1068,7 @@ fn analyzeInst(
9381068 .c_va_copy,
9391069 => {
9401070 const o = inst_datas[inst].ty_op;
941 return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });
1071 return analyzeOperands(a, pass, data, inst, .{ o.operand, .none, .none });
9421072 },
9431073
9441074 .is_null,
......@@ -951,8 +1081,6 @@ fn analyzeInst(
9511081 .is_non_err_ptr,
9521082 .ptrtoint,
9531083 .bool_to_int,
954 .ret,
955 .ret_load,
9561084 .is_named_enum_value,
9571085 .tag_name,
9581086 .error_name,
......@@ -977,7 +1105,14 @@ fn analyzeInst(
9771105 .c_va_end,
9781106 => {
9791107 const operand = inst_datas[inst].un_op;
980 return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none });
1108 return analyzeOperands(a, pass, data, inst, .{ operand, .none, .none });
1109 },
1110
1111 .ret,
1112 .ret_load,
1113 => {
1114 const operand = inst_datas[inst].un_op;
1115 return analyzeFuncEnd(a, pass, data, inst, .{ operand, .none, .none });
9811116 },
9821117
9831118 .add_with_overflow,
......@@ -992,19 +1127,19 @@ fn analyzeInst(
9921127 => {
9931128 const ty_pl = inst_datas[inst].ty_pl;
9941129 const extra = a.air.extraData(Air.Bin, ty_pl.payload).data;
995 return trackOperands(a, new_set, inst, main_tomb, .{ extra.lhs, extra.rhs, .none });
1130 return analyzeOperands(a, pass, data, inst, .{ extra.lhs, extra.rhs, .none });
9961131 },
9971132
9981133 .dbg_var_ptr,
9991134 .dbg_var_val,
10001135 => {
10011136 const operand = inst_datas[inst].pl_op.operand;
1002 return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none });
1137 return analyzeOperands(a, pass, data, inst, .{ operand, .none, .none });
10031138 },
10041139
10051140 .prefetch => {
10061141 const prefetch = inst_datas[inst].prefetch;
1007 return trackOperands(a, new_set, inst, main_tomb, .{ prefetch.ptr, .none, .none });
1142 return analyzeOperands(a, pass, data, inst, .{ prefetch.ptr, .none, .none });
10081143 },
10091144
10101145 .call, .call_always_tail, .call_never_tail, .call_never_inline => {
......@@ -1016,37 +1151,35 @@ fn analyzeInst(
10161151 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
10171152 buf[0] = callee;
10181153 std.mem.copy(Air.Inst.Ref, buf[1..], args);
1019 return trackOperands(a, new_set, inst, main_tomb, buf);
1154 return analyzeOperands(a, pass, data, inst, buf);
10201155 }
1021 var extra_tombs: ExtraTombs = .{
1022 .analysis = a,
1023 .new_set = new_set,
1024 .inst = inst,
1025 .main_tomb = main_tomb,
1026 };
1027 defer extra_tombs.deinit();
1028 try extra_tombs.feed(callee);
1029 for (args) |arg| {
1030 try extra_tombs.feed(arg);
1156
1157 var big = try AnalyzeBigOperands(pass).init(a, data, inst, args.len + 1);
1158 defer big.deinit();
1159 var i: usize = args.len;
1160 while (i > 0) {
1161 i -= 1;
1162 try big.feed(args[i]);
10311163 }
1032 return extra_tombs.finish();
1164 try big.feed(callee);
1165 return big.finish();
10331166 },
10341167 .select => {
10351168 const pl_op = inst_datas[inst].pl_op;
10361169 const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
1037 return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.lhs, extra.rhs });
1170 return analyzeOperands(a, pass, data, inst, .{ pl_op.operand, extra.lhs, extra.rhs });
10381171 },
10391172 .shuffle => {
10401173 const extra = a.air.extraData(Air.Shuffle, inst_datas[inst].ty_pl.payload).data;
1041 return trackOperands(a, new_set, inst, main_tomb, .{ extra.a, extra.b, .none });
1174 return analyzeOperands(a, pass, data, inst, .{ extra.a, extra.b, .none });
10421175 },
10431176 .reduce, .reduce_optimized => {
10441177 const reduce = inst_datas[inst].reduce;
1045 return trackOperands(a, new_set, inst, main_tomb, .{ reduce.operand, .none, .none });
1178 return analyzeOperands(a, pass, data, inst, .{ reduce.operand, .none, .none });
10461179 },
10471180 .cmp_vector, .cmp_vector_optimized => {
10481181 const extra = a.air.extraData(Air.VectorCmp, inst_datas[inst].ty_pl.payload).data;
1049 return trackOperands(a, new_set, inst, main_tomb, .{ extra.lhs, extra.rhs, .none });
1182 return analyzeOperands(a, pass, data, inst, .{ extra.lhs, extra.rhs, .none });
10501183 },
10511184 .aggregate_init => {
10521185 const ty_pl = inst_datas[inst].ty_pl;
......@@ -1057,62 +1190,58 @@ fn analyzeInst(
10571190 if (elements.len <= bpi - 1) {
10581191 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
10591192 std.mem.copy(Air.Inst.Ref, &buf, elements);
1060 return trackOperands(a, new_set, inst, main_tomb, buf);
1193 return analyzeOperands(a, pass, data, inst, buf);
10611194 }
1062 var extra_tombs: ExtraTombs = .{
1063 .analysis = a,
1064 .new_set = new_set,
1065 .inst = inst,
1066 .main_tomb = main_tomb,
1067 };
1068 defer extra_tombs.deinit();
1069 for (elements) |elem| {
1070 try extra_tombs.feed(elem);
1195
1196 var big = try AnalyzeBigOperands(pass).init(a, data, inst, elements.len);
1197 defer big.deinit();
1198 var i: usize = elements.len;
1199 while (i > 0) {
1200 i -= 1;
1201 try big.feed(elements[i]);
10711202 }
1072 return extra_tombs.finish();
1203 return big.finish();
10731204 },
10741205 .union_init => {
10751206 const extra = a.air.extraData(Air.UnionInit, inst_datas[inst].ty_pl.payload).data;
1076 return trackOperands(a, new_set, inst, main_tomb, .{ extra.init, .none, .none });
1207 return analyzeOperands(a, pass, data, inst, .{ extra.init, .none, .none });
10771208 },
10781209 .struct_field_ptr, .struct_field_val => {
10791210 const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data;
1080 return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_operand, .none, .none });
1211 return analyzeOperands(a, pass, data, inst, .{ extra.struct_operand, .none, .none });
10811212 },
10821213 .field_parent_ptr => {
10831214 const extra = a.air.extraData(Air.FieldParentPtr, inst_datas[inst].ty_pl.payload).data;
1084 return trackOperands(a, new_set, inst, main_tomb, .{ extra.field_ptr, .none, .none });
1215 return analyzeOperands(a, pass, data, inst, .{ extra.field_ptr, .none, .none });
10851216 },
10861217 .cmpxchg_strong, .cmpxchg_weak => {
10871218 const extra = a.air.extraData(Air.Cmpxchg, inst_datas[inst].ty_pl.payload).data;
1088 return trackOperands(a, new_set, inst, main_tomb, .{ extra.ptr, extra.expected_value, extra.new_value });
1219 return analyzeOperands(a, pass, data, inst, .{ extra.ptr, extra.expected_value, extra.new_value });
10891220 },
10901221 .mul_add => {
10911222 const pl_op = inst_datas[inst].pl_op;
10921223 const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
1093 return trackOperands(a, new_set, inst, main_tomb, .{ extra.lhs, extra.rhs, pl_op.operand });
1224 return analyzeOperands(a, pass, data, inst, .{ extra.lhs, extra.rhs, pl_op.operand });
10941225 },
10951226 .atomic_load => {
10961227 const ptr = inst_datas[inst].atomic_load.ptr;
1097 return trackOperands(a, new_set, inst, main_tomb, .{ ptr, .none, .none });
1228 return analyzeOperands(a, pass, data, inst, .{ ptr, .none, .none });
10981229 },
10991230 .atomic_rmw => {
11001231 const pl_op = inst_datas[inst].pl_op;
11011232 const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data;
1102 return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.operand, .none });
1233 return analyzeOperands(a, pass, data, inst, .{ pl_op.operand, extra.operand, .none });
11031234 },
11041235 .memset,
11051236 .memcpy,
11061237 => {
11071238 const pl_op = inst_datas[inst].pl_op;
11081239 const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
1109 return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.lhs, extra.rhs });
1240 return analyzeOperands(a, pass, data, inst, .{ pl_op.operand, extra.lhs, extra.rhs });
11101241 },
11111242
1112 .br => {
1113 const br = inst_datas[inst].br;
1114 return trackOperands(a, new_set, inst, main_tomb, .{ br.operand, .none, .none });
1115 },
1243 .br => return analyzeInstBr(a, pass, data, inst),
1244
11161245 .assembly => {
11171246 const extra = a.air.extraData(Air.Asm, inst_datas[inst].ty_pl.payload);
11181247 var extra_i: usize = extra.end;
......@@ -1121,912 +1250,875 @@ fn analyzeInst(
11211250 const inputs = @ptrCast([]const Air.Inst.Ref, a.air.extra[extra_i..][0..extra.data.inputs_len]);
11221251 extra_i += inputs.len;
11231252
1124 simple: {
1253 const num_operands = simple: {
11251254 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
11261255 var buf_index: usize = 0;
11271256 for (outputs) |output| {
11281257 if (output != .none) {
1129 if (buf_index >= buf.len) break :simple;
1130 buf[buf_index] = output;
1258 if (buf_index < buf.len) buf[buf_index] = output;
11311259 buf_index += 1;
11321260 }
11331261 }
1134 if (buf_index + inputs.len > buf.len) break :simple;
1262 if (buf_index + inputs.len > buf.len) {
1263 break :simple buf_index + inputs.len;
1264 }
11351265 std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs);
1136 return trackOperands(a, new_set, inst, main_tomb, buf);
1137 }
1138 var extra_tombs: ExtraTombs = .{
1139 .analysis = a,
1140 .new_set = new_set,
1141 .inst = inst,
1142 .main_tomb = main_tomb,
1266 return analyzeOperands(a, pass, data, inst, buf);
11431267 };
1144 defer extra_tombs.deinit();
1145 for (outputs) |output| {
1146 if (output != .none) {
1147 try extra_tombs.feed(output);
1148 }
1268
1269 var big = try AnalyzeBigOperands(pass).init(a, data, inst, num_operands);
1270 defer big.deinit();
1271 var i: usize = inputs.len;
1272 while (i > 0) {
1273 i -= 1;
1274 try big.feed(inputs[i]);
11491275 }
1150 for (inputs) |input| {
1151 try extra_tombs.feed(input);
1276 i = outputs.len;
1277 while (i > 0) {
1278 i -= 1;
1279 if (outputs[i] != .none) {
1280 try big.feed(outputs[i]);
1281 }
11521282 }
1153 return extra_tombs.finish();
1283 return big.finish();
11541284 },
1155 .block => {
1156 const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
1157 const body = a.air.extra[extra.end..][0..extra.data.body_len];
1158 try analyzeWithContext(a, new_set, body);
1159 return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none });
1285
1286 .block => return analyzeInstBlock(a, pass, data, inst),
1287 .loop => return analyzeInstLoop(a, pass, data, inst),
1288
1289 .@"try" => return analyzeInstCondBr(a, pass, data, inst, .@"try"),
1290 .try_ptr => return analyzeInstCondBr(a, pass, data, inst, .try_ptr),
1291 .cond_br => return analyzeInstCondBr(a, pass, data, inst, .cond_br),
1292 .switch_br => return analyzeInstSwitchBr(a, pass, data, inst),
1293
1294 .wasm_memory_grow => {
1295 const pl_op = inst_datas[inst].pl_op;
1296 return analyzeOperands(a, pass, data, inst, .{ pl_op.operand, .none, .none });
11601297 },
1161 .loop => {
1162 const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
1163 const body = a.air.extra[extra.end..][0..extra.data.body_len];
1298 }
1299}
11641300
1165 var body_table: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};
1166 defer body_table.deinit(gpa);
1301/// Every instruction should hit this (after handling any nested bodies), in every pass. In the
1302/// initial pass, it is responsible for marking deaths of the (first three) operands and noticing
1303/// immediate deaths.
1304fn analyzeOperands(
1305 a: *Analysis,
1306 comptime pass: LivenessPass,
1307 data: *LivenessPassData(pass),
1308 inst: Air.Inst.Index,
1309 operands: [bpi - 1]Air.Inst.Ref,
1310) Allocator.Error!void {
1311 const gpa = a.gpa;
1312 const inst_tags = a.air.instructions.items(.tag);
11671313
1168 // Instructions outside the loop body cannot die within the loop, since further loop
1169 // iterations may occur. Track deaths from the loop body - we'll remove all of these
1170 // retroactively, and add them to our extra data.
1314 switch (pass) {
1315 .loop_analysis => {
1316 _ = data.live_set.remove(inst);
11711317
1172 try analyzeWithContext(a, &body_table, body);
1318 for (operands) |op_ref| {
1319 const operand = Air.refToIndex(op_ref) orelse continue;
11731320
1174 if (new_set) |ns| {
1175 try ns.ensureUnusedCapacity(gpa, body_table.count());
1176 var it = body_table.keyIterator();
1177 while (it.next()) |key| {
1178 _ = ns.putAssumeCapacity(key.*, {});
1321 // Don't compute any liveness for constants
1322 switch (inst_tags[operand]) {
1323 .constant, .const_ty => continue,
1324 else => {},
11791325 }
1326
1327 _ = try data.live_set.put(gpa, operand, {});
11801328 }
1329 },
11811330
1182 try a.extra.ensureUnusedCapacity(gpa, std.meta.fields(Loop).len + body_table.count());
1183 const extra_index = a.addExtraAssumeCapacity(Loop{
1184 .death_count = body_table.count(),
1185 });
1186 {
1187 var it = body_table.keyIterator();
1188 while (it.next()) |key| {
1189 a.extra.appendAssumeCapacity(key.*);
1190 }
1331 .main_analysis => {
1332 const usize_index = (inst * bpi) / @bitSizeOf(usize);
1333
1334 var tomb_bits: Bpi = 0;
1335
1336 if (data.branch_deaths.remove(inst)) {
1337 log.debug("[{}] %{}: resolved branch death to birth (immediate death)", .{ pass, inst });
1338 tomb_bits |= @as(Bpi, 1) << (bpi - 1);
1339 assert(!data.live_set.contains(inst));
1340 } else if (data.live_set.remove(inst)) {
1341 log.debug("[{}] %{}: removed from live set", .{ pass, inst });
1342 } else {
1343 log.debug("[{}] %{}: immediate death", .{ pass, inst });
1344 tomb_bits |= @as(Bpi, 1) << (bpi - 1);
11911345 }
1192 try a.special.put(gpa, inst, extra_index);
11931346
1194 // We'll remove invalid deaths in a separate pass after main liveness analysis. See
1195 // removeDeaths for more details.
1347 // Note that it's important we iterate over the operands backwards, so that if a dying
1348 // operand is used multiple times we mark its last use as its death.
1349 var i = operands.len;
1350 while (i > 0) {
1351 i -= 1;
1352 const op_ref = operands[i];
1353 const operand = Air.refToIndex(op_ref) orelse continue;
1354
1355 // Don't compute any liveness for constants
1356 switch (inst_tags[operand]) {
1357 .constant, .const_ty => continue,
1358 else => {},
1359 }
1360
1361 const mask = @as(Bpi, 1) << @intCast(OperandInt, i);
11961362
1197 return; // Loop has no operands and it is always unreferenced.
1198 },
1199 .@"try" => {
1200 const pl_op = inst_datas[inst].pl_op;
1201 const extra = a.air.extraData(Air.Try, pl_op.payload);
1202 const body = a.air.extra[extra.end..][0..extra.data.body_len];
1203 try analyzeWithContext(a, new_set, body);
1204 return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, .none, .none });
1363 if ((try data.live_set.fetchPut(gpa, operand, {})) == null) {
1364 log.debug("[{}] %{}: added %{} to live set (operand dies here)", .{ pass, inst, operand });
1365 tomb_bits |= mask;
1366 if (data.branch_deaths.remove(operand)) {
1367 log.debug("[{}] %{}: resolved branch death of %{} to this usage", .{ pass, inst, operand });
1368 }
1369 }
1370 }
1371
1372 a.tomb_bits[usize_index] |= @as(usize, tomb_bits) <<
1373 @intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi);
12051374 },
1206 .try_ptr => {
1207 const extra = a.air.extraData(Air.TryPtr, inst_datas[inst].ty_pl.payload);
1208 const body = a.air.extra[extra.end..][0..extra.data.body_len];
1209 try analyzeWithContext(a, new_set, body);
1210 return trackOperands(a, new_set, inst, main_tomb, .{ extra.data.ptr, .none, .none });
1375 }
1376}
1377
1378/// Like `analyzeOperands`, but for an instruction which returns from a function, so should
1379/// effectively kill every remaining live value other than its operands.
1380fn analyzeFuncEnd(
1381 a: *Analysis,
1382 comptime pass: LivenessPass,
1383 data: *LivenessPassData(pass),
1384 inst: Air.Inst.Index,
1385 operands: [bpi - 1]Air.Inst.Ref,
1386) Allocator.Error!void {
1387 switch (pass) {
1388 .loop_analysis => {
1389 // No operands need to be alive if we're returning from the function, so we don't need
1390 // to touch `breaks` here even though this is sort of like a break to the top level.
12111391 },
1212 .cond_br => {
1213 // Each death that occurs inside one branch, but not the other, needs
1214 // to be added as a death immediately upon entering the other branch.
1215 const inst_data = inst_datas[inst].pl_op;
1216 const condition = inst_data.operand;
1217 const extra = a.air.extraData(Air.CondBr, inst_data.payload);
1218 const then_body = a.air.extra[extra.end..][0..extra.data.then_body_len];
1219 const else_body = a.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
12201392
1221 var then_table: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};
1222 defer then_table.deinit(gpa);
1223 try analyzeWithContext(a, &then_table, then_body);
1393 .main_analysis => {
1394 const gpa = a.gpa;
12241395
1225 // Reset the table back to its state from before the branch.
1226 {
1227 var it = then_table.keyIterator();
1228 while (it.next()) |key| {
1229 assert(table.remove(key.*));
1230 }
1396 // Note that we preserve previous branch deaths - anything that needs to die in our
1397 // "parent" branch also needs to die for us.
1398
1399 try data.branch_deaths.ensureUnusedCapacity(gpa, data.live_set.count());
1400 var it = data.live_set.keyIterator();
1401 while (it.next()) |key| {
1402 const alive = key.*;
1403 data.branch_deaths.putAssumeCapacity(alive, {});
12311404 }
1405 data.live_set.clearRetainingCapacity();
1406 },
1407 }
1408
1409 return analyzeOperands(a, pass, data, inst, operands);
1410}
1411
1412fn analyzeInstBr(
1413 a: *Analysis,
1414 comptime pass: LivenessPass,
1415 data: *LivenessPassData(pass),
1416 inst: Air.Inst.Index,
1417) !void {
1418 const inst_datas = a.air.instructions.items(.data);
1419 const br = inst_datas[inst].br;
1420 const gpa = a.gpa;
12321421
1233 var else_table: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};
1234 defer else_table.deinit(gpa);
1235 try analyzeWithContext(a, &else_table, else_body);
1422 switch (pass) {
1423 .loop_analysis => {
1424 try data.breaks.put(gpa, br.block_inst, {});
1425 },
12361426
1237 var then_entry_deaths = std.ArrayList(Air.Inst.Index).init(gpa);
1238 defer then_entry_deaths.deinit();
1239 var else_entry_deaths = std.ArrayList(Air.Inst.Index).init(gpa);
1240 defer else_entry_deaths.deinit();
1427 .main_analysis => {
1428 const block_scope = data.block_scopes.get(br.block_inst).?; // we should always be breaking from an enclosing block
12411429
1242 {
1243 var it = else_table.keyIterator();
1244 while (it.next()) |key| {
1245 const else_death = key.*;
1246 if (!then_table.contains(else_death)) {
1247 try then_entry_deaths.append(else_death);
1248 }
1249 }
1250 }
1251 // This loop is the same, except it's for the then branch, and it additionally
1252 // has to put its items back into the table to undo the reset.
1253 {
1254 var it = then_table.keyIterator();
1255 while (it.next()) |key| {
1256 const then_death = key.*;
1257 if (!else_table.contains(then_death)) {
1258 try else_entry_deaths.append(then_death);
1259 }
1260 try table.put(gpa, then_death, {});
1261 }
1430 // We mostly preserve previous branch deaths - anything that should die for our
1431 // enclosing branch should die for us too. However, if our break target requires such an
1432 // operand to be alive, it's actually not something we want to kill, since its "last
1433 // use" (i.e. the point at which it should die) is outside of our scope.
1434 var it = block_scope.live_set.keyIterator();
1435 while (it.next()) |key| {
1436 const alive = key.*;
1437 _ = data.branch_deaths.remove(alive);
12621438 }
1263 // Now we have to correctly populate new_set.
1264 if (new_set) |ns| {
1265 try ns.ensureUnusedCapacity(gpa, @intCast(u32, then_table.count() + else_table.count()));
1266 var it = then_table.keyIterator();
1267 while (it.next()) |key| {
1268 _ = ns.putAssumeCapacity(key.*, {});
1269 }
1270 it = else_table.keyIterator();
1271 while (it.next()) |key| {
1272 _ = ns.putAssumeCapacity(key.*, {});
1439 log.debug("[{}] %{}: preserved branch deaths are {}", .{ pass, inst, fmtInstSet(&data.branch_deaths) });
1440
1441 // Anything that's currently alive but our target doesn't need becomes a branch death.
1442 it = data.live_set.keyIterator();
1443 while (it.next()) |key| {
1444 const alive = key.*;
1445 if (!block_scope.live_set.contains(alive)) {
1446 _ = try data.branch_deaths.put(gpa, alive, {});
1447 log.debug("[{}] %{}: added branch death of {}", .{ pass, inst, alive });
12731448 }
12741449 }
1275 const then_death_count = @intCast(u32, then_entry_deaths.items.len);
1276 const else_death_count = @intCast(u32, else_entry_deaths.items.len);
1450 const new_live_set = try block_scope.live_set.clone(gpa);
1451 data.live_set.deinit(gpa);
1452 data.live_set = new_live_set;
1453 },
1454 }
12771455
1278 try a.extra.ensureUnusedCapacity(gpa, std.meta.fields(Air.CondBr).len +
1279 then_death_count + else_death_count);
1280 const extra_index = a.addExtraAssumeCapacity(CondBr{
1281 .then_death_count = then_death_count,
1282 .else_death_count = else_death_count,
1283 });
1284 a.extra.appendSliceAssumeCapacity(then_entry_deaths.items);
1285 a.extra.appendSliceAssumeCapacity(else_entry_deaths.items);
1286 try a.special.put(gpa, inst, extra_index);
1456 return analyzeOperands(a, pass, data, inst, .{ br.operand, .none, .none });
1457}
12871458
1288 // Continue on with the instruction analysis. The following code will find the condition
1289 // instruction, and the deaths flag for the CondBr instruction will indicate whether the
1290 // condition's lifetime ends immediately before entering any branch.
1291 return trackOperands(a, new_set, inst, main_tomb, .{ condition, .none, .none });
1292 },
1293 .switch_br => {
1294 const pl_op = inst_datas[inst].pl_op;
1295 const condition = pl_op.operand;
1296 const switch_br = a.air.extraData(Air.SwitchBr, pl_op.payload);
1459fn analyzeInstBlock(
1460 a: *Analysis,
1461 comptime pass: LivenessPass,
1462 data: *LivenessPassData(pass),
1463 inst: Air.Inst.Index,
1464) !void {
1465 const inst_datas = a.air.instructions.items(.data);
1466 const ty_pl = inst_datas[inst].ty_pl;
1467 const extra = a.air.extraData(Air.Block, ty_pl.payload);
1468 const body = a.air.extra[extra.end..][0..extra.data.body_len];
12971469
1298 const Table = std.AutoHashMapUnmanaged(Air.Inst.Index, void);
1299 const case_tables = try gpa.alloc(Table, switch_br.data.cases_len + 1); // +1 for else
1300 defer gpa.free(case_tables);
1470 const gpa = a.gpa;
13011471
1302 std.mem.set(Table, case_tables, .{});
1303 defer for (case_tables) |*ct| ct.deinit(gpa);
1472 // We actually want to do `analyzeOperands` *first*, since our result logically doesn't
1473 // exist until the block body ends (and we're iterating backwards)
1474 try analyzeOperands(a, pass, data, inst, .{ .none, .none, .none });
13041475
1305 var air_extra_index: usize = switch_br.end;
1306 for (case_tables[0..switch_br.data.cases_len]) |*case_table| {
1307 const case = a.air.extraData(Air.SwitchBr.Case, air_extra_index);
1308 const case_body = a.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];
1309 air_extra_index = case.end + case.data.items_len + case_body.len;
1310 try analyzeWithContext(a, case_table, case_body);
1476 switch (pass) {
1477 .loop_analysis => {
1478 try analyzeBody(a, pass, data, body);
1479 _ = data.breaks.remove(inst);
1480 },
13111481
1312 // Reset the table back to its state from before the case.
1313 var it = case_table.keyIterator();
1314 while (it.next()) |key| {
1315 assert(table.remove(key.*));
1316 }
1482 .main_analysis => {
1483 log.debug("[{}] %{}: block live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
1484 try data.block_scopes.put(gpa, inst, .{
1485 .live_set = try data.live_set.clone(gpa),
1486 });
1487 defer {
1488 log.debug("[{}] %{}: popped block scope", .{ pass, inst });
1489 var scope = data.block_scopes.fetchRemove(inst).?.value;
1490 scope.live_set.deinit(gpa);
13171491 }
1318 { // else
1319 const else_table = &case_tables[case_tables.len - 1];
1320 const else_body = a.air.extra[air_extra_index..][0..switch_br.data.else_body_len];
1321 try analyzeWithContext(a, else_table, else_body);
13221492
1323 // Reset the table back to its state from before the case.
1324 var it = else_table.keyIterator();
1325 while (it.next()) |key| {
1326 assert(table.remove(key.*));
1327 }
1328 }
1493 log.debug("[{}] %{}: pushed new block scope", .{ pass, inst });
1494 try analyzeBody(a, pass, data, body);
13291495
1330 const List = std.ArrayListUnmanaged(Air.Inst.Index);
1331 const case_deaths = try gpa.alloc(List, case_tables.len); // includes else
1332 defer gpa.free(case_deaths);
1496 // If the block is noreturn, block deaths not only aren't useful, they're impossible to
1497 // find: there could be more stuff alive after the block than before it!
1498 if (!a.air.getRefType(ty_pl.ty).isNoReturn()) {
1499 // The block kills the difference in the live sets
1500 const block_scope = data.block_scopes.get(inst).?;
1501 const num_deaths = data.live_set.count() - block_scope.live_set.count();
13331502
1334 std.mem.set(List, case_deaths, .{});
1335 defer for (case_deaths) |*cd| cd.deinit(gpa);
1503 try a.extra.ensureUnusedCapacity(gpa, num_deaths + std.meta.fields(Block).len);
1504 const extra_index = a.addExtraAssumeCapacity(Block{
1505 .death_count = num_deaths,
1506 });
13361507
1337 var total_deaths: u32 = 0;
1338 for (case_tables, 0..) |*ct, i| {
1339 total_deaths += ct.count();
1340 var it = ct.keyIterator();
1508 var measured_num: u32 = 0;
1509 var it = data.live_set.keyIterator();
13411510 while (it.next()) |key| {
1342 const case_death = key.*;
1343 for (case_tables, 0..) |*ct_inner, j| {
1344 if (i == j) continue;
1345 if (!ct_inner.contains(case_death)) {
1346 // instruction is not referenced in this case
1347 try case_deaths[j].append(gpa, case_death);
1348 }
1511 const alive = key.*;
1512 if (!block_scope.live_set.contains(alive)) {
1513 // Dies in block
1514 a.extra.appendAssumeCapacity(alive);
1515 measured_num += 1;
13491516 }
1350 // undo resetting the table
1351 try table.put(gpa, case_death, {});
13521517 }
1518 assert(measured_num == num_deaths); // post-live-set should be a subset of pre-live-set
1519 try a.special.put(gpa, inst, extra_index);
1520 log.debug("[{}] %{}: block deaths are {}", .{
1521 pass,
1522 inst,
1523 fmtInstList(a.extra.items[extra_index + 1 ..][0..num_deaths]),
1524 });
13531525 }
1526 },
1527 }
1528}
13541529
1355 // Now we have to correctly populate new_set.
1356 if (new_set) |ns| {
1357 try ns.ensureUnusedCapacity(gpa, total_deaths);
1358 for (case_tables) |*ct| {
1359 var it = ct.keyIterator();
1360 while (it.next()) |key| {
1361 _ = ns.putAssumeCapacity(key.*, {});
1362 }
1363 }
1530fn analyzeInstLoop(
1531 a: *Analysis,
1532 comptime pass: LivenessPass,
1533 data: *LivenessPassData(pass),
1534 inst: Air.Inst.Index,
1535) !void {
1536 const inst_datas = a.air.instructions.items(.data);
1537 const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
1538 const body = a.air.extra[extra.end..][0..extra.data.body_len];
1539 const gpa = a.gpa;
1540
1541 try analyzeOperands(a, pass, data, inst, .{ .none, .none, .none });
1542
1543 switch (pass) {
1544 .loop_analysis => {
1545 var old_breaks = data.breaks.move();
1546 defer old_breaks.deinit(gpa);
1547
1548 var old_live = data.live_set.move();
1549 defer old_live.deinit(gpa);
1550
1551 try analyzeBody(a, pass, data, body);
1552
1553 const num_breaks = data.breaks.count();
1554 try a.extra.ensureUnusedCapacity(gpa, 1 + num_breaks);
1555
1556 const extra_index = @intCast(u32, a.extra.items.len);
1557 a.extra.appendAssumeCapacity(num_breaks);
1558
1559 var it = data.breaks.keyIterator();
1560 while (it.next()) |key| {
1561 const block_inst = key.*;
1562 a.extra.appendAssumeCapacity(block_inst);
13641563 }
1564 log.debug("[{}] %{}: includes breaks to {}", .{ pass, inst, fmtInstSet(&data.breaks) });
13651565
1366 const else_death_count = @intCast(u32, case_deaths[case_deaths.len - 1].items.len);
1367 const extra_index = try a.addExtra(SwitchBr{
1368 .else_death_count = else_death_count,
1369 });
1370 for (case_deaths[0 .. case_deaths.len - 1]) |*cd| {
1371 const case_death_count = @intCast(u32, cd.items.len);
1372 try a.extra.ensureUnusedCapacity(gpa, 1 + case_death_count + else_death_count);
1373 a.extra.appendAssumeCapacity(case_death_count);
1374 a.extra.appendSliceAssumeCapacity(cd.items);
1566 // Now we put the live operands from the loop body in too
1567 const num_live = data.live_set.count();
1568 try a.extra.ensureUnusedCapacity(gpa, 1 + num_live);
1569
1570 a.extra.appendAssumeCapacity(num_live);
1571 it = data.live_set.keyIterator();
1572 while (it.next()) |key| {
1573 const alive = key.*;
1574 a.extra.appendAssumeCapacity(alive);
13751575 }
1376 a.extra.appendSliceAssumeCapacity(case_deaths[case_deaths.len - 1].items);
1576 log.debug("[{}] %{}: maintain liveness of {}", .{ pass, inst, fmtInstSet(&data.live_set) });
1577
13771578 try a.special.put(gpa, inst, extra_index);
13781579
1379 return trackOperands(a, new_set, inst, main_tomb, .{ condition, .none, .none });
1380 },
1381 .wasm_memory_grow => {
1382 const pl_op = inst_datas[inst].pl_op;
1383 return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, .none, .none });
1580 // Add back operands which were previously alive
1581 it = old_live.keyIterator();
1582 while (it.next()) |key| {
1583 const alive = key.*;
1584 try data.live_set.put(gpa, alive, {});
1585 }
1586
1587 // And the same for breaks
1588 it = old_breaks.keyIterator();
1589 while (it.next()) |key| {
1590 const block_inst = key.*;
1591 try data.breaks.put(gpa, block_inst, {});
1592 }
13841593 },
1385 }
1386}
13871594
1388fn trackOperands(
1389 a: *Analysis,
1390 new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
1391 inst: Air.Inst.Index,
1392 main_tomb: bool,
1393 operands: [bpi - 1]Air.Inst.Ref,
1394) Allocator.Error!void {
1395 const table = &a.table;
1396 const gpa = a.gpa;
1595 .main_analysis => {
1596 const extra_idx = a.special.fetchRemove(inst).?.value; // remove because this data does not exist after analysis
13971597
1398 var tomb_bits: Bpi = @boolToInt(main_tomb);
1399 var i = operands.len;
1598 const num_breaks = data.old_extra.items[extra_idx];
1599 const breaks = data.old_extra.items[extra_idx + 1 ..][0..num_breaks];
14001600
1401 while (i > 0) {
1402 i -= 1;
1403 tomb_bits <<= 1;
1404 const op_int = @enumToInt(operands[i]);
1405 if (op_int < Air.Inst.Ref.typed_value_map.len) continue;
1406 const operand: Air.Inst.Index = op_int - @intCast(u32, Air.Inst.Ref.typed_value_map.len);
1407 const prev = try table.fetchPut(gpa, operand, {});
1408 if (prev == null) {
1409 // Death.
1410 tomb_bits |= 1;
1411 if (new_set) |ns| try ns.putNoClobber(gpa, operand, {});
1412 }
1413 }
1414 a.storeTombBits(inst, tomb_bits);
1415}
1601 const num_loop_live = data.old_extra.items[extra_idx + num_breaks + 1];
1602 const loop_live = data.old_extra.items[extra_idx + num_breaks + 2 ..][0..num_loop_live];
14161603
1417const ExtraTombs = struct {
1418 analysis: *Analysis,
1419 new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
1420 inst: Air.Inst.Index,
1421 main_tomb: bool,
1422 bit_index: usize = 0,
1423 tomb_bits: Bpi = 0,
1424 big_tomb_bits: u32 = 0,
1425 big_tomb_bits_extra: std.ArrayListUnmanaged(u32) = .{},
1426
1427 fn feed(et: *ExtraTombs, op_ref: Air.Inst.Ref) !void {
1428 const this_bit_index = et.bit_index;
1429 et.bit_index += 1;
1430 const gpa = et.analysis.gpa;
1431 const op_index = Air.refToIndex(op_ref) orelse return;
1432 const prev = try et.analysis.table.fetchPut(gpa, op_index, {});
1433 if (prev == null) {
1434 // Death.
1435 if (et.new_set) |ns| try ns.putNoClobber(gpa, op_index, {});
1436 const available_tomb_bits = bpi - 1;
1437 if (this_bit_index < available_tomb_bits) {
1438 et.tomb_bits |= @as(Bpi, 1) << @intCast(OperandInt, this_bit_index);
1439 } else {
1440 const big_bit_index = this_bit_index - available_tomb_bits;
1441 while (big_bit_index >= (et.big_tomb_bits_extra.items.len + 1) * 31) {
1442 // We need another element in the extra array.
1443 try et.big_tomb_bits_extra.append(gpa, et.big_tomb_bits);
1444 et.big_tomb_bits = 0;
1445 } else {
1446 const final_bit_index = big_bit_index - et.big_tomb_bits_extra.items.len * 31;
1447 et.big_tomb_bits |= @as(u32, 1) << @intCast(u5, final_bit_index);
1448 }
1604 // This is necessarily not in the same control flow branch, because loops are noreturn
1605 data.live_set.clearRetainingCapacity();
1606
1607 try data.live_set.ensureUnusedCapacity(gpa, @intCast(u32, loop_live.len));
1608 for (loop_live) |alive| {
1609 data.live_set.putAssumeCapacity(alive, {});
1610 // If the loop requires a branch death operand to be alive, it's not something we
1611 // want to kill: its "last use" (i.e. the point at which it should die) is the loop
1612 // body itself.
1613 _ = data.branch_deaths.remove(alive);
14491614 }
1450 }
1451 }
14521615
1453 fn finish(et: *ExtraTombs) !void {
1454 et.tomb_bits |= @as(Bpi, @boolToInt(et.main_tomb)) << (bpi - 1);
1455 // Signal the terminal big_tomb_bits element.
1456 et.big_tomb_bits |= @as(u32, 1) << 31;
1457
1458 et.analysis.storeTombBits(et.inst, et.tomb_bits);
1459 const extra_index = @intCast(u32, et.analysis.extra.items.len);
1460 try et.analysis.extra.ensureUnusedCapacity(et.analysis.gpa, et.big_tomb_bits_extra.items.len + 1);
1461 try et.analysis.special.put(et.analysis.gpa, et.inst, extra_index);
1462 et.analysis.extra.appendSliceAssumeCapacity(et.big_tomb_bits_extra.items);
1463 et.analysis.extra.appendAssumeCapacity(et.big_tomb_bits);
1464 }
1616 log.debug("[{}] %{}: block live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
14651617
1466 fn deinit(et: *ExtraTombs) void {
1467 et.big_tomb_bits_extra.deinit(et.analysis.gpa);
1468 }
1469};
1618 for (breaks) |block_inst| {
1619 // We might break to this block, so include every operand that the block needs alive
1620 const block_scope = data.block_scopes.get(block_inst).?;
14701621
1471/// Remove any deaths invalidated by the deaths from an enclosing `loop`. Reshuffling deaths stored
1472/// in `extra` causes it to become non-dense, but that's fine - we won't remove too much data.
1473/// Making it dense would be a lot more work - it'd require recomputing every index in `special`.
1474fn removeDeaths(
1475 a: *Analysis,
1476 to_remove: *std.AutoHashMapUnmanaged(Air.Inst.Index, void),
1477 body: []const Air.Inst.Index,
1478) error{OutOfMemory}!void {
1479 for (body) |inst| {
1480 try removeInstDeaths(a, to_remove, inst);
1622 var it = block_scope.live_set.keyIterator();
1623 while (it.next()) |key| {
1624 const alive = key.*;
1625 try data.live_set.put(gpa, alive, {});
1626 }
1627 }
1628
1629 try analyzeBody(a, pass, data, body);
1630 },
14811631 }
14821632}
14831633
1484fn removeInstDeaths(
1634/// Despite its name, this function is used for analysis of not only `cond_br` instructions, but
1635/// also `try` and `try_ptr`, which are highly related. The `inst_type` parameter indicates which
1636/// type of instruction `inst` points to.
1637fn analyzeInstCondBr(
14851638 a: *Analysis,
1486 to_remove: *std.AutoHashMapUnmanaged(Air.Inst.Index, void),
1639 comptime pass: LivenessPass,
1640 data: *LivenessPassData(pass),
14871641 inst: Air.Inst.Index,
1642 comptime inst_type: enum { cond_br, @"try", try_ptr },
14881643) !void {
1489 const inst_tags = a.air.instructions.items(.tag);
14901644 const inst_datas = a.air.instructions.items(.data);
1645 const gpa = a.gpa;
14911646
1492 switch (inst_tags[inst]) {
1493 .add,
1494 .add_optimized,
1495 .addwrap,
1496 .addwrap_optimized,
1497 .add_sat,
1498 .sub,
1499 .sub_optimized,
1500 .subwrap,
1501 .subwrap_optimized,
1502 .sub_sat,
1503 .mul,
1504 .mul_optimized,
1505 .mulwrap,
1506 .mulwrap_optimized,
1507 .mul_sat,
1508 .div_float,
1509 .div_float_optimized,
1510 .div_trunc,
1511 .div_trunc_optimized,
1512 .div_floor,
1513 .div_floor_optimized,
1514 .div_exact,
1515 .div_exact_optimized,
1516 .rem,
1517 .rem_optimized,
1518 .mod,
1519 .mod_optimized,
1520 .bit_and,
1521 .bit_or,
1522 .xor,
1523 .cmp_lt,
1524 .cmp_lt_optimized,
1525 .cmp_lte,
1526 .cmp_lte_optimized,
1527 .cmp_eq,
1528 .cmp_eq_optimized,
1529 .cmp_gte,
1530 .cmp_gte_optimized,
1531 .cmp_gt,
1532 .cmp_gt_optimized,
1533 .cmp_neq,
1534 .cmp_neq_optimized,
1535 .bool_and,
1536 .bool_or,
1537 .store,
1538 .array_elem_val,
1539 .slice_elem_val,
1540 .ptr_elem_val,
1541 .shl,
1542 .shl_exact,
1543 .shl_sat,
1544 .shr,
1545 .shr_exact,
1546 .atomic_store_unordered,
1547 .atomic_store_monotonic,
1548 .atomic_store_release,
1549 .atomic_store_seq_cst,
1550 .set_union_tag,
1551 .min,
1552 .max,
1553 => {
1554 const o = inst_datas[inst].bin_op;
1555 removeOperandDeaths(a, to_remove, inst, .{ o.lhs, o.rhs, .none });
1556 },
1647 const extra = switch (inst_type) {
1648 .cond_br => a.air.extraData(Air.CondBr, inst_datas[inst].pl_op.payload),
1649 .@"try" => a.air.extraData(Air.Try, inst_datas[inst].pl_op.payload),
1650 .try_ptr => a.air.extraData(Air.TryPtr, inst_datas[inst].ty_pl.payload),
1651 };
15571652
1558 .vector_store_elem => {
1559 const o = inst_datas[inst].vector_store_elem;
1560 const extra = a.air.extraData(Air.Bin, o.payload).data;
1561 removeOperandDeaths(a, to_remove, inst, .{ o.vector_ptr, extra.lhs, extra.rhs });
1562 },
1653 const condition = switch (inst_type) {
1654 .cond_br, .@"try" => inst_datas[inst].pl_op.operand,
1655 .try_ptr => extra.data.ptr,
1656 };
15631657
1564 .arg,
1565 .alloc,
1566 .ret_ptr,
1567 .constant,
1568 .const_ty,
1569 .trap,
1570 .breakpoint,
1571 .dbg_stmt,
1572 .dbg_inline_begin,
1573 .dbg_inline_end,
1574 .dbg_block_begin,
1575 .dbg_block_end,
1576 .unreach,
1577 .fence,
1578 .ret_addr,
1579 .frame_addr,
1580 .wasm_memory_size,
1581 .err_return_trace,
1582 .save_err_return_trace_index,
1583 .c_va_start,
1584 .work_item_id,
1585 .work_group_size,
1586 .work_group_id,
1587 => {},
1658 const then_body = switch (inst_type) {
1659 .cond_br => a.air.extra[extra.end..][0..extra.data.then_body_len],
1660 else => {}, // we won't use this
1661 };
15881662
1589 .not,
1590 .bitcast,
1591 .load,
1592 .fpext,
1593 .fptrunc,
1594 .intcast,
1595 .trunc,
1596 .optional_payload,
1597 .optional_payload_ptr,
1598 .optional_payload_ptr_set,
1599 .errunion_payload_ptr_set,
1600 .wrap_optional,
1601 .unwrap_errunion_payload,
1602 .unwrap_errunion_err,
1603 .unwrap_errunion_payload_ptr,
1604 .unwrap_errunion_err_ptr,
1605 .wrap_errunion_payload,
1606 .wrap_errunion_err,
1607 .slice_ptr,
1608 .slice_len,
1609 .ptr_slice_len_ptr,
1610 .ptr_slice_ptr_ptr,
1611 .struct_field_ptr_index_0,
1612 .struct_field_ptr_index_1,
1613 .struct_field_ptr_index_2,
1614 .struct_field_ptr_index_3,
1615 .array_to_slice,
1616 .float_to_int,
1617 .float_to_int_optimized,
1618 .int_to_float,
1619 .get_union_tag,
1620 .clz,
1621 .ctz,
1622 .popcount,
1623 .byte_swap,
1624 .bit_reverse,
1625 .splat,
1626 .error_set_has_value,
1627 .addrspace_cast,
1628 .c_va_arg,
1629 .c_va_copy,
1630 => {
1631 const o = inst_datas[inst].ty_op;
1632 removeOperandDeaths(a, to_remove, inst, .{ o.operand, .none, .none });
1633 },
1663 const else_body = switch (inst_type) {
1664 .cond_br => a.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len],
1665 .@"try", .try_ptr => a.air.extra[extra.end..][0..extra.data.body_len],
1666 };
16341667
1635 .is_null,
1636 .is_non_null,
1637 .is_null_ptr,
1638 .is_non_null_ptr,
1639 .is_err,
1640 .is_non_err,
1641 .is_err_ptr,
1642 .is_non_err_ptr,
1643 .ptrtoint,
1644 .bool_to_int,
1645 .ret,
1646 .ret_load,
1647 .is_named_enum_value,
1648 .tag_name,
1649 .error_name,
1650 .sqrt,
1651 .sin,
1652 .cos,
1653 .tan,
1654 .exp,
1655 .exp2,
1656 .log,
1657 .log2,
1658 .log10,
1659 .fabs,
1660 .floor,
1661 .ceil,
1662 .round,
1663 .trunc_float,
1664 .neg,
1665 .neg_optimized,
1666 .cmp_lt_errors_len,
1667 .set_err_return_trace,
1668 .c_va_end,
1669 => {
1670 const operand = inst_datas[inst].un_op;
1671 removeOperandDeaths(a, to_remove, inst, .{ operand, .none, .none });
1672 },
1668 switch (pass) {
1669 .loop_analysis => {
1670 switch (inst_type) {
1671 .cond_br => try analyzeBody(a, pass, data, then_body),
1672 .@"try", .try_ptr => {},
1673 }
1674 try analyzeBody(a, pass, data, else_body);
1675 },
1676
1677 .main_analysis => {
1678 var then_info: ControlBranchInfo = switch (inst_type) {
1679 .cond_br => try analyzeBodyResetBranch(a, pass, data, then_body),
1680 .@"try", .try_ptr => blk: {
1681 var branch_deaths = try data.branch_deaths.clone(gpa);
1682 errdefer branch_deaths.deinit(gpa);
1683 var live_set = try data.live_set.clone(gpa);
1684 errdefer live_set.deinit(gpa);
1685 break :blk .{
1686 .branch_deaths = branch_deaths,
1687 .live_set = live_set,
1688 };
1689 },
1690 };
1691 defer then_info.branch_deaths.deinit(gpa);
1692 defer then_info.live_set.deinit(gpa);
1693
1694 // If this is a `try`, the "then body" (rest of the branch) might have referenced our
1695 // result. If so, we want to avoid this value being considered live while analyzing the
1696 // else branch.
1697 switch (inst_type) {
1698 .cond_br => {},
1699 .@"try", .try_ptr => _ = data.live_set.remove(inst),
1700 }
16731701
1674 .add_with_overflow,
1675 .sub_with_overflow,
1676 .mul_with_overflow,
1677 .shl_with_overflow,
1678 .ptr_add,
1679 .ptr_sub,
1680 .ptr_elem_ptr,
1681 .slice_elem_ptr,
1682 .slice,
1683 => {
1684 const ty_pl = inst_datas[inst].ty_pl;
1685 const extra = a.air.extraData(Air.Bin, ty_pl.payload).data;
1686 removeOperandDeaths(a, to_remove, inst, .{ extra.lhs, extra.rhs, .none });
1687 },
1702 try analyzeBody(a, pass, data, else_body);
1703 var else_info: ControlBranchInfo = .{
1704 .branch_deaths = data.branch_deaths.move(),
1705 .live_set = data.live_set.move(),
1706 };
1707 defer else_info.branch_deaths.deinit(gpa);
1708 defer else_info.live_set.deinit(gpa);
16881709
1689 .dbg_var_ptr,
1690 .dbg_var_val,
1691 => {
1692 const operand = inst_datas[inst].pl_op.operand;
1693 removeOperandDeaths(a, to_remove, inst, .{ operand, .none, .none });
1694 },
1710 // Any queued deaths shared between both branches can be queued for us instead
1711 {
1712 var it = then_info.branch_deaths.keyIterator();
1713 while (it.next()) |key| {
1714 const death = key.*;
1715 if (else_info.branch_deaths.remove(death)) {
1716 // We'll remove it from then_deaths below
1717 try data.branch_deaths.put(gpa, death, {});
1718 }
1719 }
1720 log.debug("[{}] %{}: bubbled deaths {}", .{ pass, inst, fmtInstSet(&data.branch_deaths) });
1721 it = data.branch_deaths.keyIterator();
1722 while (it.next()) |key| {
1723 const death = key.*;
1724 assert(then_info.branch_deaths.remove(death));
1725 }
1726 }
16951727
1696 .prefetch => {
1697 const prefetch = inst_datas[inst].prefetch;
1698 removeOperandDeaths(a, to_remove, inst, .{ prefetch.ptr, .none, .none });
1699 },
1728 log.debug("[{}] %{}: remaining 'then' branch deaths are {}", .{ pass, inst, fmtInstSet(&then_info.branch_deaths) });
1729 log.debug("[{}] %{}: remaining 'else' branch deaths are {}", .{ pass, inst, fmtInstSet(&else_info.branch_deaths) });
17001730
1701 .call, .call_always_tail, .call_never_tail, .call_never_inline => {
1702 const inst_data = inst_datas[inst].pl_op;
1703 const callee = inst_data.operand;
1704 const extra = a.air.extraData(Air.Call, inst_data.payload);
1705 const args = @ptrCast([]const Air.Inst.Ref, a.air.extra[extra.end..][0..extra.data.args_len]);
1731 // Deaths that occur in one branch but not another need to be made to occur at the start
1732 // of the other branch.
17061733
1707 var death_remover = BigTombDeathRemover.init(a, to_remove, inst);
1708 death_remover.feed(callee);
1709 for (args) |operand| {
1710 death_remover.feed(operand);
1711 }
1712 death_remover.finish();
1713 },
1714 .select => {
1715 const pl_op = inst_datas[inst].pl_op;
1716 const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
1717 removeOperandDeaths(a, to_remove, inst, .{ pl_op.operand, extra.lhs, extra.rhs });
1718 },
1719 .shuffle => {
1720 const extra = a.air.extraData(Air.Shuffle, inst_datas[inst].ty_pl.payload).data;
1721 removeOperandDeaths(a, to_remove, inst, .{ extra.a, extra.b, .none });
1722 },
1723 .reduce, .reduce_optimized => {
1724 const reduce = inst_datas[inst].reduce;
1725 removeOperandDeaths(a, to_remove, inst, .{ reduce.operand, .none, .none });
1726 },
1727 .cmp_vector, .cmp_vector_optimized => {
1728 const extra = a.air.extraData(Air.VectorCmp, inst_datas[inst].ty_pl.payload).data;
1729 removeOperandDeaths(a, to_remove, inst, .{ extra.lhs, extra.rhs, .none });
1730 },
1731 .aggregate_init => {
1732 const ty_pl = inst_datas[inst].ty_pl;
1733 const aggregate_ty = a.air.getRefType(ty_pl.ty);
1734 const len = @intCast(usize, aggregate_ty.arrayLen());
1735 const elements = @ptrCast([]const Air.Inst.Ref, a.air.extra[ty_pl.payload..][0..len]);
1734 var then_mirrored_deaths: std.ArrayListUnmanaged(Air.Inst.Index) = .{};
1735 defer then_mirrored_deaths.deinit(gpa);
17361736
1737 var death_remover = BigTombDeathRemover.init(a, to_remove, inst);
1738 for (elements) |elem| {
1739 death_remover.feed(elem);
1740 }
1741 death_remover.finish();
1742 },
1743 .union_init => {
1744 const extra = a.air.extraData(Air.UnionInit, inst_datas[inst].ty_pl.payload).data;
1745 removeOperandDeaths(a, to_remove, inst, .{ extra.init, .none, .none });
1746 },
1747 .struct_field_ptr, .struct_field_val => {
1748 const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data;
1749 removeOperandDeaths(a, to_remove, inst, .{ extra.struct_operand, .none, .none });
1750 },
1751 .field_parent_ptr => {
1752 const extra = a.air.extraData(Air.FieldParentPtr, inst_datas[inst].ty_pl.payload).data;
1753 removeOperandDeaths(a, to_remove, inst, .{ extra.field_ptr, .none, .none });
1754 },
1755 .cmpxchg_strong, .cmpxchg_weak => {
1756 const extra = a.air.extraData(Air.Cmpxchg, inst_datas[inst].ty_pl.payload).data;
1757 removeOperandDeaths(a, to_remove, inst, .{ extra.ptr, extra.expected_value, extra.new_value });
1758 },
1759 .mul_add => {
1760 const pl_op = inst_datas[inst].pl_op;
1761 const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
1762 removeOperandDeaths(a, to_remove, inst, .{ extra.lhs, extra.rhs, pl_op.operand });
1763 },
1764 .atomic_load => {
1765 const ptr = inst_datas[inst].atomic_load.ptr;
1766 removeOperandDeaths(a, to_remove, inst, .{ ptr, .none, .none });
1767 },
1768 .atomic_rmw => {
1769 const pl_op = inst_datas[inst].pl_op;
1770 const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data;
1771 removeOperandDeaths(a, to_remove, inst, .{ pl_op.operand, extra.operand, .none });
1772 },
1773 .memset,
1774 .memcpy,
1775 => {
1776 const pl_op = inst_datas[inst].pl_op;
1777 const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
1778 removeOperandDeaths(a, to_remove, inst, .{ pl_op.operand, extra.lhs, extra.rhs });
1779 },
1737 var else_mirrored_deaths: std.ArrayListUnmanaged(Air.Inst.Index) = .{};
1738 defer else_mirrored_deaths.deinit(gpa);
17801739
1781 .br => {
1782 const br = inst_datas[inst].br;
1783 removeOperandDeaths(a, to_remove, inst, .{ br.operand, .none, .none });
1784 },
1785 .assembly => {
1786 const extra = a.air.extraData(Air.Asm, inst_datas[inst].ty_pl.payload);
1787 var extra_i: usize = extra.end;
1788 const outputs = @ptrCast([]const Air.Inst.Ref, a.air.extra[extra_i..][0..extra.data.outputs_len]);
1789 extra_i += outputs.len;
1790 const inputs = @ptrCast([]const Air.Inst.Ref, a.air.extra[extra_i..][0..extra.data.inputs_len]);
1791 extra_i += inputs.len;
1740 // Note: this invalidates `else_info.live_set`, but expands `then_info.live_set` to
1741 // be their union
1742 {
1743 var it = then_info.live_set.keyIterator();
1744 while (it.next()) |key| {
1745 const death = key.*;
1746 if (else_info.live_set.remove(death)) continue; // removing makes the loop below faster
1747 if (else_info.branch_deaths.contains(death)) continue;
1748
1749 // If this is a `try`, the "then body" (rest of the branch) might have
1750 // referenced our result. We want to avoid killing this value in the else branch
1751 // if that's the case, since it only exists in the (fake) then branch.
1752 switch (inst_type) {
1753 .cond_br => {},
1754 .@"try", .try_ptr => if (death == inst) continue,
1755 }
17921756
1793 var death_remover = BigTombDeathRemover.init(a, to_remove, inst);
1794 for (outputs) |output| {
1795 if (output != .none) {
1796 death_remover.feed(output);
1757 try else_mirrored_deaths.append(gpa, death);
1758 }
1759 // Since we removed common stuff above, `else_info.live_set` is now only operands
1760 // which are *only* alive in the else branch
1761 it = else_info.live_set.keyIterator();
1762 while (it.next()) |key| {
1763 const death = key.*;
1764 if (!then_info.branch_deaths.contains(death)) {
1765 try then_mirrored_deaths.append(gpa, death);
1766 }
1767 // Make `then_info.live_set` contain the full live set (i.e. union of both)
1768 try then_info.live_set.put(gpa, death, {});
17971769 }
17981770 }
1799 for (inputs) |input| {
1800 death_remover.feed(input);
1801 }
1802 death_remover.finish();
1803 },
1804 .block => {
1805 const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
1806 const body = a.air.extra[extra.end..][0..extra.data.body_len];
1807 try removeDeaths(a, to_remove, body);
1808 },
1809 .loop => {
1810 const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
1811 const body = a.air.extra[extra.end..][0..extra.data.body_len];
18121771
1813 const liveness_extra_idx = a.special.get(inst) orelse {
1814 try removeDeaths(a, to_remove, body);
1815 return;
1816 };
1772 log.debug("[{}] %{}: 'then' branch mirrored deaths are {}", .{ pass, inst, fmtInstList(then_mirrored_deaths.items) });
1773 log.debug("[{}] %{}: 'else' branch mirrored deaths are {}", .{ pass, inst, fmtInstList(else_mirrored_deaths.items) });
18171774
1818 const death_count = a.extra.items[liveness_extra_idx];
1819 var deaths = a.extra.items[liveness_extra_idx + 1 ..][0..death_count];
1775 data.live_set.deinit(gpa);
1776 data.live_set = then_info.live_set.move();
18201777
1821 // Remove any deaths in `to_remove` from this loop's deaths
1822 deaths.len = removeExtraDeaths(to_remove, deaths);
1823 a.extra.items[liveness_extra_idx] = @intCast(u32, deaths.len);
1778 log.debug("[{}] %{}: new live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
18241779
1825 // Temporarily add any deaths of ours to `to_remove`
1826 try to_remove.ensureUnusedCapacity(a.gpa, @intCast(u32, deaths.len));
1827 for (deaths) |d| {
1828 to_remove.putAssumeCapacity(d, {});
1780 // Write the branch deaths to `extra`
1781 const then_death_count = then_info.branch_deaths.count() + @intCast(u32, then_mirrored_deaths.items.len);
1782 const else_death_count = else_info.branch_deaths.count() + @intCast(u32, else_mirrored_deaths.items.len);
1783
1784 try a.extra.ensureUnusedCapacity(gpa, std.meta.fields(CondBr).len + then_death_count + else_death_count);
1785 const extra_index = a.addExtraAssumeCapacity(CondBr{
1786 .then_death_count = then_death_count,
1787 .else_death_count = else_death_count,
1788 });
1789 a.extra.appendSliceAssumeCapacity(then_mirrored_deaths.items);
1790 {
1791 var it = then_info.branch_deaths.keyIterator();
1792 while (it.next()) |key| a.extra.appendAssumeCapacity(key.*);
18291793 }
1830 try removeDeaths(a, to_remove, body);
1831 for (deaths) |d| {
1832 _ = to_remove.remove(d);
1794 a.extra.appendSliceAssumeCapacity(else_mirrored_deaths.items);
1795 {
1796 var it = else_info.branch_deaths.keyIterator();
1797 while (it.next()) |key| a.extra.appendAssumeCapacity(key.*);
18331798 }
1799 try a.special.put(gpa, inst, extra_index);
18341800 },
1835 .@"try" => {
1836 const pl_op = inst_datas[inst].pl_op;
1837 const extra = a.air.extraData(Air.Try, pl_op.payload);
1838 const body = a.air.extra[extra.end..][0..extra.data.body_len];
1839 try removeDeaths(a, to_remove, body);
1840 removeOperandDeaths(a, to_remove, inst, .{ pl_op.operand, .none, .none });
1841 },
1842 .try_ptr => {
1843 const extra = a.air.extraData(Air.TryPtr, inst_datas[inst].ty_pl.payload);
1844 const body = a.air.extra[extra.end..][0..extra.data.body_len];
1845 try removeDeaths(a, to_remove, body);
1846 removeOperandDeaths(a, to_remove, inst, .{ extra.data.ptr, .none, .none });
1847 },
1848 .cond_br => {
1849 const inst_data = inst_datas[inst].pl_op;
1850 const condition = inst_data.operand;
1851 const extra = a.air.extraData(Air.CondBr, inst_data.payload);
1852 const then_body = a.air.extra[extra.end..][0..extra.data.then_body_len];
1853 const else_body = a.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
1854
1855 if (a.special.get(inst)) |liveness_extra_idx| {
1856 const then_death_count = a.extra.items[liveness_extra_idx + 0];
1857 const else_death_count = a.extra.items[liveness_extra_idx + 1];
1858 var then_deaths = a.extra.items[liveness_extra_idx + 2 ..][0..then_death_count];
1859 var else_deaths = a.extra.items[liveness_extra_idx + 2 + then_death_count ..][0..else_death_count];
1860
1861 const new_then_death_count = removeExtraDeaths(to_remove, then_deaths);
1862 const new_else_death_count = removeExtraDeaths(to_remove, else_deaths);
1863
1864 a.extra.items[liveness_extra_idx + 0] = new_then_death_count;
1865 a.extra.items[liveness_extra_idx + 1] = new_else_death_count;
1866
1867 if (new_then_death_count < then_death_count) {
1868 // `else` deaths need to be moved earlier in `extra`
1869 const src = a.extra.items[liveness_extra_idx + 2 + then_death_count ..];
1870 const dest = a.extra.items[liveness_extra_idx + 2 + new_then_death_count ..];
1871 std.mem.copy(u32, dest, src[0..new_else_death_count]);
1872 }
1873 }
1801 }
18741802
1875 try removeDeaths(a, to_remove, then_body);
1876 try removeDeaths(a, to_remove, else_body);
1803 try analyzeOperands(a, pass, data, inst, .{ condition, .none, .none });
1804}
18771805
1878 removeOperandDeaths(a, to_remove, inst, .{ condition, .none, .none });
1806fn analyzeInstSwitchBr(
1807 a: *Analysis,
1808 comptime pass: LivenessPass,
1809 data: *LivenessPassData(pass),
1810 inst: Air.Inst.Index,
1811) !void {
1812 const inst_datas = a.air.instructions.items(.data);
1813 const pl_op = inst_datas[inst].pl_op;
1814 const condition = pl_op.operand;
1815 const switch_br = a.air.extraData(Air.SwitchBr, pl_op.payload);
1816 const gpa = a.gpa;
1817 const ncases = switch_br.data.cases_len;
1818
1819 switch (pass) {
1820 .loop_analysis => {
1821 var air_extra_index: usize = switch_br.end;
1822 for (0..ncases) |_| {
1823 const case = a.air.extraData(Air.SwitchBr.Case, air_extra_index);
1824 const case_body = a.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];
1825 air_extra_index = case.end + case.data.items_len + case_body.len;
1826 try analyzeBody(a, pass, data, case_body);
1827 }
1828 { // else
1829 const else_body = a.air.extra[air_extra_index..][0..switch_br.data.else_body_len];
1830 try analyzeBody(a, pass, data, else_body);
1831 }
18791832 },
1880 .switch_br => {
1881 const pl_op = inst_datas[inst].pl_op;
1882 const condition = pl_op.operand;
1883 const switch_br = a.air.extraData(Air.SwitchBr, pl_op.payload);
1833
1834 .main_analysis => {
1835 // This is, all in all, just a messier version of the `cond_br` logic. If you're trying
1836 // to understand it, I encourage looking at `analyzeInstCondBr` first.
1837
1838 const DeathSet = std.AutoHashMapUnmanaged(Air.Inst.Index, void);
1839 const DeathList = std.ArrayListUnmanaged(Air.Inst.Index);
1840
1841 var case_infos = try gpa.alloc(ControlBranchInfo, ncases + 1); // +1 for else
1842 defer gpa.free(case_infos);
1843
1844 std.mem.set(ControlBranchInfo, case_infos, .{});
1845 defer for (case_infos) |*info| {
1846 info.branch_deaths.deinit(gpa);
1847 info.live_set.deinit(gpa);
1848 };
18841849
18851850 var air_extra_index: usize = switch_br.end;
1886 for (0..switch_br.data.cases_len) |_| {
1851 for (case_infos[0..ncases]) |*info| {
18871852 const case = a.air.extraData(Air.SwitchBr.Case, air_extra_index);
18881853 const case_body = a.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];
18891854 air_extra_index = case.end + case.data.items_len + case_body.len;
1890 try removeDeaths(a, to_remove, case_body);
1855 info.* = try analyzeBodyResetBranch(a, pass, data, case_body);
18911856 }
18921857 { // else
18931858 const else_body = a.air.extra[air_extra_index..][0..switch_br.data.else_body_len];
1894 try removeDeaths(a, to_remove, else_body);
1859 try analyzeBody(a, pass, data, else_body);
1860 case_infos[ncases] = .{
1861 .branch_deaths = data.branch_deaths.move(),
1862 .live_set = data.live_set.move(),
1863 };
18951864 }
18961865
1897 if (a.special.get(inst)) |liveness_extra_idx| {
1898 const else_death_count = a.extra.items[liveness_extra_idx];
1899 var read_idx = liveness_extra_idx + 1;
1900 var write_idx = read_idx; // write_idx <= read_idx always
1901 for (0..switch_br.data.cases_len) |_| {
1902 const case_death_count = a.extra.items[read_idx];
1903 const case_deaths = a.extra.items[read_idx + 1 ..][0..case_death_count];
1904 const new_death_count = removeExtraDeaths(to_remove, case_deaths);
1905 a.extra.items[write_idx] = new_death_count;
1906 if (write_idx < read_idx) {
1907 std.mem.copy(u32, a.extra.items[write_idx + 1 ..], a.extra.items[read_idx + 1 ..][0..new_death_count]);
1866 // Queued deaths common to all cases can be bubbled up
1867 {
1868 // We can't remove from the set we're iterating over, so we'll store the shared deaths here
1869 // temporarily to remove them
1870 var shared_deaths: DeathSet = .{};
1871 defer shared_deaths.deinit(gpa);
1872
1873 var it = case_infos[0].branch_deaths.keyIterator();
1874 while (it.next()) |key| {
1875 const death = key.*;
1876 for (case_infos[1..]) |*info| {
1877 if (!info.branch_deaths.contains(death)) break;
1878 } else try shared_deaths.put(gpa, death, {});
1879 }
1880
1881 log.debug("[{}] %{}: bubbled deaths {}", .{ pass, inst, fmtInstSet(&shared_deaths) });
1882
1883 try data.branch_deaths.ensureUnusedCapacity(gpa, shared_deaths.count());
1884 it = shared_deaths.keyIterator();
1885 while (it.next()) |key| {
1886 const death = key.*;
1887 data.branch_deaths.putAssumeCapacity(death, {});
1888 for (case_infos) |*info| {
1889 _ = info.branch_deaths.remove(death);
19081890 }
1909 read_idx += 1 + case_death_count;
1910 write_idx += 1 + new_death_count;
19111891 }
1912 const else_deaths = a.extra.items[read_idx..][0..else_death_count];
1913 const new_else_death_count = removeExtraDeaths(to_remove, else_deaths);
1914 a.extra.items[liveness_extra_idx] = new_else_death_count;
1915 if (write_idx < read_idx) {
1916 std.mem.copy(u32, a.extra.items[write_idx..], a.extra.items[read_idx..][0..new_else_death_count]);
1892
1893 for (case_infos, 0..) |*info, i| {
1894 log.debug("[{}] %{}: case {} remaining branch deaths are {}", .{ pass, inst, i, fmtInstSet(&info.branch_deaths) });
19171895 }
19181896 }
19191897
1920 removeOperandDeaths(a, to_remove, inst, .{ condition, .none, .none });
1921 },
1922 .wasm_memory_grow => {
1923 const pl_op = inst_datas[inst].pl_op;
1924 removeOperandDeaths(a, to_remove, inst, .{ pl_op.operand, .none, .none });
1925 },
1926 }
1927}
1898 const mirrored_deaths = try gpa.alloc(DeathList, ncases + 1);
1899 defer gpa.free(mirrored_deaths);
19281900
1929fn removeOperandDeaths(
1930 a: *Analysis,
1931 to_remove: *const std.AutoHashMapUnmanaged(Air.Inst.Index, void),
1932 inst: Air.Inst.Index,
1933 operands: [bpi - 1]Air.Inst.Ref,
1934) void {
1935 const usize_index = (inst * bpi) / @bitSizeOf(usize);
1901 std.mem.set(DeathList, mirrored_deaths, .{});
1902 defer for (mirrored_deaths) |*md| md.deinit(gpa);
19361903
1937 const cur_tomb = @truncate(Bpi, a.tomb_bits[usize_index] >>
1938 @intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi));
1904 {
1905 var all_alive: DeathSet = .{};
1906 defer all_alive.deinit(gpa);
19391907
1940 var toggle_bits: Bpi = 0;
1908 for (case_infos) |*info| {
1909 try all_alive.ensureUnusedCapacity(gpa, info.live_set.count());
1910 var it = info.live_set.keyIterator();
1911 while (it.next()) |key| {
1912 const alive = key.*;
1913 all_alive.putAssumeCapacity(alive, {});
1914 }
1915 }
19411916
1942 for (operands, 0..) |op_ref, i| {
1943 const mask = @as(Bpi, 1) << @intCast(OperandInt, i);
1944 const op_int = @enumToInt(op_ref);
1945 if (op_int < Air.Inst.Ref.typed_value_map.len) continue;
1946 const operand: Air.Inst.Index = op_int - @intCast(u32, Air.Inst.Ref.typed_value_map.len);
1947 if ((cur_tomb & mask) != 0 and to_remove.contains(operand)) {
1948 log.debug("remove death of %{} in %{}", .{ operand, inst });
1949 toggle_bits ^= mask;
1950 }
1917 for (mirrored_deaths, case_infos) |*mirrored, *info| {
1918 var it = all_alive.keyIterator();
1919 while (it.next()) |key| {
1920 const alive = key.*;
1921 if (!info.live_set.contains(alive) and !info.branch_deaths.contains(alive)) {
1922 // Should die at the start of this branch
1923 try mirrored.append(gpa, alive);
1924 }
1925 }
1926 }
1927
1928 for (mirrored_deaths, 0..) |mirrored, i| {
1929 log.debug("[{}] %{}: case {} mirrored deaths are {}", .{ pass, inst, i, fmtInstList(mirrored.items) });
1930 }
1931
1932 data.live_set.deinit(gpa);
1933 data.live_set = all_alive.move();
1934
1935 log.debug("[{}] %{}: new live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
1936 }
1937
1938 const else_death_count = case_infos[ncases].branch_deaths.count() + @intCast(u32, mirrored_deaths[ncases].items.len);
1939
1940 const extra_index = try a.addExtra(SwitchBr{
1941 .else_death_count = else_death_count,
1942 });
1943 for (mirrored_deaths[0..ncases], case_infos[0..ncases]) |mirrored, info| {
1944 const num = info.branch_deaths.count() + @intCast(u32, mirrored.items.len);
1945 try a.extra.ensureUnusedCapacity(gpa, num + 1);
1946 a.extra.appendAssumeCapacity(num);
1947 a.extra.appendSliceAssumeCapacity(mirrored.items);
1948 {
1949 var it = info.branch_deaths.keyIterator();
1950 while (it.next()) |key| a.extra.appendAssumeCapacity(key.*);
1951 }
1952 }
1953 try a.extra.ensureUnusedCapacity(gpa, else_death_count);
1954 a.extra.appendSliceAssumeCapacity(mirrored_deaths[ncases].items);
1955 {
1956 var it = case_infos[ncases].branch_deaths.keyIterator();
1957 while (it.next()) |key| a.extra.appendAssumeCapacity(key.*);
1958 }
1959 try a.special.put(gpa, inst, extra_index);
1960 },
19511961 }
19521962
1953 a.tomb_bits[usize_index] ^= @as(usize, toggle_bits) <<
1954 @intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi);
1963 try analyzeOperands(a, pass, data, inst, .{ condition, .none, .none });
19551964}
19561965
1957fn removeExtraDeaths(
1958 to_remove: *const std.AutoHashMapUnmanaged(Air.Inst.Index, void),
1959 deaths: []Air.Inst.Index,
1960) u32 {
1961 var new_len = @intCast(u32, deaths.len);
1962 var i: usize = 0;
1963 while (i < new_len) {
1964 if (to_remove.contains(deaths[i])) {
1965 log.debug("remove extra death of %{}", .{deaths[i]});
1966 deaths[i] = deaths[new_len - 1];
1967 new_len -= 1;
1968 } else {
1969 i += 1;
1966fn AnalyzeBigOperands(comptime pass: LivenessPass) type {
1967 return struct {
1968 a: *Analysis,
1969 data: *LivenessPassData(pass),
1970 inst: Air.Inst.Index,
1971
1972 operands_remaining: u32,
1973 small: [bpi - 1]Air.Inst.Ref = .{.none} ** (bpi - 1),
1974 extra_tombs: []u32,
1975
1976 const Self = @This();
1977
1978 fn init(
1979 a: *Analysis,
1980 data: *LivenessPassData(pass),
1981 inst: Air.Inst.Index,
1982 total_operands: usize,
1983 ) !Self {
1984 const extra_operands = @intCast(u32, total_operands) -| (bpi - 1);
1985 const max_extra_tombs = (extra_operands + 30) / 31;
1986
1987 const extra_tombs: []u32 = switch (pass) {
1988 .loop_analysis => &.{},
1989 .main_analysis => try a.gpa.alloc(u32, max_extra_tombs),
1990 };
1991 errdefer a.gpa.free(extra_tombs);
1992
1993 std.mem.set(u32, extra_tombs, 0);
1994
1995 return .{
1996 .a = a,
1997 .data = data,
1998 .inst = inst,
1999 .operands_remaining = @intCast(u32, total_operands),
2000 .extra_tombs = extra_tombs,
2001 };
19702002 }
1971 }
1972 return new_len;
1973}
19742003
1975const BigTombDeathRemover = struct {
1976 a: *Analysis,
1977 to_remove: *const std.AutoHashMapUnmanaged(Air.Inst.Index, void),
1978 inst: Air.Inst.Index,
2004 /// Must be called with operands in reverse order.
2005 fn feed(big: *Self, op_ref: Air.Inst.Ref) !void {
2006 // Note that after this, `operands_remaining` becomes the index of the current operand
2007 big.operands_remaining -= 1;
19792008
1980 operands: [bpi - 1]Air.Inst.Ref = .{.none} ** (bpi - 1),
1981 next_oper: OperandInt = 0,
2009 if (big.operands_remaining < bpi - 1) {
2010 big.small[big.operands_remaining] = op_ref;
2011 return;
2012 }
19822013
1983 bit_index: u32 = 0,
1984 // Initialized once we finish the small tomb operands: see `feed`
1985 extra_start: u32 = undefined,
1986 extra_offset: u32 = 0,
2014 const operand = Air.refToIndex(op_ref) orelse return;
19872015
1988 fn init(a: *Analysis, to_remove: *const std.AutoHashMapUnmanaged(Air.Inst.Index, void), inst: Air.Inst.Index) BigTombDeathRemover {
1989 return .{
1990 .a = a,
1991 .to_remove = to_remove,
1992 .inst = inst,
1993 };
1994 }
2016 // Don't compute any liveness for constants
2017 const inst_tags = big.a.air.instructions.items(.tag);
2018 switch (inst_tags[operand]) {
2019 .constant, .const_ty => return,
2020 else => {},
2021 }
2022
2023 const extra_byte = (big.operands_remaining - (bpi - 1)) / 31;
2024 const extra_bit = @intCast(u5, big.operands_remaining - (bpi - 1) - extra_byte * 31);
2025
2026 const gpa = big.a.gpa;
2027
2028 switch (pass) {
2029 .loop_analysis => {
2030 _ = try big.data.live_set.put(gpa, operand, {});
2031 },
19952032
1996 fn feed(dr: *BigTombDeathRemover, operand: Air.Inst.Ref) void {
1997 if (dr.next_oper < bpi - 1) {
1998 dr.operands[dr.next_oper] = operand;
1999 dr.next_oper += 1;
2000 if (dr.next_oper == bpi - 1) {
2001 removeOperandDeaths(dr.a, dr.to_remove, dr.inst, dr.operands);
2002 if (dr.a.special.get(dr.inst)) |idx| dr.extra_start = idx;
2033 .main_analysis => {
2034 if ((try big.data.live_set.fetchPut(gpa, operand, {})) == null) {
2035 log.debug("[{}] %{}: added %{} to live set (operand dies here)", .{ pass, big.inst, operand });
2036 big.extra_tombs[extra_byte] |= @as(u32, 1) << extra_bit;
2037 if (big.data.branch_deaths.remove(operand)) {
2038 log.debug("[{}] %{}: resolved branch death of %{} to this usage", .{ pass, big.inst, operand });
2039 }
2040 }
2041 },
20032042 }
2004 return;
20052043 }
20062044
2007 defer dr.bit_index += 1;
2045 fn finish(big: *Self) !void {
2046 const gpa = big.a.gpa;
2047
2048 std.debug.assert(big.operands_remaining == 0);
2049
2050 switch (pass) {
2051 .loop_analysis => {},
2052
2053 .main_analysis => {
2054 // Note that the MSB is set on the final tomb to indicate the terminal element. This
2055 // allows for an optimisation where we only add as many extra tombs as are needed to
2056 // represent the dying operands. Each pass modifies operand bits and so needs to write
2057 // back, so let's figure out how many extra tombs we really need. Note that we always
2058 // keep at least one.
2059 var num: usize = big.extra_tombs.len;
2060 while (num > 1) {
2061 if (@truncate(u31, big.extra_tombs[num - 1]) != 0) {
2062 // Some operand dies here
2063 break;
2064 }
2065 num -= 1;
2066 }
2067 // Mark final tomb
2068 big.extra_tombs[num - 1] |= @as(u32, 1) << 31;
20082069
2009 const op_int = @enumToInt(operand);
2010 if (op_int < Air.Inst.Ref.typed_value_map.len) return;
2070 const extra_tombs = big.extra_tombs[0..num];
20112071
2012 const op_inst: Air.Inst.Index = op_int - @intCast(u32, Air.Inst.Ref.typed_value_map.len);
2072 const extra_index = @intCast(u32, big.a.extra.items.len);
2073 try big.a.extra.appendSlice(gpa, extra_tombs);
2074 try big.a.special.put(gpa, big.inst, extra_index);
2075 },
2076 }
20132077
2014 while (dr.bit_index - dr.extra_offset * 31 >= 31) {
2015 dr.extra_offset += 1;
2078 try analyzeOperands(big.a, pass, big.data, big.inst, big.small);
20162079 }
2017 const dies = @truncate(u1, dr.a.extra.items[dr.extra_start + dr.extra_offset] >>
2018 @intCast(u5, dr.bit_index - dr.extra_offset * 31)) != 0;
20192080
2020 if (dies and dr.to_remove.contains(op_inst)) {
2021 log.debug("remove big death of %{}", .{op_inst});
2022 dr.a.extra.items[dr.extra_start + dr.extra_offset] ^=
2023 (@as(u32, 1) << @intCast(u5, dr.bit_index - dr.extra_offset * 31));
2081 fn deinit(big: *Self) void {
2082 big.a.gpa.free(big.extra_tombs);
2083 }
2084 };
2085}
2086
2087fn fmtInstSet(set: *const std.AutoHashMapUnmanaged(Air.Inst.Index, void)) FmtInstSet {
2088 return .{ .set = set };
2089}
2090
2091const FmtInstSet = struct {
2092 set: *const std.AutoHashMapUnmanaged(Air.Inst.Index, void),
2093
2094 pub fn format(val: FmtInstSet, comptime _: []const u8, _: std.fmt.FormatOptions, w: anytype) !void {
2095 if (val.set.count() == 0) {
2096 try w.writeAll("[no instructions]");
2097 return;
2098 }
2099 var it = val.set.keyIterator();
2100 try w.print("%{}", .{it.next().?.*});
2101 while (it.next()) |key| {
2102 try w.print(" %{}", .{key.*});
20242103 }
20252104 }
2105};
2106
2107fn fmtInstList(list: []const Air.Inst.Index) FmtInstList {
2108 return .{ .list = list };
2109}
20262110
2027 fn finish(dr: *BigTombDeathRemover) void {
2028 if (dr.next_oper < bpi) {
2029 removeOperandDeaths(dr.a, dr.to_remove, dr.inst, dr.operands);
2111const FmtInstList = struct {
2112 list: []const Air.Inst.Index,
2113
2114 pub fn format(val: FmtInstList, comptime _: []const u8, _: std.fmt.FormatOptions, w: anytype) !void {
2115 if (val.list.len == 0) {
2116 try w.writeAll("[no instructions]");
2117 return;
2118 }
2119 try w.print("%{}", .{val.list[0]});
2120 for (val.list[1..]) |inst| {
2121 try w.print(" %{}", .{inst});
20302122 }
20312123 }
20322124};
src/arch/aarch64/CodeGen.zig-6
......@@ -5000,17 +5000,11 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
50005000 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
50015001 const loop = self.air.extraData(Air.Block, ty_pl.payload);
50025002 const body = self.air.extra[loop.end..][0..loop.data.body_len];
5003 const liveness_loop = self.liveness.getLoop(inst);
50045003 const start_index = @intCast(u32, self.mir_instructions.len);
50055004
50065005 try self.genBody(body);
50075006 try self.jump(start_index);
50085007
5009 try self.ensureProcessDeathCapacity(liveness_loop.deaths.len);
5010 for (liveness_loop.deaths) |operand| {
5011 self.processDeath(operand);
5012 }
5013
50145008 return self.finishAirBookkeeping();
50155009}
50165010
src/arch/arm/CodeGen.zig-6
......@@ -4923,17 +4923,11 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
49234923 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
49244924 const loop = self.air.extraData(Air.Block, ty_pl.payload);
49254925 const body = self.air.extra[loop.end..][0..loop.data.body_len];
4926 const liveness_loop = self.liveness.getLoop(inst);
49274926 const start_index = @intCast(Mir.Inst.Index, self.mir_instructions.len);
49284927
49294928 try self.genBody(body);
49304929 try self.jump(start_index);
49314930
4932 try self.ensureProcessDeathCapacity(liveness_loop.deaths.len);
4933 for (liveness_loop.deaths) |operand| {
4934 self.processDeath(operand);
4935 }
4936
49374931 return self.finishAirBookkeeping();
49384932}
49394933
src/arch/sparc64/CodeGen.zig-6
......@@ -1750,17 +1750,11 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
17501750 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
17511751 const loop = self.air.extraData(Air.Block, ty_pl.payload);
17521752 const body = self.air.extra[loop.end .. loop.end + loop.data.body_len];
1753 const liveness_loop = self.liveness.getLoop(inst);
17541753 const start = @intCast(u32, self.mir_instructions.len);
17551754
17561755 try self.genBody(body);
17571756 try self.jump(start);
17581757
1759 try self.ensureProcessDeathCapacity(liveness_loop.deaths.len);
1760 for (liveness_loop.deaths) |operand| {
1761 self.processDeath(operand);
1762 }
1763
17641758 return self.finishAirBookkeeping();
17651759}
17661760
src/arch/wasm/CodeGen.zig-6
......@@ -3183,7 +3183,6 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
31833183 const ty_pl = func.air.instructions.items(.data)[inst].ty_pl;
31843184 const loop = func.air.extraData(Air.Block, ty_pl.payload);
31853185 const body = func.air.extra[loop.end..][0..loop.data.body_len];
3186 const liveness_loop = func.liveness.getLoop(inst);
31873186
31883187 // result type of loop is always 'noreturn', meaning we can always
31893188 // emit the wasm type 'block_empty'.
......@@ -3194,11 +3193,6 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
31943193 try func.addLabel(.br, 0);
31953194 try func.endBlock();
31963195
3197 try func.currentBranch().values.ensureUnusedCapacity(func.gpa, @intCast(u32, liveness_loop.deaths.len));
3198 for (liveness_loop.deaths) |death| {
3199 func.processDeath(Air.indexToRef(death));
3200 }
3201
32023196 func.finishAir(inst, .none, &.{});
32033197}
32043198
src/arch/x86_64/CodeGen.zig-6
......@@ -6439,7 +6439,6 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
64396439 const loop = self.air.extraData(Air.Block, ty_pl.payload);
64406440 const body = self.air.extra[loop.end..][0..loop.data.body_len];
64416441 const jmp_target = @intCast(u32, self.mir_instructions.len);
6442 const liveness_loop = self.liveness.getLoop(inst);
64436442
64446443 {
64456444 try self.branch_stack.append(.{});
......@@ -6464,11 +6463,6 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
64646463
64656464 _ = try self.asmJmpReloc(jmp_target);
64666465
6467 try self.ensureProcessDeathCapacity(liveness_loop.deaths.len);
6468 for (liveness_loop.deaths) |operand| {
6469 self.processDeath(operand);
6470 }
6471
64726466 return self.finishAirBookkeeping();
64736467}
64746468
src/codegen/c.zig-5
......@@ -4637,17 +4637,12 @@ fn airLoop(f: *Function, inst: Air.Inst.Index) !CValue {
46374637 const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
46384638 const loop = f.air.extraData(Air.Block, ty_pl.payload);
46394639 const body = f.air.extra[loop.end..][0..loop.data.body_len];
4640 const liveness_loop = f.liveness.getLoop(inst);
46414640 const writer = f.object.writer();
46424641
46434642 try writer.writeAll("for (;;) ");
46444643 try genBody(f, body);
46454644 try writer.writeByte('\n');
46464645
4647 for (liveness_loop.deaths) |operand| {
4648 try die(f, inst, Air.indexToRef(operand));
4649 }
4650
46514646 return .none;
46524647}
46534648
src/print_air.zig-9
......@@ -405,7 +405,6 @@ const Writer = struct {
405405 const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
406406 const extra = w.air.extraData(Air.Block, ty_pl.payload);
407407 const body = w.air.extra[extra.end..][0..extra.data.body_len];
408 const liveness_loop = w.liveness.getLoop(inst);
409408
410409 try w.writeType(s, w.air.getRefType(ty_pl.ty));
411410 if (w.skip_body) return s.writeAll(", ...");
......@@ -413,14 +412,6 @@ const Writer = struct {
413412 const old_indent = w.indent;
414413 w.indent += 2;
415414 try w.writeBody(s, body);
416 if (liveness_loop.deaths.len != 0) {
417 try s.writeByteNTimes(' ', w.indent);
418 for (liveness_loop.deaths, 0..) |operand, i| {
419 if (i != 0) try s.writeAll(" ");
420 try s.print("%{d}!", .{operand});
421 }
422 try s.writeAll("\n");
423 }
424415 w.indent = old_indent;
425416 try s.writeByteNTimes(' ', w.indent);
426417 try s.writeAll("}");