| author | |
| committer | |
| log | 4b3b487627d71fa082b0316383344b49c95bab0e |
| tree | 39d03d14a6b0bd4dca62d0994ec7b7d14d1ef0ce |
| parent | d4c3475f3dce8ecf81ba2baaf97b6d25426919ec |
5 files changed, 98 insertions(+), 27 deletions(-)
src/arch/aarch64/CodeGen.zig+7| ... | @@ -31,6 +31,7 @@ const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; | ... | @@ -31,6 +31,7 @@ const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; |
| 31 | const InnerError = error{ | 31 | const InnerError = error{ |
| 32 | OutOfMemory, | 32 | OutOfMemory, |
| 33 | CodegenFail, | 33 | CodegenFail, |
| 34 | OutOfRegisters, | ||
| 34 | }; | 35 | }; |
| 35 | 36 | ||
| 36 | gpa: Allocator, | 37 | gpa: Allocator, |
| ... | @@ -274,6 +275,9 @@ pub fn generate( | ... | @@ -274,6 +275,9 @@ pub fn generate( |
| 274 | 275 | ||
| 275 | var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) { | 276 | var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) { |
| 276 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, | 277 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, |
| 278 | error.OutOfRegisters => return FnResult{ | ||
| 279 | .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), | ||
| 280 | }, | ||
| 277 | else => |e| return e, | 281 | else => |e| return e, |
| 278 | }; | 282 | }; |
| 279 | defer call_info.deinit(&function); | 283 | defer call_info.deinit(&function); |
| ... | @@ -285,6 +289,9 @@ pub fn generate( | ... | @@ -285,6 +289,9 @@ pub fn generate( |
| 285 | 289 | ||
| 286 | function.gen() catch |err| switch (err) { | 290 | function.gen() catch |err| switch (err) { |
| 287 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, | 291 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, |
| 292 | error.OutOfRegisters => return FnResult{ | ||
| 293 | .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), | ||
| 294 | }, | ||
| 288 | else => |e| return e, | 295 | else => |e| return e, |
| 289 | }; | 296 | }; |
| 290 | 297 |
src/arch/arm/CodeGen.zig+7| ... | @@ -31,6 +31,7 @@ const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; | ... | @@ -31,6 +31,7 @@ const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; |
| 31 | const InnerError = error{ | 31 | const InnerError = error{ |
| 32 | OutOfMemory, | 32 | OutOfMemory, |
| 33 | CodegenFail, | 33 | CodegenFail, |
| 34 | OutOfRegisters, | ||
| 34 | }; | 35 | }; |
| 35 | 36 | ||
| 36 | gpa: Allocator, | 37 | gpa: Allocator, |
| ... | @@ -279,6 +280,9 @@ pub fn generate( | ... | @@ -279,6 +280,9 @@ pub fn generate( |
| 279 | 280 | ||
| 280 | var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) { | 281 | var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) { |
| 281 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, | 282 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, |
| 283 | error.OutOfRegisters => return FnResult{ | ||
| 284 | .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), | ||
| 285 | }, | ||
| 282 | else => |e| return e, | 286 | else => |e| return e, |
| 283 | }; | 287 | }; |
| 284 | defer call_info.deinit(&function); | 288 | defer call_info.deinit(&function); |
| ... | @@ -290,6 +294,9 @@ pub fn generate( | ... | @@ -290,6 +294,9 @@ pub fn generate( |
| 290 | 294 | ||
| 291 | function.gen() catch |err| switch (err) { | 295 | function.gen() catch |err| switch (err) { |
| 292 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, | 296 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, |
| 297 | error.OutOfRegisters => return FnResult{ | ||
| 298 | .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), | ||
| 299 | }, | ||
| 293 | else => |e| return e, | 300 | else => |e| return e, |
| 294 | }; | 301 | }; |
| 295 | 302 |
src/arch/riscv64/CodeGen.zig+7| ... | @@ -31,6 +31,7 @@ const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; | ... | @@ -31,6 +31,7 @@ const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; |
| 31 | const InnerError = error{ | 31 | const InnerError = error{ |
| 32 | OutOfMemory, | 32 | OutOfMemory, |
| 33 | CodegenFail, | 33 | CodegenFail, |
| 34 | OutOfRegisters, | ||
| 34 | }; | 35 | }; |
| 35 | 36 | ||
| 36 | gpa: Allocator, | 37 | gpa: Allocator, |
| ... | @@ -280,6 +281,9 @@ pub fn generate( | ... | @@ -280,6 +281,9 @@ pub fn generate( |
| 280 | 281 | ||
| 281 | var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) { | 282 | var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) { |
| 282 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, | 283 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, |
| 284 | error.OutOfRegisters => return FnResult{ | ||
| 285 | .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), | ||
| 286 | }, | ||
| 283 | else => |e| return e, | 287 | else => |e| return e, |
| 284 | }; | 288 | }; |
| 285 | defer call_info.deinit(&function); | 289 | defer call_info.deinit(&function); |
| ... | @@ -291,6 +295,9 @@ pub fn generate( | ... | @@ -291,6 +295,9 @@ pub fn generate( |
| 291 | 295 | ||
| 292 | function.gen() catch |err| switch (err) { | 296 | function.gen() catch |err| switch (err) { |
| 293 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, | 297 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, |
| 298 | error.OutOfRegisters => return FnResult{ | ||
| 299 | .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), | ||
| 300 | }, | ||
| 294 | else => |e| return e, | 301 | else => |e| return e, |
| 295 | }; | 302 | }; |
| 296 | 303 |
src/arch/x86_64/CodeGen.zig+7| ... | @@ -31,6 +31,7 @@ const Zir = @import("../../Zir.zig"); | ... | @@ -31,6 +31,7 @@ const Zir = @import("../../Zir.zig"); |
| 31 | const InnerError = error{ | 31 | const InnerError = error{ |
| 32 | OutOfMemory, | 32 | OutOfMemory, |
| 33 | CodegenFail, | 33 | CodegenFail, |
| 34 | OutOfRegisters, | ||
| 34 | }; | 35 | }; |
| 35 | 36 | ||
| 36 | const RegisterManager = RegisterManagerFn(Self, Register, &callee_preserved_regs); | 37 | const RegisterManager = RegisterManagerFn(Self, Register, &callee_preserved_regs); |
| ... | @@ -308,6 +309,9 @@ pub fn generate( | ... | @@ -308,6 +309,9 @@ pub fn generate( |
| 308 | 309 | ||
| 309 | var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) { | 310 | var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) { |
| 310 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, | 311 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, |
| 312 | error.OutOfRegisters => return FnResult{ | ||
| 313 | .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), | ||
| 314 | }, | ||
| 311 | else => |e| return e, | 315 | else => |e| return e, |
| 312 | }; | 316 | }; |
| 313 | defer call_info.deinit(&function); | 317 | defer call_info.deinit(&function); |
| ... | @@ -319,6 +323,9 @@ pub fn generate( | ... | @@ -319,6 +323,9 @@ pub fn generate( |
| 319 | 323 | ||
| 320 | function.gen() catch |err| switch (err) { | 324 | function.gen() catch |err| switch (err) { |
| 321 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, | 325 | error.CodegenFail => return FnResult{ .fail = function.err_msg.? }, |
| 326 | error.OutOfRegisters => return FnResult{ | ||
| 327 | .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), | ||
| 328 | }, | ||
| 322 | else => |e| return e, | 329 | else => |e| return e, |
| 323 | }; | 330 | }; |
| 324 | 331 |
src/register_manager.zig+70-27| ... | @@ -12,6 +12,17 @@ const expectEqualSlices = std.testing.expectEqualSlices; | ... | @@ -12,6 +12,17 @@ const expectEqualSlices = std.testing.expectEqualSlices; |
| 12 | 12 | ||
| 13 | const log = std.log.scoped(.register_manager); | 13 | const log = std.log.scoped(.register_manager); |
| 14 | 14 | ||
| 15 | pub const AllocateRegistersError = error{ | ||
| 16 | /// No registers are available anymore | ||
| 17 | OutOfRegisters, | ||
| 18 | /// Can happen when spilling an instruction in codegen runs out of | ||
| 19 | /// memory, so we propagate that error | ||
| 20 | OutOfMemory, | ||
| 21 | /// Can happen when spilling an instruction triggers a codegen | ||
| 22 | /// error, so we propagate that error | ||
| 23 | CodegenFail, | ||
| 24 | }; | ||
| 25 | |||
| 15 | pub fn RegisterManager( | 26 | pub fn RegisterManager( |
| 16 | comptime Function: type, | 27 | comptime Function: type, |
| 17 | comptime Register: type, | 28 | comptime Register: type, |
| ... | @@ -168,8 +179,9 @@ pub fn RegisterManager( | ... | @@ -168,8 +179,9 @@ pub fn RegisterManager( |
| 168 | self: *Self, | 179 | self: *Self, |
| 169 | comptime count: comptime_int, | 180 | comptime count: comptime_int, |
| 170 | insts: [count]?Air.Inst.Index, | 181 | insts: [count]?Air.Inst.Index, |
| 171 | ) ![count]Register { | 182 | ) AllocateRegistersError![count]Register { |
| 172 | comptime assert(count > 0 and count <= callee_preserved_regs.len); | 183 | comptime assert(count > 0 and count <= callee_preserved_regs.len); |
| 184 | if (count > callee_preserved_regs.len - @popCount(FreeRegInt, self.frozen_registers)) return error.OutOfRegisters; | ||
| 173 | 185 | ||
| 174 | const result = self.tryAllocRegs(count, insts) orelse blk: { | 186 | const result = self.tryAllocRegs(count, insts) orelse blk: { |
| 175 | // We'll take over the first count registers. Spill | 187 | // We'll take over the first count registers. Spill |
| ... | @@ -214,14 +226,14 @@ pub fn RegisterManager( | ... | @@ -214,14 +226,14 @@ pub fn RegisterManager( |
| 214 | 226 | ||
| 215 | /// Allocates a register and optionally tracks it with a | 227 | /// Allocates a register and optionally tracks it with a |
| 216 | /// corresponding instruction. | 228 | /// corresponding instruction. |
| 217 | pub fn allocReg(self: *Self, inst: ?Air.Inst.Index) !Register { | 229 | pub fn allocReg(self: *Self, inst: ?Air.Inst.Index) AllocateRegistersError!Register { |
| 218 | return (try self.allocRegs(1, .{inst}))[0]; | 230 | return (try self.allocRegs(1, .{inst}))[0]; |
| 219 | } | 231 | } |
| 220 | 232 | ||
| 221 | /// Spills the register if it is currently allocated. If a | 233 | /// Spills the register if it is currently allocated. If a |
| 222 | /// corresponding instruction is passed, will also track this | 234 | /// corresponding instruction is passed, will also track this |
| 223 | /// register. | 235 | /// register. |
| 224 | pub fn getReg(self: *Self, reg: Register, inst: ?Air.Inst.Index) !void { | 236 | pub fn getReg(self: *Self, reg: Register, inst: ?Air.Inst.Index) AllocateRegistersError!void { |
| 225 | const index = reg.allocIndex() orelse return; | 237 | const index = reg.allocIndex() orelse return; |
| 226 | self.markRegAllocated(reg); | 238 | self.markRegAllocated(reg); |
| 227 | 239 | ||
| ... | @@ -317,6 +329,13 @@ fn MockFunction(comptime Register: type) type { | ... | @@ -317,6 +329,13 @@ fn MockFunction(comptime Register: type) type { |
| 317 | _ = inst; | 329 | _ = inst; |
| 318 | try self.spilled.append(self.allocator, reg); | 330 | try self.spilled.append(self.allocator, reg); |
| 319 | } | 331 | } |
| 332 | |||
| 333 | pub fn genAdd(self: *Self, res: Register, lhs: Register, rhs: Register) !void { | ||
| 334 | _ = self; | ||
| 335 | _ = res; | ||
| 336 | _ = lhs; | ||
| 337 | _ = rhs; | ||
| 338 | } | ||
| 320 | }; | 339 | }; |
| 321 | } | 340 | } |
| 322 | 341 | ||
| ... | @@ -431,7 +450,9 @@ test "tryAllocRegs" { | ... | @@ -431,7 +450,9 @@ test "tryAllocRegs" { |
| 431 | try expect(function.register_manager.isRegAllocated(.r3)); | 450 | try expect(function.register_manager.isRegAllocated(.r3)); |
| 432 | } | 451 | } |
| 433 | 452 | ||
| 434 | test "allocRegs" { | 453 | test "allocRegs: normal usage" { |
| 454 | // TODO: convert this into a decltest once that is supported | ||
| 455 | |||
| 435 | const allocator = std.testing.allocator; | 456 | const allocator = std.testing.allocator; |
| 436 | 457 | ||
| 437 | var function = MockFunction2{ | 458 | var function = MockFunction2{ |
| ... | @@ -439,35 +460,57 @@ test "allocRegs" { | ... | @@ -439,35 +460,57 @@ test "allocRegs" { |
| 439 | }; | 460 | }; |
| 440 | defer function.deinit(); | 461 | defer function.deinit(); |
| 441 | 462 | ||
| 442 | const mock_instruction: Air.Inst.Index = 1; | 463 | { |
| 464 | const result_reg: MockRegister2 = .r1; | ||
| 465 | |||
| 466 | // The result register is known and fixed at this point, we | ||
| 467 | // don't want to accidentally allocate lhs or rhs to the | ||
| 468 | // result register, this is why we freeze it. | ||
| 469 | // | ||
| 470 | // Using defer unfreeze right after freeze is a good idea in | ||
| 471 | // most cases as you probably are using the frozen registers | ||
| 472 | // in the remainder of this scope and don't need to use it | ||
| 473 | // after the end of this scope. However, in some situations, | ||
| 474 | // it may make sense to manually unfreeze registers before the | ||
| 475 | // end of the scope when you are certain that they don't | ||
| 476 | // contain any valuable data anymore and can be reused. For an | ||
| 477 | // example of that, see `selectively reducing register | ||
| 478 | // pressure`. | ||
| 479 | function.register_manager.freezeRegs(&.{result_reg}); | ||
| 480 | defer function.register_manager.unfreezeRegs(&.{result_reg}); | ||
| 481 | |||
| 482 | const regs = try function.register_manager.allocRegs(2, .{ null, null }); | ||
| 483 | try function.genAdd(result_reg, regs[0], regs[1]); | ||
| 484 | } | ||
| 485 | } | ||
| 443 | 486 | ||
| 444 | try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, try function.register_manager.allocRegs(3, .{ | 487 | test "allocRegs: selectively reducing register pressure" { |
| 445 | mock_instruction, | 488 | // TODO: convert this into a decltest once that is supported |
| 446 | mock_instruction, | ||
| 447 | mock_instruction, | ||
| 448 | })); | ||
| 449 | 489 | ||
| 450 | try expect(function.register_manager.isRegAllocated(.r0)); | 490 | const allocator = std.testing.allocator; |
| 451 | try expect(function.register_manager.isRegAllocated(.r1)); | 491 | |
| 452 | try expect(function.register_manager.isRegAllocated(.r2)); | 492 | var function = MockFunction2{ |
| 453 | try expect(!function.register_manager.isRegAllocated(.r3)); | 493 | .allocator = allocator, |
| 494 | }; | ||
| 495 | defer function.deinit(); | ||
| 454 | 496 | ||
| 455 | // Frozen registers | ||
| 456 | function.register_manager.freeReg(.r0); | ||
| 457 | function.register_manager.freeReg(.r2); | ||
| 458 | function.register_manager.freeReg(.r3); | ||
| 459 | { | 497 | { |
| 460 | function.register_manager.freezeRegs(&.{.r1}); | 498 | const result_reg: MockRegister2 = .r1; |
| 461 | defer function.register_manager.unfreezeRegs(&.{.r1}); | ||
| 462 | 499 | ||
| 463 | try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, try function.register_manager.allocRegs(3, .{ null, null, null })); | 500 | function.register_manager.freezeRegs(&.{result_reg}); |
| 464 | } | 501 | defer function.register_manager.unfreezeRegs(&.{result_reg}); |
| 465 | try expect(!function.register_manager.frozenRegsExist()); | ||
| 466 | 502 | ||
| 467 | try expect(function.register_manager.isRegAllocated(.r0)); | 503 | // Here, we don't defer unfreeze because we manually unfreeze |
| 468 | try expect(function.register_manager.isRegAllocated(.r1)); | 504 | // after genAdd |
| 469 | try expect(function.register_manager.isRegAllocated(.r2)); | 505 | const regs = try function.register_manager.allocRegs(2, .{ null, null }); |
| 470 | try expect(function.register_manager.isRegAllocated(.r3)); | 506 | function.register_manager.freezeRegs(&.{result_reg}); |
| 507 | |||
| 508 | try function.genAdd(result_reg, regs[0], regs[1]); | ||
| 509 | function.register_manager.unfreezeRegs(&regs); | ||
| 510 | |||
| 511 | const extra_summand_reg = try function.register_manager.allocReg(null); | ||
| 512 | try function.genAdd(result_reg, result_reg, extra_summand_reg); | ||
| 513 | } | ||
| 471 | } | 514 | } |
| 472 | 515 | ||
| 473 | test "getReg" { | 516 | test "getReg" { |