| ... | @@ -691,6 +691,14 @@ const MockRegister1 = enum(u2) { | ... | @@ -691,6 +691,14 @@ const MockRegister1 = enum(u2) { |
| 691 | } | 691 | } |
| 692 | | 692 | |
| 693 | const allocatable_registers = [_]MockRegister1{ .r2, .r3 }; | 693 | const allocatable_registers = [_]MockRegister1{ .r2, .r3 }; |
| | 694 | |
| | 695 | const RM = RegisterManager( |
| | 696 | MockFunction1, |
| | 697 | MockRegister1, |
| | 698 | &MockRegister1.allocatable_registers, |
| | 699 | ); |
| | 700 | |
| | 701 | const gp: RM.RegisterBitSet = std.math.maxInt(RM.RegisterBitSet); |
| 694 | }; | 702 | }; |
| 695 | | 703 | |
| 696 | const MockRegister2 = enum(u2) { | 704 | const MockRegister2 = enum(u2) { |
| ... | @@ -704,20 +712,62 @@ const MockRegister2 = enum(u2) { | ... | @@ -704,20 +712,62 @@ const MockRegister2 = enum(u2) { |
| 704 | } | 712 | } |
| 705 | | 713 | |
| 706 | const allocatable_registers = [_]MockRegister2{ .r0, .r1, .r2, .r3 }; | 714 | const allocatable_registers = [_]MockRegister2{ .r0, .r1, .r2, .r3 }; |
| | 715 | |
| | 716 | const RM = RegisterManager( |
| | 717 | MockFunction2, |
| | 718 | MockRegister2, |
| | 719 | &MockRegister2.allocatable_registers, |
| | 720 | ); |
| | 721 | |
| | 722 | const gp: RM.RegisterBitSet = std.math.maxInt(RM.RegisterBitSet); |
| | 723 | }; |
| | 724 | |
| | 725 | const MockRegister3 = enum(u3) { |
| | 726 | r0, |
| | 727 | r1, |
| | 728 | r2, |
| | 729 | r3, |
| | 730 | x0, |
| | 731 | x1, |
| | 732 | x2, |
| | 733 | x3, |
| | 734 | |
| | 735 | pub fn id(reg: MockRegister3) u3 { |
| | 736 | return switch (@enumToInt(reg)) { |
| | 737 | 0...3 => @as(u3, @truncate(u2, @enumToInt(reg))), |
| | 738 | 4...7 => @enumToInt(reg), |
| | 739 | }; |
| | 740 | } |
| | 741 | |
| | 742 | pub fn enc(reg: MockRegister3) u2 { |
| | 743 | return @truncate(u2, @enumToInt(reg)); |
| | 744 | } |
| | 745 | |
| | 746 | const gp_regs = [_]MockRegister3{ .r0, .r1, .r2, .r3 }; |
| | 747 | const ext_regs = [_]MockRegister3{ .x0, .x1, .x2, .x3 }; |
| | 748 | const allocatable_registers = gp_regs ++ ext_regs; |
| | 749 | |
| | 750 | const RM = RegisterManager( |
| | 751 | MockFunction3, |
| | 752 | MockRegister3, |
| | 753 | &MockRegister3.allocatable_registers, |
| | 754 | ); |
| | 755 | |
| | 756 | const gp: RM.RegisterBitSet = @as(RM.RegisterBitSet, std.math.maxInt(std.meta.Int( |
| | 757 | .unsigned, |
| | 758 | gp_regs.len, |
| | 759 | ))); |
| | 760 | const ext: RM.RegisterBitSet = std.math.maxInt(RM.RegisterBitSet) - gp; |
| 707 | }; | 761 | }; |
| 708 | | 762 | |
| 709 | fn MockFunction(comptime Register: type) type { | 763 | fn MockFunction(comptime Register: type) type { |
| 710 | return struct { | 764 | return struct { |
| 711 | allocator: Allocator, | 765 | allocator: Allocator, |
| 712 | register_manager: RegisterManagerT = .{}, | 766 | register_manager: Register.RM = .{}, |
| 713 | spilled: std.ArrayListUnmanaged(Register) = .{}, | 767 | spilled: std.ArrayListUnmanaged(Register) = .{}, |
| 714 | | 768 | |
| 715 | const Self = @This(); | 769 | const Self = @This(); |
| 716 | | 770 | |
| 717 | const RegisterManagerT = RegisterManager(Self, Register, &Register.allocatable_registers); | | |
| 718 | | | |
| 719 | pub const reg_class: RegisterManagerT.RegisterBitSet = math.maxInt(RegisterManagerT.RegisterBitSet); | | |
| 720 | | | |
| 721 | pub fn deinit(self: *Self) void { | 771 | pub fn deinit(self: *Self) void { |
| 722 | self.spilled.deinit(self.allocator); | 772 | self.spilled.deinit(self.allocator); |
| 723 | } | 773 | } |
| ... | @@ -738,6 +788,7 @@ fn MockFunction(comptime Register: type) type { | ... | @@ -738,6 +788,7 @@ fn MockFunction(comptime Register: type) type { |
| 738 | | 788 | |
| 739 | const MockFunction1 = MockFunction(MockRegister1); | 789 | const MockFunction1 = MockFunction(MockRegister1); |
| 740 | const MockFunction2 = MockFunction(MockRegister2); | 790 | const MockFunction2 = MockFunction(MockRegister2); |
| | 791 | const MockFunction3 = MockFunction(MockRegister3); |
| 741 | | 792 | |
| 742 | test "default state" { | 793 | test "default state" { |
| 743 | const allocator = std.testing.allocator; | 794 | const allocator = std.testing.allocator; |
| ... | @@ -762,20 +813,11 @@ test "tryAllocReg: no spilling" { | ... | @@ -762,20 +813,11 @@ test "tryAllocReg: no spilling" { |
| 762 | defer function.deinit(); | 813 | defer function.deinit(); |
| 763 | | 814 | |
| 764 | const mock_instruction: Air.Inst.Index = 1; | 815 | const mock_instruction: Air.Inst.Index = 1; |
| 765 | const reg_class = MockFunction1.reg_class; | 816 | const gp = MockRegister1.gp; |
| 766 | | 817 | |
| 767 | try expectEqual(@as(?MockRegister1, .r2), function.register_manager.tryAllocReg( | 818 | try expectEqual(@as(?MockRegister1, .r2), function.register_manager.tryAllocReg(mock_instruction, gp)); |
| 768 | mock_instruction, | 819 | try expectEqual(@as(?MockRegister1, .r3), function.register_manager.tryAllocReg(mock_instruction, gp)); |
| 769 | reg_class, | 820 | try expectEqual(@as(?MockRegister1, null), function.register_manager.tryAllocReg(mock_instruction, gp)); |
| 770 | )); | | |
| 771 | try expectEqual(@as(?MockRegister1, .r3), function.register_manager.tryAllocReg( | | |
| 772 | mock_instruction, | | |
| 773 | reg_class, | | |
| 774 | )); | | |
| 775 | try expectEqual(@as(?MockRegister1, null), function.register_manager.tryAllocReg( | | |
| 776 | mock_instruction, | | |
| 777 | reg_class, | | |
| 778 | )); | | |
| 779 | | 821 | |
| 780 | try expect(function.register_manager.isRegAllocated(.r2)); | 822 | try expect(function.register_manager.isRegAllocated(.r2)); |
| 781 | try expect(function.register_manager.isRegAllocated(.r3)); | 823 | try expect(function.register_manager.isRegAllocated(.r3)); |
| ... | @@ -800,30 +842,18 @@ test "allocReg: spilling" { | ... | @@ -800,30 +842,18 @@ test "allocReg: spilling" { |
| 800 | defer function.deinit(); | 842 | defer function.deinit(); |
| 801 | | 843 | |
| 802 | const mock_instruction: Air.Inst.Index = 1; | 844 | const mock_instruction: Air.Inst.Index = 1; |
| 803 | const reg_class = MockFunction1.reg_class; | 845 | const gp = MockRegister1.gp; |
| 804 | | 846 | |
| 805 | try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg( | 847 | try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction, gp)); |
| 806 | mock_instruction, | 848 | try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, gp)); |
| 807 | reg_class, | | |
| 808 | )); | | |
| 809 | try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg( | | |
| 810 | mock_instruction, | | |
| 811 | reg_class, | | |
| 812 | )); | | |
| 813 | | 849 | |
| 814 | // Spill a register | 850 | // Spill a register |
| 815 | try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg( | 851 | try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction, gp)); |
| 816 | mock_instruction, | | |
| 817 | reg_class, | | |
| 818 | )); | | |
| 819 | try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items); | 852 | try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items); |
| 820 | | 853 | |
| 821 | // No spilling necessary | 854 | // No spilling necessary |
| 822 | function.register_manager.freeReg(.r3); | 855 | function.register_manager.freeReg(.r3); |
| 823 | try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg( | 856 | try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, gp)); |
| 824 | mock_instruction, | | |
| 825 | reg_class, | | |
| 826 | )); | | |
| 827 | try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items); | 857 | try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items); |
| 828 | | 858 | |
| 829 | // Locked registers | 859 | // Locked registers |
| ... | @@ -832,10 +862,7 @@ test "allocReg: spilling" { | ... | @@ -832,10 +862,7 @@ test "allocReg: spilling" { |
| 832 | const lock = function.register_manager.lockReg(.r2); | 862 | const lock = function.register_manager.lockReg(.r2); |
| 833 | defer if (lock) |reg| function.register_manager.unlockReg(reg); | 863 | defer if (lock) |reg| function.register_manager.unlockReg(reg); |
| 834 | | 864 | |
| 835 | try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg( | 865 | try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, gp)); |
| 836 | mock_instruction, | | |
| 837 | reg_class, | | |
| 838 | )); | | |
| 839 | } | 866 | } |
| 840 | try expect(!function.register_manager.lockedRegsExist()); | 867 | try expect(!function.register_manager.lockedRegsExist()); |
| 841 | } | 868 | } |
| ... | @@ -848,13 +875,13 @@ test "tryAllocRegs" { | ... | @@ -848,13 +875,13 @@ test "tryAllocRegs" { |
| 848 | }; | 875 | }; |
| 849 | defer function.deinit(); | 876 | defer function.deinit(); |
| 850 | | 877 | |
| 851 | const reg_class = MockFunction2.reg_class; | 878 | const gp = MockRegister2.gp; |
| 852 | | 879 | |
| 853 | try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, function.register_manager.tryAllocRegs( | 880 | try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, function.register_manager.tryAllocRegs(3, .{ |
| 854 | 3, | 881 | null, |
| 855 | .{ null, null, null }, | 882 | null, |
| 856 | reg_class, | 883 | null, |
| 857 | ).?); | 884 | }, gp).?); |
| 858 | | 885 | |
| 859 | try expect(function.register_manager.isRegAllocated(.r0)); | 886 | try expect(function.register_manager.isRegAllocated(.r0)); |
| 860 | try expect(function.register_manager.isRegAllocated(.r1)); | 887 | try expect(function.register_manager.isRegAllocated(.r1)); |
| ... | @@ -869,11 +896,11 @@ test "tryAllocRegs" { | ... | @@ -869,11 +896,11 @@ test "tryAllocRegs" { |
| 869 | const lock = function.register_manager.lockReg(.r1); | 896 | const lock = function.register_manager.lockReg(.r1); |
| 870 | defer if (lock) |reg| function.register_manager.unlockReg(reg); | 897 | defer if (lock) |reg| function.register_manager.unlockReg(reg); |
| 871 | | 898 | |
| 872 | try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, function.register_manager.tryAllocRegs( | 899 | try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, function.register_manager.tryAllocRegs(3, .{ |
| 873 | 3, | 900 | null, |
| 874 | .{ null, null, null }, | 901 | null, |
| 875 | reg_class, | 902 | null, |
| 876 | ).?); | 903 | }, gp).?); |
| 877 | } | 904 | } |
| 878 | try expect(!function.register_manager.lockedRegsExist()); | 905 | try expect(!function.register_manager.lockedRegsExist()); |
| 879 | | 906 | |
| ... | @@ -893,7 +920,7 @@ test "allocRegs: normal usage" { | ... | @@ -893,7 +920,7 @@ test "allocRegs: normal usage" { |
| 893 | }; | 920 | }; |
| 894 | defer function.deinit(); | 921 | defer function.deinit(); |
| 895 | | 922 | |
| 896 | const reg_class = MockFunction2.reg_class; | 923 | const gp = MockRegister2.gp; |
| 897 | | 924 | |
| 898 | { | 925 | { |
| 899 | const result_reg: MockRegister2 = .r1; | 926 | const result_reg: MockRegister2 = .r1; |
| ... | @@ -914,7 +941,7 @@ test "allocRegs: normal usage" { | ... | @@ -914,7 +941,7 @@ test "allocRegs: normal usage" { |
| 914 | const lock = function.register_manager.lockReg(result_reg); | 941 | const lock = function.register_manager.lockReg(result_reg); |
| 915 | defer if (lock) |reg| function.register_manager.unlockReg(reg); | 942 | defer if (lock) |reg| function.register_manager.unlockReg(reg); |
| 916 | | 943 | |
| 917 | const regs = try function.register_manager.allocRegs(2, .{ null, null }, reg_class); | 944 | const regs = try function.register_manager.allocRegs(2, .{ null, null }, gp); |
| 918 | try function.genAdd(result_reg, regs[0], regs[1]); | 945 | try function.genAdd(result_reg, regs[0], regs[1]); |
| 919 | } | 946 | } |
| 920 | } | 947 | } |
| ... | @@ -929,7 +956,7 @@ test "allocRegs: selectively reducing register pressure" { | ... | @@ -929,7 +956,7 @@ test "allocRegs: selectively reducing register pressure" { |
| 929 | }; | 956 | }; |
| 930 | defer function.deinit(); | 957 | defer function.deinit(); |
| 931 | | 958 | |
| 932 | const reg_class = MockFunction2.reg_class; | 959 | const gp = MockRegister2.gp; |
| 933 | | 960 | |
| 934 | { | 961 | { |
| 935 | const result_reg: MockRegister2 = .r1; | 962 | const result_reg: MockRegister2 = .r1; |
| ... | @@ -938,12 +965,12 @@ test "allocRegs: selectively reducing register pressure" { | ... | @@ -938,12 +965,12 @@ test "allocRegs: selectively reducing register pressure" { |
| 938 | | 965 | |
| 939 | // Here, we don't defer unlock because we manually unlock | 966 | // Here, we don't defer unlock because we manually unlock |
| 940 | // after genAdd | 967 | // after genAdd |
| 941 | const regs = try function.register_manager.allocRegs(2, .{ null, null }, reg_class); | 968 | const regs = try function.register_manager.allocRegs(2, .{ null, null }, gp); |
| 942 | | 969 | |
| 943 | try function.genAdd(result_reg, regs[0], regs[1]); | 970 | try function.genAdd(result_reg, regs[0], regs[1]); |
| 944 | function.register_manager.unlockReg(lock.?); | 971 | function.register_manager.unlockReg(lock.?); |
| 945 | | 972 | |
| 946 | const extra_summand_reg = try function.register_manager.allocReg(null, reg_class); | 973 | const extra_summand_reg = try function.register_manager.allocReg(null, gp); |
| 947 | try function.genAdd(result_reg, result_reg, extra_summand_reg); | 974 | try function.genAdd(result_reg, result_reg, extra_summand_reg); |
| 948 | } | 975 | } |
| 949 | } | 976 | } |
| ... | @@ -974,3 +1001,39 @@ test "getReg" { | ... | @@ -974,3 +1001,39 @@ test "getReg" { |
| 974 | try expect(!function.register_manager.isRegFree(.r3)); | 1001 | try expect(!function.register_manager.isRegFree(.r3)); |
| 975 | try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r3}, function.spilled.items); | 1002 | try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r3}, function.spilled.items); |
| 976 | } | 1003 | } |
| | 1004 | |
| | 1005 | test "allocReg with multiple, non-overlapping register classes" { |
| | 1006 | const allocator = std.testing.allocator; |
| | 1007 | |
| | 1008 | var function = MockFunction3{ |
| | 1009 | .allocator = allocator, |
| | 1010 | }; |
| | 1011 | defer function.deinit(); |
| | 1012 | |
| | 1013 | const gp = MockRegister3.gp; |
| | 1014 | const ext = MockRegister3.ext; |
| | 1015 | |
| | 1016 | const gp_reg = try function.register_manager.allocReg(null, gp); |
| | 1017 | |
| | 1018 | try expect(function.register_manager.isRegAllocated(.r0)); |
| | 1019 | try expect(!function.register_manager.isRegAllocated(.x0)); |
| | 1020 | |
| | 1021 | const ext_reg = try function.register_manager.allocReg(null, ext); |
| | 1022 | |
| | 1023 | try expect(function.register_manager.isRegAllocated(.r0)); |
| | 1024 | try expect(!function.register_manager.isRegAllocated(.r1)); |
| | 1025 | try expect(function.register_manager.isRegAllocated(.x0)); |
| | 1026 | try expect(!function.register_manager.isRegAllocated(.x1)); |
| | 1027 | try expect(gp_reg.enc() == ext_reg.enc()); |
| | 1028 | |
| | 1029 | const ext_lock = function.register_manager.lockRegAssumeUnused(ext_reg); |
| | 1030 | defer function.register_manager.unlockReg(ext_lock); |
| | 1031 | |
| | 1032 | const ext_reg2 = try function.register_manager.allocReg(null, ext); |
| | 1033 | |
| | 1034 | try expect(function.register_manager.isRegAllocated(.r0)); |
| | 1035 | try expect(function.register_manager.isRegAllocated(.x0)); |
| | 1036 | try expect(!function.register_manager.isRegAllocated(.r1)); |
| | 1037 | try expect(function.register_manager.isRegAllocated(.x1)); |
| | 1038 | try expect(ext_reg2.enc() == MockRegister3.r1.enc()); |
| | 1039 | } |