stage2: implement register copying

This commit is contained in:
Andrew Kelley 2020-07-17 15:51:15 -07:00
parent ef9aeb6ac4
commit 896472c20e
4 changed files with 124 additions and 36 deletions

View File

@ -11,8 +11,6 @@ const ErrorMsg = Module.ErrorMsg;
const Target = std.Target;
const Allocator = mem.Allocator;
const trace = @import("tracy.zig").trace;
const x86_64 = @import("codegen/x86_64.zig");
const x86 = @import("codegen/x86.zig");
/// The codegen-related data that is stored in `ir.Inst.Block` instructions.
pub const BlockData = struct {
@ -232,7 +230,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
/// The constant was emitted into the code, at this offset.
embedded_in_code: usize,
/// The value is in a target-specific register.
register: Reg,
register: Register,
/// The value is in memory at a hard-coded address.
memory: u64,
/// The value is one of the stack variables.
@ -280,9 +278,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const Branch = struct {
inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{},
/// The key is an enum value of an arch-specific register.
registers: std.AutoHashMapUnmanaged(usize, RegisterAllocation) = .{},
registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{},
free_registers: FreeRegInt = std.math.maxInt(FreeRegInt),
/// Maps offset to what is stored there.
stack: std.AutoHashMapUnmanaged(usize, StackAllocation) = .{},
@ -292,6 +289,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
/// to place a new stack allocation, it goes here, and then bumps `max_end_stack`.
next_stack_offset: u32 = 0,
fn markRegUsed(self: *Branch, reg: Register) void {
const index = reg.allocIndex() orelse return;
const ShiftInt = std.math.Log2Int(FreeRegInt);
const shift = @intCast(ShiftInt, index);
self.free_registers &= ~(@as(FreeRegInt, 1) << shift);
}
fn markRegFree(self: *Branch, reg: Register) void {
const index = reg.allocIndex() orelse return;
const ShiftInt = std.math.Log2Int(FreeRegInt);
const shift = @intCast(ShiftInt, index);
self.free_registers |= @as(FreeRegInt, 1) << shift;
}
fn deinit(self: *Branch, gpa: *Allocator) void {
self.inst_table.deinit(gpa);
self.registers.deinit(gpa);
@ -516,7 +527,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// Both operands cannot be memory.
src_inst = op_rhs;
if (lhs.isMemory() and rhs.isMemory()) {
dst_mcv = try self.moveToNewRegister(op_lhs);
dst_mcv = try self.copyToNewRegister(op_lhs);
src_mcv = rhs;
} else {
dst_mcv = lhs;
@ -527,7 +538,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// Both operands cannot be memory.
src_inst = op_lhs;
if (lhs.isMemory() and rhs.isMemory()) {
dst_mcv = try self.moveToNewRegister(op_rhs);
dst_mcv = try self.copyToNewRegister(op_rhs);
src_mcv = lhs;
} else {
dst_mcv = rhs;
@ -535,11 +546,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
} else {
if (lhs.isMemory()) {
dst_mcv = try self.moveToNewRegister(op_lhs);
dst_mcv = try self.copyToNewRegister(op_lhs);
src_mcv = rhs;
src_inst = op_rhs;
} else {
dst_mcv = try self.moveToNewRegister(op_rhs);
dst_mcv = try self.copyToNewRegister(op_rhs);
src_mcv = lhs;
src_inst = op_lhs;
}
@ -552,7 +563,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (src_mcv) {
.immediate => |imm| {
if (imm > std.math.maxInt(u31)) {
src_mcv = try self.moveToNewRegister(src_inst);
src_mcv = try self.copyToNewRegister(src_inst);
}
},
else => {},
@ -614,9 +625,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn genArg(self: *Self, inst: *ir.Inst.Arg) !MCValue {
const i = self.arg_index;
if (FreeRegInt == u0) {
return self.fail(inst.base.src, "TODO implement Register enum for {}", .{self.target.cpu.arch});
}
if (inst.base.isUnused())
return MCValue.dead;
const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
const result = self.args[self.arg_index];
self.arg_index += 1;
return self.args[i];
switch (result) {
.register => |reg| {
branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = &inst.base });
branch.markRegUsed(reg);
},
else => {},
}
return result;
}
fn genBreakpoint(self: *Self, src: usize) !MCValue {
@ -737,7 +765,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// 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.moveToNewRegister(inst.args.lhs)
try self.copyToNewRegister(inst.args.lhs)
else
lhs;
// This instruction supports only signed 32-bit immediates at most.
@ -949,7 +977,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
fn genSetReg(self: *Self, src: usize, reg: Reg, mcv: MCValue) error{ CodegenFail, OutOfMemory }!void {
fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) error{ CodegenFail, OutOfMemory }!void {
switch (arch) {
.x86_64 => switch (mcv) {
.dead => unreachable,
@ -1171,9 +1199,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
fn moveToNewRegister(self: *Self, inst: *ir.Inst) !MCValue {
/// Does not "move" the instruction.
fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue {
const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
return self.fail(inst.src, "TODO implement moveToNewRegister", .{});
try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
try branch.inst_table.ensureCapacity(self.gpa, branch.inst_table.items().len + 1);
const free_index = @ctz(FreeRegInt, branch.free_registers);
if (free_index >= callee_preserved_regs.len)
return self.fail(inst.src, "TODO implement spilling register to stack", .{});
branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
const reg = callee_preserved_regs[free_index];
branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst });
const old_mcv = branch.inst_table.get(inst).?;
const new_mcv: MCValue = .{ .register = reg };
try self.genSetReg(inst.src, reg, old_mcv);
return new_mcv;
}
/// If the MCValue is an immediate, and it does not fit within this type,
@ -1194,7 +1235,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
},
});
if (imm >= std.math.maxInt(U)) {
return self.moveToNewRegister(inst);
return self.copyToNewRegister(inst);
}
},
else => {},
@ -1249,15 +1290,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
var next_int_reg: usize = 0;
var next_stack_offset: u32 = 0;
const integer_registers = [_]Reg{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
for (param_types) |ty, i| {
switch (ty.zigTypeTag()) {
.Bool, .Int => {
if (next_int_reg >= integer_registers.len) {
if (next_int_reg >= c_abi_int_param_regs.len) {
results[i] = .{ .stack_offset = next_stack_offset };
next_stack_offset += @intCast(u32, ty.abiSize(self.target.*));
} else {
results[i] = .{ .register = integer_registers[next_int_reg] };
results[i] = .{ .register = c_abi_int_param_regs[next_int_reg] };
next_int_reg += 1;
}
},
@ -1280,14 +1320,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return error.CodegenFail;
}
const Reg = switch (arch) {
.i386 => x86.Register,
.x86_64 => x86_64.Register,
else => enum { dummy },
usingnamespace switch (arch) {
.i386 => @import("codegen/x86.zig"),
.x86_64 => @import("codegen/x86_64.zig"),
else => struct {
pub const Register = enum {
dummy,
pub fn allocIndex(self: Register) ?u4 {
return null;
}
};
pub const callee_preserved_regs = [_]Register{};
},
};
fn parseRegName(name: []const u8) ?Reg {
return std.meta.stringToEnum(Reg, name);
/// An integer whose bits represent all the registers and whether they are free.
const FreeRegInt = @Type(.{ .Int = .{ .is_signed = false, .bits = callee_preserved_regs.len } });
fn parseRegName(name: []const u8) ?Register {
return std.meta.stringToEnum(Register, name);
}
};
}

View File

@ -25,6 +25,20 @@ pub const Register = enum(u8) {
pub fn id(self: @This()) u3 {
return @truncate(u3, @enumToInt(self));
}
/// Returns the index into `callee_preserved_regs`.
pub fn allocIndex(self: Register) ?u4 {
return switch (self) {
.eax, .ax, .al => 0,
.ecx, .cx, .cl => 1,
.edx, .dx, .dl => 2,
.esi, .si => 3,
.edi, .di => 4,
else => null,
};
}
};
// zig fmt: on
pub const callee_preserved_regs = [_]Register{ .eax, .ecx, .edx, .esi, .edi };

View File

@ -38,7 +38,7 @@ pub const Register = enum(u8) {
r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b,
/// Returns the bit-width of the register.
pub fn size(self: @This()) u7 {
pub fn size(self: Register) u7 {
return switch (@enumToInt(self)) {
0...15 => 64,
16...31 => 32,
@ -53,7 +53,7 @@ pub const Register = enum(u8) {
/// other variant of access to those registers, such as r8b, r15d, and so
/// on. This is needed because access to these registers requires special
/// handling via the REX prefix, via the B or R bits, depending on context.
pub fn isExtended(self: @This()) bool {
pub fn isExtended(self: Register) bool {
return @enumToInt(self) & 0x08 != 0;
}
@ -62,12 +62,29 @@ pub const Register = enum(u8) {
/// an instruction (@see isExtended), and requires special handling. The
/// lower three bits are often embedded directly in instructions (such as
/// the B8 variant of moves), or used in R/M bytes.
pub fn id(self: @This()) u4 {
pub fn id(self: Register) u4 {
return @truncate(u4, @enumToInt(self));
}
/// Returns the index into `callee_preserved_regs`.
pub fn allocIndex(self: Register) ?u4 {
return switch (self) {
.rax, .eax, .ax, .al => 0,
.rcx, .ecx, .cx, .cl => 1,
.rdx, .edx, .dx, .dl => 2,
.rsi, .esi, .si => 3,
.rdi, .edi, .di => 4,
.r8, .r8d, .r8w, .r8b => 5,
.r9, .r9d, .r9w, .r9b => 6,
.r10, .r10d, .r10w, .r10b => 7,
.r11, .r11d, .r11w, .r11b => 8,
else => null,
};
}
};
// zig fmt: on
/// These registers belong to the called function.
pub const callee_preserved = [_]Register{ rax, rcx, rdx, rsi, rdi, r8, r9, r10, r11 };
pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 };
pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };

View File

@ -169,9 +169,8 @@ pub fn addCases(ctx: *TestContext) !void {
,
"",
);
}
{
var case = ctx.exe("assert function", linux_x64);
// Tests the assert() function.
case.addCompareOutput(
\\export fn _start() noreturn {
\\ add(3, 4);
@ -199,15 +198,21 @@ pub fn addCases(ctx: *TestContext) !void {
,
"",
);
// Tests copying a register. For the `c = a + b`, it has to
// preserve both a and b, because they are both used later.
case.addCompareOutput(
\\export fn _start() noreturn {
\\ add(100, 200);
\\ add(3, 4);
\\
\\ exit();
\\}
\\
\\fn add(a: u32, b: u32) void {
\\ assert(a + b == 300);
\\ const c = a + b; // 7
\\ const d = a + c; // 10
\\ const e = d + b; // 14
\\ assert(e == 14);
\\}
\\
\\pub fn assert(ok: bool) void {