TPDE
Loading...
Searching...
No Matches
ValueRef.hpp
1// SPDX-FileCopyrightText: 2025 Contributors to TPDE <https://tpde.org>
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3#pragma once
4
5#include "tpde/ValueAssignment.hpp"
6
7#include <cstring>
8
9namespace tpde {
10
11template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
12struct CompilerBase<Adaptor, Derived, Config>::ValueRef {
13 struct AssignmentData {
14 /// 0 = unowned reference/invalid, 1 = ref-counted, 2 = owned
15 uint8_t mode;
16 ValLocalIdx local_idx;
17 ValueAssignment *assignment;
18 };
19 static_assert(ValRefSpecialStruct<AssignmentData>);
20
21 union {
22 AssignmentData a;
23 Derived::ValRefSpecial s;
24 } state;
25
26 CompilerBase *compiler;
27
28 ValueRef(CompilerBase *compiler) noexcept
29 : state{AssignmentData()}, compiler(compiler) {}
30
31 ValueRef(CompilerBase *compiler, ValLocalIdx local_idx) noexcept
32 : state{AssignmentData{
33 .local_idx = local_idx,
34 .assignment = compiler->val_assignment(local_idx),
35 }
36 }, compiler(compiler) {
37 assert(!state.a.assignment->pending_free && "access of free'd assignment");
38
39 // Extended liveness checks in debug builds.
40#ifndef NDEBUG
41 if (!variable_ref()) {
42 const auto &liveness =
43 compiler->analyzer.liveness_info(state.a.local_idx);
44 assert(liveness.last >= compiler->cur_block_idx &&
45 "ref-counted value used outside of its live range");
46 assert(state.a.assignment->references_left != 0);
47 if (state.a.assignment->references_left == 1 && !liveness.last_full) {
48 assert(liveness.last == compiler->cur_block_idx &&
49 "liveness of non-last-full value must end at last use");
50 }
51 }
52#endif
53
54 if (variable_ref()) {
55 state.a.mode = 0;
56 } else if (state.a.assignment->references_left <= 1 &&
57 !state.a.assignment->delay_free) {
58 state.a.mode = 2;
59 } else {
60 state.a.mode = 1;
61 }
62 }
63
64 template <typename... T>
65 ValueRef(CompilerBase *compiler, T &&...args) noexcept
66 : state{.s = typename Derived::ValRefSpecial(std::forward<T>(args)...)},
67 compiler(compiler) {
68 assert(state.a.mode >= 4);
69 }
70
71private:
72 // Private copy constructor.
73 ValueRef(const ValueRef &other) = default;
74
75public:
76 ValueRef(ValueRef &&other) noexcept
77 : state{other.state}, compiler(other.compiler) {
78 other.state.a = AssignmentData{};
79 }
80
81 ~ValueRef() noexcept { reset(); }
82
83 ValueRef &operator=(const ValueRef &) = delete;
84
85 ValueRef &operator=(ValueRef &&other) noexcept {
86 if (this == &other) {
87 return *this;
88 }
89 reset();
90 assert(compiler == other.compiler);
91 this->state = other.state;
92 other.state.a.mode = 0;
93 return *this;
94 }
95
96 bool has_assignment() const noexcept { return state.a.mode < 4; }
97
98 [[nodiscard]] ValueAssignment *assignment() const noexcept {
99 assert(has_assignment());
100 assert(state.a.assignment != nullptr);
101 return state.a.assignment;
102 }
103
104 /// Returns whether the value is destroyed after this use.
105 bool is_owned() noexcept { return state.a.mode == 2; }
106
107 /// Convert into an unowned reference; must be called before first part is
108 /// accessed.
109 void disown() noexcept {
110 if (has_assignment()) {
111 state.a.mode = 0;
112 }
113 }
114
115 /// Get an unowned reference to this value. Previously accessed parts might
116 /// already have been destroyed if the value is in its last use.
117 ValueRef disowned() noexcept {
118 ValueRef res = *this;
119 res.disown();
120 return res;
121 }
122
123 ValLocalIdx local_idx() const noexcept {
124 assert(has_assignment());
125 return state.a.local_idx;
126 }
127
128 ValuePartRef part(unsigned part) noexcept TPDE_LIFETIMEBOUND {
129 if (has_assignment()) {
130 return ValuePartRef{
131 compiler, local_idx(), state.a.assignment, part, is_owned()};
132 }
133 return ValuePartRef{
134 compiler, compiler->derived()->val_part_ref_special(state.s, part)};
135 }
136
137 /// Like part(), but the returned part is always unowned and will not release
138 /// registers of the value assignment when reset.
139 ValuePartRef part_unowned(unsigned part) noexcept TPDE_LIFETIMEBOUND {
140 if (has_assignment()) {
141 return ValuePartRef{
142 compiler, local_idx(), state.a.assignment, part, false};
143 }
144 return ValuePartRef{
145 compiler, compiler->derived()->val_part_ref_special(state.s, part)};
146 }
147
148 /// Reset the reference to the value part
149 void reset() noexcept;
150
151 bool variable_ref() const noexcept {
152 assert(has_assignment());
153 return state.a.assignment->variable_ref;
154 }
155};
156
157template <IRAdaptor Adaptor, typename Derived, CompilerConfig Config>
158void CompilerBase<Adaptor, Derived, Config>::ValueRef::reset() noexcept {
159 if (state.a.mode == 1 || state.a.mode == 2) {
160 state.a.mode = 0;
161
162 assert(!state.a.assignment->pending_free && "access of free'd assignment");
163
164 auto &ref_count = state.a.assignment->references_left;
165 assert(ref_count != 0);
166 if (--ref_count == 0) {
167 compiler->release_assignment(state.a.local_idx, state.a.assignment);
168 }
169 }
170
171#ifndef NDEBUG
172 state.a.assignment = nullptr;
173 state.a.local_idx = INVALID_VAL_LOCAL_IDX;
174#endif
175}
176} // namespace tpde
CompilerBase(Adaptor *adaptor)
Initialize a CompilerBase, should be called by the derived classes.