From 4b3b487627d71fa082b0316383344b49c95bab0e Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sat, 5 Feb 2022 17:13:11 +0100 Subject: [PATCH] stage2 regalloc: Introduce error.OutOfRegisters --- src/arch/aarch64/CodeGen.zig | 7 +++ src/arch/arm/CodeGen.zig | 7 +++ src/arch/riscv64/CodeGen.zig | 7 +++ src/arch/x86_64/CodeGen.zig | 7 +++ src/register_manager.zig | 101 +++++++++++++++++++++++++---------- 5 files changed, 100 insertions(+), 29 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 6202d2e74f..2b8c5e62d4 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/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, }; diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 3ab395f069..c87f750831 100644 --- a/src/arch/arm/CodeGen.zig +++ b/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, }; diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 9e850bd751..612ff78bd6 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/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, }; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index b3a292a7f4..e464dd3cf9 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/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, }; diff --git a/src/register_manager.zig b/src/register_manager.zig index 63a0efbad8..81c9fa5734 100644 --- a/src/register_manager.zig +++ b/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; - - try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, try function.register_manager.allocRegs(3, .{ - mock_instruction, - mock_instruction, - mock_instruction, - })); - - 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)); - - // 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 })); + // 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 expect(!function.register_manager.frozenRegsExist()); +} - 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)); +test "allocRegs: selectively reducing register pressure" { + // TODO: convert this into a decltest once that is supported + + const allocator = std.testing.allocator; + + var function = MockFunction2{ + .allocator = allocator, + }; + defer function.deinit(); + + { + const result_reg: MockRegister2 = .r1; + + function.register_manager.freezeRegs(&.{result_reg}); + defer function.register_manager.unfreezeRegs(&.{result_reg}); + + // 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(®s); + + const extra_summand_reg = try function.register_manager.allocReg(null); + try function.genAdd(result_reg, result_reg, extra_summand_reg); + } } test "getReg" {