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) noexcept : reg_id(static_cast<u8>(id)) {
18 assert(id <= 255);
19 }
20
21 constexpr u8 id() const noexcept { return reg_id; }
22
23 constexpr bool invalid() const noexcept { return reg_id == 0xFF; }
24
25 constexpr bool valid() const noexcept { return reg_id != 0xFF; }
26
27 constexpr static Reg make_invalid() noexcept { return Reg{(u8)0xFF}; }
28
29 constexpr bool operator==(const Reg &other) const noexcept {
30 return reg_id == other.reg_id;
31 }
32};
33
34struct RegBank {
35private:
36 u8 bank;
37
38public:
39 constexpr RegBank() noexcept : bank(u8(-1)) {}
40
41 constexpr explicit RegBank(u8 bank) noexcept : bank(bank) {}
42
43 constexpr u8 id() const noexcept { return bank; }
44
45 constexpr bool operator==(const RegBank &other) const noexcept {
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 are currently unevictable. Requires used.
69 RegBitSet fixed = 0;
70 /// Registers that were clobbered at some point. Used to track registers that
71 /// need to be saved/restored.
72 RegBitSet clobbered = 0;
73 std::array<u8, NumBanks> clocks{};
74
75 struct Assignment {
76 ValLocalIdx local_idx;
77 u32 part;
78 };
79
80 std::array<Assignment, NumRegs> assignments;
81
82 std::array<u8, NumRegs> lock_counts{};
83
84 void reset() noexcept {
85 used = {};
86 fixed = {};
87 clobbered = {};
88 clocks = {};
89 lock_counts = {};
90 }
91
92 [[nodiscard]] bool is_used(const Reg reg) const noexcept {
93 assert(reg.id() < NumRegs);
94 return (used & 1ull << reg.id()) != 0;
95 }
96
97 [[nodiscard]] bool is_fixed(const Reg reg) const noexcept {
98 assert(reg.id() < NumRegs);
99 return (fixed & 1ull << reg.id()) != 0;
100 }
101
102 [[nodiscard]] bool is_clobbered(const Reg reg) const noexcept {
103 assert(reg.id() < NumRegs);
104 return (clobbered & 1ull << reg.id()) != 0;
105 }
106
107 void mark_used(const Reg reg,
108 const ValLocalIdx local_idx,
109 const u32 part) noexcept {
110 assert(reg.id() < NumRegs);
111 assert(!is_used(reg));
112 assert(!is_fixed(reg));
113 assert(lock_counts[reg.id()] == 0);
114 used |= (1ull << reg.id());
115 assignments[reg.id()] = Assignment{.local_idx = local_idx, .part = part};
116 }
117
118 void update_reg_assignment(const Reg reg,
119 const ValLocalIdx local_idx,
120 const u32 part) noexcept {
121 assert(is_used(reg));
122 assignments[reg.id()].local_idx = local_idx;
123 assignments[reg.id()].part = part;
124 }
125
126 void unmark_used(const Reg reg) noexcept {
127 assert(reg.id() < NumRegs);
128 assert(is_used(reg));
129 assert(!is_fixed(reg));
130 assert(lock_counts[reg.id()] == 0);
131 used &= ~(1ull << reg.id());
132 }
133
134 void mark_fixed(const Reg reg) noexcept {
135 assert(reg.id() < NumRegs);
136 assert(is_used(reg));
137 fixed |= (1ull << reg.id());
138 }
139
140 void unmark_fixed(const Reg reg) noexcept {
141 assert(reg.id() < NumRegs);
142 assert(is_used(reg));
143 assert(is_fixed(reg));
144 fixed &= ~(1ull << reg.id());
145 }
146
147 void inc_lock_count(const Reg reg) noexcept {
148 assert(reg.id() < NumRegs);
149 assert(is_used(reg));
150 mark_fixed(reg);
151 ++lock_counts[reg.id()];
152 }
153
154 /// Returns true if the last lock was released.
155 bool dec_lock_count(const Reg reg) noexcept {
156 assert(reg.id() < NumRegs);
157 assert(is_used(reg));
158 assert(lock_counts[reg.id()] > 0);
159 if (--lock_counts[reg.id()] == 0) {
160 unmark_fixed(reg);
161 return true;
162 }
163 return false;
164 }
165
166 /// Decrement lock count, and assert that the register is now unlocked
167 void dec_lock_count_must_zero(const Reg reg) noexcept {
168 assert(reg.id() < NumRegs);
169 assert(is_used(reg));
170 assert(lock_counts[reg.id()] == 1);
171 lock_counts[reg.id()] = 0;
172 unmark_fixed(reg);
173 }
174
175 void mark_clobbered(const Reg reg) noexcept {
176 assert(reg.id() < NumRegs);
177 clobbered |= (1ull << reg.id());
178 }
179
180 [[nodiscard]] ValLocalIdx reg_local_idx(const Reg reg) const noexcept {
181 assert(is_used(reg));
182 return assignments[reg.id()].local_idx;
183 }
184
185 [[nodiscard]] u32 reg_part(const Reg reg) const noexcept {
186 assert(is_used(reg));
187 return assignments[reg.id()].part;
188 }
189
190 [[nodiscard]] util::BitSetIterator<> used_regs() const noexcept {
191 return util::BitSetIterator<>{used};
192 }
193
194 [[nodiscard]] util::BitSetIterator<> used_nonfixed_regs() const noexcept {
195 return util::BitSetIterator<>{used & ~fixed};
196 }
197
198 [[nodiscard]] Reg
199 find_first_free_excluding(const RegBank bank,
200 const u64 exclusion_mask) const noexcept {
201 // TODO(ts): implement preferred registers
202 const RegBitSet free_bank = allocatable & ~used & bank_regs(bank);
203 const RegBitSet selectable = free_bank & ~exclusion_mask;
204 if (selectable == 0) {
205 return Reg::make_invalid();
206 }
207 return Reg{static_cast<u8>(util::cnt_tz(selectable))};
208 }
209
210 [[nodiscard]] Reg
211 find_first_nonfixed_excluding(const RegBank bank,
212 const u64 exclusion_mask) const noexcept {
213 // TODO(ts): implement preferred registers
214 const RegBitSet allocatable_bank = allocatable & ~fixed & bank_regs(bank);
215 const RegBitSet selectable = allocatable_bank & ~exclusion_mask;
216 if (selectable == 0) {
217 return Reg::make_invalid();
218 }
219 return Reg{util::cnt_tz(selectable)};
220 }
221
222 [[nodiscard]] Reg
223 find_clocked_nonfixed_excluding(const RegBank bank,
224 const u64 exclusion_mask) noexcept {
225 const RegBitSet allocatable_bank = allocatable & ~fixed & bank_regs(bank);
226 const RegBitSet selectable = allocatable_bank & ~exclusion_mask;
227 if (selectable == 0) {
228 return Reg::make_invalid();
229 }
230
231 auto clock = clocks[bank.id()];
232 const u64 reg_id = (selectable >> clock)
233 ? util::cnt_tz(selectable >> clock) + clock
234 : util::cnt_tz(selectable);
235
236 // always move clock to after the found reg_id
237 clock = reg_id + 1;
238 if (clock >= RegsPerBank) {
239 clock = 0;
240 }
241 clocks[bank.id()] = clock;
242
243 return Reg{static_cast<u8>(reg_id)};
244 }
245
246 [[nodiscard]] static RegBank reg_bank(const Reg reg) noexcept {
247 return RegBank(reg.id() / RegsPerBank);
248 }
249
250 [[nodiscard]] static RegBitSet bank_regs(const RegBank bank) noexcept {
251 assert(bank.id() <= 1);
252 return ((1ull << RegsPerBank) - 1) << (bank.id() * RegsPerBank);
253 }
254};
255
256} // namespace tpde