| author | |
| committer | |
| log | 135580c1621513e7cfeed8098c087d1d6941fa97 |
| tree | 1a27c301ae4f98002cb9a54ff6d28d5f772ca742 |
| parent | 5da5ded74326fce0ca52c6cb00f22824f7beb7eb |
2 files changed, 40 insertions(+), 16 deletions(-)
src-self-hosted/ir.zig+13-8| ... | ... | @@ -14,30 +14,35 @@ pub const Inst = struct { |
| 14 | 14 | tag: Tag, |
| 15 | 15 | /// Each bit represents the index of an `Inst` parameter in the `args` field. |
| 16 | 16 | /// If a bit is set, it marks the end of the lifetime of the corresponding |
| 17 | /// instruction parameter. For example, 0b000_00101 means that the first and | |
| 17 | /// instruction parameter. For example, 0b101 means that the first and | |
| 18 | 18 | /// third `Inst` parameters' lifetimes end after this instruction, and will |
| 19 | 19 | /// not have any more following references. |
| 20 | 20 | /// The most significant bit being set means that the instruction itself is |
| 21 | 21 | /// never referenced, in other words its lifetime ends as soon as it finishes. |
| 22 | /// If bit 7 (0b1xxx_xxxx) is set, it means this instruction itself is unreferenced. | |
| 23 | /// If bit 6 (0bx1xx_xxxx) is set, it means this is a special case and the | |
| 22 | /// If bit 15 (0b1xxx_xxxx_xxxx_xxxx) is set, it means this instruction itself is unreferenced. | |
| 23 | /// If bit 14 (0bx1xx_xxxx_xxxx_xxxx) is set, it means this is a special case and the | |
| 24 | 24 | /// lifetimes of operands are encoded elsewhere. |
| 25 | deaths: u8 = undefined, | |
| 25 | deaths: DeathsInt = undefined, | |
| 26 | 26 | ty: Type, |
| 27 | 27 | /// Byte offset into the source. |
| 28 | 28 | src: usize, |
| 29 | 29 | |
| 30 | pub const DeathsInt = u16; | |
| 31 | pub const DeathsBitIndex = std.math.Log2Int(DeathsInt); | |
| 32 | pub const unreferenced_bit_index = @typeInfo(DeathsInt).Int.bits - 1; | |
| 33 | pub const deaths_bits = unreferenced_bit_index - 1; | |
| 34 | ||
| 30 | 35 | pub fn isUnused(self: Inst) bool { |
| 31 | return (self.deaths & 0b1000_0000) != 0; | |
| 36 | return (self.deaths & (1 << unreferenced_bit_index)) != 0; | |
| 32 | 37 | } |
| 33 | 38 | |
| 34 | pub fn operandDies(self: Inst, index: u3) bool { | |
| 35 | assert(index < 6); | |
| 39 | pub fn operandDies(self: Inst, index: DeathsBitIndex) bool { | |
| 40 | assert(index < deaths_bits); | |
| 36 | 41 | return @truncate(u1, self.deaths << index) != 0; |
| 37 | 42 | } |
| 38 | 43 | |
| 39 | 44 | pub fn specialOperandDeaths(self: Inst) bool { |
| 40 | return (self.deaths & 0b1000_0000) != 0; | |
| 45 | return (self.deaths & (1 << deaths_bits)) != 0; | |
| 41 | 46 | } |
| 42 | 47 | |
| 43 | 48 | pub const Tag = enum { |
src-self-hosted/liveness.zig+27-8| ... | ... | @@ -34,7 +34,7 @@ fn analyzeInstGeneric(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Ins |
| 34 | 34 | inline for (std.meta.declarations(ir.Inst)) |decl| { |
| 35 | 35 | switch (decl.data) { |
| 36 | 36 | .Type => |T| { |
| 37 | if (@hasDecl(T, "base_tag")) { | |
| 37 | if (@typeInfo(T) == .Struct and @hasDecl(T, "base_tag")) { | |
| 38 | 38 | if (T.base_tag == base.tag) { |
| 39 | 39 | return analyzeInst(arena, table, T, @fieldParentPtr(T, "base", base)); |
| 40 | 40 | } |
| ... | ... | @@ -47,7 +47,13 @@ fn analyzeInstGeneric(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Ins |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | 49 | fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), comptime T: type, inst: *T) error{OutOfMemory}!void { |
| 50 | inst.base.deaths = 0; | |
| 50 | if (table.contains(&inst.base)) { | |
| 51 | inst.base.deaths = 0; | |
| 52 | } else { | |
| 53 | // No tombstone for this instruction means it is never referenced, | |
| 54 | // and its birth marks its own death. Very metal 🤘 | |
| 55 | inst.base.deaths = 1 << ir.Inst.unreferenced_bit_index; | |
| 56 | } | |
| 51 | 57 | |
| 52 | 58 | switch (T) { |
| 53 | 59 | ir.Inst.Constant => return, |
| ... | ... | @@ -106,15 +112,28 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void |
| 106 | 112 | // instruction, and the deaths flag for the CondBr instruction will indicate whether the |
| 107 | 113 | // condition's lifetime ends immediately before entering any branch. |
| 108 | 114 | }, |
| 115 | ir.Inst.Call => { | |
| 116 | // Call instructions have a runtime-known number of operands so we have to handle them ourselves here. | |
| 117 | const needed_bits = 1 + inst.args.args.len; | |
| 118 | if (needed_bits <= ir.Inst.deaths_bits) { | |
| 119 | var bit_i: ir.Inst.DeathsBitIndex = 0; | |
| 120 | { | |
| 121 | const prev = try table.fetchPut(inst.args.func, {}); | |
| 122 | if (prev == null) inst.base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i; | |
| 123 | bit_i += 1; | |
| 124 | } | |
| 125 | for (inst.args.args) |arg| { | |
| 126 | const prev = try table.fetchPut(arg, {}); | |
| 127 | if (prev == null) inst.base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i; | |
| 128 | bit_i += 1; | |
| 129 | } | |
| 130 | } else { | |
| 131 | @panic("Handle liveness analysis for function calls with many parameters"); | |
| 132 | } | |
| 133 | }, | |
| 109 | 134 | else => {}, |
| 110 | 135 | } |
| 111 | 136 | |
| 112 | if (!table.contains(&inst.base)) { | |
| 113 | // No tombstone for this instruction means it is never referenced, | |
| 114 | // and its birth marks its own death. Very metal 🤘 | |
| 115 | inst.base.deaths |= 1 << 7; | |
| 116 | } | |
| 117 | ||
| 118 | 137 | const Args = ir.Inst.Args(T); |
| 119 | 138 | if (Args == void) { |
| 120 | 139 | return; |