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
signaturelock-open 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,...@@ -24,8 +24,10 @@ tomb_bits: []usize,
24/// Sparse table of specially handled instructions. The value is an index into the `extra`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.25/// array. The meaning of the data depends on the AIR tag.
26/// * `cond_br` - points to a `CondBr` in `extra` at this index.26/// * `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".
27/// * `switch_br` - points to a `SwitchBr` in `extra` at this index.29/// * `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.
29/// * `asm`, `call`, `aggregate_init` - the value is a set of bits which are the extra tomb31/// * `asm`, `call`, `aggregate_init` - the value is a set of bits which are the extra tomb
30/// bits of operands.32/// bits of operands.
31/// The main tomb bits are still used and the extra ones are starting with the lsb of the33/// 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 {...@@ -52,11 +54,88 @@ pub const SwitchBr = struct {
52 else_death_count: u32,54 else_death_count: u32,
53};55};
5456
55/// Trailing is the set of instructions whose lifetimes end at the end of the loop body.57/// Trailing is the set of instructions which die in the block. Note that these are not additional
56pub const Loop = struct {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 {
57 death_count: u32,61 death_count: u32,
58};62};
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
60pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness {139pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness {
61 const tracy = trace(@src());140 const tracy = trace(@src());
62 defer tracy.end();141 defer tracy.end();
...@@ -64,7 +143,6 @@ pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness {...@@ -64,7 +143,6 @@ pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness {
64 var a: Analysis = .{143 var a: Analysis = .{
65 .gpa = gpa,144 .gpa = gpa,
66 .air = air,145 .air = air,
67 .table = .{},
68 .tomb_bits = try gpa.alloc(146 .tomb_bits = try gpa.alloc(
69 usize,147 usize,
70 (air.instructions.len * bpi + @bitSizeOf(usize) - 1) / @bitSizeOf(usize),148 (air.instructions.len * bpi + @bitSizeOf(usize) - 1) / @bitSizeOf(usize),
...@@ -75,19 +153,27 @@ pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness {...@@ -75,19 +153,27 @@ pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness {
75 errdefer gpa.free(a.tomb_bits);153 errdefer gpa.free(a.tomb_bits);
76 errdefer a.special.deinit(gpa);154 errdefer a.special.deinit(gpa);
77 defer a.extra.deinit(gpa);155 defer a.extra.deinit(gpa);
78 defer a.table.deinit(gpa);
79156
80 std.mem.set(usize, a.tomb_bits, 0);157 std.mem.set(usize, a.tomb_bits, 0);
81158
82 const main_body = air.getMainBody();159 const main_body = air.getMainBody();
83 try a.table.ensureTotalCapacity(gpa, @intCast(u32, main_body.len));160
84 try analyzeWithContext(&a, null, main_body);161 {
162 var data: LivenessPassData(.loop_analysis) = .{};
163 defer data.deinit(gpa);
164 try analyzeBody(&a, .loop_analysis, &data, main_body);
165 }
166
85 {167 {
86 var to_remove: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};168 var data: LivenessPassData(.main_analysis) = .{};
87 defer to_remove.deinit(gpa);169 defer data.deinit(gpa);
88 try removeDeaths(&a, &to_remove, main_body);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);
89 }174 }
90 return Liveness{175
176 return .{
91 .tomb_bits = a.tomb_bits,177 .tomb_bits = a.tomb_bits,
92 .special = a.special,178 .special = a.special,
93 .extra = try a.extra.toOwnedSlice(gpa),179 .extra = try a.extra.toOwnedSlice(gpa),
...@@ -661,18 +747,27 @@ pub fn getSwitchBr(l: Liveness, gpa: Allocator, inst: Air.Inst.Index, cases_len:...@@ -661,18 +747,27 @@ pub fn getSwitchBr(l: Liveness, gpa: Allocator, inst: Air.Inst.Index, cases_len:
661 };747 };
662}748}
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 {
665 deaths: []const Air.Inst.Index,753 deaths: []const Air.Inst.Index,
666};754};
667755
668pub fn getLoop(l: Liveness, inst: Air.Inst.Index) LoopSlice {756pub fn getBlock(l: Liveness, inst: Air.Inst.Index) BlockSlices {
669 const index: usize = l.special.get(inst) orelse return .{757 const index: usize = l.special.get(inst) orelse return .{
670 .deaths = &.{},758 .deaths = &.{},
671 };759 };
672 const death_count = l.extra[index];760 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 };
674}765}
675766
767pub const LoopSlice = struct {
768 deaths: []const Air.Inst.Index,
769};
770
676pub fn deinit(l: *Liveness, gpa: Allocator) void {771pub fn deinit(l: *Liveness, gpa: Allocator) void {
677 gpa.free(l.tomb_bits);772 gpa.free(l.tomb_bits);
678 gpa.free(l.extra);773 gpa.free(l.extra);
...@@ -687,6 +782,7 @@ pub fn iterateBigTomb(l: Liveness, inst: Air.Inst.Index) BigTomb {...@@ -687,6 +782,7 @@ pub fn iterateBigTomb(l: Liveness, inst: Air.Inst.Index) BigTomb {
687 .extra_offset = 0,782 .extra_offset = 0,
688 .extra = l.extra,783 .extra = l.extra,
689 .bit_index = 0,784 .bit_index = 0,
785 .reached_end = false,
690 };786 };
691}787}
692788
...@@ -702,13 +798,16 @@ pub const BigTomb = struct {...@@ -702,13 +798,16 @@ pub const BigTomb = struct {
702 extra_start: u32,798 extra_start: u32,
703 extra_offset: u32,799 extra_offset: u32,
704 extra: []const u32,800 extra: []const u32,
801 reached_end: bool,
705802
706 /// Returns whether the next operand dies.803 /// Returns whether the next operand dies.
707 pub fn feed(bt: *BigTomb) bool {804 pub fn feed(bt: *BigTomb) bool {
805 if (bt.reached_end) return false;
806
708 const this_bit_index = bt.bit_index;807 const this_bit_index = bt.bit_index;
709 bt.bit_index += 1;808 bt.bit_index += 1;
710809
711 const small_tombs = Liveness.bpi - 1;810 const small_tombs = bpi - 1;
712 if (this_bit_index < small_tombs) {811 if (this_bit_index < small_tombs) {
713 const dies = @truncate(u1, bt.tomb_bits >> @intCast(Liveness.OperandInt, this_bit_index)) != 0;812 const dies = @truncate(u1, bt.tomb_bits >> @intCast(Liveness.OperandInt, this_bit_index)) != 0;
714 return dies;813 return dies;
...@@ -716,6 +815,10 @@ pub const BigTomb = struct {...@@ -716,6 +815,10 @@ pub const BigTomb = struct {
716815
717 const big_bit_index = this_bit_index - small_tombs;816 const big_bit_index = this_bit_index - small_tombs;
718 while (big_bit_index - bt.extra_offset * 31 >= 31) {817 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 }
719 bt.extra_offset += 1;822 bt.extra_offset += 1;
720 }823 }
721 const dies = @truncate(u1, bt.extra[bt.extra_start + bt.extra_offset] >>824 const dies = @truncate(u1, bt.extra[bt.extra_start + bt.extra_offset] >>
...@@ -728,7 +831,6 @@ pub const BigTomb = struct {...@@ -728,7 +831,6 @@ pub const BigTomb = struct {
728const Analysis = struct {831const Analysis = struct {
729 gpa: Allocator,832 gpa: Allocator,
730 air: Air,833 air: Air,
731 table: std.AutoHashMapUnmanaged(Air.Inst.Index, void),
732 tomb_bits: []usize,834 tomb_bits: []usize,
733 special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32),835 special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32),
734 extra: std.ArrayListUnmanaged(u32),836 extra: std.ArrayListUnmanaged(u32),
...@@ -758,46 +860,70 @@ const Analysis = struct {...@@ -758,46 +860,70 @@ const Analysis = struct {
758 }860 }
759};861};
760862
761fn analyzeWithContext(863fn analyzeBody(
762 a: *Analysis,864 a: *Analysis,
763 new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),865 comptime pass: LivenessPass,
866 data: *LivenessPassData(pass),
764 body: []const Air.Inst.Index,867 body: []const Air.Inst.Index,
765) Allocator.Error!void {868) Allocator.Error!void {
766 var i: usize = body.len;869 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| {877const ControlBranchInfo = struct {
769 // We are only interested in doing this for instructions which are born878 branch_deaths: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
770 // before a conditional branch, so after obtaining the new set for879 live_set: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
771 // each branch we prune the instructions which were born within.880};
772 while (i != 0) {881
773 i -= 1;882/// Helper function for running `analyzeBody`, but resetting `branch_deaths` and `live_set` to their
774 const inst = body[i];883/// original states before returning, returning the modified versions of them. Only makes sense in
775 _ = ns.remove(inst);884/// the `main_analysis` pass.
776 try analyzeInst(a, new_set, inst);885fn analyzeBodyResetBranch(
777 }886 a: *Analysis,
778 } else {887 comptime pass: LivenessPass,
779 while (i != 0) {888 data: *LivenessPassData(pass),
780 i -= 1;889 body: []const Air.Inst.Index,
781 const inst = body[i];890) !ControlBranchInfo {
782 try analyzeInst(a, new_set, inst);891 switch (pass) {
783 }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;
784 }908 }
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 };
785}916}
786917
787fn analyzeInst(918fn analyzeInst(
788 a: *Analysis,919 a: *Analysis,
789 new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),920 comptime pass: LivenessPass,
921 data: *LivenessPassData(pass),
790 inst: Air.Inst.Index,922 inst: Air.Inst.Index,
791) Allocator.Error!void {923) Allocator.Error!void {
792 const gpa = a.gpa;
793 const table = &a.table;
794 const inst_tags = a.air.instructions.items(.tag);924 const inst_tags = a.air.instructions.items(.tag);
795 const inst_datas = a.air.instructions.items(.data);925 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
801 switch (inst_tags[inst]) {927 switch (inst_tags[inst]) {
802 .add,928 .add,
803 .add_optimized,929 .add_optimized,
...@@ -861,28 +987,24 @@ fn analyzeInst(...@@ -861,28 +987,24 @@ fn analyzeInst(
861 .max,987 .max,
862 => {988 => {
863 const o = inst_datas[inst].bin_op;989 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 });
865 },991 },
866992
867 .vector_store_elem => {993 .vector_store_elem => {
868 const o = inst_datas[inst].vector_store_elem;994 const o = inst_datas[inst].vector_store_elem;
869 const extra = a.air.extraData(Air.Bin, o.payload).data;995 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 });
871 },997 },
872998
873 .arg,999 .arg,
874 .alloc,1000 .alloc,
875 .ret_ptr,1001 .ret_ptr,
876 .constant,
877 .const_ty,
878 .trap,
879 .breakpoint,1002 .breakpoint,
880 .dbg_stmt,1003 .dbg_stmt,
881 .dbg_inline_begin,1004 .dbg_inline_begin,
882 .dbg_inline_end,1005 .dbg_inline_end,
883 .dbg_block_begin,1006 .dbg_block_begin,
884 .dbg_block_end,1007 .dbg_block_end,
885 .unreach,
886 .fence,1008 .fence,
887 .ret_addr,1009 .ret_addr,
888 .frame_addr,1010 .frame_addr,
...@@ -893,7 +1015,15 @@ fn analyzeInst(...@@ -893,7 +1015,15 @@ fn analyzeInst(
893 .work_item_id,1015 .work_item_id,
894 .work_group_size,1016 .work_group_size,
895 .work_group_id,1017 .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
898 .not,1028 .not,
899 .bitcast,1029 .bitcast,
...@@ -938,7 +1068,7 @@ fn analyzeInst(...@@ -938,7 +1068,7 @@ fn analyzeInst(
938 .c_va_copy,1068 .c_va_copy,
939 => {1069 => {
940 const o = inst_datas[inst].ty_op;1070 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 });
942 },1072 },
9431073
944 .is_null,1074 .is_null,
...@@ -951,8 +1081,6 @@ fn analyzeInst(...@@ -951,8 +1081,6 @@ fn analyzeInst(
951 .is_non_err_ptr,1081 .is_non_err_ptr,
952 .ptrtoint,1082 .ptrtoint,
953 .bool_to_int,1083 .bool_to_int,
954 .ret,
955 .ret_load,
956 .is_named_enum_value,1084 .is_named_enum_value,
957 .tag_name,1085 .tag_name,
958 .error_name,1086 .error_name,
...@@ -977,7 +1105,14 @@ fn analyzeInst(...@@ -977,7 +1105,14 @@ fn analyzeInst(
977 .c_va_end,1105 .c_va_end,
978 => {1106 => {
979 const operand = inst_datas[inst].un_op;1107 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 });
981 },1116 },
9821117
983 .add_with_overflow,1118 .add_with_overflow,
...@@ -992,19 +1127,19 @@ fn analyzeInst(...@@ -992,19 +1127,19 @@ fn analyzeInst(
992 => {1127 => {
993 const ty_pl = inst_datas[inst].ty_pl;1128 const ty_pl = inst_datas[inst].ty_pl;
994 const extra = a.air.extraData(Air.Bin, ty_pl.payload).data;1129 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 });
996 },1131 },
9971132
998 .dbg_var_ptr,1133 .dbg_var_ptr,
999 .dbg_var_val,1134 .dbg_var_val,
1000 => {1135 => {
1001 const operand = inst_datas[inst].pl_op.operand;1136 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 });
1003 },1138 },
10041139
1005 .prefetch => {1140 .prefetch => {
1006 const prefetch = inst_datas[inst].prefetch;1141 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 });
1008 },1143 },
10091144
1010 .call, .call_always_tail, .call_never_tail, .call_never_inline => {1145 .call, .call_always_tail, .call_never_tail, .call_never_inline => {
...@@ -1016,37 +1151,35 @@ fn analyzeInst(...@@ -1016,37 +1151,35 @@ fn analyzeInst(
1016 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);1151 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
1017 buf[0] = callee;1152 buf[0] = callee;
1018 std.mem.copy(Air.Inst.Ref, buf[1..], args);1153 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);
1020 }1155 }
1021 var extra_tombs: ExtraTombs = .{1156
1022 .analysis = a,1157 var big = try AnalyzeBigOperands(pass).init(a, data, inst, args.len + 1);
1023 .new_set = new_set,1158 defer big.deinit();
1024 .inst = inst,1159 var i: usize = args.len;
1025 .main_tomb = main_tomb,1160 while (i > 0) {
1026 };1161 i -= 1;
1027 defer extra_tombs.deinit();1162 try big.feed(args[i]);
1028 try extra_tombs.feed(callee);
1029 for (args) |arg| {
1030 try extra_tombs.feed(arg);
1031 }1163 }
1032 return extra_tombs.finish();1164 try big.feed(callee);
1165 return big.finish();
1033 },1166 },
1034 .select => {1167 .select => {
1035 const pl_op = inst_datas[inst].pl_op;1168 const pl_op = inst_datas[inst].pl_op;
1036 const extra = a.air.extraData(Air.Bin, pl_op.payload).data;1169 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 });
1038 },1171 },
1039 .shuffle => {1172 .shuffle => {
1040 const extra = a.air.extraData(Air.Shuffle, inst_datas[inst].ty_pl.payload).data;1173 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 });
1042 },1175 },
1043 .reduce, .reduce_optimized => {1176 .reduce, .reduce_optimized => {
1044 const reduce = inst_datas[inst].reduce;1177 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 });
1046 },1179 },
1047 .cmp_vector, .cmp_vector_optimized => {1180 .cmp_vector, .cmp_vector_optimized => {
1048 const extra = a.air.extraData(Air.VectorCmp, inst_datas[inst].ty_pl.payload).data;1181 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 });
1050 },1183 },
1051 .aggregate_init => {1184 .aggregate_init => {
1052 const ty_pl = inst_datas[inst].ty_pl;1185 const ty_pl = inst_datas[inst].ty_pl;
...@@ -1057,62 +1190,58 @@ fn analyzeInst(...@@ -1057,62 +1190,58 @@ fn analyzeInst(
1057 if (elements.len <= bpi - 1) {1190 if (elements.len <= bpi - 1) {
1058 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);1191 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
1059 std.mem.copy(Air.Inst.Ref, &buf, elements);1192 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);
1061 }1194 }
1062 var extra_tombs: ExtraTombs = .{1195
1063 .analysis = a,1196 var big = try AnalyzeBigOperands(pass).init(a, data, inst, elements.len);
1064 .new_set = new_set,1197 defer big.deinit();
1065 .inst = inst,1198 var i: usize = elements.len;
1066 .main_tomb = main_tomb,1199 while (i > 0) {
1067 };1200 i -= 1;
1068 defer extra_tombs.deinit();1201 try big.feed(elements[i]);
1069 for (elements) |elem| {
1070 try extra_tombs.feed(elem);
1071 }1202 }
1072 return extra_tombs.finish();1203 return big.finish();
1073 },1204 },
1074 .union_init => {1205 .union_init => {
1075 const extra = a.air.extraData(Air.UnionInit, inst_datas[inst].ty_pl.payload).data;1206 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 });
1077 },1208 },
1078 .struct_field_ptr, .struct_field_val => {1209 .struct_field_ptr, .struct_field_val => {
1079 const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data;1210 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 });
1081 },1212 },
1082 .field_parent_ptr => {1213 .field_parent_ptr => {
1083 const extra = a.air.extraData(Air.FieldParentPtr, inst_datas[inst].ty_pl.payload).data;1214 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 });
1085 },1216 },
1086 .cmpxchg_strong, .cmpxchg_weak => {1217 .cmpxchg_strong, .cmpxchg_weak => {
1087 const extra = a.air.extraData(Air.Cmpxchg, inst_datas[inst].ty_pl.payload).data;1218 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 });
1089 },1220 },
1090 .mul_add => {1221 .mul_add => {
1091 const pl_op = inst_datas[inst].pl_op;1222 const pl_op = inst_datas[inst].pl_op;
1092 const extra = a.air.extraData(Air.Bin, pl_op.payload).data;1223 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 });
1094 },1225 },
1095 .atomic_load => {1226 .atomic_load => {
1096 const ptr = inst_datas[inst].atomic_load.ptr;1227 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 });
1098 },1229 },
1099 .atomic_rmw => {1230 .atomic_rmw => {
1100 const pl_op = inst_datas[inst].pl_op;1231 const pl_op = inst_datas[inst].pl_op;
1101 const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data;1232 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 });
1103 },1234 },
1104 .memset,1235 .memset,
1105 .memcpy,1236 .memcpy,
1106 => {1237 => {
1107 const pl_op = inst_datas[inst].pl_op;1238 const pl_op = inst_datas[inst].pl_op;
1108 const extra = a.air.extraData(Air.Bin, pl_op.payload).data;1239 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 });
1110 },1241 },
11111242
1112 .br => {1243 .br => return analyzeInstBr(a, pass, data, inst),
1113 const br = inst_datas[inst].br;1244
1114 return trackOperands(a, new_set, inst, main_tomb, .{ br.operand, .none, .none });
1115 },
1116 .assembly => {1245 .assembly => {
1117 const extra = a.air.extraData(Air.Asm, inst_datas[inst].ty_pl.payload);1246 const extra = a.air.extraData(Air.Asm, inst_datas[inst].ty_pl.payload);
1118 var extra_i: usize = extra.end;1247 var extra_i: usize = extra.end;
...@@ -1121,912 +1250,875 @@ fn analyzeInst(...@@ -1121,912 +1250,875 @@ fn analyzeInst(
1121 const inputs = @ptrCast([]const Air.Inst.Ref, a.air.extra[extra_i..][0..extra.data.inputs_len]);1250 const inputs = @ptrCast([]const Air.Inst.Ref, a.air.extra[extra_i..][0..extra.data.inputs_len]);
1122 extra_i += inputs.len;1251 extra_i += inputs.len;
11231252
1124 simple: {1253 const num_operands = simple: {
1125 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);1254 var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
1126 var buf_index: usize = 0;1255 var buf_index: usize = 0;
1127 for (outputs) |output| {1256 for (outputs) |output| {
1128 if (output != .none) {1257 if (output != .none) {
1129 if (buf_index >= buf.len) break :simple;1258 if (buf_index < buf.len) buf[buf_index] = output;
1130 buf[buf_index] = output;
1131 buf_index += 1;1259 buf_index += 1;
1132 }1260 }
1133 }1261 }
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 }
1135 std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs);1265 std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs);
1136 return trackOperands(a, new_set, inst, main_tomb, buf);1266 return analyzeOperands(a, pass, data, inst, buf);
1137 }
1138 var extra_tombs: ExtraTombs = .{
1139 .analysis = a,
1140 .new_set = new_set,
1141 .inst = inst,
1142 .main_tomb = main_tomb,
1143 };1267 };
1144 defer extra_tombs.deinit();1268
1145 for (outputs) |output| {1269 var big = try AnalyzeBigOperands(pass).init(a, data, inst, num_operands);
1146 if (output != .none) {1270 defer big.deinit();
1147 try extra_tombs.feed(output);1271 var i: usize = inputs.len;
1148 }1272 while (i > 0) {
1273 i -= 1;
1274 try big.feed(inputs[i]);
1149 }1275 }
1150 for (inputs) |input| {1276 i = outputs.len;
1151 try extra_tombs.feed(input);1277 while (i > 0) {
1278 i -= 1;
1279 if (outputs[i] != .none) {
1280 try big.feed(outputs[i]);
1281 }
1152 }1282 }
1153 return extra_tombs.finish();1283 return big.finish();
1154 },1284 },
1155 .block => {1285
1156 const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);1286 .block => return analyzeInstBlock(a, pass, data, inst),
1157 const body = a.air.extra[extra.end..][0..extra.data.body_len];1287 .loop => return analyzeInstLoop(a, pass, data, inst),
1158 try analyzeWithContext(a, new_set, body);1288
1159 return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none });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 });
1160 },1297 },
1161 .loop => {1298 }
1162 const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);1299}
1163 const body = a.air.extra[extra.end..][0..extra.data.body_len];
11641300
1165 var body_table: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};1301/// Every instruction should hit this (after handling any nested bodies), in every pass. In the
1166 defer body_table.deinit(gpa);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 loop1314 switch (pass) {
1169 // iterations may occur. Track deaths from the loop body - we'll remove all of these1315 .loop_analysis => {
1170 // retroactively, and add them to our extra data.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| {1321 // Don't compute any liveness for constants
1175 try ns.ensureUnusedCapacity(gpa, body_table.count());1322 switch (inst_tags[operand]) {
1176 var it = body_table.keyIterator();1323 .constant, .const_ty => continue,
1177 while (it.next()) |key| {1324 else => {},
1178 _ = ns.putAssumeCapacity(key.*, {});
1179 }1325 }
1326
1327 _ = try data.live_set.put(gpa, operand, {});
1180 }1328 }
1329 },
11811330
1182 try a.extra.ensureUnusedCapacity(gpa, std.meta.fields(Loop).len + body_table.count());1331 .main_analysis => {
1183 const extra_index = a.addExtraAssumeCapacity(Loop{1332 const usize_index = (inst * bpi) / @bitSizeOf(usize);
1184 .death_count = body_table.count(),1333
1185 });1334 var tomb_bits: Bpi = 0;
1186 {1335
1187 var it = body_table.keyIterator();1336 if (data.branch_deaths.remove(inst)) {
1188 while (it.next()) |key| {1337 log.debug("[{}] %{}: resolved branch death to birth (immediate death)", .{ pass, inst });
1189 a.extra.appendAssumeCapacity(key.*);1338 tomb_bits |= @as(Bpi, 1) << (bpi - 1);
1190 }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);
1191 }1345 }
1192 try a.special.put(gpa, inst, extra_index);
11931346
1194 // We'll remove invalid deaths in a separate pass after main liveness analysis. See1347 // Note that it's important we iterate over the operands backwards, so that if a dying
1195 // removeDeaths for more details.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.1363 if ((try data.live_set.fetchPut(gpa, operand, {})) == null) {
1198 },1364 log.debug("[{}] %{}: added %{} to live set (operand dies here)", .{ pass, inst, operand });
1199 .@"try" => {1365 tomb_bits |= mask;
1200 const pl_op = inst_datas[inst].pl_op;1366 if (data.branch_deaths.remove(operand)) {
1201 const extra = a.air.extraData(Air.Try, pl_op.payload);1367 log.debug("[{}] %{}: resolved branch death of %{} to this usage", .{ pass, inst, operand });
1202 const body = a.air.extra[extra.end..][0..extra.data.body_len];1368 }
1203 try analyzeWithContext(a, new_set, body);1369 }
1204 return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, .none, .none });1370 }
1371
1372 a.tomb_bits[usize_index] |= @as(usize, tomb_bits) <<
1373 @intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi);
1205 },1374 },
1206 .try_ptr => {1375 }
1207 const extra = a.air.extraData(Air.TryPtr, inst_datas[inst].ty_pl.payload);1376}
1208 const body = a.air.extra[extra.end..][0..extra.data.body_len];1377
1209 try analyzeWithContext(a, new_set, body);1378/// Like `analyzeOperands`, but for an instruction which returns from a function, so should
1210 return trackOperands(a, new_set, inst, main_tomb, .{ extra.data.ptr, .none, .none });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.
1211 },1391 },
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) = .{};1393 .main_analysis => {
1222 defer then_table.deinit(gpa);1394 const gpa = a.gpa;
1223 try analyzeWithContext(a, &then_table, then_body);
12241395
1225 // Reset the table back to its state from before the branch.1396 // Note that we preserve previous branch deaths - anything that needs to die in our
1226 {1397 // "parent" branch also needs to die for us.
1227 var it = then_table.keyIterator();1398
1228 while (it.next()) |key| {1399 try data.branch_deaths.ensureUnusedCapacity(gpa, data.live_set.count());
1229 assert(table.remove(key.*));1400 var it = data.live_set.keyIterator();
1230 }1401 while (it.next()) |key| {
1402 const alive = key.*;
1403 data.branch_deaths.putAssumeCapacity(alive, {});
1231 }1404 }
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) = .{};1422 switch (pass) {
1234 defer else_table.deinit(gpa);1423 .loop_analysis => {
1235 try analyzeWithContext(a, &else_table, else_body);1424 try data.breaks.put(gpa, br.block_inst, {});
1425 },
12361426
1237 var then_entry_deaths = std.ArrayList(Air.Inst.Index).init(gpa);1427 .main_analysis => {
1238 defer then_entry_deaths.deinit();1428 const block_scope = data.block_scopes.get(br.block_inst).?; // we should always be breaking from an enclosing block
1239 var else_entry_deaths = std.ArrayList(Air.Inst.Index).init(gpa);
1240 defer else_entry_deaths.deinit();
12411429
1242 {1430 // We mostly preserve previous branch deaths - anything that should die for our
1243 var it = else_table.keyIterator();1431 // enclosing branch should die for us too. However, if our break target requires such an
1244 while (it.next()) |key| {1432 // operand to be alive, it's actually not something we want to kill, since its "last
1245 const else_death = key.*;1433 // use" (i.e. the point at which it should die) is outside of our scope.
1246 if (!then_table.contains(else_death)) {1434 var it = block_scope.live_set.keyIterator();
1247 try then_entry_deaths.append(else_death);1435 while (it.next()) |key| {
1248 }1436 const alive = key.*;
1249 }1437 _ = data.branch_deaths.remove(alive);
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 }
1262 }1438 }
1263 // Now we have to correctly populate new_set.1439 log.debug("[{}] %{}: preserved branch deaths are {}", .{ pass, inst, fmtInstSet(&data.branch_deaths) });
1264 if (new_set) |ns| {1440
1265 try ns.ensureUnusedCapacity(gpa, @intCast(u32, then_table.count() + else_table.count()));1441 // Anything that's currently alive but our target doesn't need becomes a branch death.
1266 var it = then_table.keyIterator();1442 it = data.live_set.keyIterator();
1267 while (it.next()) |key| {1443 while (it.next()) |key| {
1268 _ = ns.putAssumeCapacity(key.*, {});1444 const alive = key.*;
1269 }1445 if (!block_scope.live_set.contains(alive)) {
1270 it = else_table.keyIterator();1446 _ = try data.branch_deaths.put(gpa, alive, {});
1271 while (it.next()) |key| {1447 log.debug("[{}] %{}: added branch death of {}", .{ pass, inst, alive });
1272 _ = ns.putAssumeCapacity(key.*, {});
1273 }1448 }
1274 }1449 }
1275 const then_death_count = @intCast(u32, then_entry_deaths.items.len);1450 const new_live_set = try block_scope.live_set.clone(gpa);
1276 const else_death_count = @intCast(u32, else_entry_deaths.items.len);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 +1456 return analyzeOperands(a, pass, data, inst, .{ br.operand, .none, .none });
1279 then_death_count + else_death_count);1457}
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);
12871458
1288 // Continue on with the instruction analysis. The following code will find the condition1459fn analyzeInstBlock(
1289 // instruction, and the deaths flag for the CondBr instruction will indicate whether the1460 a: *Analysis,
1290 // condition's lifetime ends immediately before entering any branch.1461 comptime pass: LivenessPass,
1291 return trackOperands(a, new_set, inst, main_tomb, .{ condition, .none, .none });1462 data: *LivenessPassData(pass),
1292 },1463 inst: Air.Inst.Index,
1293 .switch_br => {1464) !void {
1294 const pl_op = inst_datas[inst].pl_op;1465 const inst_datas = a.air.instructions.items(.data);
1295 const condition = pl_op.operand;1466 const ty_pl = inst_datas[inst].ty_pl;
1296 const switch_br = a.air.extraData(Air.SwitchBr, pl_op.payload);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);1470 const gpa = a.gpa;
1299 const case_tables = try gpa.alloc(Table, switch_br.data.cases_len + 1); // +1 for else
1300 defer gpa.free(case_tables);
13011471
1302 std.mem.set(Table, case_tables, .{});1472 // We actually want to do `analyzeOperands` *first*, since our result logically doesn't
1303 defer for (case_tables) |*ct| ct.deinit(gpa);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;1476 switch (pass) {
1306 for (case_tables[0..switch_br.data.cases_len]) |*case_table| {1477 .loop_analysis => {
1307 const case = a.air.extraData(Air.SwitchBr.Case, air_extra_index);1478 try analyzeBody(a, pass, data, body);
1308 const case_body = a.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];1479 _ = data.breaks.remove(inst);
1309 air_extra_index = case.end + case.data.items_len + case_body.len;1480 },
1310 try analyzeWithContext(a, case_table, case_body);
13111481
1312 // Reset the table back to its state from before the case.1482 .main_analysis => {
1313 var it = case_table.keyIterator();1483 log.debug("[{}] %{}: block live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
1314 while (it.next()) |key| {1484 try data.block_scopes.put(gpa, inst, .{
1315 assert(table.remove(key.*));1485 .live_set = try data.live_set.clone(gpa),
1316 }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);
1317 }1491 }
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.1493 log.debug("[{}] %{}: pushed new block scope", .{ pass, inst });
1324 var it = else_table.keyIterator();1494 try analyzeBody(a, pass, data, body);
1325 while (it.next()) |key| {
1326 assert(table.remove(key.*));
1327 }
1328 }
13291495
1330 const List = std.ArrayListUnmanaged(Air.Inst.Index);1496 // If the block is noreturn, block deaths not only aren't useful, they're impossible to
1331 const case_deaths = try gpa.alloc(List, case_tables.len); // includes else1497 // find: there could be more stuff alive after the block than before it!
1332 defer gpa.free(case_deaths);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, .{});1503 try a.extra.ensureUnusedCapacity(gpa, num_deaths + std.meta.fields(Block).len);
1335 defer for (case_deaths) |*cd| cd.deinit(gpa);1504 const extra_index = a.addExtraAssumeCapacity(Block{
1505 .death_count = num_deaths,
1506 });
13361507
1337 var total_deaths: u32 = 0;1508 var measured_num: u32 = 0;
1338 for (case_tables, 0..) |*ct, i| {1509 var it = data.live_set.keyIterator();
1339 total_deaths += ct.count();
1340 var it = ct.keyIterator();
1341 while (it.next()) |key| {1510 while (it.next()) |key| {
1342 const case_death = key.*;1511 const alive = key.*;
1343 for (case_tables, 0..) |*ct_inner, j| {1512 if (!block_scope.live_set.contains(alive)) {
1344 if (i == j) continue;1513 // Dies in block
1345 if (!ct_inner.contains(case_death)) {1514 a.extra.appendAssumeCapacity(alive);
1346 // instruction is not referenced in this case1515 measured_num += 1;
1347 try case_deaths[j].append(gpa, case_death);
1348 }
1349 }1516 }
1350 // undo resetting the table
1351 try table.put(gpa, case_death, {});
1352 }1517 }
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 });
1353 }1525 }
1526 },
1527 }
1528}
13541529
1355 // Now we have to correctly populate new_set.1530fn analyzeInstLoop(
1356 if (new_set) |ns| {1531 a: *Analysis,
1357 try ns.ensureUnusedCapacity(gpa, total_deaths);1532 comptime pass: LivenessPass,
1358 for (case_tables) |*ct| {1533 data: *LivenessPassData(pass),
1359 var it = ct.keyIterator();1534 inst: Air.Inst.Index,
1360 while (it.next()) |key| {1535) !void {
1361 _ = ns.putAssumeCapacity(key.*, {});1536 const inst_datas = a.air.instructions.items(.data);
1362 }1537 const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
1363 }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);
1364 }1563 }
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);1566 // Now we put the live operands from the loop body in too
1367 const extra_index = try a.addExtra(SwitchBr{1567 const num_live = data.live_set.count();
1368 .else_death_count = else_death_count,1568 try a.extra.ensureUnusedCapacity(gpa, 1 + num_live);
1369 });1569
1370 for (case_deaths[0 .. case_deaths.len - 1]) |*cd| {1570 a.extra.appendAssumeCapacity(num_live);
1371 const case_death_count = @intCast(u32, cd.items.len);1571 it = data.live_set.keyIterator();
1372 try a.extra.ensureUnusedCapacity(gpa, 1 + case_death_count + else_death_count);1572 while (it.next()) |key| {
1373 a.extra.appendAssumeCapacity(case_death_count);1573 const alive = key.*;
1374 a.extra.appendSliceAssumeCapacity(cd.items);1574 a.extra.appendAssumeCapacity(alive);
1375 }1575 }
1376 a.extra.appendSliceAssumeCapacity(case_deaths[case_deaths.len - 1].items);1576 log.debug("[{}] %{}: maintain liveness of {}", .{ pass, inst, fmtInstSet(&data.live_set) });
1577
1377 try a.special.put(gpa, inst, extra_index);1578 try a.special.put(gpa, inst, extra_index);
13781579
1379 return trackOperands(a, new_set, inst, main_tomb, .{ condition, .none, .none });1580 // Add back operands which were previously alive
1380 },1581 it = old_live.keyIterator();
1381 .wasm_memory_grow => {1582 while (it.next()) |key| {
1382 const pl_op = inst_datas[inst].pl_op;1583 const alive = key.*;
1383 return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, .none, .none });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 }
1384 },1593 },
1385 }
1386}
13871594
1388fn trackOperands(1595 .main_analysis => {
1389 a: *Analysis,1596 const extra_idx = a.special.fetchRemove(inst).?.value; // remove because this data does not exist after 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;
13971597
1398 var tomb_bits: Bpi = @boolToInt(main_tomb);1598 const num_breaks = data.old_extra.items[extra_idx];
1399 var i = operands.len;1599 const breaks = data.old_extra.items[extra_idx + 1 ..][0..num_breaks];
14001600
1401 while (i > 0) {1601 const num_loop_live = data.old_extra.items[extra_idx + num_breaks + 1];
1402 i -= 1;1602 const loop_live = data.old_extra.items[extra_idx + num_breaks + 2 ..][0..num_loop_live];
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}
14161603
1417const ExtraTombs = struct {1604 // This is necessarily not in the same control flow branch, because loops are noreturn
1418 analysis: *Analysis,1605 data.live_set.clearRetainingCapacity();
1419 new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),1606
1420 inst: Air.Inst.Index,1607 try data.live_set.ensureUnusedCapacity(gpa, @intCast(u32, loop_live.len));
1421 main_tomb: bool,1608 for (loop_live) |alive| {
1422 bit_index: usize = 0,1609 data.live_set.putAssumeCapacity(alive, {});
1423 tomb_bits: Bpi = 0,1610 // If the loop requires a branch death operand to be alive, it's not something we
1424 big_tomb_bits: u32 = 0,1611 // want to kill: its "last use" (i.e. the point at which it should die) is the loop
1425 big_tomb_bits_extra: std.ArrayListUnmanaged(u32) = .{},1612 // body itself.
14261613 _ = data.branch_deaths.remove(alive);
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 }
1449 }1614 }
1450 }
1451 }
14521615
1453 fn finish(et: *ExtraTombs) !void {1616 log.debug("[{}] %{}: block live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
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 }
14651617
1466 fn deinit(et: *ExtraTombs) void {1618 for (breaks) |block_inst| {
1467 et.big_tomb_bits_extra.deinit(et.analysis.gpa);1619 // We might break to this block, so include every operand that the block needs alive
1468 }1620 const block_scope = data.block_scopes.get(block_inst).?;
1469};
14701621
1471/// Remove any deaths invalidated by the deaths from an enclosing `loop`. Reshuffling deaths stored1622 var it = block_scope.live_set.keyIterator();
1472/// in `extra` causes it to become non-dense, but that's fine - we won't remove too much data.1623 while (it.next()) |key| {
1473/// Making it dense would be a lot more work - it'd require recomputing every index in `special`.1624 const alive = key.*;
1474fn removeDeaths(1625 try data.live_set.put(gpa, alive, {});
1475 a: *Analysis,1626 }
1476 to_remove: *std.AutoHashMapUnmanaged(Air.Inst.Index, void),1627 }
1477 body: []const Air.Inst.Index,1628
1478) error{OutOfMemory}!void {1629 try analyzeBody(a, pass, data, body);
1479 for (body) |inst| {1630 },
1480 try removeInstDeaths(a, to_remove, inst);
1481 }1631 }
1482}1632}
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(
1485 a: *Analysis,1638 a: *Analysis,
1486 to_remove: *std.AutoHashMapUnmanaged(Air.Inst.Index, void),1639 comptime pass: LivenessPass,
1640 data: *LivenessPassData(pass),
1487 inst: Air.Inst.Index,1641 inst: Air.Inst.Index,
1642 comptime inst_type: enum { cond_br, @"try", try_ptr },
1488) !void {1643) !void {
1489 const inst_tags = a.air.instructions.items(.tag);
1490 const inst_datas = a.air.instructions.items(.data);1644 const inst_datas = a.air.instructions.items(.data);
1645 const gpa = a.gpa;
14911646
1492 switch (inst_tags[inst]) {1647 const extra = switch (inst_type) {
1493 .add,1648 .cond_br => a.air.extraData(Air.CondBr, inst_datas[inst].pl_op.payload),
1494 .add_optimized,1649 .@"try" => a.air.extraData(Air.Try, inst_datas[inst].pl_op.payload),
1495 .addwrap,1650 .try_ptr => a.air.extraData(Air.TryPtr, inst_datas[inst].ty_pl.payload),
1496 .addwrap_optimized,1651 };
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 },
15571652
1558 .vector_store_elem => {1653 const condition = switch (inst_type) {
1559 const o = inst_datas[inst].vector_store_elem;1654 .cond_br, .@"try" => inst_datas[inst].pl_op.operand,
1560 const extra = a.air.extraData(Air.Bin, o.payload).data;1655 .try_ptr => extra.data.ptr,
1561 removeOperandDeaths(a, to_remove, inst, .{ o.vector_ptr, extra.lhs, extra.rhs });1656 };
1562 },
15631657
1564 .arg,1658 const then_body = switch (inst_type) {
1565 .alloc,1659 .cond_br => a.air.extra[extra.end..][0..extra.data.then_body_len],
1566 .ret_ptr,1660 else => {}, // we won't use this
1567 .constant,1661 };
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 => {},
15881662
1589 .not,1663 const else_body = switch (inst_type) {
1590 .bitcast,1664 .cond_br => a.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len],
1591 .load,1665 .@"try", .try_ptr => a.air.extra[extra.end..][0..extra.data.body_len],
1592 .fpext,1666 };
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 },
16341667
1635 .is_null,1668 switch (pass) {
1636 .is_non_null,1669 .loop_analysis => {
1637 .is_null_ptr,1670 switch (inst_type) {
1638 .is_non_null_ptr,1671 .cond_br => try analyzeBody(a, pass, data, then_body),
1639 .is_err,1672 .@"try", .try_ptr => {},
1640 .is_non_err,1673 }
1641 .is_err_ptr,1674 try analyzeBody(a, pass, data, else_body);
1642 .is_non_err_ptr,1675 },
1643 .ptrtoint,1676
1644 .bool_to_int,1677 .main_analysis => {
1645 .ret,1678 var then_info: ControlBranchInfo = switch (inst_type) {
1646 .ret_load,1679 .cond_br => try analyzeBodyResetBranch(a, pass, data, then_body),
1647 .is_named_enum_value,1680 .@"try", .try_ptr => blk: {
1648 .tag_name,1681 var branch_deaths = try data.branch_deaths.clone(gpa);
1649 .error_name,1682 errdefer branch_deaths.deinit(gpa);
1650 .sqrt,1683 var live_set = try data.live_set.clone(gpa);
1651 .sin,1684 errdefer live_set.deinit(gpa);
1652 .cos,1685 break :blk .{
1653 .tan,1686 .branch_deaths = branch_deaths,
1654 .exp,1687 .live_set = live_set,
1655 .exp2,1688 };
1656 .log,1689 },
1657 .log2,1690 };
1658 .log10,1691 defer then_info.branch_deaths.deinit(gpa);
1659 .fabs,1692 defer then_info.live_set.deinit(gpa);
1660 .floor,1693
1661 .ceil,1694 // If this is a `try`, the "then body" (rest of the branch) might have referenced our
1662 .round,1695 // result. If so, we want to avoid this value being considered live while analyzing the
1663 .trunc_float,1696 // else branch.
1664 .neg,1697 switch (inst_type) {
1665 .neg_optimized,1698 .cond_br => {},
1666 .cmp_lt_errors_len,1699 .@"try", .try_ptr => _ = data.live_set.remove(inst),
1667 .set_err_return_trace,1700 }
1668 .c_va_end,
1669 => {
1670 const operand = inst_datas[inst].un_op;
1671 removeOperandDeaths(a, to_remove, inst, .{ operand, .none, .none });
1672 },
16731701
1674 .add_with_overflow,1702 try analyzeBody(a, pass, data, else_body);
1675 .sub_with_overflow,1703 var else_info: ControlBranchInfo = .{
1676 .mul_with_overflow,1704 .branch_deaths = data.branch_deaths.move(),
1677 .shl_with_overflow,1705 .live_set = data.live_set.move(),
1678 .ptr_add,1706 };
1679 .ptr_sub,1707 defer else_info.branch_deaths.deinit(gpa);
1680 .ptr_elem_ptr,1708 defer else_info.live_set.deinit(gpa);
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 },
16881709
1689 .dbg_var_ptr,1710 // Any queued deaths shared between both branches can be queued for us instead
1690 .dbg_var_val,1711 {
1691 => {1712 var it = then_info.branch_deaths.keyIterator();
1692 const operand = inst_datas[inst].pl_op.operand;1713 while (it.next()) |key| {
1693 removeOperandDeaths(a, to_remove, inst, .{ operand, .none, .none });1714 const death = key.*;
1694 },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 => {1728 log.debug("[{}] %{}: remaining 'then' branch deaths are {}", .{ pass, inst, fmtInstSet(&then_info.branch_deaths) });
1697 const prefetch = inst_datas[inst].prefetch;1729 log.debug("[{}] %{}: remaining 'else' branch deaths are {}", .{ pass, inst, fmtInstSet(&else_info.branch_deaths) });
1698 removeOperandDeaths(a, to_remove, inst, .{ prefetch.ptr, .none, .none });
1699 },
17001730
1701 .call, .call_always_tail, .call_never_tail, .call_never_inline => {1731 // Deaths that occur in one branch but not another need to be made to occur at the start
1702 const inst_data = inst_datas[inst].pl_op;1732 // of the other branch.
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]);
17061733
1707 var death_remover = BigTombDeathRemover.init(a, to_remove, inst);1734 var then_mirrored_deaths: std.ArrayListUnmanaged(Air.Inst.Index) = .{};
1708 death_remover.feed(callee);1735 defer then_mirrored_deaths.deinit(gpa);
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]);
17361736
1737 var death_remover = BigTombDeathRemover.init(a, to_remove, inst);1737 var else_mirrored_deaths: std.ArrayListUnmanaged(Air.Inst.Index) = .{};
1738 for (elements) |elem| {1738 defer else_mirrored_deaths.deinit(gpa);
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 },
17801739
1781 .br => {1740 // Note: this invalidates `else_info.live_set`, but expands `then_info.live_set` to
1782 const br = inst_datas[inst].br;1741 // be their union
1783 removeOperandDeaths(a, to_remove, inst, .{ br.operand, .none, .none });1742 {
1784 },1743 var it = then_info.live_set.keyIterator();
1785 .assembly => {1744 while (it.next()) |key| {
1786 const extra = a.air.extraData(Air.Asm, inst_datas[inst].ty_pl.payload);1745 const death = key.*;
1787 var extra_i: usize = extra.end;1746 if (else_info.live_set.remove(death)) continue; // removing makes the loop below faster
1788 const outputs = @ptrCast([]const Air.Inst.Ref, a.air.extra[extra_i..][0..extra.data.outputs_len]);1747 if (else_info.branch_deaths.contains(death)) continue;
1789 extra_i += outputs.len;1748
1790 const inputs = @ptrCast([]const Air.Inst.Ref, a.air.extra[extra_i..][0..extra.data.inputs_len]);1749 // If this is a `try`, the "then body" (rest of the branch) might have
1791 extra_i += inputs.len;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);1757 try else_mirrored_deaths.append(gpa, death);
1794 for (outputs) |output| {1758 }
1795 if (output != .none) {1759 // Since we removed common stuff above, `else_info.live_set` is now only operands
1796 death_remover.feed(output);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, {});
1797 }1769 }
1798 }1770 }
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 {1772 log.debug("[{}] %{}: 'then' branch mirrored deaths are {}", .{ pass, inst, fmtInstList(then_mirrored_deaths.items) });
1814 try removeDeaths(a, to_remove, body);1773 log.debug("[{}] %{}: 'else' branch mirrored deaths are {}", .{ pass, inst, fmtInstList(else_mirrored_deaths.items) });
1815 return;
1816 };
18171774
1818 const death_count = a.extra.items[liveness_extra_idx];1775 data.live_set.deinit(gpa);
1819 var deaths = a.extra.items[liveness_extra_idx + 1 ..][0..death_count];1776 data.live_set = then_info.live_set.move();
18201777
1821 // Remove any deaths in `to_remove` from this loop's deaths1778 log.debug("[{}] %{}: new live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
1822 deaths.len = removeExtraDeaths(to_remove, deaths);
1823 a.extra.items[liveness_extra_idx] = @intCast(u32, deaths.len);
18241779
1825 // Temporarily add any deaths of ours to `to_remove`1780 // Write the branch deaths to `extra`
1826 try to_remove.ensureUnusedCapacity(a.gpa, @intCast(u32, deaths.len));1781 const then_death_count = then_info.branch_deaths.count() + @intCast(u32, then_mirrored_deaths.items.len);
1827 for (deaths) |d| {1782 const else_death_count = else_info.branch_deaths.count() + @intCast(u32, else_mirrored_deaths.items.len);
1828 to_remove.putAssumeCapacity(d, {});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.*);
1829 }1793 }
1830 try removeDeaths(a, to_remove, body);1794 a.extra.appendSliceAssumeCapacity(else_mirrored_deaths.items);
1831 for (deaths) |d| {1795 {
1832 _ = to_remove.remove(d);1796 var it = else_info.branch_deaths.keyIterator();
1797 while (it.next()) |key| a.extra.appendAssumeCapacity(key.*);
1833 }1798 }
1799 try a.special.put(gpa, inst, extra_index);
1834 },1800 },
1835 .@"try" => {1801 }
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 }
18741802
1875 try removeDeaths(a, to_remove, then_body);1803 try analyzeOperands(a, pass, data, inst, .{ condition, .none, .none });
1876 try removeDeaths(a, to_remove, else_body);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 }
1879 },1832 },
1880 .switch_br => {1833
1881 const pl_op = inst_datas[inst].pl_op;1834 .main_analysis => {
1882 const condition = pl_op.operand;1835 // This is, all in all, just a messier version of the `cond_br` logic. If you're trying
1883 const switch_br = a.air.extraData(Air.SwitchBr, pl_op.payload);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
1885 var air_extra_index: usize = switch_br.end;1850 var air_extra_index: usize = switch_br.end;
1886 for (0..switch_br.data.cases_len) |_| {1851 for (case_infos[0..ncases]) |*info| {
1887 const case = a.air.extraData(Air.SwitchBr.Case, air_extra_index);1852 const case = a.air.extraData(Air.SwitchBr.Case, air_extra_index);
1888 const case_body = a.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];1853 const case_body = a.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];
1889 air_extra_index = case.end + case.data.items_len + case_body.len;1854 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);
1891 }1856 }
1892 { // else1857 { // else
1893 const else_body = a.air.extra[air_extra_index..][0..switch_br.data.else_body_len];1858 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 };
1895 }1864 }
18961865
1897 if (a.special.get(inst)) |liveness_extra_idx| {1866 // Queued deaths common to all cases can be bubbled up
1898 const else_death_count = a.extra.items[liveness_extra_idx];1867 {
1899 var read_idx = liveness_extra_idx + 1;1868 // We can't remove from the set we're iterating over, so we'll store the shared deaths here
1900 var write_idx = read_idx; // write_idx <= read_idx always1869 // temporarily to remove them
1901 for (0..switch_br.data.cases_len) |_| {1870 var shared_deaths: DeathSet = .{};
1902 const case_death_count = a.extra.items[read_idx];1871 defer shared_deaths.deinit(gpa);
1903 const case_deaths = a.extra.items[read_idx + 1 ..][0..case_death_count];1872
1904 const new_death_count = removeExtraDeaths(to_remove, case_deaths);1873 var it = case_infos[0].branch_deaths.keyIterator();
1905 a.extra.items[write_idx] = new_death_count;1874 while (it.next()) |key| {
1906 if (write_idx < read_idx) {1875 const death = key.*;
1907 std.mem.copy(u32, a.extra.items[write_idx + 1 ..], a.extra.items[read_idx + 1 ..][0..new_death_count]);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);
1908 }1890 }
1909 read_idx += 1 + case_death_count;
1910 write_idx += 1 + new_death_count;
1911 }1891 }
1912 const else_deaths = a.extra.items[read_idx..][0..else_death_count];1892
1913 const new_else_death_count = removeExtraDeaths(to_remove, else_deaths);1893 for (case_infos, 0..) |*info, i| {
1914 a.extra.items[liveness_extra_idx] = new_else_death_count;1894 log.debug("[{}] %{}: case {} remaining branch deaths are {}", .{ pass, inst, i, fmtInstSet(&info.branch_deaths) });
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]);
1917 }1895 }
1918 }1896 }
19191897
1920 removeOperandDeaths(a, to_remove, inst, .{ condition, .none, .none });1898 const mirrored_deaths = try gpa.alloc(DeathList, ncases + 1);
1921 },1899 defer gpa.free(mirrored_deaths);
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}
19281900
1929fn removeOperandDeaths(1901 std.mem.set(DeathList, mirrored_deaths, .{});
1930 a: *Analysis,1902 defer for (mirrored_deaths) |*md| md.deinit(gpa);
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);
19361903
1937 const cur_tomb = @truncate(Bpi, a.tomb_bits[usize_index] >>1904 {
1938 @intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi));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| {1917 for (mirrored_deaths, case_infos) |*mirrored, *info| {
1943 const mask = @as(Bpi, 1) << @intCast(OperandInt, i);1918 var it = all_alive.keyIterator();
1944 const op_int = @enumToInt(op_ref);1919 while (it.next()) |key| {
1945 if (op_int < Air.Inst.Ref.typed_value_map.len) continue;1920 const alive = key.*;
1946 const operand: Air.Inst.Index = op_int - @intCast(u32, Air.Inst.Ref.typed_value_map.len);1921 if (!info.live_set.contains(alive) and !info.branch_deaths.contains(alive)) {
1947 if ((cur_tomb & mask) != 0 and to_remove.contains(operand)) {1922 // Should die at the start of this branch
1948 log.debug("remove death of %{} in %{}", .{ operand, inst });1923 try mirrored.append(gpa, alive);
1949 toggle_bits ^= mask;1924 }
1950 }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 },
1951 }1961 }
19521962
1953 a.tomb_bits[usize_index] ^= @as(usize, toggle_bits) <<1963 try analyzeOperands(a, pass, data, inst, .{ condition, .none, .none });
1954 @intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi);
1955}1964}
19561965
1957fn removeExtraDeaths(1966fn AnalyzeBigOperands(comptime pass: LivenessPass) type {
1958 to_remove: *const std.AutoHashMapUnmanaged(Air.Inst.Index, void),1967 return struct {
1959 deaths: []Air.Inst.Index,1968 a: *Analysis,
1960) u32 {1969 data: *LivenessPassData(pass),
1961 var new_len = @intCast(u32, deaths.len);1970 inst: Air.Inst.Index,
1962 var i: usize = 0;1971
1963 while (i < new_len) {1972 operands_remaining: u32,
1964 if (to_remove.contains(deaths[i])) {1973 small: [bpi - 1]Air.Inst.Ref = .{.none} ** (bpi - 1),
1965 log.debug("remove extra death of %{}", .{deaths[i]});1974 extra_tombs: []u32,
1966 deaths[i] = deaths[new_len - 1];1975
1967 new_len -= 1;1976 const Self = @This();
1968 } else {1977
1969 i += 1;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 };
1970 }2002 }
1971 }
1972 return new_len;
1973}
19742003
1975const BigTombDeathRemover = struct {2004 /// Must be called with operands in reverse order.
1976 a: *Analysis,2005 fn feed(big: *Self, op_ref: Air.Inst.Ref) !void {
1977 to_remove: *const std.AutoHashMapUnmanaged(Air.Inst.Index, void),2006 // Note that after this, `operands_remaining` becomes the index of the current operand
1978 inst: Air.Inst.Index,2007 big.operands_remaining -= 1;
19792008
1980 operands: [bpi - 1]Air.Inst.Ref = .{.none} ** (bpi - 1),2009 if (big.operands_remaining < bpi - 1) {
1981 next_oper: OperandInt = 0,2010 big.small[big.operands_remaining] = op_ref;
2011 return;
2012 }
19822013
1983 bit_index: u32 = 0,2014 const operand = Air.refToIndex(op_ref) orelse return;
1984 // Initialized once we finish the small tomb operands: see `feed`
1985 extra_start: u32 = undefined,
1986 extra_offset: u32 = 0,
19872015
1988 fn init(a: *Analysis, to_remove: *const std.AutoHashMapUnmanaged(Air.Inst.Index, void), inst: Air.Inst.Index) BigTombDeathRemover {2016 // Don't compute any liveness for constants
1989 return .{2017 const inst_tags = big.a.air.instructions.items(.tag);
1990 .a = a,2018 switch (inst_tags[operand]) {
1991 .to_remove = to_remove,2019 .constant, .const_ty => return,
1992 .inst = inst,2020 else => {},
1993 };2021 }
1994 }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 {2033 .main_analysis => {
1997 if (dr.next_oper < bpi - 1) {2034 if ((try big.data.live_set.fetchPut(gpa, operand, {})) == null) {
1998 dr.operands[dr.next_oper] = operand;2035 log.debug("[{}] %{}: added %{} to live set (operand dies here)", .{ pass, big.inst, operand });
1999 dr.next_oper += 1;2036 big.extra_tombs[extra_byte] |= @as(u32, 1) << extra_bit;
2000 if (dr.next_oper == bpi - 1) {2037 if (big.data.branch_deaths.remove(operand)) {
2001 removeOperandDeaths(dr.a, dr.to_remove, dr.inst, dr.operands);2038 log.debug("[{}] %{}: resolved branch death of %{} to this usage", .{ pass, big.inst, operand });
2002 if (dr.a.special.get(dr.inst)) |idx| dr.extra_start = idx;2039 }
2040 }
2041 },
2003 }2042 }
2004 return;
2005 }2043 }
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);2070 const extra_tombs = big.extra_tombs[0..num];
2010 if (op_int < Air.Inst.Ref.typed_value_map.len) return;
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) {2078 try analyzeOperands(big.a, pass, big.data, big.inst, big.small);
2015 dr.extra_offset += 1;
2016 }2079 }
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)) {2081 fn deinit(big: *Self) void {
2021 log.debug("remove big death of %{}", .{op_inst});2082 big.a.gpa.free(big.extra_tombs);
2022 dr.a.extra.items[dr.extra_start + dr.extra_offset] ^=2083 }
2023 (@as(u32, 1) << @intCast(u5, dr.bit_index - dr.extra_offset * 31));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.*});
2024 }2103 }
2025 }2104 }
2105};
2106
2107fn fmtInstList(list: []const Air.Inst.Index) FmtInstList {
2108 return .{ .list = list };
2109}
20262110
2027 fn finish(dr: *BigTombDeathRemover) void {2111const FmtInstList = struct {
2028 if (dr.next_oper < bpi) {2112 list: []const Air.Inst.Index,
2029 removeOperandDeaths(dr.a, dr.to_remove, dr.inst, dr.operands);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});
2030 }2122 }
2031 }2123 }
2032};2124};
src/arch/aarch64/CodeGen.zig-6
...@@ -5000,17 +5000,11 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {...@@ -5000,17 +5000,11 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
5000 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;5000 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
5001 const loop = self.air.extraData(Air.Block, ty_pl.payload);5001 const loop = self.air.extraData(Air.Block, ty_pl.payload);
5002 const body = self.air.extra[loop.end..][0..loop.data.body_len];5002 const body = self.air.extra[loop.end..][0..loop.data.body_len];
5003 const liveness_loop = self.liveness.getLoop(inst);
5004 const start_index = @intCast(u32, self.mir_instructions.len);5003 const start_index = @intCast(u32, self.mir_instructions.len);
50055004
5006 try self.genBody(body);5005 try self.genBody(body);
5007 try self.jump(start_index);5006 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
5014 return self.finishAirBookkeeping();5008 return self.finishAirBookkeeping();
5015}5009}
50165010
src/arch/arm/CodeGen.zig-6
...@@ -4923,17 +4923,11 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {...@@ -4923,17 +4923,11 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
4923 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;4923 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
4924 const loop = self.air.extraData(Air.Block, ty_pl.payload);4924 const loop = self.air.extraData(Air.Block, ty_pl.payload);
4925 const body = self.air.extra[loop.end..][0..loop.data.body_len];4925 const body = self.air.extra[loop.end..][0..loop.data.body_len];
4926 const liveness_loop = self.liveness.getLoop(inst);
4927 const start_index = @intCast(Mir.Inst.Index, self.mir_instructions.len);4926 const start_index = @intCast(Mir.Inst.Index, self.mir_instructions.len);
49284927
4929 try self.genBody(body);4928 try self.genBody(body);
4930 try self.jump(start_index);4929 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
4937 return self.finishAirBookkeeping();4931 return self.finishAirBookkeeping();
4938}4932}
49394933
src/arch/sparc64/CodeGen.zig-6
...@@ -1750,17 +1750,11 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {...@@ -1750,17 +1750,11 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
1750 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;1750 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
1751 const loop = self.air.extraData(Air.Block, ty_pl.payload);1751 const loop = self.air.extraData(Air.Block, ty_pl.payload);
1752 const body = self.air.extra[loop.end .. loop.end + loop.data.body_len];1752 const body = self.air.extra[loop.end .. loop.end + loop.data.body_len];
1753 const liveness_loop = self.liveness.getLoop(inst);
1754 const start = @intCast(u32, self.mir_instructions.len);1753 const start = @intCast(u32, self.mir_instructions.len);
17551754
1756 try self.genBody(body);1755 try self.genBody(body);
1757 try self.jump(start);1756 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
1764 return self.finishAirBookkeeping();1758 return self.finishAirBookkeeping();
1765}1759}
17661760
src/arch/wasm/CodeGen.zig-6
...@@ -3183,7 +3183,6 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3183,7 +3183,6 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3183 const ty_pl = func.air.instructions.items(.data)[inst].ty_pl;3183 const ty_pl = func.air.instructions.items(.data)[inst].ty_pl;
3184 const loop = func.air.extraData(Air.Block, ty_pl.payload);3184 const loop = func.air.extraData(Air.Block, ty_pl.payload);
3185 const body = func.air.extra[loop.end..][0..loop.data.body_len];3185 const body = func.air.extra[loop.end..][0..loop.data.body_len];
3186 const liveness_loop = func.liveness.getLoop(inst);
31873186
3188 // result type of loop is always 'noreturn', meaning we can always3187 // result type of loop is always 'noreturn', meaning we can always
3189 // emit the wasm type 'block_empty'.3188 // emit the wasm type 'block_empty'.
...@@ -3194,11 +3193,6 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3194,11 +3193,6 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3194 try func.addLabel(.br, 0);3193 try func.addLabel(.br, 0);
3195 try func.endBlock();3194 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
3202 func.finishAir(inst, .none, &.{});3196 func.finishAir(inst, .none, &.{});
3203}3197}
32043198
src/arch/x86_64/CodeGen.zig-6
...@@ -6439,7 +6439,6 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {...@@ -6439,7 +6439,6 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
6439 const loop = self.air.extraData(Air.Block, ty_pl.payload);6439 const loop = self.air.extraData(Air.Block, ty_pl.payload);
6440 const body = self.air.extra[loop.end..][0..loop.data.body_len];6440 const body = self.air.extra[loop.end..][0..loop.data.body_len];
6441 const jmp_target = @intCast(u32, self.mir_instructions.len);6441 const jmp_target = @intCast(u32, self.mir_instructions.len);
6442 const liveness_loop = self.liveness.getLoop(inst);
64436442
6444 {6443 {
6445 try self.branch_stack.append(.{});6444 try self.branch_stack.append(.{});
...@@ -6464,11 +6463,6 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {...@@ -6464,11 +6463,6 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
64646463
6465 _ = try self.asmJmpReloc(jmp_target);6464 _ = 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
6472 return self.finishAirBookkeeping();6466 return self.finishAirBookkeeping();
6473}6467}
64746468
src/codegen/c.zig-5
...@@ -4637,17 +4637,12 @@ fn airLoop(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -4637,17 +4637,12 @@ fn airLoop(f: *Function, inst: Air.Inst.Index) !CValue {
4637 const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;4637 const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
4638 const loop = f.air.extraData(Air.Block, ty_pl.payload);4638 const loop = f.air.extraData(Air.Block, ty_pl.payload);
4639 const body = f.air.extra[loop.end..][0..loop.data.body_len];4639 const body = f.air.extra[loop.end..][0..loop.data.body_len];
4640 const liveness_loop = f.liveness.getLoop(inst);
4641 const writer = f.object.writer();4640 const writer = f.object.writer();
46424641
4643 try writer.writeAll("for (;;) ");4642 try writer.writeAll("for (;;) ");
4644 try genBody(f, body);4643 try genBody(f, body);
4645 try writer.writeByte('\n');4644 try writer.writeByte('\n');
46464645
4647 for (liveness_loop.deaths) |operand| {
4648 try die(f, inst, Air.indexToRef(operand));
4649 }
4650
4651 return .none;4646 return .none;
4652}4647}
46534648
src/print_air.zig-9
...@@ -405,7 +405,6 @@ const Writer = struct {...@@ -405,7 +405,6 @@ const Writer = struct {
405 const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;405 const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
406 const extra = w.air.extraData(Air.Block, ty_pl.payload);406 const extra = w.air.extraData(Air.Block, ty_pl.payload);
407 const body = w.air.extra[extra.end..][0..extra.data.body_len];407 const body = w.air.extra[extra.end..][0..extra.data.body_len];
408 const liveness_loop = w.liveness.getLoop(inst);
409408
410 try w.writeType(s, w.air.getRefType(ty_pl.ty));409 try w.writeType(s, w.air.getRefType(ty_pl.ty));
411 if (w.skip_body) return s.writeAll(", ...");410 if (w.skip_body) return s.writeAll(", ...");
...@@ -413,14 +412,6 @@ const Writer = struct {...@@ -413,14 +412,6 @@ const Writer = struct {
413 const old_indent = w.indent;412 const old_indent = w.indent;
414 w.indent += 2;413 w.indent += 2;
415 try w.writeBody(s, body);414 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 }
424 w.indent = old_indent;415 w.indent = old_indent;
425 try s.writeByteNTimes(' ', w.indent);416 try s.writeByteNTimes(' ', w.indent);
426 try s.writeAll("}");417 try s.writeAll("}");