stage2: register allocator processes operand deaths

also rework the IR data structures
This commit is contained in:
Andrew Kelley
2020-07-20 13:11:07 -07:00
parent a8065a05a5
commit ef91b11295
8 changed files with 708 additions and 527 deletions

View File

@@ -290,6 +290,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
next_stack_offset: u32 = 0,
fn markRegUsed(self: *Branch, reg: Register) void {
if (FreeRegInt == u0) return;
const index = reg.allocIndex() orelse return;
const ShiftInt = std.math.Log2Int(FreeRegInt);
const shift = @intCast(ShiftInt, index);
@@ -297,6 +298,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn markRegFree(self: *Branch, reg: Register) void {
if (FreeRegInt == u0) return;
const index = reg.allocIndex() orelse return;
const ShiftInt = std.math.Log2Int(FreeRegInt);
const shift = @intCast(ShiftInt, index);
@@ -407,40 +409,64 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
for (body.instructions) |inst| {
const new_inst = try self.genFuncInst(inst);
try inst_table.putNoClobber(self.gpa, inst, new_inst);
// TODO process operand deaths
var i: ir.Inst.DeathsBitIndex = 0;
while (inst.getOperand(i)) |operand| : (i += 1) {
if (inst.operandDies(i))
self.processDeath(operand);
}
}
}
fn processDeath(self: *Self, inst: *ir.Inst) void {
const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
const entry = branch.inst_table.getEntry(inst) orelse return;
const prev_value = entry.value;
entry.value = .dead;
switch (prev_value) {
.register => |reg| {
_ = branch.registers.remove(reg);
branch.markRegFree(reg);
},
else => {}, // TODO process stack allocation death
}
}
fn genFuncInst(self: *Self, inst: *ir.Inst) !MCValue {
switch (inst.tag) {
.add => return self.genAdd(inst.cast(ir.Inst.Add).?),
.arg => return self.genArg(inst.cast(ir.Inst.Arg).?),
.assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?),
.bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?),
.block => return self.genBlock(inst.cast(ir.Inst.Block).?),
.br => return self.genBr(inst.cast(ir.Inst.Br).?),
.add => return self.genAdd(inst.castTag(.add).?),
.arg => return self.genArg(inst.castTag(.arg).?),
.assembly => return self.genAsm(inst.castTag(.assembly).?),
.bitcast => return self.genBitCast(inst.castTag(.bitcast).?),
.block => return self.genBlock(inst.castTag(.block).?),
.br => return self.genBr(inst.castTag(.br).?),
.breakpoint => return self.genBreakpoint(inst.src),
.brvoid => return self.genBrVoid(inst.cast(ir.Inst.BrVoid).?),
.call => return self.genCall(inst.cast(ir.Inst.Call).?),
.cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?),
.condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?),
.brvoid => return self.genBrVoid(inst.castTag(.brvoid).?),
.call => return self.genCall(inst.castTag(.call).?),
.cmp_lt => return self.genCmp(inst.castTag(.cmp_lt).?, .lt),
.cmp_lte => return self.genCmp(inst.castTag(.cmp_lte).?, .lte),
.cmp_eq => return self.genCmp(inst.castTag(.cmp_eq).?, .eq),
.cmp_gte => return self.genCmp(inst.castTag(.cmp_gte).?, .gte),
.cmp_gt => return self.genCmp(inst.castTag(.cmp_gt).?, .gt),
.cmp_neq => return self.genCmp(inst.castTag(.cmp_neq).?, .neq),
.condbr => return self.genCondBr(inst.castTag(.condbr).?),
.constant => unreachable, // excluded from function bodies
.isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?),
.isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?),
.ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?),
.ret => return self.genRet(inst.cast(ir.Inst.Ret).?),
.retvoid => return self.genRetVoid(inst.cast(ir.Inst.RetVoid).?),
.sub => return self.genSub(inst.cast(ir.Inst.Sub).?),
.isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?),
.isnull => return self.genIsNull(inst.castTag(.isnull).?),
.ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?),
.ret => return self.genRet(inst.castTag(.ret).?),
.retvoid => return self.genRetVoid(inst.castTag(.retvoid).?),
.sub => return self.genSub(inst.castTag(.sub).?),
.unreach => return MCValue{ .unreach = {} },
.not => return self.genNot(inst.cast(ir.Inst.Not).?),
.not => return self.genNot(inst.castTag(.not).?),
}
}
fn genNot(self: *Self, inst: *ir.Inst.Not) !MCValue {
fn genNot(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
const operand = try self.resolveInst(inst.args.operand);
const operand = try self.resolveInst(inst.operand);
switch (operand) {
.dead => unreachable,
.unreach => unreachable,
@@ -473,36 +499,36 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.base = .{
.tag = .constant,
.deaths = 0,
.ty = inst.args.operand.ty,
.src = inst.args.operand.src,
.ty = inst.operand.ty,
.src = inst.operand.src,
},
.val = Value.initTag(.bool_true),
};
return try self.genX8664BinMath(&inst.base, inst.args.operand, &imm.base, 6, 0x30);
return try self.genX8664BinMath(&inst.base, inst.operand, &imm.base, 6, 0x30);
},
else => return self.fail(inst.base.src, "TODO implement NOT for {}", .{self.target.cpu.arch}),
}
}
fn genAdd(self: *Self, inst: *ir.Inst.Add) !MCValue {
fn genAdd(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
switch (arch) {
.x86_64 => {
return try self.genX8664BinMath(&inst.base, inst.args.lhs, inst.args.rhs, 0, 0x00);
return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 0, 0x00);
},
else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}),
}
}
fn genSub(self: *Self, inst: *ir.Inst.Sub) !MCValue {
fn genSub(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
switch (arch) {
.x86_64 => {
return try self.genX8664BinMath(&inst.base, inst.args.lhs, inst.args.rhs, 5, 0x28);
return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 5, 0x28);
},
else => return self.fail(inst.base.src, "TODO implement sub for {}", .{self.target.cpu.arch}),
}
@@ -625,7 +651,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
fn genArg(self: *Self, inst: *ir.Inst.Arg) !MCValue {
fn genArg(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
if (FreeRegInt == u0) {
return self.fail(inst.base.src, "TODO implement Register enum for {}", .{self.target.cpu.arch});
}
@@ -659,7 +685,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn genCall(self: *Self, inst: *ir.Inst.Call) !MCValue {
const fn_ty = inst.args.func.ty;
const fn_ty = inst.func.ty;
const cc = fn_ty.fnCallingConvention();
const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
defer self.gpa.free(param_types);
@@ -671,8 +697,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (arch) {
.x86_64 => {
for (mc_args) |mc_arg, arg_i| {
const arg = inst.args.args[arg_i];
const arg_mcv = try self.resolveInst(inst.args.args[arg_i]);
const arg = inst.args[arg_i];
const arg_mcv = try self.resolveInst(inst.args[arg_i]);
switch (mc_arg) {
.none => continue,
.register => |reg| {
@@ -694,7 +720,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| {
if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
const func = func_val.func;
const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];
@@ -742,16 +768,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return .unreach;
}
fn genRet(self: *Self, inst: *ir.Inst.Ret) !MCValue {
const operand = try self.resolveInst(inst.args.operand);
fn genRet(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
const operand = try self.resolveInst(inst.operand);
return self.ret(inst.base.src, operand);
}
fn genRetVoid(self: *Self, inst: *ir.Inst.RetVoid) !MCValue {
fn genRetVoid(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
return self.ret(inst.base.src, .none);
}
fn genCmp(self: *Self, inst: *ir.Inst.Cmp) !MCValue {
fn genCmp(self: *Self, inst: *ir.Inst.BinOp, op: std.math.CompareOperator) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
@@ -759,25 +785,25 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.x86_64 => {
try self.code.ensureCapacity(self.code.items.len + 8);
const lhs = try self.resolveInst(inst.args.lhs);
const rhs = try self.resolveInst(inst.args.rhs);
const lhs = try self.resolveInst(inst.lhs);
const rhs = try self.resolveInst(inst.rhs);
// There are 2 operands, destination and source.
// Either one, but not both, can be a memory operand.
// Source operand can be an immediate, 8 bits or 32 bits.
const dst_mcv = if (lhs.isImmediate() or (lhs.isMemory() and rhs.isMemory()))
try self.copyToNewRegister(inst.args.lhs)
try self.copyToNewRegister(inst.lhs)
else
lhs;
// This instruction supports only signed 32-bit immediates at most.
const src_mcv = try self.limitImmediateType(inst.args.rhs, i32);
const src_mcv = try self.limitImmediateType(inst.rhs, i32);
try self.genX8664BinMathCode(inst.base.src, dst_mcv, src_mcv, 7, 0x38);
const info = inst.args.lhs.ty.intInfo(self.target.*);
const info = inst.lhs.ty.intInfo(self.target.*);
if (info.signed) {
return MCValue{ .compare_flags_signed = inst.args.op };
return MCValue{ .compare_flags_signed = op };
} else {
return MCValue{ .compare_flags_unsigned = inst.args.op };
return MCValue{ .compare_flags_unsigned = op };
}
},
else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}),
@@ -789,7 +815,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.x86_64 => {
try self.code.ensureCapacity(self.code.items.len + 6);
const cond = try self.resolveInst(inst.args.condition);
const cond = try self.resolveInst(inst.condition);
switch (cond) {
.compare_flags_signed => |cmp_op| {
// Here we map to the opposite opcode because the jump is to the false branch.
@@ -838,19 +864,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode });
const reloc = Reloc{ .rel32 = self.code.items.len };
self.code.items.len += 4;
try self.genBody(inst.args.true_body);
try self.genBody(inst.then_body);
try self.performReloc(inst.base.src, reloc);
try self.genBody(inst.args.false_body);
try self.genBody(inst.else_body);
return MCValue.unreach;
}
fn genIsNull(self: *Self, inst: *ir.Inst.IsNull) !MCValue {
fn genIsNull(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
switch (arch) {
else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.target.cpu.arch}),
}
}
fn genIsNonNull(self: *Self, inst: *ir.Inst.IsNonNull) !MCValue {
fn genIsNonNull(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
// Here you can specialize this instruction if it makes sense to, otherwise the default
// will call genIsNull and invert the result.
switch (arch) {
@@ -864,7 +890,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
// A block is nothing but a setup to be able to jump to the end.
defer inst.codegen.relocs.deinit(self.gpa);
try self.genBody(inst.args.body);
try self.genBody(inst.body);
for (inst.codegen.relocs.items) |reloc| try self.performReloc(inst.base.src, reloc);
@@ -883,17 +909,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn genBr(self: *Self, inst: *ir.Inst.Br) !MCValue {
if (!inst.args.operand.ty.hasCodeGenBits())
return self.brVoid(inst.base.src, inst.args.block);
if (!inst.operand.ty.hasCodeGenBits())
return self.brVoid(inst.base.src, inst.block);
const operand = try self.resolveInst(inst.args.operand);
const operand = try self.resolveInst(inst.operand);
switch (arch) {
else => return self.fail(inst.base.src, "TODO implement br for {}", .{self.target.cpu.arch}),
}
}
fn genBrVoid(self: *Self, inst: *ir.Inst.BrVoid) !MCValue {
return self.brVoid(inst.base.src, inst.args.block);
return self.brVoid(inst.base.src, inst.block);
}
fn brVoid(self: *Self, src: usize, block: *ir.Inst.Block) !MCValue {
@@ -915,29 +941,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn genAsm(self: *Self, inst: *ir.Inst.Assembly) !MCValue {
if (!inst.args.is_volatile and inst.base.isUnused())
if (!inst.is_volatile and inst.base.isUnused())
return MCValue.dead;
if (arch != .x86_64 and arch != .i386) {
return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{});
}
for (inst.args.inputs) |input, i| {
for (inst.inputs) |input, i| {
if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input});
}
const reg_name = input[1 .. input.len - 1];
const reg = parseRegName(reg_name) orelse
return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
const arg = try self.resolveInst(inst.args.args[i]);
const arg = try self.resolveInst(inst.args[i]);
try self.genSetReg(inst.base.src, reg, arg);
}
if (mem.eql(u8, inst.args.asm_source, "syscall")) {
if (mem.eql(u8, inst.asm_source, "syscall")) {
try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 });
} else {
return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{});
}
if (inst.args.output) |output| {
if (inst.output) |output| {
if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output});
}
@@ -1169,13 +1195,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
fn genPtrToInt(self: *Self, inst: *ir.Inst.PtrToInt) !MCValue {
fn genPtrToInt(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
// no-op
return self.resolveInst(inst.args.ptr);
return self.resolveInst(inst.operand);
}
fn genBitCast(self: *Self, inst: *ir.Inst.BitCast) !MCValue {
const operand = try self.resolveInst(inst.args.operand);
fn genBitCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
const operand = try self.resolveInst(inst.operand);
return operand;
}