diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 6c0c807468..c654bef611 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -14,30 +14,35 @@ pub const Inst = struct { tag: Tag, /// Each bit represents the index of an `Inst` parameter in the `args` field. /// If a bit is set, it marks the end of the lifetime of the corresponding - /// instruction parameter. For example, 0b000_00101 means that the first and + /// instruction parameter. For example, 0b101 means that the first and /// third `Inst` parameters' lifetimes end after this instruction, and will /// not have any more following references. /// The most significant bit being set means that the instruction itself is /// never referenced, in other words its lifetime ends as soon as it finishes. - /// If bit 7 (0b1xxx_xxxx) is set, it means this instruction itself is unreferenced. - /// If bit 6 (0bx1xx_xxxx) is set, it means this is a special case and the + /// If bit 15 (0b1xxx_xxxx_xxxx_xxxx) is set, it means this instruction itself is unreferenced. + /// If bit 14 (0bx1xx_xxxx_xxxx_xxxx) is set, it means this is a special case and the /// lifetimes of operands are encoded elsewhere. - deaths: u8 = undefined, + deaths: DeathsInt = undefined, ty: Type, /// Byte offset into the source. src: usize, + pub const DeathsInt = u16; + pub const DeathsBitIndex = std.math.Log2Int(DeathsInt); + pub const unreferenced_bit_index = @typeInfo(DeathsInt).Int.bits - 1; + pub const deaths_bits = unreferenced_bit_index - 1; + pub fn isUnused(self: Inst) bool { - return (self.deaths & 0b1000_0000) != 0; + return (self.deaths & (1 << unreferenced_bit_index)) != 0; } - pub fn operandDies(self: Inst, index: u3) bool { - assert(index < 6); + pub fn operandDies(self: Inst, index: DeathsBitIndex) bool { + assert(index < deaths_bits); return @truncate(u1, self.deaths << index) != 0; } pub fn specialOperandDeaths(self: Inst) bool { - return (self.deaths & 0b1000_0000) != 0; + return (self.deaths & (1 << deaths_bits)) != 0; } pub const Tag = enum { diff --git a/src-self-hosted/liveness.zig b/src-self-hosted/liveness.zig index 797a55a80c..a06a4dd1d1 100644 --- a/src-self-hosted/liveness.zig +++ b/src-self-hosted/liveness.zig @@ -34,7 +34,7 @@ fn analyzeInstGeneric(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Ins inline for (std.meta.declarations(ir.Inst)) |decl| { switch (decl.data) { .Type => |T| { - if (@hasDecl(T, "base_tag")) { + if (@typeInfo(T) == .Struct and @hasDecl(T, "base_tag")) { if (T.base_tag == base.tag) { return analyzeInst(arena, table, T, @fieldParentPtr(T, "base", base)); } @@ -47,7 +47,13 @@ fn analyzeInstGeneric(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Ins } fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), comptime T: type, inst: *T) error{OutOfMemory}!void { - inst.base.deaths = 0; + if (table.contains(&inst.base)) { + inst.base.deaths = 0; + } else { + // No tombstone for this instruction means it is never referenced, + // and its birth marks its own death. Very metal 🤘 + inst.base.deaths = 1 << ir.Inst.unreferenced_bit_index; + } switch (T) { ir.Inst.Constant => return, @@ -106,15 +112,28 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void // instruction, and the deaths flag for the CondBr instruction will indicate whether the // condition's lifetime ends immediately before entering any branch. }, + ir.Inst.Call => { + // Call instructions have a runtime-known number of operands so we have to handle them ourselves here. + const needed_bits = 1 + inst.args.args.len; + if (needed_bits <= ir.Inst.deaths_bits) { + var bit_i: ir.Inst.DeathsBitIndex = 0; + { + const prev = try table.fetchPut(inst.args.func, {}); + if (prev == null) inst.base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i; + bit_i += 1; + } + for (inst.args.args) |arg| { + const prev = try table.fetchPut(arg, {}); + if (prev == null) inst.base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i; + bit_i += 1; + } + } else { + @panic("Handle liveness analysis for function calls with many parameters"); + } + }, else => {}, } - if (!table.contains(&inst.base)) { - // No tombstone for this instruction means it is never referenced, - // and its birth marks its own death. Very metal 🤘 - inst.base.deaths |= 1 << 7; - } - const Args = ir.Inst.Args(T); if (Args == void) { return;