TPDE
Loading...
Searching...
No Matches
IRAdaptor.hpp
1// SPDX-FileCopyrightText: 2025 Contributors to TPDE <https://tpde.org>
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5#pragma once
6#include "CompilerConfig.hpp"
7#include "ValLocalIdx.hpp"
8#include "base.hpp"
9#include <concepts>
10#include <format>
11#include <string_view>
12
13#ifdef ARG
14 #error ARG is used as a temporary preprocessor macro
15#endif
16
17#define ARG(x) std::declval<x>()
18
19namespace tpde {
20
21template <typename T>
22concept CanBeFormatted = requires(T a) {
23 { std::format("{}", a) };
24};
25
26template <bool B>
27concept IsFalse = (B == false);
28
29/// Concept describing an iterator over some range
30///
31/// It is purposefully kept simple (and probably wrong)
32template <typename T, typename Value>
33concept IRIter = requires(T i, T i2) {
34 { *i } -> std::convertible_to<Value>;
35 { ++i } -> std::convertible_to<T>;
36 { i != i2 } -> std::convertible_to<bool>;
37};
38
39template <typename T, typename Value, typename IRIter>
40concept IREndIter = requires(IRIter i, T i2) {
41 { i != i2 } -> std::convertible_to<bool>;
42};
43
44/// Concept describing a very simple range
45///
46/// It is purposefully kept simple since the full concepts from std::ranges want
47/// too much code
48template <typename T, typename Value>
49concept IRRange = requires(T r) {
50 { r.begin() } -> IRIter<Value>;
51 { r.end() } -> IREndIter<Value, decltype(r.begin())>;
52};
53
54/// PHI-Nodes are a special case of IRValues and need to be inspected more
55/// thoroughly by the compiler. Therefore, they need to expose their special
56/// properties, namely the number of incoming values, the value and block for
57/// each slot and the incoming value for each block
58template <typename T, typename IRValue, typename IRBlockRef>
59concept PHIRef = requires(T r) {
60 /// Provides the number of incoming values
61 { r.incoming_count() } -> std::convertible_to<u32>;
62
63 /// Provides the incoming value for a slot
64 {
65 r.incoming_val_for_slot(std::declval<u32>())
66 } -> std::convertible_to<IRValue>;
67
68 /// Provides the incoming block for a slot
69 {
70 r.incoming_block_for_slot(std::declval<u32>())
71 } -> std::convertible_to<IRBlockRef>;
72
73 /// Provides the incoming value for a specific block
74 {
75 r.incoming_val_for_block(std::declval<IRBlockRef>())
76 } -> std::convertible_to<IRValue>;
77};
78
79/// The IRAdaptor specifies the interface with which the IR-independent parts of
80/// the compiler interact with the source IR
81///
82/// It provides type definitions for values and blocks, ways to iterate over
83/// blocks, values and their arguments.
84/// Additionally, it exposes information about functions in the IR.
85///
86/// Since we also expect that most IRAdaptors will allocate some space or have
87/// some available in the storage of values/blocks, we ask the adaptor to also
88/// store some information about values/blocks for setting/retrieval by the
89/// compiler.
90template <typename T>
91concept IRAdaptor = requires(T a) {
92 /// A reference to a value in your IR. Should not be greater than 8 bytes
93 typename T::IRValueRef;
94 requires sizeof(typename T::IRValueRef) <= 8;
95
96 /// A reference to an IR instruction.
97 typename T::IRInstRef;
98
99 /// A reference to a block in your IR. Should not be greater than 8 bytes
100 typename T::IRBlockRef;
101 requires sizeof(typename T::IRBlockRef) <= 8;
102
103 /// A reference to a function in your IR. Should not be greater than 8 bytes
104 typename T::IRFuncRef;
105 requires sizeof(typename T::IRFuncRef) <= 8;
106
107 /// An invalid value reference
108 { T::INVALID_VALUE_REF } -> SameBaseAs<typename T::IRValueRef>;
109 requires requires(typename T::IRValueRef r, typename T::IRValueRef w) {
110 { r == T::INVALID_VALUE_REF } -> std::convertible_to<bool>;
111 { r != T::INVALID_VALUE_REF } -> std::convertible_to<bool>;
112 };
113
114
115 /// An invalid block reference
116 { T::INVALID_BLOCK_REF } -> SameBaseAs<typename T::IRBlockRef>;
117 requires requires(typename T::IRBlockRef r) {
118 { r == T::INVALID_BLOCK_REF } -> std::convertible_to<bool>;
119 { r != T::INVALID_BLOCK_REF } -> std::convertible_to<bool>;
120 };
121
122 /// An invalid function reference
123 { T::INVALID_FUNC_REF } -> SameBaseAs<typename T::IRFuncRef>;
124 requires requires(typename T::IRFuncRef r) {
125 { r == T::INVALID_FUNC_REF } -> std::convertible_to<bool>;
126 { r != T::INVALID_FUNC_REF } -> std::convertible_to<bool>;
127 };
128
129 /// Can the adaptor provide information about the highest value index in the
130 /// function to be compiled ahead of time?
131 { T::TPDE_PROVIDES_HIGHEST_VAL_IDX } -> SameBaseAs<bool>;
132
133 /// Does the liveness analysis have to explicitly visit the function
134 /// arguments or do they show up as values in the entry block?
135 ///
136 /// Note: One of these has to be true
137 { T::TPDE_LIVENESS_VISIT_ARGS } -> SameBaseAs<bool>;
138
139 // Can the adaptor store two 32 bit values for efficient access through the
140 // block reference?
141 // { T::TPDE_CAN_STORE_BLOCK_AUX } -> std::same_as<bool>;
142 // TODO(ts): think about if this is optional
143
144
145 // general module information
146
147 /// Provides the number of functions to compile
148 { a.func_count() } -> std::convertible_to<u32>;
149
150 /// Provides an iterator over all functions in the current module
151 { a.funcs() } -> IRRange<typename T::IRFuncRef>;
152
153 /// Provides an iterator over all functions that should actually be compiled
154 { a.funcs_to_compile() } -> IRRange<typename T::IRFuncRef>;
155
156
157 // information about functions that needs to be always available because of
158 // calls
159
160 /// Provides the linkage name of the specified function
161 {
162 a.func_link_name(ARG(typename T::IRFuncRef))
163 } -> std::convertible_to<std::string_view>;
164
165 /// Is the specified function not going to be compiled?
166 { a.func_extern(ARG(typename T::IRFuncRef)) } -> std::convertible_to<bool>;
167
168 /// Is the specified function only visible to the current module
169 {
170 a.func_only_local(ARG(typename T::IRFuncRef))
171 } -> std::convertible_to<bool>;
172
173 /// Does the specified function have weak linkage
174 {
175 a.func_has_weak_linkage(ARG(typename T::IRFuncRef))
176 } -> std::convertible_to<bool>;
177
178
179 // information about the current function
180
181 /// Does the current function need unwind info
182 { a.cur_needs_unwind_info() } -> std::convertible_to<bool>;
183
184 /// Does the current function take a variable number of arguments
185 { a.cur_is_vararg() } -> std::convertible_to<bool>;
186
187 /// Provides the highest value index in the current function.
188 /// Only needs to be implemented if TPDE_PROVIDES_HIGHEST_VAL_IDX is true
189 requires IsFalse<T::TPDE_PROVIDES_HIGHEST_VAL_IDX> || requires {
190 { a.cur_highest_val_idx() } -> std::convertible_to<u32>;
191 };
192
193 /// Provides an iterator over the arguments of the current function
194 { a.cur_args() } -> IRRange<typename T::IRValueRef>;
195
196 /// Is the argument specified by the given index copied before it is passed
197 /// to the callee argument?
198 { a.cur_arg_is_byval(ARG(u32)) } -> std::convertible_to<bool>;
199
200 /// The size of a byval argument
201 { a.cur_arg_byval_size(ARG(u32)) } -> std::same_as<u32>;
202
203 /// The alignment of a byval argument
204 { a.cur_arg_byval_align(ARG(u32)) } -> std::same_as<u32>;
205
206 /// Is the argument specified by the given index actually a returned struct?
207 { a.cur_arg_is_sret(ARG(u32)) } -> std::convertible_to<bool>;
208
209 // TODO(ts): func sret
210
211 /// Provides an iterator of static allocas
212 { a.cur_static_allocas() } -> IRRange<typename T::IRValueRef>;
213
214 /// Does the current function have dynamically sized stack allocations?
215 { a.cur_has_dynamic_alloca() } -> std::convertible_to<bool>;
216
217 /// Provides a reference to the entry block of the current function
218 { a.cur_entry_block() } -> std::convertible_to<typename T::IRBlockRef>;
219
220 /// Provides a range of all blocks in the function. First block must be the
221 /// entry block, others need to be in an arbitrary but consistent order.
222 ///
223 /// Note: The order influences the block layout generated by the analyzer
224 /// as it favors keeping blocks on the same level in this order
225 { a.cur_blocks() } -> IRRange<typename T::IRBlockRef>;
226
227
228 // information about blocks
229
230 /// Provides an iterator over the successors of a block
231 {
232 a.block_succs(ARG(typename T::IRBlockRef))
234
235 /// Provides an iterator over the (non-PHI) instructions in a block
236 {
237 a.block_insts(ARG(typename T::IRBlockRef))
239
240 /// Provides an iterator over the PHIs in a block
241 {
242 a.block_phis(ARG(typename T::IRBlockRef))
244
245 /// The compiler needs to store some values that are attached to a block.
246 /// Some adaptors may be able to store them efficiently so we ask the
247 /// adaptor to handle the storage for them
248 { a.block_info(ARG(typename T::IRBlockRef)) } -> std::same_as<u32>;
249
250 /// Set the block info
251 { a.block_set_info(ARG(typename T::IRBlockRef), ARG(u32)) };
252
253 /// The compiler needs to store some values that are attached to a block.
254 /// Some adaptors may be able to store them efficiently so we ask the
255 /// adaptor to handle the storage for them
256 { a.block_info2(ARG(typename T::IRBlockRef)) } -> std::same_as<u32>;
257
258 /// Set the block info
259 { a.block_set_info2(ARG(typename T::IRBlockRef), ARG(u32)) };
260
261 /// If logging is enabled, we want to be able to print blocks and want to
262 /// give the adaptor the opportunity to dictate how that is done
263 { a.block_fmt_ref(ARG(typename T::IRBlockRef)) } -> CanBeFormatted;
264
265
266 // information about values
267
268 /// Provides the local index for a value
269 ///
270 /// The compiler maintains a few per-value data structures which are
271 /// implemented as arrays for efficiency reasons. To facilitate access to
272 /// them we ask the IRAdaptor to assign each value (including globals and
273 /// possibly functions if they are exposed to the compiler) a local index in
274 /// the context of the current function
275 {
276 a.val_local_idx(ARG(typename T::IRValueRef))
277 } -> std::convertible_to<ValLocalIdx>;
278
279 // TODO(ts): add separate function to get local idx which is only used in
280 // the analyzer if the adaptor wants to lazily assign indices?
281
282 /// Should a value be ignored in the liveness analysis?
283 /// This is recommended for values classified as variable references
284 /// such as globals or allocas
285 {
286 a.val_ignore_in_liveness_analysis(ARG(typename T::IRValueRef))
287 } -> std::convertible_to<bool>;
288
289 /// Indicate whether a value is the result of a PHI node. Used to detect and
290 /// resolve dependencies between PHI nodes.
291 { a.val_is_phi(ARG(typename T::IRValueRef)) } -> std::convertible_to<bool>;
292
293 /// Provides the PHIRef for a PHI
294 {
295 a.val_as_phi(ARG(typename T::IRValueRef))
297
298 /// Provides the allocation size for an alloca
299 {
300 a.val_alloca_size(ARG(typename T::IRValueRef))
301 } -> std::convertible_to<u32>;
302
303 /// Provides the alignment for an alloca
304 {
305 a.val_alloca_align(ARG(typename T::IRValueRef))
306 } -> std::convertible_to<u32>;
307
308 /// If logging is enabled, we want to be able to print values and want to
309 /// give the adaptor the opportunity to dictate how that is done
310 { a.value_fmt_ref(ARG(typename T::IRValueRef)) } -> CanBeFormatted;
311
312 // Instruction information
313
314 /// Provides an iterator over the operands of an instruction.
315 {
316 a.inst_operands(ARG(typename T::IRInstRef))
318
319 /// Result values of an instruction.
320 {
321 a.inst_results(ARG(typename T::IRInstRef))
323
324 /// Whether to skip the instruction during compilation.
325 { a.inst_fused(ARG(typename T::IRInstRef)) } -> std::convertible_to<bool>;
326
327 /// If logging is enabled, we want to be able to print values and want to
328 /// give the adaptor the opportunity to dictate how that is done
329 { a.inst_fmt_ref(ARG(typename T::IRInstRef)) } -> CanBeFormatted;
330
331
332 // compilation lifecycle
333
334 /// The compilation was started
335 { a.start_compile() };
336
337 /// The compilation was finished
338 { a.end_compile() };
339
340 /// The compiler is now compiling the specified function. Return false if
341 /// compiling this function is not supported.
342 { a.switch_func(ARG(typename T::IRFuncRef)) } -> std::convertible_to<bool>;
343
344 /// The compiler is being reset. If there is any data remaining that
345 /// would cause problems with recompiling it should be cleared
346 { a.reset() };
347};
348
349#undef ARG
350
351} // namespace tpde