Commit 4b3b487627

joachimschmidt557 <joachim.schmidt557@outlook.com>
2022-02-05 17:13:11
stage2 regalloc: Introduce error.OutOfRegisters
1 parent d4c3475
Changed files (5)
src/arch/aarch64/CodeGen.zig
@@ -31,6 +31,7 @@ const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
 const InnerError = error{
     OutOfMemory,
     CodegenFail,
+    OutOfRegisters,
 };
 
 gpa: Allocator,
@@ -274,6 +275,9 @@ pub fn generate(
 
     var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) {
         error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
+        error.OutOfRegisters => return FnResult{
+            .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
+        },
         else => |e| return e,
     };
     defer call_info.deinit(&function);
@@ -285,6 +289,9 @@ pub fn generate(
 
     function.gen() catch |err| switch (err) {
         error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
+        error.OutOfRegisters => return FnResult{
+            .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
+        },
         else => |e| return e,
     };
 
src/arch/arm/CodeGen.zig
@@ -31,6 +31,7 @@ const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
 const InnerError = error{
     OutOfMemory,
     CodegenFail,
+    OutOfRegisters,
 };
 
 gpa: Allocator,
@@ -279,6 +280,9 @@ pub fn generate(
 
     var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) {
         error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
+        error.OutOfRegisters => return FnResult{
+            .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
+        },
         else => |e| return e,
     };
     defer call_info.deinit(&function);
@@ -290,6 +294,9 @@ pub fn generate(
 
     function.gen() catch |err| switch (err) {
         error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
+        error.OutOfRegisters => return FnResult{
+            .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
+        },
         else => |e| return e,
     };
 
src/arch/riscv64/CodeGen.zig
@@ -31,6 +31,7 @@ const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
 const InnerError = error{
     OutOfMemory,
     CodegenFail,
+    OutOfRegisters,
 };
 
 gpa: Allocator,
@@ -280,6 +281,9 @@ pub fn generate(
 
     var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) {
         error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
+        error.OutOfRegisters => return FnResult{
+            .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
+        },
         else => |e| return e,
     };
     defer call_info.deinit(&function);
@@ -291,6 +295,9 @@ pub fn generate(
 
     function.gen() catch |err| switch (err) {
         error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
+        error.OutOfRegisters => return FnResult{
+            .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
+        },
         else => |e| return e,
     };
 
src/arch/x86_64/CodeGen.zig
@@ -31,6 +31,7 @@ const Zir = @import("../../Zir.zig");
 const InnerError = error{
     OutOfMemory,
     CodegenFail,
+    OutOfRegisters,
 };
 
 const RegisterManager = RegisterManagerFn(Self, Register, &callee_preserved_regs);
@@ -308,6 +309,9 @@ pub fn generate(
 
     var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) {
         error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
+        error.OutOfRegisters => return FnResult{
+            .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
+        },
         else => |e| return e,
     };
     defer call_info.deinit(&function);
@@ -319,6 +323,9 @@ pub fn generate(
 
     function.gen() catch |err| switch (err) {
         error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
+        error.OutOfRegisters => return FnResult{
+            .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
+        },
         else => |e| return e,
     };
 
src/register_manager.zig
@@ -12,6 +12,17 @@ const expectEqualSlices = std.testing.expectEqualSlices;
 
 const log = std.log.scoped(.register_manager);
 
+pub const AllocateRegistersError = error{
+    /// No registers are available anymore
+    OutOfRegisters,
+    /// Can happen when spilling an instruction in codegen runs out of
+    /// memory, so we propagate that error
+    OutOfMemory,
+    /// Can happen when spilling an instruction triggers a codegen
+    /// error, so we propagate that error
+    CodegenFail,
+};
+
 pub fn RegisterManager(
     comptime Function: type,
     comptime Register: type,
@@ -168,8 +179,9 @@ pub fn RegisterManager(
             self: *Self,
             comptime count: comptime_int,
             insts: [count]?Air.Inst.Index,
-        ) ![count]Register {
+        ) 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;
 
             const result = self.tryAllocRegs(count, insts) orelse blk: {
                 // We'll take over the first count registers. Spill
@@ -214,14 +226,14 @@ pub fn RegisterManager(
 
         /// Allocates a register and optionally tracks it with a
         /// corresponding instruction.
-        pub fn allocReg(self: *Self, inst: ?Air.Inst.Index) !Register {
+        pub fn allocReg(self: *Self, inst: ?Air.Inst.Index) AllocateRegistersError!Register {
             return (try self.allocRegs(1, .{inst}))[0];
         }
 
         /// Spills the register if it is currently allocated. If a
         /// corresponding instruction is passed, will also track this
         /// register.
-        pub fn getReg(self: *Self, reg: Register, inst: ?Air.Inst.Index) !void {
+        pub fn getReg(self: *Self, reg: Register, inst: ?Air.Inst.Index) AllocateRegistersError!void {
             const index = reg.allocIndex() orelse return;
             self.markRegAllocated(reg);
 
@@ -317,6 +329,13 @@ fn MockFunction(comptime Register: type) type {
             _ = inst;
             try self.spilled.append(self.allocator, reg);
         }
+
+        pub fn genAdd(self: *Self, res: Register, lhs: Register, rhs: Register) !void {
+            _ = self;
+            _ = res;
+            _ = lhs;
+            _ = rhs;
+        }
     };
 }
 
@@ -431,7 +450,9 @@ test "tryAllocRegs" {
     try expect(function.register_manager.isRegAllocated(.r3));
 }
 
-test "allocRegs" {
+test "allocRegs: normal usage" {
+    // TODO: convert this into a decltest once that is supported
+
     const allocator = std.testing.allocator;
 
     var function = MockFunction2{
@@ -439,35 +460,57 @@ test "allocRegs" {
     };
     defer function.deinit();
 
-    const mock_instruction: Air.Inst.Index = 1;
+    {
+        const result_reg: MockRegister2 = .r1;
+
+        // The result register is known and fixed at this point, we
+        // don't want to accidentally allocate lhs or rhs to the
+        // result register, this is why we freeze it.
+        //
+        // Using defer unfreeze right after freeze is a good idea in
+        // most cases as you probably are using the frozen registers
+        // in the remainder of this scope and don't need to use it
+        // after the end of this scope. However, in some situations,
+        // it may make sense to manually unfreeze registers before the
+        // end of the scope when you are certain that they don't
+        // contain any valuable data anymore and can be reused. For an
+        // example of that, see `selectively reducing register
+        // pressure`.
+        function.register_manager.freezeRegs(&.{result_reg});
+        defer function.register_manager.unfreezeRegs(&.{result_reg});
+
+        const regs = try function.register_manager.allocRegs(2, .{ null, null });
+        try function.genAdd(result_reg, regs[0], regs[1]);
+    }
+}
 
-    try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, try function.register_manager.allocRegs(3, .{
-        mock_instruction,
-        mock_instruction,
-        mock_instruction,
-    }));
+test "allocRegs: selectively reducing register pressure" {
+    // TODO: convert this into a decltest once that is supported
 
-    try expect(function.register_manager.isRegAllocated(.r0));
-    try expect(function.register_manager.isRegAllocated(.r1));
-    try expect(function.register_manager.isRegAllocated(.r2));
-    try expect(!function.register_manager.isRegAllocated(.r3));
+    const allocator = std.testing.allocator;
+
+    var function = MockFunction2{
+        .allocator = allocator,
+    };
+    defer function.deinit();
 
-    // Frozen registers
-    function.register_manager.freeReg(.r0);
-    function.register_manager.freeReg(.r2);
-    function.register_manager.freeReg(.r3);
     {
-        function.register_manager.freezeRegs(&.{.r1});
-        defer function.register_manager.unfreezeRegs(&.{.r1});
+        const result_reg: MockRegister2 = .r1;
 
-        try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, try function.register_manager.allocRegs(3, .{ null, null, null }));
-    }
-    try expect(!function.register_manager.frozenRegsExist());
+        function.register_manager.freezeRegs(&.{result_reg});
+        defer function.register_manager.unfreezeRegs(&.{result_reg});
 
-    try expect(function.register_manager.isRegAllocated(.r0));
-    try expect(function.register_manager.isRegAllocated(.r1));
-    try expect(function.register_manager.isRegAllocated(.r2));
-    try expect(function.register_manager.isRegAllocated(.r3));
+        // Here, we don't defer unfreeze because we manually unfreeze
+        // after genAdd
+        const regs = try function.register_manager.allocRegs(2, .{ null, null });
+        function.register_manager.freezeRegs(&.{result_reg});
+
+        try function.genAdd(result_reg, regs[0], regs[1]);
+        function.register_manager.unfreezeRegs(&regs);
+
+        const extra_summand_reg = try function.register_manager.allocReg(null);
+        try function.genAdd(result_reg, result_reg, extra_summand_reg);
+    }
 }
 
 test "getReg" {