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 { r < w } -> std::convertible_to<bool>;
113 };
114
115
116 /// An invalid block reference
117 { T::INVALID_BLOCK_REF } -> SameBaseAs<typename T::IRBlockRef>;
118 requires requires(typename T::IRBlockRef r) {
119 { r == T::INVALID_BLOCK_REF } -> std::convertible_to<bool>;
120 { r != T::INVALID_BLOCK_REF } -> std::convertible_to<bool>;
121 };
122
123 /// An invalid function reference
124 { T::INVALID_FUNC_REF } -> SameBaseAs<typename T::IRFuncRef>;
125 requires requires(typename T::IRFuncRef r) {
126 { r == T::INVALID_FUNC_REF } -> std::convertible_to<bool>;
127 { r != T::INVALID_FUNC_REF } -> std::convertible_to<bool>;
128 };
129
130 /// Can the adaptor provide information about the highest value index in the
131 /// function to be compiled ahead of time?
132 { T::TPDE_PROVIDES_HIGHEST_VAL_IDX } -> SameBaseAs<bool>;
133
134 /// Does the liveness analysis have to explicitly visit the function
135 /// arguments or do they show up as values in the entry block?
136 ///
137 /// Note: One of these has to be true
138 { T::TPDE_LIVENESS_VISIT_ARGS } -> SameBaseAs<bool>;
139
140 // Can the adaptor store two 32 bit values for efficient access through the
141 // block reference?
142 // { T::TPDE_CAN_STORE_BLOCK_AUX } -> std::same_as<bool>;
143 // TODO(ts): think about if this is optional
144
145
146 // general module information
147
148 /// Provides the number of functions to compile
149 { a.func_count() } -> std::convertible_to<u32>;
150
151 /// Provides an iterator over all functions in the current module
152 { a.funcs() } -> IRRange<typename T::IRFuncRef>;
153
154 /// Provides an iterator over all functions that should actually be compiled
155 { a.funcs_to_compile() } -> IRRange<typename T::IRFuncRef>;
156
157
158 // information about functions that needs to be always available because of
159 // calls
160
161 /// Provides the linkage name of the specified function
162 {
163 a.func_link_name(ARG(typename T::IRFuncRef))
164 } -> std::convertible_to<std::string_view>;
165
166 /// Is the specified function not going to be compiled?
167 { a.func_extern(ARG(typename T::IRFuncRef)) } -> std::convertible_to<bool>;
168
169 /// Is the specified function only visible to the current module
170 {
171 a.func_only_local(ARG(typename T::IRFuncRef))
172 } -> std::convertible_to<bool>;
173
174 /// Does the specified function have weak linkage
175 {
176 a.func_has_weak_linkage(ARG(typename T::IRFuncRef))
177 } -> std::convertible_to<bool>;
178
179
180 // information about the current function
181
182 /// Does the current function need unwind info
183 { a.cur_needs_unwind_info() } -> std::convertible_to<bool>;
184
185 /// Does the current function take a variable number of arguments
186 { a.cur_is_vararg() } -> std::convertible_to<bool>;
187
188 /// Provides the highest value index in the current function.
189 /// Only needs to be implemented if TPDE_PROVIDES_HIGHEST_VAL_IDX is true
190 requires IsFalse<T::TPDE_PROVIDES_HIGHEST_VAL_IDX> || requires {
191 { a.cur_highest_val_idx() } -> std::convertible_to<u32>;
192 };
193
194 /// Provides an iterator over the arguments of the current function
195 { a.cur_args() } -> IRRange<typename T::IRValueRef>;
196
197 /// Is the argument specified by the given index copied before it is passed
198 /// to the callee argument?
199 { a.cur_arg_is_byval(ARG(u32)) } -> std::convertible_to<bool>;
200
201 /// The size of a byval argument
202 { a.cur_arg_byval_size(ARG(u32)) } -> std::same_as<u32>;
203
204 /// The alignment of a byval argument
205 { a.cur_arg_byval_align(ARG(u32)) } -> std::same_as<u32>;
206
207 /// Is the argument specified by the given index actually a returned struct?
208 { a.cur_arg_is_sret(ARG(u32)) } -> std::convertible_to<bool>;
209
210 // TODO(ts): func sret
211
212 /// Provides an iterator of static allocas
213 { a.cur_static_allocas() } -> IRRange<typename T::IRValueRef>;
214
215 /// Does the current function have dynamically sized stack allocations?
216 { a.cur_has_dynamic_alloca() } -> std::convertible_to<bool>;
217
218 /// Provides a reference to the entry block of the current function
219 { a.cur_entry_block() } -> std::convertible_to<typename T::IRBlockRef>;
220
221 /// Provides a range of all blocks in the function. First block must be the
222 /// entry block, others need to be in an arbitrary but consistent order.
223 ///
224 /// Note: The order influences the block layout generated by the analyzer
225 /// as it favors keeping blocks on the same level in this order
226 { a.cur_blocks() } -> IRRange<typename T::IRBlockRef>;
227
228
229 // information about blocks
230
231 /// Provides an iterator over the successors of a block
232 {
233 a.block_succs(ARG(typename T::IRBlockRef))
235
236 /// Provides an iterator over the (non-PHI) instructions in a block
237 {
238 a.block_insts(ARG(typename T::IRBlockRef))
240
241 /// Provides an iterator over the PHIs in a block
242 {
243 a.block_phis(ARG(typename T::IRBlockRef))
245
246 /// The compiler needs to store some values that are attached to a block.
247 /// Some adaptors may be able to store them efficiently so we ask the
248 /// adaptor to handle the storage for them
249 { a.block_info(ARG(typename T::IRBlockRef)) } -> std::same_as<u32>;
250
251 /// Set the block info
252 { a.block_set_info(ARG(typename T::IRBlockRef), ARG(u32)) };
253
254 /// The compiler needs to store some values that are attached to a block.
255 /// Some adaptors may be able to store them efficiently so we ask the
256 /// adaptor to handle the storage for them
257 { a.block_info2(ARG(typename T::IRBlockRef)) } -> std::same_as<u32>;
258
259 /// Set the block info
260 { a.block_set_info2(ARG(typename T::IRBlockRef), ARG(u32)) };
261
262 /// If logging is enabled, we want to be able to print blocks and want to
263 /// give the adaptor the opportunity to dictate how that is done
264 { a.block_fmt_ref(ARG(typename T::IRBlockRef)) } -> CanBeFormatted;
265
266
267 // information about values
268
269 /// Provides the local index for a value
270 ///
271 /// The compiler maintains a few per-value data structures which are
272 /// implemented as arrays for efficiency reasons. To facilitate access to
273 /// them we ask the IRAdaptor to assign each value (including globals and
274 /// possibly functions if they are exposed to the compiler) a local index in
275 /// the context of the current function
276 {
277 a.val_local_idx(ARG(typename T::IRValueRef))
278 } -> std::convertible_to<ValLocalIdx>;
279
280 // TODO(ts): add separate function to get local idx which is only used in
281 // the analyzer if the adaptor wants to lazily assign indices?
282
283 /// Should a value be ignored in the liveness analysis?
284 /// This is recommended for values classified as variable references
285 /// such as globals or allocas
286 {
287 a.val_ignore_in_liveness_analysis(ARG(typename T::IRValueRef))
288 } -> std::convertible_to<bool>;
289
290 /// Indicate whether a value is the result of a PHI node. Used to detect and
291 /// resolve dependencies between PHI nodes.
292 { a.val_is_phi(ARG(typename T::IRValueRef)) } -> std::convertible_to<bool>;
293
294 /// Provides the PHIRef for a PHI
295 {
296 a.val_as_phi(ARG(typename T::IRValueRef))
298
299 /// Provides the allocation size for an alloca
300 {
301 a.val_alloca_size(ARG(typename T::IRValueRef))
302 } -> std::convertible_to<u32>;
303
304 /// Provides the alignment for an alloca
305 {
306 a.val_alloca_align(ARG(typename T::IRValueRef))
307 } -> std::convertible_to<u32>;
308
309 /// If logging is enabled, we want to be able to print values and want to
310 /// give the adaptor the opportunity to dictate how that is done
311 { a.value_fmt_ref(ARG(typename T::IRValueRef)) } -> CanBeFormatted;
312
313 // Instruction information
314
315 /// Provides an iterator over the operands of an instruction.
316 {
317 a.inst_operands(ARG(typename T::IRInstRef))
319
320 /// Result values of an instruction.
321 {
322 a.inst_results(ARG(typename T::IRInstRef))
324
325 /// Whether to skip the instruction during compilation.
326 { a.inst_fused(ARG(typename T::IRInstRef)) } -> std::convertible_to<bool>;
327
328 /// If logging is enabled, we want to be able to print values and want to
329 /// give the adaptor the opportunity to dictate how that is done
330 { a.inst_fmt_ref(ARG(typename T::IRInstRef)) } -> CanBeFormatted;
331
332
333 // compilation lifecycle
334
335 /// The compilation was started
336 { a.start_compile() };
337
338 /// The compilation was finished
339 { a.end_compile() };
340
341 /// The compiler is now compiling the specified function. Return false if
342 /// compiling this function is not supported.
343 { a.switch_func(ARG(typename T::IRFuncRef)) } -> std::convertible_to<bool>;
344
345 /// The compiler is being resetted. If there is any data remaining that
346 /// would cause problems with recompiling it should be cleared
347 { a.reset() };
348};
349
350#undef ARG
351
352} // namespace tpde