6#include "tpde/ValueAssignment.hpp"
13template <IRAdaptor Adaptor,
typename Derived, CompilerConfig Config>
14class CompilerBase<Adaptor, Derived, Config>::ValuePart {
17 AsmReg reg = AsmReg::make_invalid();
18 bool has_assignment =
false;
21 bool const_inline : 1;
31 AsmReg reg = AsmReg::make_invalid();
32 bool has_assignment =
true;
34 ValLocalIdx local_idx;
36 ValueAssignment *assignment;
45 ValuePart() noexcept : state{ConstantData{.is_const =
false}} {}
47 ValuePart(RegBank bank) noexcept
49 ConstantData{.is_const =
false, .bank = bank}
51 assert(bank.id() < Config::NUM_BANKS);
54 ValuePart(ValLocalIdx local_idx,
55 ValueAssignment *assignment,
61 .local_idx = local_idx,
63 .assignment = assignment,
66 assert(this->assignment().variable_ref() ||
67 state.v.assignment->references_left);
68 assert(!owned || state.v.assignment->references_left == 1);
71 ValuePart(
const u64 *data, u32 size, RegBank bank) noexcept
73 .c = ConstantData{.is_const =
true,
74 .const_inline =
false,
79 assert(data &&
"constant data must not be null");
80 assert(bank.id() < Config::NUM_BANKS);
83 ValuePart(
const u64 val, u32 size, RegBank bank) noexcept
85 .c = ConstantData{.is_const =
true,
91 assert(size <=
sizeof(val));
92 assert(bank.id() < Config::NUM_BANKS);
95 explicit ValuePart(
const ValuePart &) =
delete;
97 ValuePart(ValuePart &&other) noexcept : state{other.state} {
98 other.state.c = ConstantData{};
101 ~ValuePart() noexcept {
102 assert(!state.c.reg.valid() &&
"must call reset() on ValuePart explicitly");
105 ValuePart &operator=(
const ValuePart &) =
delete;
107 ValuePart &operator=(ValuePart &&other)
noexcept {
108 if (
this == &other) {
111 assert(!state.c.reg.valid() &&
"must call reset() on ValuePart explicitly");
112 this->state = other.state;
113 other.state.c = ConstantData{};
117 bool has_assignment() const noexcept {
return state.v.has_assignment; }
119 bool is_const() const noexcept {
120 return !state.c.has_assignment && state.c.is_const;
123 bool is_owned() const noexcept {
124 assert(has_assignment());
125 return state.c.owned;
128 [[nodiscard]] AssignmentPartRef assignment() const noexcept {
129 assert(has_assignment());
130 return AssignmentPartRef{state.v.assignment, state.v.part};
135 AsmReg cur_reg() const noexcept {
136 assert(state.v.reg.valid());
142 AsmReg cur_reg_unlocked() const noexcept {
143 if (state.v.reg.valid()) {
146 if (has_assignment()) {
147 if (
auto ap = assignment(); ap.register_valid()) {
151 return AsmReg::make_invalid();
155 bool is_in_reg(AsmReg reg)
const noexcept {
157 return cur_reg() == reg;
159 if (has_assignment()) {
160 auto ap = assignment();
161 return ap.register_valid() && ap.get_reg() == reg;
166 bool has_reg() const noexcept {
return state.v.reg.valid(); }
169 AsmReg alloc_reg_impl(CompilerBase *compiler,
171 bool reload)
noexcept;
172 AsmReg alloc_specific_impl(CompilerBase *compiler,
174 bool reload)
noexcept;
179 AsmReg alloc_reg(CompilerBase *compiler, u64 exclusion_mask = 0) noexcept {
180 return alloc_reg_impl(compiler, exclusion_mask,
false);
198 AsmReg alloc_try_reuse(CompilerBase *compiler, ValuePart &ref)
noexcept {
199 assert(ref.has_reg());
200 if (!has_assignment() || !assignment().register_valid()) {
201 assert(!has_assignment() || !assignment().fixed_assignment());
202 if (ref.can_salvage()) {
203 set_value(compiler, std::move(ref));
204 if (has_assignment()) {
210 return alloc_reg(compiler);
218 void alloc_specific(CompilerBase *compiler, AsmReg reg)
noexcept {
219 alloc_specific_impl(compiler, reg,
false);
225 AsmReg load_to_reg(CompilerBase *compiler)
noexcept {
226 return alloc_reg_impl(compiler, 0,
true);
236 void load_to_specific(CompilerBase *compiler, AsmReg reg)
noexcept {
237 alloc_specific_impl(compiler, reg,
true);
241 AsmReg reload_into_specific_fixed(CompilerBase *compiler,
243 unsigned size = 0) noexcept;
246 ValuePart get_unowned() noexcept {
248 ValuePart res{bank()};
250 ConstantData{.reg = cur_reg(), .owned =
false, .is_const =
false};
255 ValuePart into_temporary(CompilerBase *compiler) &&
noexcept {
257 assert((has_assignment() || state.c.owned) &&
258 "into_temporary from unowned ValuePart not implemented");
259 ValuePart res{bank()};
260 res.set_value(compiler, std::move(*
this));
261 if (!res.has_reg()) [[unlikely]] {
262 assert(res.is_const());
263 res.load_to_reg(compiler);
270 ValuePart into_extended(CompilerBase *compiler,
273 u32 to) &&
noexcept {
274 assert(from < to &&
"invalid integer extension sizes");
275 if (is_const() && to <= 64) {
276 u64 val = const_data()[0];
277 u64 extended = sign ? util::sext(val, from) : util::zext(val, from);
278 return ValuePart{extended, (to + 7) / 8, state.c.bank};
280 ValuePart res{bank()};
281 Reg src_reg = has_reg() ? cur_reg() : load_to_reg(compiler);
283 res.set_value(compiler, std::move(*
this));
284 assert(src_reg == res.cur_reg());
286 res.alloc_reg(compiler);
288 compiler->derived()->generate_raw_intext(
289 res.cur_reg(), src_reg, sign, from, to);
293 void lock(CompilerBase *compiler)
noexcept;
294 void unlock(CompilerBase *compiler)
noexcept;
296 void set_modified() noexcept {
297 assert(has_reg() && has_assignment());
298 assignment().set_modified(
true);
301 void set_value(CompilerBase *compiler, ValuePart &&other)
noexcept;
307 void set_value_reg(CompilerBase *compiler, AsmReg reg)
noexcept;
309 bool can_salvage() const noexcept {
310 if (!has_assignment()) {
311 return state.c.owned && state.c.reg.valid();
314 return state.v.owned && assignment().register_valid();
318 AsmReg salvage_keep_used(CompilerBase *compiler)
noexcept;
323 AsmReg salvage(CompilerBase *compiler)
noexcept {
324 AsmReg reg = salvage_keep_used(compiler);
325 compiler->register_file.unmark_used(reg);
329 ValLocalIdx local_idx() const noexcept {
330 assert(has_assignment());
331 return state.v.local_idx;
334 u32 part() const noexcept {
335 assert(has_assignment());
339 RegBank bank() const noexcept {
340 return !has_assignment() ? state.c.bank : assignment().bank();
343 u32 part_size() const noexcept {
344 return !has_assignment() ? state.c.size : assignment().part_size();
347 std::span<const u64> const_data() const noexcept {
349 if (state.c.const_inline) {
350 return {&state.c.inline_data, 1};
352 return {state.c.data, (state.c.size + 7) / 8};
356 void reset(CompilerBase *compiler)
noexcept;
359template <IRAdaptor Adaptor,
typename Derived, CompilerConfig Config>
360typename CompilerBase<Adaptor, Derived, Config>::AsmReg
361 CompilerBase<Adaptor, Derived, Config>::ValuePart::alloc_reg_impl(
362 CompilerBase *compiler,
364 const bool reload)
noexcept {
368 assert(compiler->may_change_value_state());
369 assert(!state.c.reg.valid());
372 if (has_assignment()) {
373 auto ap = assignment();
374 if (ap.register_valid()) {
377 assert((exclusion_mask & (1ull << state.v.reg.id())) == 0 &&
378 "moving registers in alloc_reg is unsupported");
387 auto ®_file = compiler->register_file;
388 auto reg = reg_file.find_first_free_excluding(bank, exclusion_mask);
391 if (reg.invalid()) [[unlikely]] {
392 reg = reg_file.find_clocked_nonfixed_excluding(bank, exclusion_mask);
393 if (reg.invalid()) [[unlikely]] {
394 TPDE_FATAL(
"ran out of registers for value part");
396 compiler->evict_reg(reg);
399 reg_file.mark_clobbered(reg);
400 if (has_assignment()) {
401 reg_file.mark_used(reg, state.v.local_idx, state.v.part);
402 auto ap = assignment();
404 ap.set_register_valid(
true);
411 compiler->derived()->reload_to_reg(reg, ap);
413 assert(!ap.stack_valid() &&
"alloc_reg called on initialized value");
416 reg_file.mark_used(reg, INVALID_VAL_LOCAL_IDX, 0);
417 reg_file.mark_fixed(reg);
419 state.c.owned =
true;
422 assert(is_const() &&
"cannot reload temporary value");
423 compiler->derived()->materialize_constant(
424 const_data().data(), state.c.bank, state.c.size, reg);
431template <IRAdaptor Adaptor,
typename Derived, CompilerConfig Config>
432typename CompilerBase<Adaptor, Derived, Config>::AsmReg
433 CompilerBase<Adaptor, Derived, Config>::ValuePart::alloc_specific_impl(
434 CompilerBase *compiler, AsmReg reg,
const bool reload)
noexcept {
435 assert(!state.c.reg.valid());
437 if (has_assignment()) {
438 auto ap = assignment();
439 assert(!ap.fixed_assignment());
441 if (ap.register_valid() && ap.get_reg() == reg) {
447 auto ®_file = compiler->register_file;
448 if (reg_file.is_used(reg)) {
449 compiler->evict_reg(reg);
452 reg_file.mark_clobbered(reg);
453 if (has_assignment()) {
454 assert(compiler->may_change_value_state());
456 reg_file.mark_used(reg, state.v.local_idx, state.v.part);
457 auto ap = assignment();
458 auto old_reg = AsmReg::make_invalid();
459 if (ap.register_valid()) {
460 old_reg = ap.get_reg();
464 ap.set_register_valid(
true);
471 if (old_reg.valid()) {
472 compiler->derived()->mov(reg, old_reg, ap.part_size());
473 reg_file.unmark_used(old_reg);
475 compiler->derived()->reload_to_reg(reg, ap);
478 assert(!ap.stack_valid() &&
"alloc_reg with valid stack slot");
481 reg_file.mark_used(reg, INVALID_VAL_LOCAL_IDX, 0);
482 reg_file.mark_fixed(reg);
485 if (state.c.reg.valid()) {
487 compiler->derived()->mov(reg, state.c.reg, 8);
488 reg_file.unmark_fixed(state.c.reg);
489 reg_file.unmark_used(state.c.reg);
491 assert(is_const() &&
"cannot reload temporary value");
492 compiler->derived()->materialize_constant(
493 const_data().data(), state.c.bank, state.c.size, reg);
498 state.c.owned =
true;
504template <IRAdaptor Adaptor,
typename Derived, CompilerConfig Config>
505typename CompilerBase<Adaptor, Derived, Config>::AsmReg
506 CompilerBase<Adaptor, Derived, Config>::ValuePart::
507 reload_into_specific_fixed(CompilerBase *compiler,
509 unsigned size)
noexcept {
511 compiler->derived()->materialize_constant(
512 const_data().data(), state.c.bank, state.c.size, reg);
515 if (!has_assignment()) {
517 assert(reg != cur_reg());
520 compiler->derived()->mov(reg, cur_reg(), size);
524 auto ap = assignment();
526 assert(cur_reg() != reg);
527 compiler->derived()->mov(reg, cur_reg(), ap.part_size());
528 }
else if (ap.register_valid()) {
529 assert(ap.get_reg() != reg);
531 compiler->derived()->mov(reg, ap.get_reg(), ap.part_size());
533 assert(!ap.fixed_assignment());
534 compiler->derived()->reload_to_reg(reg, ap);
537 compiler->register_file.mark_clobbered(reg);
541template <IRAdaptor Adaptor,
typename Derived, CompilerConfig Config>
542void CompilerBase<Adaptor, Derived, Config>::ValuePart::lock(
543 CompilerBase *compiler)
noexcept {
544 assert(has_assignment());
546 auto ap = assignment();
547 assert(ap.register_valid());
549 const auto reg = ap.get_reg();
550 compiler->register_file.inc_lock_count(reg);
554template <IRAdaptor Adaptor,
typename Derived, CompilerConfig Config>
555void CompilerBase<Adaptor, Derived, Config>::ValuePart::unlock(
556 CompilerBase *compiler)
noexcept {
557 assert(has_assignment());
558 if (!state.v.reg.valid()) {
562 compiler->register_file.dec_lock_count(state.v.reg);
563 state.v.reg = AsmReg::make_invalid();
566template <IRAdaptor Adaptor,
typename Derived, CompilerConfig Config>
567void CompilerBase<Adaptor, Derived, Config>::ValuePart::set_value(
568 CompilerBase *compiler, ValuePart &&other)
noexcept {
569 auto ®_file = compiler->register_file;
570 if (!has_assignment()) {
572 if (!other.can_salvage()) {
574 AsmReg cur_reg = alloc_reg(compiler);
575 other.reload_into_specific_fixed(compiler, cur_reg);
576 other.reset(compiler);
582 if (!other.has_assignment()) {
583 assert(other.state.c.owned &&
"can_salvage true for unowned value??");
587 *
this = std::move(other);
594 state.c.reg = other.salvage_keep_used(compiler);
595 state.c.owned =
true;
596 reg_file.mark_fixed(state.c.reg);
597 reg_file.update_reg_assignment(state.c.reg, INVALID_VAL_LOCAL_IDX, 0);
602 auto ap = assignment();
603 assert(!ap.variable_ref() &&
"cannot update variable ref");
605 if (ap.fixed_assignment() || !other.can_salvage()) {
607 AsmReg cur_reg = alloc_reg(compiler);
608 other.reload_into_specific_fixed(compiler, cur_reg, ap.part_size());
609 other.reset(compiler);
610 ap.set_register_valid(
true);
611 ap.set_modified(
true);
616 if (ap.register_valid()) {
619 auto cur_reg = ap.get_reg();
620 assert(!reg_file.is_fixed(cur_reg));
621 reg_file.unmark_used(cur_reg);
624 AsmReg new_reg = other.salvage_keep_used(compiler);
625 reg_file.update_reg_assignment(new_reg, local_idx(), part());
627 ap.set_register_valid(
true);
628 ap.set_modified(
true);
631template <IRAdaptor Adaptor,
typename Derived, CompilerConfig Config>
632void CompilerBase<Adaptor, Derived, Config>::ValuePart::set_value_reg(
633 CompilerBase *compiler, AsmReg value_reg)
noexcept {
634 assert(compiler->may_change_value_state());
636 auto ®_file = compiler->register_file;
639 assert(value_reg.valid() &&
"cannot initialize with invalid register");
640 assert(!state.c.reg.valid() &&
641 "attempted to overwrite already initialized and locked ValuePartRef");
643 if (!has_assignment()) {
644 assert(!is_const() &&
"cannot mutate constant ValuePartRef");
645 state.c.reg = value_reg;
646 state.c.owned =
true;
647 reg_file.mark_used(state.c.reg, INVALID_VAL_LOCAL_IDX, 0);
648 reg_file.mark_fixed(state.c.reg);
653 auto ap = assignment();
654 assert(!ap.variable_ref() &&
"cannot update variable ref");
656 if (ap.fixed_assignment()) {
658 auto cur_reg = ap.get_reg();
659 assert(reg_file.is_used(cur_reg));
660 assert(reg_file.is_fixed(cur_reg));
661 assert(reg_file.reg_local_idx(cur_reg) == local_idx());
663 assert(cur_reg != value_reg);
664 compiler->derived()->mov(cur_reg, value_reg, ap.part_size());
665 ap.set_register_valid(
true);
666 ap.set_modified(
true);
671 assert(!ap.register_valid() && !ap.stack_valid() &&
672 "attempted to overwrite already initialized ValuePartRef");
674 reg_file.mark_used(value_reg, local_idx(), part());
675 reg_file.mark_clobbered(value_reg);
676 ap.set_reg(value_reg);
677 ap.set_register_valid(
true);
678 ap.set_modified(
true);
681template <IRAdaptor Adaptor,
typename Derived, CompilerConfig Config>
682typename CompilerBase<Adaptor, Derived, Config>::AsmReg
683 CompilerBase<Adaptor, Derived, Config>::ValuePart::salvage_keep_used(
684 CompilerBase *compiler)
noexcept {
685 assert(compiler->may_change_value_state());
686 assert(can_salvage());
687 if (!has_assignment()) {
688 AsmReg reg = state.c.reg;
689 compiler->register_file.unmark_fixed(reg);
690 state.c.reg = AsmReg::make_invalid();
694 auto ap = assignment();
695 assert(ap.register_valid());
696 auto cur_reg = ap.get_reg();
699 assert(ap.fixed_assignment() || !compiler->register_file.is_fixed(cur_reg));
700 if (ap.fixed_assignment()) {
701 compiler->register_file.dec_lock_count(cur_reg);
704 ap.set_register_valid(
false);
705 ap.set_fixed_assignment(
false);
709template <IRAdaptor Adaptor,
typename Derived, CompilerConfig Config>
710void CompilerBase<Adaptor, Derived, Config>::ValuePart::reset(
711 CompilerBase *compiler)
noexcept {
712 AsmReg reg = state.c.reg;
720 assert(!has_assignment() || assignment().modified() ||
true);
723 if (!has_assignment()) {
725 compiler->register_file.unmark_fixed(reg);
726 compiler->register_file.unmark_used(reg);
729 assert(compiler->may_change_value_state());
730 bool reg_unlocked = compiler->register_file.dec_lock_count(reg);
731 if (reg_unlocked && state.v.owned) {
732 assert(assignment().register_valid());
733 assert(!assignment().fixed_assignment());
734 compiler->register_file.unmark_used(reg);
735 assignment().set_register_valid(
false);
739 state.c.reg = AsmReg::make_invalid();
742template <IRAdaptor Adaptor,
typename Derived, CompilerConfig Config>
743struct CompilerBase<Adaptor, Derived, Config>::ValuePartRef : ValuePart {
746 template <
typename... Args>
747 ValuePartRef(CompilerBase *compiler, Args &&...args) noexcept
748 : ValuePart(std::forward<Args>(args)...), compiler(compiler) {}
750 explicit ValuePartRef(
const ValuePartRef &) =
delete;
752 ValuePartRef(ValuePartRef &&other) noexcept
753 : ValuePart(std::move(other)), compiler(other.compiler) {}
755 ~ValuePartRef() noexcept {
reset(); }
757 ValuePartRef &operator=(
const ValuePartRef &) =
delete;
759 ValuePartRef &operator=(ValuePartRef &&other)
noexcept {
760 if (
this == &other) {
764 ValuePart::operator=(std::move(other));
768 AsmReg alloc_reg(u64 exclusion_mask = 0) noexcept {
769 return ValuePart::alloc_reg(compiler, exclusion_mask);
772 AsmReg alloc_try_reuse(ValuePart &ref)
noexcept {
773 return ValuePart::alloc_try_reuse(compiler, ref);
776 void alloc_specific(AsmReg reg)
noexcept {
777 ValuePart::alloc_specific(compiler, reg);
780 AsmReg load_to_reg() noexcept {
return ValuePart::load_to_reg(compiler); }
782 void load_to_specific(AsmReg reg)
noexcept {
783 ValuePart::load_to_specific(compiler, reg);
786 AsmReg reload_into_specific_fixed(AsmReg reg,
unsigned size = 0) noexcept {
787 return ValuePart::reload_into_specific_fixed(compiler, reg, size);
790 AsmReg reload_into_specific_fixed(CompilerBase *compiler,
792 unsigned size = 0) noexcept {
793 return ValuePart::reload_into_specific_fixed(compiler, reg, size);
796 ValuePartRef get_unowned_ref() noexcept {
797 return ValuePartRef{compiler, ValuePart::get_unowned()};
800 ValuePartRef into_temporary() &&
noexcept {
803 std::move(*
static_cast<ValuePart *
>(
this)).into_temporary(compiler)};
806 ValuePartRef into_extended(
bool sign, u32 from, u32 to) &&
noexcept {
807 return ValuePartRef{compiler,
808 std::move(*
static_cast<ValuePart *
>(
this))
809 .into_extended(compiler, sign, from, to)};
812 void lock() noexcept { ValuePart::lock(compiler); }
813 void unlock() noexcept { ValuePart::unlock(compiler); }
815 void set_value(ValuePart &&other)
noexcept {
816 ValuePart::set_value(compiler, std::move(other));
819 void set_value_reg(AsmReg value_reg)
noexcept {
820 ValuePart::set_value_reg(compiler, value_reg);
823 AsmReg salvage() noexcept {
return ValuePart::salvage(compiler); }
825 void reset() noexcept { ValuePart::reset(compiler); }
CompilerBase(Adaptor *adaptor)
Initialize a CompilerBase, should be called by the derived classes.