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