TPDE
Loading...
Searching...
No Matches
ValuePartRef.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/ValueAssignment.hpp"
7
8#include <cstring>
9#include <span>
10
11namespace tpde {
12
13template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
14class CompilerBase<Adaptor, Derived, Config>::ValuePart {
15private:
16 struct ConstantData {
17 AsmReg reg = AsmReg::make_invalid();
18 bool has_assignment = false;
19 bool owned;
20 bool is_const : 1;
21 bool const_inline : 1;
22 union {
23 const u64 *data;
24 u64 inline_data;
25 };
26 RegBank bank;
27 u32 size;
28 };
29
30 struct ValueData {
31 AsmReg reg = AsmReg::make_invalid(); // only valid if fixed/locked
32 bool has_assignment = true;
33 bool owned;
34 ValLocalIdx local_idx;
35 u32 part;
36 ValueAssignment *assignment;
37 };
38
39 union {
40 ConstantData c;
41 ValueData v;
42 } state;
43
44public:
45 ValuePart() : state{ConstantData{.is_const = false}} {}
46
47 ValuePart(RegBank bank)
48 : state{
49 ConstantData{.is_const = false, .bank = bank}
50 } {
51 assert(bank.id() < Config::NUM_BANKS);
52 }
53
54 ValuePart(ValLocalIdx local_idx,
55 ValueAssignment *assignment,
56 u32 part,
57 bool owned)
58 : state{
59 .v = ValueData{
60 .owned = owned,
61 .local_idx = local_idx,
62 .part = part,
63 .assignment = assignment,
64 }
65 } {
66 assert(this->assignment().variable_ref() ||
67 state.v.assignment->references_left);
68 assert(!owned || state.v.assignment->references_left == 1);
69 }
70
71 ValuePart(const u64 *data, u32 size, RegBank bank)
72 : state{
73 .c = ConstantData{.is_const = true,
74 .const_inline = false,
75 .data = data,
76 .bank = bank,
77 .size = size}
78 } {
79 assert(data && "constant data must not be null");
80 assert(bank.id() < Config::NUM_BANKS);
81 }
82
83 ValuePart(const u64 val, u32 size, RegBank bank)
84 : state{
85 .c = ConstantData{.is_const = true,
86 .const_inline = true,
87 .inline_data = val,
88 .bank = bank,
89 .size = size}
90 } {
91 assert(size <= sizeof(val));
92 assert(bank.id() < Config::NUM_BANKS);
93 }
94
95 explicit ValuePart(const ValuePart &) = delete;
96
97 ValuePart(ValuePart &&other) : state{other.state} {
98 other.state.c = ConstantData{.is_const = false, .bank = bank()};
99 }
100
101 ~ValuePart() {
102 assert(!state.c.reg.valid() && "must call reset() on ValuePart explicitly");
103 }
104
105 ValuePart &operator=(const ValuePart &) = delete;
106
107 ValuePart &operator=(ValuePart &&other) {
108 if (this == &other) {
109 return *this;
110 }
111 assert(!state.c.reg.valid() && "must call reset() on ValuePart explicitly");
112 this->state = other.state;
113 other.state.c = ConstantData{.is_const = false, .bank = bank()};
114 return *this;
115 }
116
117 bool has_assignment() const { return state.v.has_assignment; }
118
119 bool is_const() const { return !state.c.has_assignment && state.c.is_const; }
120
121 bool is_owned() const {
122 assert(has_assignment());
123 return state.c.owned;
124 }
125
126 [[nodiscard]] AssignmentPartRef assignment() const {
127 assert(has_assignment());
128 return AssignmentPartRef{state.v.assignment, state.v.part};
129 }
130
131 /// If it is known that the value part has a register, this function can be
132 /// used to quickly access it
133 AsmReg cur_reg() const {
134 assert(state.v.reg.valid());
135 return state.v.reg;
136 }
137
138 /// Current register or none, even if the value is unlocked and could be
139 /// evicted by any other operation.
140 AsmReg cur_reg_unlocked() const {
141 if (state.v.reg.valid()) {
142 return state.v.reg;
143 }
144 if (has_assignment()) {
145 if (auto ap = assignment(); ap.register_valid()) {
146 return ap.get_reg();
147 }
148 }
149 return AsmReg::make_invalid();
150 }
151
152 /// Is the value part currently in the specified register?
153 bool is_in_reg(AsmReg reg) const {
154 if (has_reg()) {
155 return cur_reg() == reg;
156 }
157 if (has_assignment()) {
158 auto ap = assignment();
159 return ap.register_valid() && ap.get_reg() == reg;
160 }
161 return false;
162 }
163
164 bool has_reg() const { return state.v.reg.valid(); }
165
166private:
167 template <bool Reload>
168 void alloc_reg_impl(CompilerBase *compiler);
169 AsmReg alloc_specific_impl(CompilerBase *compiler, AsmReg reg, bool reload);
170
171public:
172 /// Allocate and lock a register for the value part, *without* reloading the
173 /// value. Asserts that no register is currently allocated.
174 AsmReg alloc_reg(CompilerBase *compiler) {
175 alloc_reg_impl</*Reload=*/false>(compiler);
176 return cur_reg();
177 }
178
179 /// Allocate and lock a register for the value part, *without* reloading the
180 /// value. Does nothing if a register is already allocated.
181 AsmReg cur_reg_or_alloc(CompilerBase *compiler) {
182 if (!has_reg()) {
183 alloc_reg_impl</*Reload=*/false>(compiler);
184 }
185 return cur_reg();
186 }
187
188 /// Allocate register, but try to reuse the register from ref first. This
189 /// method is complicated and must be used carefully. If ref is locked in a
190 /// register and owns the register (can_salvage()), the ownership of the
191 /// register is transferred to this ValuePart without modifying the value.
192 /// Otherwise, a new register is allocated.
193 ///
194 /// Usage example:
195 /// AsmReg operand_reg = operand_ref.load_to_reg();
196 /// AsmReg result_reg = result_ref.alloc_try_reuse(operand_ref);
197 /// if (operand_reg == result_reg) {
198 /// // reuse successful
199 /// ASM(ADD64ri, result_reg, 1);
200 /// } else {
201 /// ASM(LEA64rm, result_reg, FE_MEM(FE_NOREG, 1, operand_reg, 1));
202 /// }
203 AsmReg alloc_try_reuse(CompilerBase *compiler, ValuePart &ref) {
204 assert(ref.has_reg());
205 if (!has_assignment() || !assignment().register_valid()) {
206 assert(!has_assignment() || !assignment().fixed_assignment());
207 if (ref.can_salvage()) {
208 set_value(compiler, std::move(ref));
209 if (has_assignment()) {
210 lock(compiler);
211 }
212 return cur_reg();
213 }
214 }
215 return alloc_reg(compiler);
216 }
217
218 /// Allocate and lock a specific register for the value part, spilling the
219 /// register if it is currently used (must not be fixed), *without* reloading
220 /// or copying the value into the new register. The value must not be locked.
221 /// An existing assignment register is discarded. Value part must not be a
222 /// fixed assignment.
223 void alloc_specific(CompilerBase *compiler, AsmReg reg) {
224 alloc_specific_impl(compiler, reg, false);
225 }
226
227 /// Allocate, fill, and lock a register for the value part, reloading from
228 /// the stack or materializing the constant if necessary. Requires that the
229 /// value is currently unlocked (i.e., has_reg() is false).
230 AsmReg load_to_reg(CompilerBase *compiler) {
231 alloc_reg_impl</*Reload=*/true>(compiler);
232 return cur_reg();
233 }
234
235 /// Allocate, fill, and lock a specific register for the value part, spilling
236 /// the register if it is currently used (must not be fixed). The value is
237 /// moved (assignment updated) or reloaded to this register. Value part must
238 /// not be a fixed assignment.
239 ///
240 /// \warning Do not overwrite the register content as it is not saved
241 /// \note The target register or the current value part may not be fixed
242 void load_to_specific(CompilerBase *compiler, AsmReg reg) {
243 alloc_specific_impl(compiler, reg, true);
244 }
245
246 /// Copy value into a different register.
247 AsmReg reload_into_specific_fixed(CompilerBase *compiler,
248 AsmReg reg,
249 unsigned size = 0);
250
251 /// For a locked value, get an unonwed ValuePart referring to the register.
252 ValuePart get_unowned() {
253 assert(has_reg());
254 ValuePart res{bank()};
255 res.state.c =
256 ConstantData{.reg = cur_reg(), .owned = false, .is_const = false};
257 return res;
258 }
259
260 /// Move into a temporary register, reuse an existing register if possible.
261 ValuePart into_temporary(CompilerBase *compiler) && {
262 if (is_const()) {
263 if (state.c.const_inline) {
264 ValuePart res{state.c.inline_data, state.c.size, state.c.bank};
265 res.load_to_reg(compiler);
266 return res;
267 } else {
268 ValuePart res{state.c.data, state.c.size, state.c.bank};
269 res.load_to_reg(compiler);
270 return res;
271 }
272 }
273
274 // TODO: implement this. This needs size information to copy the value.
275 assert((has_assignment() || state.c.owned) &&
276 "into_temporary from unowned ValuePart not implemented");
277 ValuePart res{bank()};
278 res.set_value(compiler, std::move(*this));
279 if (!res.has_reg()) [[unlikely]] {
280 assert(res.is_const());
281 res.load_to_reg(compiler);
282 }
283 return res;
284 }
285
286 /// Move into a scratch register, reuse an existing register if possible.
287 ScratchReg into_scratch(CompilerBase *compiler) && {
288 // TODO: implement this. This needs size information to copy the value.
289 assert((has_assignment() || state.c.owned || state.c.is_const) &&
290 "into_scratch from unowned ValuePart not implemented");
291 ScratchReg res{compiler};
292 if (can_salvage()) {
293 res.alloc_specific(salvage(compiler));
294 } else {
295 reload_into_specific_fixed(compiler, res.alloc(bank()));
296 }
297 return res;
298 }
299
300 /// Extend integer value, reuse existing register if possible. Constants are
301 /// extended without allocating a register.
302 ValuePart
303 into_extended(CompilerBase *compiler, bool sign, u32 from, u32 to) && {
304 assert(from < to && "invalid integer extension sizes");
305 if (is_const() && to <= 64) {
306 u64 val = const_data()[0];
307 u64 extended = sign ? util::sext(val, from) : util::zext(val, from);
308 return ValuePart{extended, (to + 7) / 8, state.c.bank};
309 }
310 ValuePart res{bank()};
311 Reg src_reg = has_reg() ? cur_reg() : load_to_reg(compiler);
312 if (can_salvage()) {
313 res.set_value(compiler, std::move(*this));
314 assert(src_reg == res.cur_reg());
315 } else {
316 res.alloc_reg(compiler);
317 }
318 compiler->derived()->generate_raw_intext(
319 res.cur_reg(), src_reg, sign, from, to);
320 return res;
321 }
322
323 void lock(CompilerBase *compiler);
324 void unlock(CompilerBase *compiler);
325
326 void set_modified() {
327 assert(has_reg() && has_assignment());
328 assignment().set_modified(true);
329 }
330
331 /// Set the value to the value of a different value part, possibly taking
332 /// ownership of allocated registers. If this value part has an assignment,
333 /// the value part will be unlocked.
334 void set_value(CompilerBase *compiler, ValuePart &&other);
335
336 /// Set the value to the value of the scratch register, taking ownership of
337 /// the register.
338 void set_value(CompilerBase *compiler, ScratchReg &&other);
339
340 /// Set the value to the value of the specified register, possibly taking
341 /// ownership of the register. Intended for filling in arguments/calls results
342 /// which inherently get stored to fixed registers. There must not be a
343 /// currently locked register.
344 void set_value_reg(CompilerBase *compiler, AsmReg reg);
345
346 bool can_salvage() const {
347 if (!has_assignment()) {
348 return state.c.owned && state.c.reg.valid();
349 }
350
351 return state.v.owned && assignment().register_valid();
352 }
353
354private:
355 AsmReg salvage_keep_used(CompilerBase *compiler);
356
357public:
358 // only call when can_salvage returns true and a register is known to be
359 // allocated
360 AsmReg salvage(CompilerBase *compiler) {
361 AsmReg reg = salvage_keep_used(compiler);
362 compiler->register_file.unmark_used(reg);
363 return reg;
364 }
365
366 ValLocalIdx local_idx() const {
367 assert(has_assignment());
368 return state.v.local_idx;
369 }
370
371 u32 part() const {
372 assert(has_assignment());
373 return state.v.part;
374 }
375
376 RegBank bank() const {
377 return !has_assignment() ? state.c.bank : assignment().bank();
378 }
379
380 u32 part_size() const {
381 return !has_assignment() ? state.c.size : assignment().part_size();
382 }
383
384 std::span<const u64> const_data() const {
385 assert(is_const());
386 if (state.c.const_inline) {
387 return {&state.c.inline_data, 1};
388 }
389 return {state.c.data, (state.c.size + 7) / 8};
390 }
391
392 /// Reset the reference to the value part
393 void reset(CompilerBase *compiler);
394};
395
396template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
397template <bool Reload>
398void CompilerBase<Adaptor, Derived, Config>::ValuePart::alloc_reg_impl(
399 CompilerBase *compiler) {
400 // The caller has no control over the selected register, so it must assume
401 // that this function evicts some register. This is not permitted if the value
402 // state ought to be the same.
403 assert(compiler->may_change_value_state());
404 assert(!state.c.reg.valid());
405
406 RegBank bank;
407 if (has_assignment()) {
408 auto ap = assignment();
409 if (ap.register_valid()) {
410 lock(compiler);
411 return;
412 }
413
414 bank = ap.bank();
415 } else {
416 bank = state.c.bank;
417 }
418
419 Reg reg = compiler->select_reg(bank);
420 auto &reg_file = compiler->register_file;
421 reg_file.mark_clobbered(reg);
422 if (has_assignment()) {
423 reg_file.mark_used(reg, state.v.local_idx, state.v.part);
424 // Essentially lock(), except that we know that the lock count is zero.
425 // We must lock the value here, otherwise, load_from_stack could evict the
426 // register again.
427 reg_file.mark_fixed(reg);
428 state.v.reg = reg;
429 auto ap = assignment();
430 ap.set_reg(reg);
431 ap.set_register_valid(true);
432
433 if constexpr (Reload) {
434 compiler->derived()->reload_to_reg(reg, ap);
435 } else {
436 assert(!ap.stack_valid() && "alloc_reg called on initialized value");
437 }
438 } else {
439 reg_file.mark_used(reg, INVALID_VAL_LOCAL_IDX, 0);
440 reg_file.mark_fixed(reg);
441 state.c.reg = reg;
442 state.c.owned = true;
443
444 if constexpr (Reload) {
445 assert(is_const() && "cannot reload temporary value");
446 compiler->derived()->materialize_constant(
447 const_data().data(), state.c.bank, state.c.size, reg);
448 }
449 }
450}
451
452template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
455 CompilerBase *compiler, AsmReg reg, const bool reload) {
456 assert(!state.c.reg.valid());
457
458 if (has_assignment()) {
459 auto ap = assignment();
460 assert(!ap.fixed_assignment());
461
462 if (ap.register_valid() && ap.get_reg() == reg) {
463 lock(compiler);
464 return ap.get_reg();
465 }
466 }
467
468 auto &reg_file = compiler->register_file;
469 if (reg_file.is_used(reg)) {
470 compiler->evict_reg(reg);
471 }
472
473 reg_file.mark_clobbered(reg);
474 if (has_assignment()) {
475 assert(compiler->may_change_value_state());
476
477 reg_file.mark_used(reg, state.v.local_idx, state.v.part);
478 auto ap = assignment();
479 auto old_reg = AsmReg::make_invalid();
480 if (ap.register_valid()) {
481 old_reg = ap.get_reg();
482 }
483
484 ap.set_reg(reg);
485 ap.set_register_valid(true);
486
487 // We must lock the value here, otherwise, load_from_stack could evict the
488 // register again.
489 lock(compiler);
490
491 if (reload) {
492 if (old_reg.valid()) {
493 compiler->derived()->mov(reg, old_reg, ap.part_size());
494 reg_file.unmark_used(old_reg);
495 } else {
496 compiler->derived()->reload_to_reg(reg, ap);
497 }
498 } else {
499 assert(!ap.stack_valid() && "alloc_reg with valid stack slot");
500 }
501 } else {
502 reg_file.mark_used(reg, INVALID_VAL_LOCAL_IDX, 0);
503 reg_file.mark_fixed(reg);
504
505 if (reload) {
506 if (state.c.reg.valid()) {
507 // TODO: size
508 compiler->derived()->mov(reg, state.c.reg, 8);
509 reg_file.unmark_fixed(state.c.reg);
510 reg_file.unmark_used(state.c.reg);
511 } else {
512 assert(is_const() && "cannot reload temporary value");
513 compiler->derived()->materialize_constant(
514 const_data().data(), state.c.bank, state.c.size, reg);
515 }
516 }
517
518 state.c.reg = reg;
519 state.c.owned = true;
520 }
521
522 return reg;
523}
524
525template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
529 AsmReg reg,
530 unsigned size) {
531 if (is_const()) {
532 compiler->derived()->materialize_constant(
533 const_data().data(), state.c.bank, state.c.size, reg);
534 return reg;
535 }
536 if (!has_assignment()) {
537 assert(has_reg());
538 assert(reg != cur_reg());
539 // TODO: value size
540 assert(size != 0);
541 compiler->derived()->mov(reg, cur_reg(), size);
542 return reg;
543 }
544
545 auto ap = assignment();
546 if (has_reg()) {
547 assert(cur_reg() != reg);
548 compiler->derived()->mov(reg, cur_reg(), ap.part_size());
549 } else if (ap.register_valid()) {
550 assert(ap.get_reg() != reg);
551
552 compiler->derived()->mov(reg, ap.get_reg(), ap.part_size());
553 } else {
554 assert(!ap.fixed_assignment());
555 compiler->derived()->reload_to_reg(reg, ap);
556 }
557
558 compiler->register_file.mark_clobbered(reg);
559 return reg;
560}
561
562template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
564 CompilerBase *compiler) {
565 assert(has_assignment());
566 assert(!has_reg());
567 auto ap = assignment();
568 assert(ap.register_valid());
569
570 const auto reg = ap.get_reg();
571 compiler->register_file.inc_lock_count(reg);
572 state.v.reg = reg;
573}
574
575template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
577 CompilerBase *compiler) {
578 assert(has_assignment());
579 if (!state.v.reg.valid()) {
580 return;
581 }
582
583 compiler->register_file.dec_lock_count(state.v.reg);
584 state.v.reg = AsmReg::make_invalid();
585}
586
587template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
589 CompilerBase *compiler, ValuePart &&other) {
590 assert(this != &other && "cannot assign ValuePart to itself");
591 auto &reg_file = compiler->register_file;
592 if (!has_assignment()) {
593 assert(!is_const()); // probably don't want to allow mutating constants
594
595 // This is a temporary, which might currently have a register. We want to
596 // have a temporary register that holds the value at the end.
597 if (!other.has_assignment()) {
598 // When other is a temporary/constant, just take the value and drop our
599 // own register (if we have any).
600 reset(compiler);
601 *this = std::move(other);
602 return;
603 }
604
605 if (!other.can_salvage()) {
606 // We cannot take the register of other, so copy the value
607 AsmReg cur_reg = alloc_reg(compiler);
608 other.reload_into_specific_fixed(compiler, cur_reg);
609 other.reset(compiler);
610 return;
611 }
612
613 // We can take the register of other.
614 reset(compiler);
615
616 state.c.reg = other.salvage_keep_used(compiler);
617 state.c.owned = true;
618 reg_file.mark_fixed(state.c.reg);
619 reg_file.update_reg_assignment(state.c.reg, INVALID_VAL_LOCAL_IDX, 0);
620 return;
621 }
622
623 // Update the value of the assignment part
624 auto ap = assignment();
625 assert(!ap.variable_ref() && "cannot update variable ref");
626
627 if (ap.fixed_assignment() || !other.can_salvage()) {
628 if constexpr (WithAsserts) {
629 // alloc_reg has the assertion that stack_valid must be false to prevent
630 // accidental loss of information. set_value behaves more like an explicit
631 // assignment, so we permit this overwrite -- but need to disable the
632 // assertion.
633 ap.set_modified(true);
634 }
635 // Source value owns no register or it is not reusable: copy value
636 AsmReg cur_reg = alloc_reg(compiler);
637 other.reload_into_specific_fixed(compiler, cur_reg, ap.part_size());
638 other.reset(compiler);
639 unlock(compiler);
640 ap.set_register_valid(true);
641 ap.set_modified(true);
642 return;
643 }
644
645 // Reuse register of other assignment
646 if (ap.register_valid()) {
647 // If we currently have a register, drop it
648 unlock(compiler);
649 auto cur_reg = ap.get_reg();
650 assert(!reg_file.is_fixed(cur_reg));
651 reg_file.unmark_used(cur_reg);
652 }
653
654 AsmReg new_reg = other.salvage_keep_used(compiler);
655 reg_file.update_reg_assignment(new_reg, local_idx(), part());
656 ap.set_reg(new_reg);
657 ap.set_register_valid(true);
658 ap.set_modified(true);
659}
660
661template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
663 CompilerBase *compiler, ScratchReg &&other) {
664 assert(compiler->may_change_value_state());
665
666 auto &reg_file = compiler->register_file;
667
668 // We could support this, but there shouldn't bee the need for that.
669 assert(other.has_reg() && "cannot initialize with invalid register");
670 Reg value_reg = other.cur_reg();
671 assert(reg_file.is_fixed(value_reg));
672 assert(reg_file.is_used(value_reg));
673 assert(reg_file.is_clobbered(value_reg));
674 assert(!state.c.reg.valid() &&
675 "attempted to overwrite already initialized and locked ValuePartRef");
676
677 if (!has_assignment()) {
678 assert(!is_const() && "cannot mutate constant ValuePartRef");
679 state.c.reg = value_reg;
680 state.c.owned = true;
681 assert(reg_file.reg_local_idx(value_reg) == INVALID_VAL_LOCAL_IDX);
682 assert(reg_file.reg_part(value_reg) == 0);
683 other.force_set_reg(AsmReg::make_invalid());
684 return;
685 }
686
687 // Update the value of the assignment part
688 auto ap = assignment();
689 assert(!ap.variable_ref() && "cannot update variable ref");
690
691 if (ap.fixed_assignment()) {
692 // For fixed assignments, copy the value into the fixed register.
693 auto cur_reg = ap.get_reg();
694 assert(reg_file.is_used(cur_reg));
695 assert(reg_file.is_fixed(cur_reg));
696 assert(reg_file.reg_local_idx(cur_reg) == local_idx());
697 assert(ap.register_valid() && !ap.stack_valid() &&
698 "invalid state for fixed assignment");
699 assert(cur_reg != value_reg);
700 compiler->derived()->mov(cur_reg, value_reg, ap.part_size());
701 other.reset();
702 return;
703 }
704
705 // Otherwise, take the register.
706 assert(!ap.register_valid() && !ap.stack_valid() &&
707 "attempted to overwrite already initialized ValuePartRef");
708
709 // ScratchReg's reg is fixed and used => unfix, keep used, update assignment
710 reg_file.unmark_fixed(value_reg);
711 reg_file.update_reg_assignment(value_reg, local_idx(), part());
712 ap.set_reg(value_reg);
713 ap.set_register_valid(true);
714 ap.set_modified(true);
715 other.force_set_reg(AsmReg::make_invalid());
716}
717
718template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
720 CompilerBase *compiler, AsmReg value_reg) {
721 assert(compiler->may_change_value_state());
722
723 auto &reg_file = compiler->register_file;
724
725 // We could support this, but there shouldn't bee the need for that.
726 assert(value_reg.valid() && "cannot initialize with invalid register");
727 assert(!state.c.reg.valid() &&
728 "attempted to overwrite already initialized and locked ValuePartRef");
729
730 if (!has_assignment()) {
731 assert(!is_const() && "cannot mutate constant ValuePartRef");
732 state.c.reg = value_reg;
733 state.c.owned = true;
734 reg_file.mark_used(state.c.reg, INVALID_VAL_LOCAL_IDX, 0);
735 reg_file.mark_fixed(state.c.reg);
736 return;
737 }
738
739 // Update the value of the assignment part
740 auto ap = assignment();
741 assert(!ap.variable_ref() && "cannot update variable ref");
742
743 if (ap.fixed_assignment()) {
744 // For fixed assignments, copy the value into the fixed register.
745 auto cur_reg = ap.get_reg();
746 assert(reg_file.is_used(cur_reg));
747 assert(reg_file.is_fixed(cur_reg));
748 assert(reg_file.reg_local_idx(cur_reg) == local_idx());
749 // TODO: can this happen? If so, conditionally emit move.
750 assert(cur_reg != value_reg);
751 compiler->derived()->mov(cur_reg, value_reg, ap.part_size());
752 ap.set_register_valid(true);
753 ap.set_modified(true);
754 return;
755 }
756
757 // Otherwise, take the register.
758 assert(!ap.register_valid() && !ap.stack_valid() &&
759 "attempted to overwrite already initialized ValuePartRef");
760
761 reg_file.mark_used(value_reg, local_idx(), part());
762 reg_file.mark_clobbered(value_reg);
763 ap.set_reg(value_reg);
764 ap.set_register_valid(true);
765 ap.set_modified(true);
766}
767
768template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
771 CompilerBase *compiler) {
772 assert(compiler->may_change_value_state());
773 assert(can_salvage());
774 if (!has_assignment()) {
775 AsmReg reg = state.c.reg;
776 compiler->register_file.unmark_fixed(reg);
777 state.c.reg = AsmReg::make_invalid();
778 return reg;
779 }
780
781 auto ap = assignment();
782 assert(ap.register_valid());
783 auto cur_reg = ap.get_reg();
784
785 unlock(compiler);
786 assert(ap.fixed_assignment() || !compiler->register_file.is_fixed(cur_reg));
787 if (ap.fixed_assignment()) {
788 compiler->register_file.dec_lock_count(cur_reg); // release fixed register
789 --compiler->assignments.cur_fixed_assignment_count[ap.bank().id()];
790 }
791
792 ap.set_register_valid(false);
793 ap.set_fixed_assignment(false);
794 return cur_reg;
795}
796
797template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
799 CompilerBase *compiler) {
800 AsmReg reg = state.c.reg;
801 if (!reg.valid()) {
802 return;
803 }
804
805 // In debug builds, touch assignment to catch cases where the assignment was
806 // already free'ed.
807 assert(!has_assignment() || assignment().modified() || true);
808
809 if (state.c.owned) {
810 if (has_assignment()) {
811 AssignmentPartRef ap = assignment();
812 bool fixed = ap.fixed_assignment();
813 ap.set_register_valid(false);
814 ap.set_fixed_assignment(false);
815 compiler->register_file.dec_lock_count_must_zero(reg, fixed ? 2 : 1);
816 if (fixed) {
817 --compiler->assignments.cur_fixed_assignment_count[ap.bank().id()];
818 }
819 } else {
820 compiler->register_file.unmark_fixed(reg);
821 }
822 compiler->register_file.unmark_used(reg);
823 } else if (has_assignment()) {
824 compiler->register_file.dec_lock_count(reg);
825 }
826
827 state.c.reg = AsmReg::make_invalid();
828}
829
830template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
831struct CompilerBase<Adaptor, Derived, Config>::ValuePartRef : ValuePart {
832 CompilerBase *compiler;
833
834 template <typename... Args>
835 ValuePartRef(CompilerBase *compiler, Args &&...args)
836 : ValuePart(std::forward<Args>(args)...), compiler(compiler) {}
837
838 explicit ValuePartRef(const ValuePartRef &) = delete;
839
840 ValuePartRef(ValuePartRef &&other)
841 : ValuePart(std::move(other)), compiler(other.compiler) {}
842
843 ~ValuePartRef() { reset(); }
844
845 ValuePartRef &operator=(const ValuePartRef &) = delete;
846
847 ValuePartRef &operator=(ValuePartRef &&other) {
848 if (this == &other) {
849 return *this;
850 }
851 reset();
852 ValuePart::operator=(std::move(other));
853 return *this;
854 }
855
856 ValuePartRef &operator=(ValuePart &&other) {
857 reset();
858 ValuePart::operator=(std::move(other));
859 return *this;
860 }
861
862 AsmReg alloc_reg() { return ValuePart::alloc_reg(compiler); }
863
864 AsmReg cur_reg_or_alloc() { return ValuePart::cur_reg_or_alloc(compiler); }
865
866 AsmReg alloc_try_reuse(ValuePart &ref) {
867 return ValuePart::alloc_try_reuse(compiler, ref);
868 }
869
870 void alloc_specific(AsmReg reg) { ValuePart::alloc_specific(compiler, reg); }
871
872 AsmReg load_to_reg() { return ValuePart::load_to_reg(compiler); }
873
874 void load_to_specific(AsmReg reg) {
875 ValuePart::load_to_specific(compiler, reg);
876 }
877
878 AsmReg reload_into_specific_fixed(AsmReg reg, unsigned size = 0) {
879 return ValuePart::reload_into_specific_fixed(compiler, reg, size);
880 }
881
882 AsmReg reload_into_specific_fixed(CompilerBase *compiler,
883 AsmReg reg,
884 unsigned size = 0) {
885 return ValuePart::reload_into_specific_fixed(compiler, reg, size);
886 }
887
888 ValuePartRef get_unowned_ref() {
889 return ValuePartRef{compiler, ValuePart::get_unowned()};
890 }
891
892 ValuePartRef into_temporary() && {
893 return ValuePartRef{
894 compiler,
895 std::move(*static_cast<ValuePart *>(this)).into_temporary(compiler)};
896 }
897
898 ScratchReg into_scratch() && {
899 return std::move(*static_cast<ValuePart *>(this)).into_scratch(compiler);
900 }
901
902 ValuePartRef into_extended(bool sign, u32 from, u32 to) && {
903 return ValuePartRef{compiler,
904 std::move(*static_cast<ValuePart *>(this))
905 .into_extended(compiler, sign, from, to)};
906 }
907
908 void lock() { ValuePart::lock(compiler); }
909 void unlock() { ValuePart::unlock(compiler); }
910
911 void set_value(ValuePart &&other) {
912 ValuePart::set_value(compiler, std::move(other));
913 }
914
915 void set_value(ScratchReg &&other) {
916 ValuePart::set_value(compiler, std::move(other));
917 }
918
919 void set_value_reg(AsmReg value_reg) {
920 ValuePart::set_value_reg(compiler, value_reg);
921 }
922
923 AsmReg salvage() { return ValuePart::salvage(compiler); }
924
925 void reset() { ValuePart::reset(compiler); }
926};
927
928} // namespace tpde
Owned unspillable and unevictable temporary register with RAII semantics.
The base class for the compiler.
void reset()
Reset any leftover data from the previous compilation such that it will not affect the next compilati...
CompilerBase(Adaptor *adaptor)
Initialize a CompilerBase, should be called by the derived classes.