╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ register_manager.zig ─────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
473 var set: RM.RegisterBitSet = .empty; 474 set.setRangeValue(.{ 475 .start = gp_regs.len, 476 .end = allocatable_registers.len, 477 }, true); 478 break :blk set; 479 }; 480 }; 481 482 fn MockFunction(comptime Register: type) type { 483 return struct { 484 allocator: Allocator, 485 register_manager: Register.RM = .{}, 486 spilled: std.ArrayList(Register) = .empty, 487 488 const Self = @This(); 489 490 pub fn deinit(self: *Self) void { 491 self.spilled.deinit(self.allocator); 492 } 493 494 pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void { 495 _ = inst; 496 try self.spilled.append(self.allocator, reg); 497 } 498 499 pub fn genAdd(self: *Self, res: Register, lhs: Register, rhs: Register) !void { 500 _ = self; 501 _ = res; 502 _ = lhs; 503 _ = rhs; 504 } 505 }; 506 } 507 508 const MockFunction1 = MockFunction(MockRegister1); 509 const MockFunction2 = MockFunction(MockRegister2); 510 const MockFunction3 = MockFunction(MockRegister3); 511 512 test "default state" { 513 const allocator = std.testing.allocator; 514 515 var function = MockFunction1{ 516 .allocator = allocator, 517 }; 518 defer function.deinit(); 519 520 try expect(!function.register_manager.isRegAllocated(.r2)); 521 try expect(!function.register_manager.isRegAllocated(.r3)); 522 try expect(function.register_manager.isRegFree(.r2)); 523 try expect(function.register_manager.isRegFree(.r3)); 524 } 525 526 test "tryAllocReg: no spilling" { 527 const allocator = std.testing.allocator; 528 529 var function = MockFunction1{ 530 .allocator = allocator, 531 }; 532 defer function.deinit(); 533 534 const mock_instruction: Air.Inst.Index = 1; 535 const gp = MockRegister1.gp; 536 537 try expectEqual(@as(?MockRegister1, .r2), function.register_manager.tryAllocReg(mock_instruction, gp)); 538 try expectEqual(@as(?MockRegister1, .r3), function.register_manager.tryAllocReg(mock_instruction, gp)); 539 try expectEqual(@as(?MockRegister1, null), function.register_manager.tryAllocReg(mock_instruction, gp)); 540 541 try expect(function.register_manager.isRegAllocated(.r2)); 542 try expect(function.register_manager.isRegAllocated(.r3)); 543 try expect(!function.register_manager.isRegFree(.r2)); 544 try expect(!function.register_manager.isRegFree(.r3)); 545 546 function.register_manager.freeReg(.r2); 547 function.register_manager.freeReg(.r3); 548 549 try expect(function.register_manager.isRegAllocated(.r2)); 550 try expect(function.register_manager.isRegAllocated(.r3)); 551 try expect(function.register_manager.isRegFree(.r2)); 552 try expect(function.register_manager.isRegFree(.r3)); 553 } 554 555 test "allocReg: spilling" { 556 const allocator = std.testing.allocator; 557 558 var function = MockFunction1{ 559 .allocator = allocator, 560 }; 561 defer function.deinit(); 562 563 const mock_instruction: Air.Inst.Index = 1; 564 const gp = MockRegister1.gp; 565 566 try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction, gp)); 567 try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, gp)); 568 569 // Spill a register 570 try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction, gp)); 571 try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items); 572 573 // No spilling necessary 574 function.register_manager.freeReg(.r3); 575 try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, gp)); 576 try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items); 577 578 // Locked registers 579 function.register_manager.freeReg(.r3); 580 { 581 const lock = function.register_manager.lockReg(.r2); 582 defer if (lock) |reg| function.register_manager.unlockReg(reg); 583 584 try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, gp)); 585 } 586 try expect(!function.register_manager.lockedRegsExist()); 587 } 588 589 test "tryAllocRegs" { 590 const allocator = std.testing.allocator; 591 592 var function = MockFunction2{ 593 .allocator = allocator, 594 }; 595 defer function.deinit(); 596 597 const gp = MockRegister2.gp; 598 599 try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, function.register_manager.tryAllocRegs(3, .{ 600 null, 601 null, 602 null, 603 }, gp).?); 604 605 try expect(function.register_manager.isRegAllocated(.r0)); 606 try expect(function.register_manager.isRegAllocated(.r1)); 607 try expect(function.register_manager.isRegAllocated(.r2)); 608 try expect(!function.register_manager.isRegAllocated(.r3)); 609 610 // Locked registers 611 function.register_manager.freeReg(.r0); 612 function.register_manager.freeReg(.r2); 613 function.register_manager.freeReg(.r3); 614 { 615 const lock = function.register_manager.lockReg(.r1); 616 defer if (lock) |reg| function.register_manager.unlockReg(reg); 617 618 try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, function.register_manager.tryAllocRegs(3, .{ 619 null, 620 null, 621 null, 622 }, gp).?); 623 } 624 try expect(!function.register_manager.lockedRegsExist()); 625 626 try expect(function.register_manager.isRegAllocated(.r0)); 627 try expect(function.register_manager.isRegAllocated(.r1)); 628 try expect(function.register_manager.isRegAllocated(.r2)); 629 try expect(function.register_manager.isRegAllocated(.r3)); 630 } 631 632 test "allocRegs: normal usage" { 633 // TODO: convert this into a decltest once that is supported 634 635 const allocator = std.testing.allocator; 636 637 var function = MockFunction2{ 638 .allocator = allocator, 639 }; 640 defer function.deinit(); 641 642 const gp = MockRegister2.gp; 643 644 { 645 const result_reg: MockRegister2 = .r1; 646 647 // The result register is known and fixed at this point, we 648 // don't want to accidentally allocate lhs or rhs to the 649 // result register, this is why we lock it. 650 // 651 // Using defer unlock right after lock is a good idea in 652 // most cases as you probably are using the locked registers 653 // in the remainder of this scope and don't need to use it 654 // after the end of this scope. However, in some situations, 655 // it may make sense to manually unlock registers before the 656 // end of the scope when you are certain that they don't 657 // contain any valuable data anymore and can be reused. For an 658 // example of that, see `selectively reducing register 659 // pressure`. 660 const lock = function.register_manager.lockReg(result_reg); 661 defer if (lock) |reg| function.register_manager.unlockReg(reg); 662 663 const regs = try function.register_manager.allocRegs(2, .{ null, null }, gp); 664 try function.genAdd(result_reg, regs[0], regs[1]); 665 } 666 } 667 668 test "allocRegs: selectively reducing register pressure" { 669 // TODO: convert this into a decltest once that is supported 670 671 const allocator = std.testing.allocator; 672 673 var function = MockFunction2{ 674 .allocator = allocator, 675 }; 676 defer function.deinit(); 677 678 const gp = MockRegister2.gp; 679 680 { 681 const result_reg: MockRegister2 = .r1; 682 683 const lock = function.register_manager.lockReg(result_reg); 684 685 // Here, we don't defer unlock because we manually unlock 686 // after genAdd 687 const regs = try function.register_manager.allocRegs(2, .{ null, null }, gp); 688 689 try function.genAdd(result_reg, regs[0], regs[1]); 690 function.register_manager.unlockReg(lock.?); 691 692 const extra_summand_reg = try function.register_manager.allocReg(null, gp); 693 try function.genAdd(result_reg, result_reg, extra_summand_reg); 694 } 695 } 696 697 test "getReg" { 698 const allocator = std.testing.allocator; 699 700 var function = MockFunction1{ 701 .allocator = allocator, 702 }; 703 defer function.deinit(); 704 705 const mock_instruction: Air.Inst.Index = 1; 706 707 try function.register_manager.getReg(.r3, mock_instruction); 708 709 try expect(!function.register_manager.isRegAllocated(.r2)); 710 try expect(function.register_manager.isRegAllocated(.r3)); 711 try expect(function.register_manager.isRegFree(.r2)); 712 try expect(!function.register_manager.isRegFree(.r3)); 713 714 // Spill r3 715 try function.register_manager.getReg(.r3, mock_instruction); 716 717 try expect(!function.register_manager.isRegAllocated(.r2)); 718 try expect(function.register_manager.isRegAllocated(.r3)); 719 try expect(function.register_manager.isRegFree(.r2)); 720 try expect(!function.register_manager.isRegFree(.r3)); 721 try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r3}, function.spilled.items); 722 } 723 724 test "allocReg with multiple, non-overlapping register classes" { 725 const allocator = std.testing.allocator; 726 727 var function = MockFunction3{ 728 .allocator = allocator, 729 }; 730 defer function.deinit(); 731 732 const gp = MockRegister3.gp; 733 const ext = MockRegister3.ext; 734 735 const gp_reg = try function.register_manager.allocReg(null, gp); 736 737 try expect(function.register_manager.isRegAllocated(.r0)); 738 try expect(!function.register_manager.isRegAllocated(.x0)); 739 740 const ext_reg = try function.register_manager.allocReg(null, ext); 741 742 try expect(function.register_manager.isRegAllocated(.r0)); 743 try expect(!function.register_manager.isRegAllocated(.r1)); 744 try expect(function.register_manager.isRegAllocated(.x0)); 745 try expect(!function.register_manager.isRegAllocated(.x1)); 746 try expect(gp_reg.enc() == ext_reg.enc()); 747 748 const ext_lock = function.register_manager.lockRegAssumeUnused(ext_reg); 749 defer function.register_manager.unlockReg(ext_lock); 750 751 const ext_reg2 = try function.register_manager.allocReg(null, ext); 752 753 try expect(function.register_manager.isRegAllocated(.r0)); 754 try expect(function.register_manager.isRegAllocated(.x0)); 755 try expect(!function.register_manager.isRegAllocated(.r1)); 756 try expect(function.register_manager.isRegAllocated(.x1)); 757 try expect(ext_reg2.enc() == MockRegister3.r1.enc()); 758 } 759