TPDE
Loading...
Searching...
No Matches
RegisterFile.hpp
1// SPDX-FileCopyrightText: 2025 Contributors to TPDE <https://tpde.org>
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4#pragma once
5
6#include "tpde/ValLocalIdx.hpp"
7#include "tpde/base.hpp"
8#include "tpde/util/misc.hpp"
9
10#include <array>
11
12namespace tpde {
13
14struct Reg {
15 u8 reg_id;
16
17 explicit constexpr Reg(const u64 id) : reg_id(static_cast<u8>(id)) {
18 assert(id <= 255);
19 }
20
21 constexpr u8 id() const { return reg_id; }
22
23 constexpr bool invalid() const { return reg_id == 0xFF; }
24
25 constexpr bool valid() const { return reg_id != 0xFF; }
26
27 constexpr static Reg make_invalid() { return Reg{(u8)0xFF}; }
28
29 constexpr bool operator==(const Reg &other) const {
30 return reg_id == other.reg_id;
31 }
32};
33
34struct RegBank {
35private:
36 u8 bank;
37
38public:
39 constexpr RegBank() : bank(u8(-1)) {}
40
41 constexpr explicit RegBank(u8 bank) : bank(bank) {}
42
43 constexpr u8 id() const { return bank; }
44
45 constexpr bool operator==(const RegBank &other) const {
46 return bank == other.bank;
47 }
48};
49
50template <unsigned NumBanks, unsigned RegsPerBank>
51class RegisterFile {
52public:
53 static constexpr unsigned NumRegs = NumBanks * RegsPerBank;
54
55 static_assert(RegsPerBank > 0 && (RegsPerBank & (RegsPerBank - 1)) == 0,
56 "RegsPerBank must be a power of two");
57 static_assert(NumRegs < Reg::make_invalid().id());
58 static_assert(NumRegs <= 64);
59
60 // later add the possibility for more than 64 registers
61 // for architectures that require it
62 using RegBitSet = u64;
63
64 /// Registers that are generally allocatable and not reserved.
65 RegBitSet allocatable = 0;
66 /// Registers that are currently in use. Requires allocatable.
67 RegBitSet used = 0;
68 /// Registers that were clobbered at some point. Used to track registers that
69 /// need to be saved/restored.
70 RegBitSet clobbered = 0;
71 std::array<u8, NumBanks> clocks{};
72
73 struct Assignment {
74 ValLocalIdx local_idx;
75 u32 part;
76 };
77
78 std::array<Assignment, NumRegs> assignments;
79
80 std::array<u8, NumRegs> lock_counts{};
81
82 void reset() {
83 used = {};
84 clobbered = {};
85 clocks = {};
86 lock_counts = {};
87 }
88
89 [[nodiscard]] bool is_used(const Reg reg) const {
90 assert(reg.id() < NumRegs);
91 return (used & 1ull << reg.id()) != 0;
92 }
93
94 [[nodiscard]] bool is_fixed(const Reg reg) const {
95 assert(reg.id() < NumRegs);
96 return lock_counts[reg.id()] > 0;
97 }
98
99 [[nodiscard]] bool is_clobbered(const Reg reg) const {
100 assert(reg.id() < NumRegs);
101 return (clobbered & 1ull << reg.id()) != 0;
102 }
103
104 void mark_used(const Reg reg, const ValLocalIdx local_idx, const u32 part) {
105 assert(reg.id() < NumRegs);
106 assert(!is_used(reg));
107 assert(!is_fixed(reg));
108 assert(lock_counts[reg.id()] == 0);
109 used |= (1ull << reg.id());
110 assignments[reg.id()] = Assignment{.local_idx = local_idx, .part = part};
111 }
112
113 void update_reg_assignment(const Reg reg,
114 const ValLocalIdx local_idx,
115 const u32 part) {
116 assert(is_used(reg));
117 assignments[reg.id()].local_idx = local_idx;
118 assignments[reg.id()].part = part;
119 }
120
121 void unmark_used(const Reg reg) {
122 assert(reg.id() < NumRegs);
123 assert(is_used(reg));
124 assert(!is_fixed(reg));
125 assert(lock_counts[reg.id()] == 0);
126 used &= ~(1ull << reg.id());
127 }
128
129 void mark_fixed(const Reg reg) {
130 assert(reg.id() < NumRegs);
131 assert(is_used(reg));
132 assert(lock_counts[reg.id()] == 0);
133 lock_counts[reg.id()] = 1;
134 }
135
136 void unmark_fixed(const Reg reg) {
137 assert(reg.id() < NumRegs);
138 assert(is_used(reg));
139 assert(is_fixed(reg));
140 assert(lock_counts[reg.id()] == 1);
141 lock_counts[reg.id()] = 0;
142 }
143
144 void inc_lock_count(const Reg reg) {
145 assert(reg.id() < NumRegs);
146 assert(is_used(reg));
147 ++lock_counts[reg.id()];
148 }
149
150 /// Returns true if the last lock was released.
151 bool dec_lock_count(const Reg reg) {
152 assert(reg.id() < NumRegs);
153 assert(is_used(reg));
154 assert(lock_counts[reg.id()] > 0);
155 if (--lock_counts[reg.id()] == 0) {
156 return true;
157 }
158 return false;
159 }
160
161 /// Decrement lock count by sub, and assert that the register is now unlocked
162 void dec_lock_count_must_zero(const Reg reg, [[maybe_unused]] u8 sub = 1) {
163 assert(reg.id() < NumRegs);
164 assert(is_used(reg));
165 assert(lock_counts[reg.id()] == sub);
166 lock_counts[reg.id()] = 0;
167 }
168
169 void mark_clobbered(const Reg reg) {
170 assert(reg.id() < NumRegs);
171 clobbered |= (1ull << reg.id());
172 }
173
174 [[nodiscard]] ValLocalIdx reg_local_idx(const Reg reg) const {
175 assert(is_used(reg));
176 return assignments[reg.id()].local_idx;
177 }
178
179 [[nodiscard]] u32 reg_part(const Reg reg) const {
180 assert(is_used(reg));
181 return assignments[reg.id()].part;
182 }
183
184 [[nodiscard]] util::BitSetIterator<> used_regs() const {
185 return util::BitSetIterator<>{used};
186 }
187
188 [[nodiscard]] Reg find_first_free_excluding(const RegBank bank,
189 const u64 exclusion_mask) const {
190 // TODO(ts): implement preferred registers
191 const RegBitSet free_bank = allocatable & ~used & bank_regs(bank);
192 const RegBitSet selectable = free_bank & ~exclusion_mask;
193 if (selectable == 0) {
194 return Reg::make_invalid();
195 }
196 return Reg{static_cast<u8>(util::cnt_tz(selectable))};
197 }
198
199 [[nodiscard]] Reg
200 find_first_nonfixed_excluding(const RegBank bank,
201 const u64 exclusion_mask) const {
202 // TODO(ts): implement preferred registers
203 for (auto reg_id : util::BitSetIterator<>{used & bank_regs(bank)}) {
204 if (!is_fixed(Reg{reg_id}) && !((u64{1} << reg_id) & exclusion_mask)) {
205 return Reg{reg_id};
206 }
207 }
208 return Reg::make_invalid();
209 }
210
211 [[nodiscard]] static RegBank reg_bank(const Reg reg) {
212 return RegBank(reg.id() / RegsPerBank);
213 }
214
215 [[nodiscard]] static RegBitSet bank_regs(const RegBank bank) {
216 assert(bank.id() <= 1);
217 return ((1ull << RegsPerBank) - 1) << (bank.id() * RegsPerBank);
218 }
219};
220
221} // namespace tpde