Commit 4590e980f7

joachimschmidt557 <joachim.schmidt557@outlook.com>
2022-03-11 14:12:11
stage2 ARM: implement caller-saved registers
1 parent 06058ed
Changed files (3)
src/arch/arm/abi.zig
@@ -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 };
src/arch/arm/CodeGen.zig
@@ -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);
                 }
             },
src/register_manager.zig
@@ -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();