| ... | ... | @@ -12,6 +12,17 @@ const expectEqualSlices = std.testing.expectEqualSlices; |
| 12 | 12 | |
| 13 | 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 | 26 | pub fn RegisterManager( |
| 16 | 27 | comptime Function: type, |
| 17 | 28 | comptime Register: type, |
| ... | ... | @@ -168,8 +179,9 @@ pub fn RegisterManager( |
| 168 | 179 | self: *Self, |
| 169 | 180 | comptime count: comptime_int, |
| 170 | 181 | insts: [count]?Air.Inst.Index, |
| 171 | | ) ![count]Register { |
| 182 | ) AllocateRegistersError![count]Register { |
| 172 | 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 | 186 | const result = self.tryAllocRegs(count, insts) orelse blk: { |
| 175 | 187 | // We'll take over the first count registers. Spill |
| ... | ... | @@ -214,14 +226,14 @@ pub fn RegisterManager( |
| 214 | 226 | |
| 215 | 227 | /// Allocates a register and optionally tracks it with a |
| 216 | 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 | 230 | return (try self.allocRegs(1, .{inst}))[0]; |
| 219 | 231 | } |
| 220 | 232 | |
| 221 | 233 | /// Spills the register if it is currently allocated. If a |
| 222 | 234 | /// corresponding instruction is passed, will also track this |
| 223 | 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 | 237 | const index = reg.allocIndex() orelse return; |
| 226 | 238 | self.markRegAllocated(reg); |
| 227 | 239 | |
| ... | ... | @@ -317,6 +329,13 @@ fn MockFunction(comptime Register: type) type { |
| 317 | 329 | _ = inst; |
| 318 | 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 | 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 | 456 | const allocator = std.testing.allocator; |
| 436 | 457 | |
| 437 | 458 | var function = MockFunction2{ |
| ... | ... | @@ -439,35 +460,57 @@ test "allocRegs" { |
| 439 | 460 | }; |
| 440 | 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, .{ |
| 445 | | mock_instruction, |
| 446 | | mock_instruction, |
| 447 | | mock_instruction, |
| 448 | | })); |
| 487 | test "allocRegs: selectively reducing register pressure" { |
| 488 | // TODO: convert this into a decltest once that is supported |
| 449 | 489 | |
| 450 | | try expect(function.register_manager.isRegAllocated(.r0)); |
| 451 | | try expect(function.register_manager.isRegAllocated(.r1)); |
| 452 | | try expect(function.register_manager.isRegAllocated(.r2)); |
| 453 | | try expect(!function.register_manager.isRegAllocated(.r3)); |
| 490 | const allocator = std.testing.allocator; |
| 491 | |
| 492 | var function = MockFunction2{ |
| 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}); |
| 461 | | defer function.register_manager.unfreezeRegs(&.{.r1}); |
| 498 | const result_reg: MockRegister2 = .r1; |
| 462 | 499 | |
| 463 | | try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, try function.register_manager.allocRegs(3, .{ null, null, null })); |
| 464 | | } |
| 465 | | try expect(!function.register_manager.frozenRegsExist()); |
| 500 | function.register_manager.freezeRegs(&.{result_reg}); |
| 501 | defer function.register_manager.unfreezeRegs(&.{result_reg}); |
| 466 | 502 | |
| 467 | | try expect(function.register_manager.isRegAllocated(.r0)); |
| 468 | | try expect(function.register_manager.isRegAllocated(.r1)); |
| 469 | | try expect(function.register_manager.isRegAllocated(.r2)); |
| 470 | | try expect(function.register_manager.isRegAllocated(.r3)); |
| 503 | // Here, we don't defer unfreeze because we manually unfreeze |
| 504 | // after genAdd |
| 505 | const regs = try function.register_manager.allocRegs(2, .{ null, null }); |
| 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 | 516 | test "getReg" { |