zig/src-self-hosted/liveness.zig
Andrew Kelley ef91b11295 stage2: register allocator processes operand deaths
also rework the IR data structures
2020-07-20 13:12:20 -07:00

119 lines
5.1 KiB
Zig

const std = @import("std");
const ir = @import("ir.zig");
const trace = @import("tracy.zig").trace;
/// Perform Liveness Analysis over the `Body`. Each `Inst` will have its `deaths` field populated.
pub fn analyze(
/// Used for temporary storage during the analysis.
gpa: *std.mem.Allocator,
/// Used to tack on extra allocations in the same lifetime as the existing instructions.
arena: *std.mem.Allocator,
body: ir.Body,
) error{OutOfMemory}!void {
const tracy = trace(@src());
defer tracy.end();
var table = std.AutoHashMap(*ir.Inst, void).init(gpa);
defer table.deinit();
try table.ensureCapacity(body.instructions.len);
try analyzeWithTable(arena, &table, body);
}
fn analyzeWithTable(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), body: ir.Body) error{OutOfMemory}!void {
var i: usize = body.instructions.len;
while (i != 0) {
i -= 1;
const base = body.instructions[i];
try analyzeInst(arena, table, base);
}
}
fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), base: *ir.Inst) error{OutOfMemory}!void {
if (table.contains(base)) {
base.deaths = 0;
} else {
// No tombstone for this instruction means it is never referenced,
// and its birth marks its own death. Very metal 🤘
base.deaths = 1 << ir.Inst.unreferenced_bit_index;
}
switch (base.tag) {
.constant => return,
.block => {
const inst = base.castTag(.block).?;
try analyzeWithTable(arena, table, inst.body);
// We let this continue so that it can possibly mark the block as
// unreferenced below.
},
.condbr => {
const inst = base.castTag(.condbr).?;
var true_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator);
defer true_table.deinit();
try true_table.ensureCapacity(inst.then_body.instructions.len);
try analyzeWithTable(arena, &true_table, inst.then_body);
var false_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator);
defer false_table.deinit();
try false_table.ensureCapacity(inst.else_body.instructions.len);
try analyzeWithTable(arena, &false_table, inst.else_body);
// Each death that occurs inside one branch, but not the other, needs
// to be added as a death immediately upon entering the other branch.
// During the iteration of the table, we additionally propagate the
// deaths to the parent table.
var true_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator);
defer true_entry_deaths.deinit();
var false_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator);
defer false_entry_deaths.deinit();
{
var it = false_table.iterator();
while (it.next()) |entry| {
const false_death = entry.key;
if (!true_table.contains(false_death)) {
try true_entry_deaths.append(false_death);
// Here we are only adding to the parent table if the following iteration
// would miss it.
try table.putNoClobber(false_death, {});
}
}
}
{
var it = true_table.iterator();
while (it.next()) |entry| {
const true_death = entry.key;
try table.putNoClobber(true_death, {});
if (!false_table.contains(true_death)) {
try false_entry_deaths.append(true_death);
}
}
}
inst.true_death_count = std.math.cast(@TypeOf(inst.true_death_count), true_entry_deaths.items.len) catch return error.OutOfMemory;
inst.false_death_count = std.math.cast(@TypeOf(inst.false_death_count), false_entry_deaths.items.len) catch return error.OutOfMemory;
const allocated_slice = try arena.alloc(*ir.Inst, true_entry_deaths.items.len + false_entry_deaths.items.len);
inst.deaths = allocated_slice.ptr;
// Continue on with the instruction analysis. The following code will find the condition
// instruction, and the deaths flag for the CondBr instruction will indicate whether the
// condition's lifetime ends immediately before entering any branch.
},
else => {},
}
const needed_bits = base.operandCount();
if (needed_bits <= ir.Inst.deaths_bits) {
var bit_i: ir.Inst.DeathsBitIndex = 0;
while (base.getOperand(bit_i)) |operand| : (bit_i += 1) {
const prev = try table.fetchPut(operand, {});
if (prev == null) {
// Death.
base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i;
}
}
} else {
@panic("Handle liveness analysis for instructions with many parameters");
}
std.log.debug(.liveness, "analyze {}: 0b{b}\n", .{ base.tag, base.deaths });
}