stage2 ARM: implement caller-saved registers

This commit is contained in:
joachimschmidt557 2022-03-11 14:12:11 +01:00
parent 06058ed6f3
commit 4590e980f7
No known key found for this signature in database
GPG Key ID: E0B575BE2884ACC5
3 changed files with 40 additions and 32 deletions

View File

@ -22,7 +22,7 @@ const leb128 = std.leb;
const log = std.log.scoped(.codegen);
const build_options = @import("build_options");
const RegisterManagerFn = @import("../../register_manager.zig").RegisterManager;
const RegisterManager = RegisterManagerFn(Self, Register, &callee_preserved_regs);
const RegisterManager = RegisterManagerFn(Self, Register, &allocatable_registers);
const FnResult = @import("../../codegen.zig").FnResult;
const GenerateSymbolError = @import("../../codegen.zig").GenerateSymbolError;
@ -34,6 +34,8 @@ const Register = bits.Register;
const Instruction = bits.Instruction;
const Condition = bits.Condition;
const callee_preserved_regs = abi.callee_preserved_regs;
const caller_preserved_regs = abi.caller_preserved_regs;
const allocatable_registers = abi.allocatable_registers;
const c_abi_int_param_regs = abi.c_abi_int_param_regs;
const c_abi_int_return_regs = abi.c_abi_int_return_regs;
@ -788,10 +790,6 @@ fn allocMemPtr(self: *Self, inst: Air.Inst.Index) !u32 {
if (!elem_ty.hasRuntimeBits()) {
// As this stack item will never be dereferenced at runtime,
// return the current stack offset
try self.stack.putNoClobber(self.gpa, self.next_stack_offset, .{
.inst = inst,
.size = 0,
});
return self.next_stack_offset;
}
@ -1569,13 +1567,13 @@ fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_ind
switch (mcv) {
.register => |reg| {
// If it's in the registers table, need to associate the register with the
// new instruction.
if (RegisterManager.indexOfRegIntoTracked(reg)) |index| {
if (!self.register_manager.isRegFree(reg)) {
self.register_manager.registers[index] = inst;
}
// We assert that this register is allocatable by asking
// for its index
const index = RegisterManager.indexOfRegIntoTracked(reg).?; // see note above
if (!self.register_manager.isRegFree(reg)) {
self.register_manager.registers[index] = inst;
}
log.debug("%{d} => {} (reused)", .{ inst, reg });
},
.stack_offset => |off| {
@ -2545,13 +2543,17 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
// Architecture, compare flags are not preserved across
// calls. Therefore, if some value is currently stored there, we
// need to save it.
//
// TODO once caller-saved registers are implemented, save them
// here too, but crucially *after* we save the compare flags as
// saving compare flags may require a new caller-saved register
try self.spillCompareFlagsIfOccupied();
// Save caller-saved registers, but crucially *after* we save the
// compare flags as saving compare flags may require a new
// caller-saved register
for (caller_preserved_regs) |reg| {
try self.register_manager.getReg(reg, null);
}
if (info.return_value == .stack_offset) {
log.debug("airCall: return by reference", .{});
const ret_ty = fn_ty.fnReturnType();
const ret_abi_size = @intCast(u32, ret_ty.abiSize(self.target.*));
const ret_abi_align = @intCast(u32, ret_ty.abiAlignment(self.target.*));
@ -2562,7 +2564,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
.data = ret_ty,
};
const ptr_ty = Type.initPayload(&ptr_ty_payload.base);
try self.register_manager.getReg(.r0, inst);
try self.register_manager.getReg(.r0, null);
try self.genSetReg(ptr_ty, .r0, .{ .ptr_stack_offset = stack_offset });
info.return_value = .{ .stack_offset = stack_offset };
@ -2662,8 +2664,9 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
const result: MCValue = result: {
switch (info.return_value) {
.register => |reg| {
if (RegisterManager.indexOfReg(&callee_preserved_regs, reg) == null) {
// Save function return value in a callee saved register
if (RegisterManager.indexOfRegIntoTracked(reg) == null) {
// Save function return value into a tracked register
log.debug("airCall: copying {} as it is not tracked", .{reg});
break :result try self.copyToNewRegister(inst, info.return_value);
}
},

View File

@ -2,5 +2,8 @@ const bits = @import("bits.zig");
const Register = bits.Register;
pub const callee_preserved_regs = [_]Register{ .r4, .r5, .r6, .r7, .r8, .r10 };
pub const caller_preserved_regs = [_]Register{ .r0, .r1, .r2, .r3 };
pub const allocatable_registers = callee_preserved_regs ++ caller_preserved_regs;
pub const c_abi_int_param_regs = [_]Register{ .r0, .r1, .r2, .r3 };
pub const c_abi_int_return_regs = [_]Register{ .r0, .r1 };

View File

@ -26,11 +26,11 @@ pub const AllocateRegistersError = error{
pub fn RegisterManager(
comptime Function: type,
comptime Register: type,
comptime callee_preserved_regs: []const Register,
comptime tracked_registers: []const Register,
) type {
// architectures which do not have a concept of registers should
// refrain from using RegisterManager
assert(callee_preserved_regs.len > 0); // see note above
assert(tracked_registers.len > 0); // see note above
return struct {
/// Tracks the AIR instruction allocated to every register. If
@ -38,7 +38,7 @@ pub fn RegisterManager(
/// register is free), the value in that slot is undefined.
///
/// The key must be canonical register.
registers: [callee_preserved_regs.len]Air.Inst.Index = undefined,
registers: [tracked_registers.len]Air.Inst.Index = undefined,
/// Tracks which registers are free (in which case the
/// corresponding bit is set to 1)
free_registers: FreeRegInt = math.maxInt(FreeRegInt),
@ -53,7 +53,7 @@ pub fn RegisterManager(
/// An integer whose bits represent all the registers and
/// whether they are free.
const FreeRegInt = std.meta.Int(.unsigned, callee_preserved_regs.len);
const FreeRegInt = std.meta.Int(.unsigned, tracked_registers.len);
const ShiftInt = math.Log2Int(FreeRegInt);
fn getFunction(self: *Self) *Function {
@ -83,14 +83,14 @@ pub fn RegisterManager(
}
pub fn indexOfReg(comptime registers: []const Register, reg: Register) ?std.math.IntFittingRange(0, registers.len - 1) {
inline for (callee_preserved_regs) |cpreg, i| {
inline for (tracked_registers) |cpreg, i| {
if (reg.id() == cpreg.id()) return i;
}
return null;
}
pub fn indexOfRegIntoTracked(reg: Register) ?ShiftInt {
return indexOfReg(callee_preserved_regs, reg);
return indexOfReg(tracked_registers, reg);
}
/// Returns true when this register is not tracked
@ -146,14 +146,14 @@ pub fn RegisterManager(
comptime count: comptime_int,
insts: [count]?Air.Inst.Index,
) ?[count]Register {
comptime assert(count > 0 and count <= callee_preserved_regs.len);
comptime assert(count > 0 and count <= tracked_registers.len);
const free_registers = @popCount(FreeRegInt, self.free_registers);
if (free_registers < count) return null;
var regs: [count]Register = undefined;
var i: usize = 0;
for (callee_preserved_regs) |reg| {
for (tracked_registers) |reg| {
if (i >= count) break;
if (self.isRegFrozen(reg)) continue;
if (self.isRegFree(reg)) {
@ -192,8 +192,8 @@ pub fn RegisterManager(
comptime count: comptime_int,
insts: [count]?Air.Inst.Index,
) AllocateRegistersError![count]Register {
comptime assert(count > 0 and count <= callee_preserved_regs.len);
if (count > callee_preserved_regs.len - @popCount(FreeRegInt, self.frozen_registers)) return error.OutOfRegisters;
comptime assert(count > 0 and count <= tracked_registers.len);
if (count > tracked_registers.len - @popCount(FreeRegInt, self.frozen_registers)) return error.OutOfRegisters;
const result = self.tryAllocRegs(count, insts) orelse blk: {
// We'll take over the first count registers. Spill
@ -201,7 +201,7 @@ pub fn RegisterManager(
// stack allocations.
var regs: [count]Register = undefined;
var i: usize = 0;
for (callee_preserved_regs) |reg| {
for (tracked_registers) |reg| {
if (i >= count) break;
if (self.isRegFrozen(reg)) continue;
@ -247,6 +247,7 @@ pub fn RegisterManager(
/// register.
pub fn getReg(self: *Self, reg: Register, inst: ?Air.Inst.Index) AllocateRegistersError!void {
const index = indexOfRegIntoTracked(reg) orelse return;
log.debug("getReg {} for inst {}", .{ reg, inst });
self.markRegAllocated(reg);
if (inst) |tracked_inst|
@ -275,6 +276,7 @@ pub fn RegisterManager(
/// spilling is necessary.
pub fn getRegAssumeFree(self: *Self, reg: Register, inst: Air.Inst.Index) void {
const index = indexOfRegIntoTracked(reg) orelse return;
log.debug("getRegAssumeFree {} for inst {}", .{ reg, inst });
self.markRegAllocated(reg);
assert(self.isRegFree(reg));
@ -303,7 +305,7 @@ const MockRegister1 = enum(u2) {
return @enumToInt(reg);
}
const callee_preserved_regs = [_]MockRegister1{ .r2, .r3 };
const allocatable_registers = [_]MockRegister1{ .r2, .r3 };
};
const MockRegister2 = enum(u2) {
@ -316,13 +318,13 @@ const MockRegister2 = enum(u2) {
return @enumToInt(reg);
}
const callee_preserved_regs = [_]MockRegister2{ .r0, .r1, .r2, .r3 };
const allocatable_registers = [_]MockRegister2{ .r0, .r1, .r2, .r3 };
};
fn MockFunction(comptime Register: type) type {
return struct {
allocator: Allocator,
register_manager: RegisterManager(Self, Register, &Register.callee_preserved_regs) = .{},
register_manager: RegisterManager(Self, Register, &Register.allocatable_registers) = .{},
spilled: std.ArrayListUnmanaged(Register) = .{},
const Self = @This();