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 /// Load and lock a register for the value part. Does nothing if a register is
236 /// already allocated.
237 AsmReg cur_reg_or_load(CompilerBase *compiler) {
238 if (!has_reg()) {
239 alloc_reg_impl</*Reload=*/true>(compiler);
240 }
241 return cur_reg();
242 }
243
244 /// Allocate, fill, and lock a specific register for the value part, spilling
245 /// the register if it is currently used (must not be fixed). The value is
246 /// moved (assignment updated) or reloaded to this register. Value part must
247 /// not be a fixed assignment.
248 ///
249 /// \warning Do not overwrite the register content as it is not saved
250 /// \note The target register or the current value part may not be fixed
251 void load_to_specific(CompilerBase *compiler, AsmReg reg) {
252 alloc_specific_impl(compiler, reg, true);
253 }
254
255 /// Copy value into a different register.
256 AsmReg reload_into_specific_fixed(CompilerBase *compiler,
257 AsmReg reg,
258 unsigned size = 0);
259
260 /// For a locked value, get an unonwed ValuePart referring to the register.
261 ValuePart get_unowned() {
262 assert(has_reg());
263 ValuePart res{bank()};
264 res.state.c =
265 ConstantData{.reg = cur_reg(), .owned = false, .is_const = false};
266 return res;
267 }
268
269 /// Move into a temporary register, reuse an existing register if possible.
270 ValuePart into_temporary(CompilerBase *compiler) && {
271 if (is_const()) {
272 if (state.c.const_inline) {
273 ValuePart res{state.c.inline_data, state.c.size, state.c.bank};
274 res.load_to_reg(compiler);
275 return res;
276 } else {
277 ValuePart res{state.c.data, state.c.size, state.c.bank};
278 res.load_to_reg(compiler);
279 return res;
280 }
281 }
282
283 // TODO: implement this. This needs size information to copy the value.
284 assert((has_assignment() || state.c.owned) &&
285 "into_temporary from unowned ValuePart not implemented");
286 ValuePart res{bank()};
287 res.set_value(compiler, std::move(*this));
288 if (!res.has_reg()) [[unlikely]] {
289 assert(res.is_const());
290 res.load_to_reg(compiler);
291 }
292 return res;
293 }
294
295 /// Move into a scratch register, reuse an existing register if possible.
296 ScratchReg into_scratch(CompilerBase *compiler) && {
297 // TODO: implement this. This needs size information to copy the value.
298 assert((has_assignment() || state.c.owned || state.c.is_const) &&
299 "into_scratch from unowned ValuePart not implemented");
300 ScratchReg res{compiler};
301 if (can_salvage()) {
302 res.alloc_specific(salvage(compiler));
303 } else {
304 reload_into_specific_fixed(compiler, res.alloc(bank()));
305 }
306 return res;
307 }
308
309 /// Extend integer value, reuse existing register if possible. Constants are
310 /// extended without allocating a register.
311 ValuePart
312 into_extended(CompilerBase *compiler, bool sign, u32 from, u32 to) && {
313 assert(from < to && "invalid integer extension sizes");
314 if (is_const() && to <= 64) {
315 u64 val = const_data()[0];
316 u64 extended = sign ? util::sext(val, from) : util::zext(val, from);
317 return ValuePart{extended, (to + 7) / 8, state.c.bank};
318 }
319 ValuePart res{bank()};
320 Reg src_reg = has_reg() ? cur_reg() : load_to_reg(compiler);
321 if (can_salvage()) {
322 res.set_value(compiler, std::move(*this));
323 assert(src_reg == res.cur_reg());
324 } else {
325 res.alloc_reg(compiler);
326 }
327 compiler->derived()->generate_raw_intext(
328 res.cur_reg(), src_reg, sign, from, to);
329 return res;
330 }
331
332 void lock(CompilerBase *compiler);
333 void unlock(CompilerBase *compiler);
334
335 void set_modified() {
336 assert(has_reg() && has_assignment());
337 assignment().set_modified(true);
338 }
339
340 /// Set the value to the value of a different value part, possibly taking
341 /// ownership of allocated registers. If this value part has an assignment,
342 /// the value part will be unlocked.
343 void set_value(CompilerBase *compiler, ValuePart &&other);
344
345 /// Set the value to the value of the scratch register, taking ownership of
346 /// the register.
347 void set_value(CompilerBase *compiler, ScratchReg &&other);
348
349 /// Set the value to the value of the specified register, possibly taking
350 /// ownership of the register. Intended for filling in arguments/calls results
351 /// which inherently get stored to fixed registers. There must not be a
352 /// currently locked register.
353 void set_value_reg(CompilerBase *compiler, AsmReg reg);
354
355 bool can_salvage() const {
356 if (!has_assignment()) {
357 return state.c.owned && state.c.reg.valid();
358 }
359
360 return state.v.owned && assignment().register_valid();
361 }
362
363private:
364 AsmReg salvage_keep_used(CompilerBase *compiler);
365
366public:
367 // only call when can_salvage returns true and a register is known to be
368 // allocated
369 AsmReg salvage(CompilerBase *compiler) {
370 AsmReg reg = salvage_keep_used(compiler);
371 compiler->register_file.unmark_used(reg);
372 return reg;
373 }
374
375 ValLocalIdx local_idx() const {
376 assert(has_assignment());
377 return state.v.local_idx;
378 }
379
380 u32 part() const {
381 assert(has_assignment());
382 return state.v.part;
383 }
384
385 RegBank bank() const {
386 return !has_assignment() ? state.c.bank : assignment().bank();
387 }
388
389 u32 part_size() const {
390 return !has_assignment() ? state.c.size : assignment().part_size();
391 }
392
393 std::span<const u64> const_data() const {
394 assert(is_const());
395 if (state.c.const_inline) {
396 return {&state.c.inline_data, 1};
397 }
398 return {state.c.data, (state.c.size + 7) / 8};
399 }
400
401 /// Reset the reference to the value part
402 void reset(CompilerBase *compiler);
403};
404
405template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
406template <bool Reload>
407void CompilerBase<Adaptor, Derived, Config>::ValuePart::alloc_reg_impl(
408 CompilerBase *compiler) {
409 // The caller has no control over the selected register, so it must assume
410 // that this function evicts some register. This is not permitted if the value
411 // state ought to be the same.
412 assert(compiler->may_change_value_state());
413 assert(!state.c.reg.valid());
414
415 RegBank bank;
416 if (has_assignment()) {
417 auto ap = assignment();
418 if (ap.register_valid()) {
419 lock(compiler);
420 return;
421 }
422
423 bank = ap.bank();
424 } else {
425 bank = state.c.bank;
426 }
427
428 Reg reg = compiler->select_reg(bank);
429 auto &reg_file = compiler->register_file;
430 reg_file.mark_clobbered(reg);
431 if (has_assignment()) {
432 reg_file.mark_used(reg, state.v.local_idx, state.v.part);
433 // Essentially lock(), except that we know that the lock count is zero.
434 // We must lock the value here, otherwise, load_from_stack could evict the
435 // register again.
436 reg_file.mark_fixed(reg);
437 state.v.reg = reg;
438 auto ap = assignment();
439 ap.set_reg(reg);
440 ap.set_register_valid(true);
441
442 if constexpr (Reload) {
443 compiler->derived()->reload_to_reg(reg, ap);
444 } else {
445 assert(!ap.stack_valid() && "alloc_reg called on initialized value");
446 }
447 } else {
448 reg_file.mark_used(reg, INVALID_VAL_LOCAL_IDX, 0);
449 reg_file.mark_fixed(reg);
450 state.c.reg = reg;
451 state.c.owned = true;
452
453 if constexpr (Reload) {
454 assert(is_const() && "cannot reload temporary value");
455 compiler->derived()->materialize_constant(
456 const_data().data(), state.c.bank, state.c.size, reg);
457 }
458 }
459}
460
461template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
464 CompilerBase *compiler, AsmReg reg, const bool reload) {
465 assert(!state.c.reg.valid());
466
467 if (has_assignment()) {
468 auto ap = assignment();
469 assert(!ap.fixed_assignment());
470
471 if (ap.register_valid() && ap.get_reg() == reg) {
472 lock(compiler);
473 return ap.get_reg();
474 }
475 }
476
477 auto &reg_file = compiler->register_file;
478 if (reg_file.is_used(reg)) {
479 compiler->evict_reg(reg);
480 }
481
482 reg_file.mark_clobbered(reg);
483 if (has_assignment()) {
484 assert(compiler->may_change_value_state());
485
486 reg_file.mark_used(reg, state.v.local_idx, state.v.part);
487 auto ap = assignment();
488 auto old_reg = AsmReg::make_invalid();
489 if (ap.register_valid()) {
490 old_reg = ap.get_reg();
491 }
492
493 ap.set_reg(reg);
494 ap.set_register_valid(true);
495
496 // We must lock the value here, otherwise, load_from_stack could evict the
497 // register again.
498 lock(compiler);
499
500 if (reload) {
501 if (old_reg.valid()) {
502 compiler->derived()->mov(reg, old_reg, ap.part_size());
503 reg_file.unmark_used(old_reg);
504 } else {
505 compiler->derived()->reload_to_reg(reg, ap);
506 }
507 } else {
508 assert(!ap.stack_valid() && "alloc_reg with valid stack slot");
509 }
510 } else {
511 reg_file.mark_used(reg, INVALID_VAL_LOCAL_IDX, 0);
512 reg_file.mark_fixed(reg);
513
514 if (reload) {
515 if (state.c.reg.valid()) {
516 // TODO: size
517 compiler->derived()->mov(reg, state.c.reg, 8);
518 reg_file.unmark_fixed(state.c.reg);
519 reg_file.unmark_used(state.c.reg);
520 } else {
521 assert(is_const() && "cannot reload temporary value");
522 compiler->derived()->materialize_constant(
523 const_data().data(), state.c.bank, state.c.size, reg);
524 }
525 }
526
527 state.c.reg = reg;
528 state.c.owned = true;
529 }
530
531 return reg;
532}
533
534template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
538 AsmReg reg,
539 unsigned size) {
540 if (is_const()) {
541 compiler->derived()->materialize_constant(
542 const_data().data(), state.c.bank, state.c.size, reg);
543 return reg;
544 }
545 if (!has_assignment()) {
546 assert(has_reg());
547 assert(reg != cur_reg());
548 // TODO: value size
549 assert(size != 0);
550 compiler->derived()->mov(reg, cur_reg(), size);
551 return reg;
552 }
553
554 auto ap = assignment();
555 if (has_reg()) {
556 assert(cur_reg() != reg);
557 compiler->derived()->mov(reg, cur_reg(), ap.part_size());
558 } else if (ap.register_valid()) {
559 assert(ap.get_reg() != reg);
560
561 compiler->derived()->mov(reg, ap.get_reg(), ap.part_size());
562 } else {
563 assert(!ap.fixed_assignment());
564 compiler->derived()->reload_to_reg(reg, ap);
565 }
566
567 compiler->register_file.mark_clobbered(reg);
568 return reg;
569}
570
571template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
573 CompilerBase *compiler) {
574 assert(has_assignment());
575 assert(!has_reg());
576 auto ap = assignment();
577 assert(ap.register_valid());
578
579 const auto reg = ap.get_reg();
580 compiler->register_file.inc_lock_count(reg);
581 state.v.reg = reg;
582}
583
584template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
586 CompilerBase *compiler) {
587 assert(has_assignment());
588 if (!state.v.reg.valid()) {
589 return;
590 }
591
592 compiler->register_file.dec_lock_count(state.v.reg);
593 state.v.reg = AsmReg::make_invalid();
594}
595
596template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
598 CompilerBase *compiler, ValuePart &&other) {
599 assert(this != &other && "cannot assign ValuePart to itself");
600 auto &reg_file = compiler->register_file;
601 if (!has_assignment()) {
602 assert(!is_const()); // probably don't want to allow mutating constants
603
604 // This is a temporary, which might currently have a register. We want to
605 // have a temporary register that holds the value at the end.
606 if (!other.has_assignment()) {
607 // When other is a temporary/constant, just take the value and drop our
608 // own register (if we have any).
609 reset(compiler);
610 *this = std::move(other);
611 return;
612 }
613
614 if (!other.can_salvage()) {
615 // We cannot take the register of other, so copy the value
616 AsmReg cur_reg = alloc_reg(compiler);
617 other.reload_into_specific_fixed(compiler, cur_reg);
618 other.reset(compiler);
619 return;
620 }
621
622 // We can take the register of other.
623 reset(compiler);
624
625 state.c.reg = other.salvage_keep_used(compiler);
626 state.c.owned = true;
627 reg_file.mark_fixed(state.c.reg);
628 reg_file.update_reg_assignment(state.c.reg, INVALID_VAL_LOCAL_IDX, 0);
629 return;
630 }
631
632 // Update the value of the assignment part
633 auto ap = assignment();
634 assert(!ap.variable_ref() && "cannot update variable ref");
635
636 if (ap.fixed_assignment() || !other.can_salvage()) {
637 if constexpr (WithAsserts) {
638 // alloc_reg has the assertion that stack_valid must be false to prevent
639 // accidental loss of information. set_value behaves more like an explicit
640 // assignment, so we permit this overwrite -- but need to disable the
641 // assertion.
642 ap.set_modified(true);
643 }
644 // Source value owns no register or it is not reusable: copy value
645 AsmReg cur_reg = alloc_reg(compiler);
646 other.reload_into_specific_fixed(compiler, cur_reg, ap.part_size());
647 other.reset(compiler);
648 unlock(compiler);
649 ap.set_register_valid(true);
650 ap.set_modified(true);
651 return;
652 }
653
654 // Reuse register of other assignment
655 if (ap.register_valid()) {
656 // If we currently have a register, drop it
657 unlock(compiler);
658 auto cur_reg = ap.get_reg();
659 assert(!reg_file.is_fixed(cur_reg));
660 reg_file.unmark_used(cur_reg);
661 }
662
663 AsmReg new_reg = other.salvage_keep_used(compiler);
664 reg_file.update_reg_assignment(new_reg, local_idx(), part());
665 ap.set_reg(new_reg);
666 ap.set_register_valid(true);
667 ap.set_modified(true);
668}
669
670template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
672 CompilerBase *compiler, ScratchReg &&other) {
673 assert(compiler->may_change_value_state());
674
675 auto &reg_file = compiler->register_file;
676
677 // We could support this, but there shouldn't bee the need for that.
678 assert(other.has_reg() && "cannot initialize with invalid register");
679 Reg value_reg = other.cur_reg();
680 assert(reg_file.is_fixed(value_reg));
681 assert(reg_file.is_used(value_reg));
682 assert(reg_file.is_clobbered(value_reg));
683 assert(!state.c.reg.valid() &&
684 "attempted to overwrite already initialized and locked ValuePartRef");
685
686 if (!has_assignment()) {
687 assert(!is_const() && "cannot mutate constant ValuePartRef");
688 state.c.reg = value_reg;
689 state.c.owned = true;
690 assert(reg_file.reg_local_idx(value_reg) == INVALID_VAL_LOCAL_IDX);
691 assert(reg_file.reg_part(value_reg) == 0);
692 other.force_set_reg(AsmReg::make_invalid());
693 return;
694 }
695
696 // Update the value of the assignment part
697 auto ap = assignment();
698 assert(!ap.variable_ref() && "cannot update variable ref");
699
700 if (ap.fixed_assignment()) {
701 // For fixed assignments, copy the value into the fixed register.
702 auto cur_reg = ap.get_reg();
703 assert(reg_file.is_used(cur_reg));
704 assert(reg_file.is_fixed(cur_reg));
705 assert(reg_file.reg_local_idx(cur_reg) == local_idx());
706 assert(ap.register_valid() && !ap.stack_valid() &&
707 "invalid state for fixed assignment");
708 assert(cur_reg != value_reg);
709 compiler->derived()->mov(cur_reg, value_reg, ap.part_size());
710 other.reset();
711 return;
712 }
713
714 // Otherwise, take the register.
715 assert(!ap.register_valid() && !ap.stack_valid() &&
716 "attempted to overwrite already initialized ValuePartRef");
717
718 // ScratchReg's reg is fixed and used => unfix, keep used, update assignment
719 reg_file.unmark_fixed(value_reg);
720 reg_file.update_reg_assignment(value_reg, local_idx(), part());
721 ap.set_reg(value_reg);
722 ap.set_register_valid(true);
723 ap.set_modified(true);
724 other.force_set_reg(AsmReg::make_invalid());
725}
726
727template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
729 CompilerBase *compiler, AsmReg value_reg) {
730 assert(compiler->may_change_value_state());
731
732 auto &reg_file = compiler->register_file;
733
734 // We could support this, but there shouldn't bee the need for that.
735 assert(value_reg.valid() && "cannot initialize with invalid register");
736 assert(!state.c.reg.valid() &&
737 "attempted to overwrite already initialized and locked ValuePartRef");
738
739 if (!has_assignment()) {
740 assert(!is_const() && "cannot mutate constant ValuePartRef");
741 state.c.reg = value_reg;
742 state.c.owned = true;
743 reg_file.mark_used(state.c.reg, INVALID_VAL_LOCAL_IDX, 0);
744 reg_file.mark_fixed(state.c.reg);
745 return;
746 }
747
748 // Update the value of the assignment part
749 auto ap = assignment();
750 assert(!ap.variable_ref() && "cannot update variable ref");
751
752 if (ap.fixed_assignment()) {
753 // For fixed assignments, copy the value into the fixed register.
754 auto cur_reg = ap.get_reg();
755 assert(reg_file.is_used(cur_reg));
756 assert(reg_file.is_fixed(cur_reg));
757 assert(reg_file.reg_local_idx(cur_reg) == local_idx());
758 // TODO: can this happen? If so, conditionally emit move.
759 assert(cur_reg != value_reg);
760 compiler->derived()->mov(cur_reg, value_reg, ap.part_size());
761 ap.set_register_valid(true);
762 ap.set_modified(true);
763 return;
764 }
765
766 // Otherwise, take the register.
767 assert(!ap.register_valid() && !ap.stack_valid() &&
768 "attempted to overwrite already initialized ValuePartRef");
769
770 reg_file.mark_used(value_reg, local_idx(), part());
771 reg_file.mark_clobbered(value_reg);
772 ap.set_reg(value_reg);
773 ap.set_register_valid(true);
774 ap.set_modified(true);
775}
776
777template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
780 CompilerBase *compiler) {
781 assert(compiler->may_change_value_state());
782 assert(can_salvage());
783 if (!has_assignment()) {
784 AsmReg reg = state.c.reg;
785 compiler->register_file.unmark_fixed(reg);
786 state.c.reg = AsmReg::make_invalid();
787 return reg;
788 }
789
790 auto ap = assignment();
791 assert(ap.register_valid());
792 auto cur_reg = ap.get_reg();
793
794 unlock(compiler);
795 assert(ap.fixed_assignment() || !compiler->register_file.is_fixed(cur_reg));
796 if (ap.fixed_assignment()) {
797 compiler->register_file.dec_lock_count(cur_reg); // release fixed register
798 --compiler->assignments.cur_fixed_assignment_count[ap.bank().id()];
799 }
800
801 ap.set_register_valid(false);
802 ap.set_fixed_assignment(false);
803 return cur_reg;
804}
805
806template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
808 CompilerBase *compiler) {
809 AsmReg reg = state.c.reg;
810 if (!reg.valid()) {
811 return;
812 }
813
814 // In debug builds, touch assignment to catch cases where the assignment was
815 // already free'ed.
816 assert(!has_assignment() || assignment().modified() || true);
817
818 if (state.c.owned) {
819 if (has_assignment()) {
820 AssignmentPartRef ap = assignment();
821 bool fixed = ap.fixed_assignment();
822 ap.set_register_valid(false);
823 ap.set_fixed_assignment(false);
824 compiler->register_file.dec_lock_count_must_zero(reg, fixed ? 2 : 1);
825 if (fixed) {
826 --compiler->assignments.cur_fixed_assignment_count[ap.bank().id()];
827 }
828 } else {
829 compiler->register_file.unmark_fixed(reg);
830 }
831 compiler->register_file.unmark_used(reg);
832 } else if (has_assignment()) {
833 compiler->register_file.dec_lock_count(reg);
834 }
835
836 state.c.reg = AsmReg::make_invalid();
837}
838
839template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
840struct CompilerBase<Adaptor, Derived, Config>::ValuePartRef : ValuePart {
841 CompilerBase *compiler;
842
843 template <typename... Args>
844 ValuePartRef(CompilerBase *compiler, Args &&...args)
845 : ValuePart(std::forward<Args>(args)...), compiler(compiler) {}
846
847 explicit ValuePartRef(const ValuePartRef &) = delete;
848
849 ValuePartRef(ValuePartRef &&other)
850 : ValuePart(std::move(other)), compiler(other.compiler) {}
851
852 ~ValuePartRef() { reset(); }
853
854 ValuePartRef &operator=(const ValuePartRef &) = delete;
855
856 ValuePartRef &operator=(ValuePartRef &&other) {
857 if (this == &other) {
858 return *this;
859 }
860 reset();
861 ValuePart::operator=(std::move(other));
862 return *this;
863 }
864
865 ValuePartRef &operator=(ValuePart &&other) {
866 reset();
867 ValuePart::operator=(std::move(other));
868 return *this;
869 }
870
871 AsmReg alloc_reg() { return ValuePart::alloc_reg(compiler); }
872
873 AsmReg cur_reg_or_alloc() { return ValuePart::cur_reg_or_alloc(compiler); }
874
875 AsmReg alloc_try_reuse(ValuePart &ref) {
876 return ValuePart::alloc_try_reuse(compiler, ref);
877 }
878
879 void alloc_specific(AsmReg reg) { ValuePart::alloc_specific(compiler, reg); }
880
881 AsmReg load_to_reg() { return ValuePart::load_to_reg(compiler); }
882
883 AsmReg cur_reg_or_load() { return ValuePart::cur_reg_or_load(compiler); }
884
885 void load_to_specific(AsmReg reg) {
886 ValuePart::load_to_specific(compiler, reg);
887 }
888
889 AsmReg reload_into_specific_fixed(AsmReg reg, unsigned size = 0) {
890 return ValuePart::reload_into_specific_fixed(compiler, reg, size);
891 }
892
893 AsmReg reload_into_specific_fixed(CompilerBase *compiler,
894 AsmReg reg,
895 unsigned size = 0) {
896 return ValuePart::reload_into_specific_fixed(compiler, reg, size);
897 }
898
899 ValuePartRef get_unowned_ref() {
900 return ValuePartRef{compiler, ValuePart::get_unowned()};
901 }
902
903 ValuePartRef into_temporary() && {
904 return ValuePartRef{
905 compiler,
906 std::move(*static_cast<ValuePart *>(this)).into_temporary(compiler)};
907 }
908
909 ScratchReg into_scratch() && {
910 return std::move(*static_cast<ValuePart *>(this)).into_scratch(compiler);
911 }
912
913 ValuePartRef into_extended(bool sign, u32 from, u32 to) && {
914 return ValuePartRef{compiler,
915 std::move(*static_cast<ValuePart *>(this))
916 .into_extended(compiler, sign, from, to)};
917 }
918
919 void lock() { ValuePart::lock(compiler); }
920 void unlock() { ValuePart::unlock(compiler); }
921
922 void set_value(ValuePart &&other) {
923 ValuePart::set_value(compiler, std::move(other));
924 }
925
926 void set_value(ScratchReg &&other) {
927 ValuePart::set_value(compiler, std::move(other));
928 }
929
930 void set_value_reg(AsmReg value_reg) {
931 ValuePart::set_value_reg(compiler, value_reg);
932 }
933
934 AsmReg salvage() { return ValuePart::salvage(compiler); }
935
936 void reset() { ValuePart::reset(compiler); }
937};
938
939} // 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.