authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-05-11 11:42:55+01:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2023-05-12 00:01:52+03:00
log214f2fc395da2e6e5ffa592b89ea20db47d3fca6
tree781db1654157a66fc0e3841011a34f434e33b80a
parent6c41d435194d1777e1a2e50879c8d293a1f1e65f

Liveness: simplify logic

`branch_deaths` was a relic from before I had a full understanding of AIR's control flow structure, and so was unnecessary. This change simplifies Liveness, fixes a bug exposed by #15235, and likely improves performance (due to cloning hashmaps less often).

1 files changed, 45 insertions(+), 241 deletions(-)

src/Liveness.zig+45-241
......@@ -103,16 +103,8 @@ fn LivenessPassData(comptime pass: LivenessPass) type {
103103 /// Every `block` currently under analysis.
104104 block_scopes: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockScope) = .{},
105105
106 /// The set of deaths which should be made to occur at the earliest possible point in
107 /// this control flow branch. These instructions die when they are last referenced in
108 /// the current branch; if unreferenced, they die at the start of the branch. Populated
109 /// when a `br` instruction is reached. If deaths are common to all branches of control
110 /// flow, they may be bubbled up to the parent branch.
111 branch_deaths: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
112
113 /// The set of instructions currently alive. Instructions which must die in this branch
114 /// (i.e. those in `branch_deaths`) are not in this set, because they must die before
115 /// this point.
106 /// The set of instructions currently alive in the current control
107 /// flow branch.
116108 live_set: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
117109
118110 /// The extra data initialized by the `loop_analysis` pass for this pass to consume.
......@@ -130,7 +122,6 @@ fn LivenessPassData(comptime pass: LivenessPass) type {
130122 block.live_set.deinit(gpa);
131123 }
132124 self.block_scopes.deinit(gpa);
133 self.branch_deaths.deinit(gpa);
134125 self.live_set.deinit(gpa);
135126 self.old_extra.deinit(gpa);
136127 }
......@@ -172,7 +163,7 @@ pub fn analyze(gpa: Allocator, air: Air) Allocator.Error!Liveness {
172163 data.old_extra = a.extra;
173164 a.extra = .{};
174165 try analyzeBody(&a, .main_analysis, &data, main_body);
175 assert(data.branch_deaths.count() == 0);
166 assert(data.live_set.count() == 0);
176167 }
177168
178169 return .{
......@@ -870,47 +861,6 @@ fn analyzeBody(
870861 }
871862}
872863
873const ControlBranchInfo = struct {
874 branch_deaths: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
875 live_set: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
876};
877
878/// Helper function for running `analyzeBody`, but resetting `branch_deaths` and `live_set` to their
879/// original states before returning, returning the modified versions of them. Only makes sense in
880/// the `main_analysis` pass.
881fn analyzeBodyResetBranch(
882 a: *Analysis,
883 comptime pass: LivenessPass,
884 data: *LivenessPassData(pass),
885 body: []const Air.Inst.Index,
886) !ControlBranchInfo {
887 switch (pass) {
888 .main_analysis => {},
889 else => @compileError("Liveness.analyzeBodyResetBranch only makes sense in LivenessPass.main_analysis"),
890 }
891
892 const gpa = a.gpa;
893
894 const old_branch_deaths = try data.branch_deaths.clone(a.gpa);
895 defer {
896 data.branch_deaths.deinit(gpa);
897 data.branch_deaths = old_branch_deaths;
898 }
899
900 const old_live_set = try data.live_set.clone(a.gpa);
901 defer {
902 data.live_set.deinit(gpa);
903 data.live_set = old_live_set;
904 }
905
906 try analyzeBody(a, pass, data, body);
907
908 return .{
909 .branch_deaths = data.branch_deaths.move(),
910 .live_set = data.live_set.move(),
911 };
912}
913
914864fn analyzeInst(
915865 a: *Analysis,
916866 comptime pass: LivenessPass,
......@@ -1325,17 +1275,13 @@ fn analyzeOperands(
13251275 const usize_index = (inst * bpi) / @bitSizeOf(usize);
13261276
13271277 // This logic must synchronize with `will_die_immediately` in `AnalyzeBigOperands.init`.
1328 var immediate_death = false;
1329 if (data.branch_deaths.remove(inst)) {
1330 log.debug("[{}] %{}: resolved branch death to birth (immediate death)", .{ pass, inst });
1331 immediate_death = true;
1332 assert(!data.live_set.contains(inst));
1333 } else if (data.live_set.remove(inst)) {
1278 const immediate_death = if (data.live_set.remove(inst)) blk: {
13341279 log.debug("[{}] %{}: removed from live set", .{ pass, inst });
1335 } else {
1280 break :blk false;
1281 } else blk: {
13361282 log.debug("[{}] %{}: immediate death", .{ pass, inst });
1337 immediate_death = true;
1338 }
1283 break :blk true;
1284 };
13391285
13401286 var tomb_bits: Bpi = @as(Bpi, @boolToInt(immediate_death)) << (bpi - 1);
13411287
......@@ -1362,9 +1308,6 @@ fn analyzeOperands(
13621308 if ((try data.live_set.fetchPut(gpa, operand, {})) == null) {
13631309 log.debug("[{}] %{}: added %{} to live set (operand dies here)", .{ pass, inst, operand });
13641310 tomb_bits |= mask;
1365 if (data.branch_deaths.remove(operand)) {
1366 log.debug("[{}] %{}: resolved branch death of %{} to this usage", .{ pass, inst, operand });
1367 }
13681311 }
13691312 }
13701313 }
......@@ -1391,17 +1334,6 @@ fn analyzeFuncEnd(
13911334 },
13921335
13931336 .main_analysis => {
1394 const gpa = a.gpa;
1395
1396 // Note that we preserve previous branch deaths - anything that needs to die in our
1397 // "parent" branch also needs to die for us.
1398
1399 try data.branch_deaths.ensureUnusedCapacity(gpa, data.live_set.count());
1400 var it = data.live_set.keyIterator();
1401 while (it.next()) |key| {
1402 const alive = key.*;
1403 data.branch_deaths.putAssumeCapacity(alive, {});
1404 }
14051337 data.live_set.clearRetainingCapacity();
14061338 },
14071339 }
......@@ -1427,26 +1359,6 @@ fn analyzeInstBr(
14271359 .main_analysis => {
14281360 const block_scope = data.block_scopes.get(br.block_inst).?; // we should always be breaking from an enclosing block
14291361
1430 // We mostly preserve previous branch deaths - anything that should die for our
1431 // enclosing branch should die for us too. However, if our break target requires such an
1432 // operand to be alive, it's actually not something we want to kill, since its "last
1433 // use" (i.e. the point at which it should die) is outside of our scope.
1434 var it = block_scope.live_set.keyIterator();
1435 while (it.next()) |key| {
1436 const alive = key.*;
1437 _ = data.branch_deaths.remove(alive);
1438 }
1439 log.debug("[{}] %{}: preserved branch deaths are {}", .{ pass, inst, fmtInstSet(&data.branch_deaths) });
1440
1441 // Anything that's currently alive but our target doesn't need becomes a branch death.
1442 it = data.live_set.keyIterator();
1443 while (it.next()) |key| {
1444 const alive = key.*;
1445 if (!block_scope.live_set.contains(alive)) {
1446 _ = try data.branch_deaths.put(gpa, alive, {});
1447 log.debug("[{}] %{}: added branch death of {}", .{ pass, inst, alive });
1448 }
1449 }
14501362 const new_live_set = try block_scope.live_set.clone(gpa);
14511363 data.live_set.deinit(gpa);
14521364 data.live_set = new_live_set;
......@@ -1607,10 +1519,6 @@ fn analyzeInstLoop(
16071519 try data.live_set.ensureUnusedCapacity(gpa, @intCast(u32, loop_live.len));
16081520 for (loop_live) |alive| {
16091521 data.live_set.putAssumeCapacity(alive, {});
1610 // If the loop requires a branch death operand to be alive, it's not something we
1611 // want to kill: its "last use" (i.e. the point at which it should die) is the loop
1612 // body itself.
1613 _ = data.branch_deaths.remove(alive);
16141522 }
16151523
16161524 log.debug("[{}] %{}: block live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
......@@ -1675,61 +1583,19 @@ fn analyzeInstCondBr(
16751583 },
16761584
16771585 .main_analysis => {
1678 var then_info: ControlBranchInfo = switch (inst_type) {
1679 .cond_br => try analyzeBodyResetBranch(a, pass, data, then_body),
1680 .@"try", .try_ptr => blk: {
1681 var branch_deaths = try data.branch_deaths.clone(gpa);
1682 errdefer branch_deaths.deinit(gpa);
1683 var live_set = try data.live_set.clone(gpa);
1684 errdefer live_set.deinit(gpa);
1685 break :blk .{
1686 .branch_deaths = branch_deaths,
1687 .live_set = live_set,
1688 };
1689 },
1690 };
1691 defer then_info.branch_deaths.deinit(gpa);
1692 defer then_info.live_set.deinit(gpa);
1693
1694 // If this is a `try`, the "then body" (rest of the branch) might have referenced our
1695 // result. If so, we want to avoid this value being considered live while analyzing the
1696 // else branch.
16971586 switch (inst_type) {
1698 .cond_br => {},
1699 .@"try", .try_ptr => _ = data.live_set.remove(inst),
1587 .cond_br => try analyzeBody(a, pass, data, then_body),
1588 .@"try", .try_ptr => {}, // The "then body" is just the remainder of this block
17001589 }
1590 var then_live = data.live_set.move();
1591 defer then_live.deinit(gpa);
17011592
17021593 try analyzeBody(a, pass, data, else_body);
1703 var else_info: ControlBranchInfo = .{
1704 .branch_deaths = data.branch_deaths.move(),
1705 .live_set = data.live_set.move(),
1706 };
1707 defer else_info.branch_deaths.deinit(gpa);
1708 defer else_info.live_set.deinit(gpa);
1594 var else_live = data.live_set.move();
1595 defer else_live.deinit(gpa);
17091596
1710 // Any queued deaths shared between both branches can be queued for us instead
1711 {
1712 var it = then_info.branch_deaths.keyIterator();
1713 while (it.next()) |key| {
1714 const death = key.*;
1715 if (else_info.branch_deaths.remove(death)) {
1716 // We'll remove it from then_deaths below
1717 try data.branch_deaths.put(gpa, death, {});
1718 }
1719 }
1720 log.debug("[{}] %{}: bubbled deaths {}", .{ pass, inst, fmtInstSet(&data.branch_deaths) });
1721 it = data.branch_deaths.keyIterator();
1722 while (it.next()) |key| {
1723 const death = key.*;
1724 assert(then_info.branch_deaths.remove(death));
1725 }
1726 }
1727
1728 log.debug("[{}] %{}: remaining 'then' branch deaths are {}", .{ pass, inst, fmtInstSet(&then_info.branch_deaths) });
1729 log.debug("[{}] %{}: remaining 'else' branch deaths are {}", .{ pass, inst, fmtInstSet(&else_info.branch_deaths) });
1730
1731 // Deaths that occur in one branch but not another need to be made to occur at the start
1732 // of the other branch.
1597 // Operands which are alive in one branch but not the other need to die at the start of
1598 // the peer branch.
17331599
17341600 var then_mirrored_deaths: std.ArrayListUnmanaged(Air.Inst.Index) = .{};
17351601 defer then_mirrored_deaths.deinit(gpa);
......@@ -1737,14 +1603,12 @@ fn analyzeInstCondBr(
17371603 var else_mirrored_deaths: std.ArrayListUnmanaged(Air.Inst.Index) = .{};
17381604 defer else_mirrored_deaths.deinit(gpa);
17391605
1740 // Note: this invalidates `else_info.live_set`, but expands `then_info.live_set` to
1741 // be their union
1606 // Note: this invalidates `else_live`, but expands `then_live` to be their union
17421607 {
1743 var it = then_info.live_set.keyIterator();
1608 var it = then_live.keyIterator();
17441609 while (it.next()) |key| {
17451610 const death = key.*;
1746 if (else_info.live_set.remove(death)) continue; // removing makes the loop below faster
1747 if (else_info.branch_deaths.contains(death)) continue;
1611 if (else_live.remove(death)) continue; // removing makes the loop below faster
17481612
17491613 // If this is a `try`, the "then body" (rest of the branch) might have
17501614 // referenced our result. We want to avoid killing this value in the else branch
......@@ -1756,16 +1620,14 @@ fn analyzeInstCondBr(
17561620
17571621 try else_mirrored_deaths.append(gpa, death);
17581622 }
1759 // Since we removed common stuff above, `else_info.live_set` is now only operands
1623 // Since we removed common stuff above, `else_live` is now only operands
17601624 // which are *only* alive in the else branch
1761 it = else_info.live_set.keyIterator();
1625 it = else_live.keyIterator();
17621626 while (it.next()) |key| {
17631627 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, {});
1628 try then_mirrored_deaths.append(gpa, death);
1629 // Make `then_live` contain the full live set (i.e. union of both)
1630 try then_live.put(gpa, death, {});
17691631 }
17701632 }
17711633
......@@ -1773,29 +1635,20 @@ fn analyzeInstCondBr(
17731635 log.debug("[{}] %{}: 'else' branch mirrored deaths are {}", .{ pass, inst, fmtInstList(else_mirrored_deaths.items) });
17741636
17751637 data.live_set.deinit(gpa);
1776 data.live_set = then_info.live_set.move();
1638 data.live_set = then_live.move(); // Really the union of both live sets
17771639
17781640 log.debug("[{}] %{}: new live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
17791641
1780 // Write the branch deaths to `extra`
1781 const then_death_count = then_info.branch_deaths.count() + @intCast(u32, then_mirrored_deaths.items.len);
1782 const else_death_count = else_info.branch_deaths.count() + @intCast(u32, else_mirrored_deaths.items.len);
1783
1642 // Write the mirrored deaths to `extra`
1643 const then_death_count = @intCast(u32, then_mirrored_deaths.items.len);
1644 const else_death_count = @intCast(u32, else_mirrored_deaths.items.len);
17841645 try a.extra.ensureUnusedCapacity(gpa, std.meta.fields(CondBr).len + then_death_count + else_death_count);
17851646 const extra_index = a.addExtraAssumeCapacity(CondBr{
17861647 .then_death_count = then_death_count,
17871648 .else_death_count = else_death_count,
17881649 });
17891650 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.*);
1793 }
17941651 a.extra.appendSliceAssumeCapacity(else_mirrored_deaths.items);
1795 {
1796 var it = else_info.branch_deaths.keyIterator();
1797 while (it.next()) |key| a.extra.appendAssumeCapacity(key.*);
1798 }
17991652 try a.special.put(gpa, inst, extra_index);
18001653 },
18011654 }
......@@ -1838,61 +1691,24 @@ fn analyzeInstSwitchBr(
18381691 const DeathSet = std.AutoHashMapUnmanaged(Air.Inst.Index, void);
18391692 const DeathList = std.ArrayListUnmanaged(Air.Inst.Index);
18401693
1841 var case_infos = try gpa.alloc(ControlBranchInfo, ncases + 1); // +1 for else
1842 defer gpa.free(case_infos);
1694 var case_live_sets = try gpa.alloc(std.AutoHashMapUnmanaged(Air.Inst.Index, void), ncases + 1); // +1 for else
1695 defer gpa.free(case_live_sets);
18431696
1844 @memset(case_infos, .{});
1845 defer for (case_infos) |*info| {
1846 info.branch_deaths.deinit(gpa);
1847 info.live_set.deinit(gpa);
1848 };
1697 @memset(case_live_sets, .{});
1698 defer for (case_live_sets) |*live_set| live_set.deinit(gpa);
18491699
18501700 var air_extra_index: usize = switch_br.end;
1851 for (case_infos[0..ncases]) |*info| {
1701 for (case_live_sets[0..ncases]) |*live_set| {
18521702 const case = a.air.extraData(Air.SwitchBr.Case, air_extra_index);
18531703 const case_body = a.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];
18541704 air_extra_index = case.end + case.data.items_len + case_body.len;
1855 info.* = try analyzeBodyResetBranch(a, pass, data, case_body);
1705 try analyzeBody(a, pass, data, case_body);
1706 live_set.* = data.live_set.move();
18561707 }
18571708 { // else
18581709 const else_body = a.air.extra[air_extra_index..][0..switch_br.data.else_body_len];
18591710 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 };
1864 }
1865
1866 // Queued deaths common to all cases can be bubbled up
1867 {
1868 // We can't remove from the set we're iterating over, so we'll store the shared deaths here
1869 // temporarily to remove them
1870 var shared_deaths: DeathSet = .{};
1871 defer shared_deaths.deinit(gpa);
1872
1873 var it = case_infos[0].branch_deaths.keyIterator();
1874 while (it.next()) |key| {
1875 const death = key.*;
1876 for (case_infos[1..]) |*info| {
1877 if (!info.branch_deaths.contains(death)) break;
1878 } else try shared_deaths.put(gpa, death, {});
1879 }
1880
1881 log.debug("[{}] %{}: bubbled deaths {}", .{ pass, inst, fmtInstSet(&shared_deaths) });
1882
1883 try data.branch_deaths.ensureUnusedCapacity(gpa, shared_deaths.count());
1884 it = shared_deaths.keyIterator();
1885 while (it.next()) |key| {
1886 const death = key.*;
1887 data.branch_deaths.putAssumeCapacity(death, {});
1888 for (case_infos) |*info| {
1889 _ = info.branch_deaths.remove(death);
1890 }
1891 }
1892
1893 for (case_infos, 0..) |*info, i| {
1894 log.debug("[{}] %{}: case {} remaining branch deaths are {}", .{ pass, inst, i, fmtInstSet(&info.branch_deaths) });
1895 }
1711 case_live_sets[ncases] = data.live_set.move();
18961712 }
18971713
18981714 const mirrored_deaths = try gpa.alloc(DeathList, ncases + 1);
......@@ -1905,20 +1721,20 @@ fn analyzeInstSwitchBr(
19051721 var all_alive: DeathSet = .{};
19061722 defer all_alive.deinit(gpa);
19071723
1908 for (case_infos) |*info| {
1909 try all_alive.ensureUnusedCapacity(gpa, info.live_set.count());
1910 var it = info.live_set.keyIterator();
1724 for (case_live_sets) |*live_set| {
1725 try all_alive.ensureUnusedCapacity(gpa, live_set.count());
1726 var it = live_set.keyIterator();
19111727 while (it.next()) |key| {
19121728 const alive = key.*;
19131729 all_alive.putAssumeCapacity(alive, {});
19141730 }
19151731 }
19161732
1917 for (mirrored_deaths, case_infos) |*mirrored, *info| {
1733 for (mirrored_deaths, case_live_sets) |*mirrored, *live_set| {
19181734 var it = all_alive.keyIterator();
19191735 while (it.next()) |key| {
19201736 const alive = key.*;
1921 if (!info.live_set.contains(alive) and !info.branch_deaths.contains(alive)) {
1737 if (!live_set.contains(alive)) {
19221738 // Should die at the start of this branch
19231739 try mirrored.append(gpa, alive);
19241740 }
......@@ -1935,27 +1751,18 @@ fn analyzeInstSwitchBr(
19351751 log.debug("[{}] %{}: new live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
19361752 }
19371753
1938 const else_death_count = case_infos[ncases].branch_deaths.count() + @intCast(u32, mirrored_deaths[ncases].items.len);
1939
1754 const else_death_count = @intCast(u32, mirrored_deaths[ncases].items.len);
19401755 const extra_index = try a.addExtra(SwitchBr{
19411756 .else_death_count = else_death_count,
19421757 });
1943 for (mirrored_deaths[0..ncases], case_infos[0..ncases]) |mirrored, info| {
1944 const num = info.branch_deaths.count() + @intCast(u32, mirrored.items.len);
1758 for (mirrored_deaths[0..ncases]) |mirrored| {
1759 const num = @intCast(u32, mirrored.items.len);
19451760 try a.extra.ensureUnusedCapacity(gpa, num + 1);
19461761 a.extra.appendAssumeCapacity(num);
19471762 a.extra.appendSliceAssumeCapacity(mirrored.items);
1948 {
1949 var it = info.branch_deaths.keyIterator();
1950 while (it.next()) |key| a.extra.appendAssumeCapacity(key.*);
1951 }
19521763 }
19531764 try a.extra.ensureUnusedCapacity(gpa, else_death_count);
19541765 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 }
19591766 try a.special.put(gpa, inst, extra_index);
19601767 },
19611768 }
......@@ -1997,7 +1804,7 @@ fn AnalyzeBigOperands(comptime pass: LivenessPass) type {
19971804
19981805 const will_die_immediately: bool = switch (pass) {
19991806 .loop_analysis => false, // track everything, since we don't have full liveness information yet
2000 .main_analysis => data.branch_deaths.contains(inst) and !data.live_set.contains(inst),
1807 .main_analysis => !data.live_set.contains(inst),
20011808 };
20021809
20031810 return .{
......@@ -2048,9 +1855,6 @@ fn AnalyzeBigOperands(comptime pass: LivenessPass) type {
20481855 if ((try big.data.live_set.fetchPut(gpa, operand, {})) == null) {
20491856 log.debug("[{}] %{}: added %{} to live set (operand dies here)", .{ pass, big.inst, operand });
20501857 big.extra_tombs[extra_byte] |= @as(u32, 1) << extra_bit;
2051 if (big.data.branch_deaths.remove(operand)) {
2052 log.debug("[{}] %{}: resolved branch death of %{} to this usage", .{ pass, big.inst, operand });
2053 }
20541858 }
20551859 },
20561860 }