midenc_hir/ir/traits.rs
1mod canonicalization;
2mod foldable;
3mod info;
4mod types;
5
6use alloc::format;
7
8pub use self::{
9 canonicalization::Canonicalizable,
10 foldable::{FoldResult, Foldable, OpFoldResult},
11 info::TraitInfo,
12 types::*,
13};
14use super::BlockRef;
15use crate::{
16 AttributeRef, Context, Operation,
17 derive::operation_trait,
18 diagnostics::{Report, Severity, Spanned},
19};
20
21/// Marker trait for commutative ops, e.g. `X op Y == Y op X`
22#[operation_trait]
23pub trait Commutative {}
24
25/// Marker trait for constant-like ops
26#[operation_trait]
27pub trait ConstantLike {}
28
29/// Marker trait for return-like ops
30#[operation_trait]
31pub trait ReturnLike {}
32
33/// Op is a terminator (i.e. it can be used to terminate a block)
34#[operation_trait]
35pub trait Terminator {}
36
37/// Op's regions do not require blocks to end with a [Terminator]
38#[operation_trait]
39pub trait NoTerminator {}
40
41/// Marker trait for idemptoent ops, i.e. `op op X == op X (unary) / X op X == X (binary)`
42#[operation_trait]
43pub trait Idempotent {}
44
45/// Marker trait for ops that exhibit the property `op op X == X`
46#[operation_trait]
47pub trait Involution {}
48
49/// Marker trait for ops which are not permitted to access values defined above them
50#[operation_trait]
51pub trait IsolatedFromAbove {}
52
53/// Marker trait for ops which have only regions of [`crate::RegionKind::Graph`]
54#[operation_trait]
55pub trait HasOnlyGraphRegion {}
56
57/// Op's regions are all single-block graph regions, that not require a terminator
58///
59/// This trait _cannot_ be derived via `derive!`
60#[operation_trait]
61pub trait GraphRegionNoTerminator:
62 NoTerminator + SingleBlock + crate::RegionKindInterface + HasOnlyGraphRegion
63{
64}
65
66// TODO(pauls): Implement verifier
67/// This interface provides information for branching terminator operations, i.e. terminator
68/// operations with successors.
69///
70/// This interface is meant to model well-defined cases of control-flow of value propagation, where
71/// what occurs along control-flow edges is assumed to be side-effect free. For example,
72/// corresponding successor operands and successor block arguments may have different types. In such
73/// cases, `are_types_compatible` can be implemented to compare types along control-flow edges. By
74/// default, type equality is used.
75pub trait BranchOpInterface: crate::Op {
76 /// Returns the operands that correspond to the arguments of the successor at `index`.
77 ///
78 /// It consists of a number of operands that are internally produced by the operation, followed
79 /// by a range of operands that are forwarded. An example operation making use of produced
80 /// operands would be:
81 ///
82 /// ```hir,ignore
83 /// invoke %function(%0)
84 /// label ^success ^error(%1 : i32)
85 ///
86 /// ^error(%e: !error, %arg0: i32):
87 /// ...
88 ///```
89 ///
90 /// The operand that would map to the `^error`s `%e` operand is produced by the `invoke`
91 /// operation, while `%1` is a forwarded operand that maps to `%arg0` in the successor.
92 ///
93 /// Produced operands always map to the first few block arguments of the successor, followed by
94 /// the forwarded operands. Mapping them in any other order is not supported by the interface.
95 ///
96 /// By having the forwarded operands last allows users of the interface to append more forwarded
97 /// operands to the branch operation without interfering with other successor operands.
98 fn get_successor_operands(&self, index: usize) -> crate::SuccessorOperandRange<'_> {
99 let op = <Self as crate::Op>::as_operation(self);
100 let operand_group = op.successors()[index].operand_group as usize;
101 crate::SuccessorOperandRange::forward(op.operands().group(operand_group))
102 }
103 /// The mutable version of [Self::get_successor_operands].
104 fn get_successor_operands_mut(&mut self, index: usize) -> crate::SuccessorOperandRangeMut<'_> {
105 let op = <Self as crate::Op>::as_operation_mut(self);
106 let operand_group = op.successors()[index].operand_group as usize;
107 crate::SuccessorOperandRangeMut::forward(op.operands_mut().group_mut(operand_group))
108 }
109 /// Returns the block argument of the successor corresponding to the operand at `operand_index`.
110 ///
111 /// Returns `None` if the specified operand is not a successor operand.
112 fn get_successor_block_argument(
113 &self,
114 operand_index: usize,
115 ) -> Option<crate::BlockArgumentRef> {
116 let op = <Self as crate::Op>::as_operation(self);
117 let operand_groups = op.operands().num_groups();
118 let mut next_index = 0usize;
119 for operand_group in 0..operand_groups {
120 let group_size = op.operands().group(operand_group).len();
121 if (next_index..(next_index + group_size)).contains(&operand_index) {
122 let arg_index = operand_index - next_index;
123 // We found the operand group, now map that to a successor
124 let succ_info =
125 op.successors().iter().find(|s| operand_group == s.operand_group as usize)?;
126 return succ_info
127 .block
128 .borrow()
129 .successor()
130 .borrow()
131 .arguments()
132 .get(arg_index)
133 .cloned();
134 }
135
136 next_index += group_size;
137 }
138
139 None
140 }
141 /// Returns the successor that would be chosen with the given constant operands.
142 ///
143 /// Each operand of this op has an entry in the `operands` slice. If the operand is non-constant,
144 /// the corresponding entry will be `None`.
145 ///
146 /// Returns `None` if a single successor could not be chosen.
147 #[inline]
148 #[allow(unused_variables)]
149 fn get_successor_for_operands(
150 &self,
151 operands: &[Option<AttributeRef>],
152 ) -> Option<crate::SuccessorInfo> {
153 None
154 }
155 /// This is called to compare types along control-flow edges.
156 ///
157 /// By default, types must be exactly equal to be compatible.
158 fn are_types_compatible(&self, lhs: &crate::Type, rhs: &crate::Type) -> bool {
159 lhs == rhs
160 }
161
162 /// Changes the destination to `new_dest` if the current destination is `old_dest`.
163 fn change_branch_destination(&mut self, old_dest: BlockRef, new_dest: BlockRef) {
164 let op = <Self as crate::Op>::as_operation_mut(self);
165 assert_eq!(old_dest.borrow().num_arguments(), new_dest.borrow().num_arguments());
166 for successor_info in op.successors_mut().iter_mut() {
167 if successor_info.successor() == old_dest {
168 successor_info.block.borrow_mut().set(new_dest);
169 }
170 }
171 }
172}
173
174/// This interface provides information for select-like operations, i.e., operations that forward
175/// specific operands to the output, depending on a binary condition.
176///
177/// If the value of the condition is 1, then the `true` operand is returned, and the third operand
178/// is ignored, even if it was poison.
179///
180/// If the value of the condition is 0, then the `false` operand is returned, and the second operand
181/// is ignored, even if it was poison.
182///
183/// If the condition is poison, then poison is returned.
184///
185/// Implementing operations can also accept shaped conditions, in which case the operation works
186/// element-wise.
187pub trait SelectLikeOpInterface {
188 /// Returns the operand that represents the boolean condition for this select-like op.
189 fn get_condition(&self) -> crate::ValueRef;
190 /// Returns the operand that would be chosen for a true condition.
191 fn get_true_value(&self) -> crate::ValueRef;
192 /// Returns the operand that would be chosen for a false condition.
193 fn get_false_value(&self) -> crate::ValueRef;
194}
195
196/// Marker trait for unary ops, i.e. those which take a single operand
197#[operation_trait]
198pub trait UnaryOp {
199 #[verifier]
200 fn is_unary_op(op: &Operation, context: &Context) -> Result<(), Report> {
201 if op.num_operands() == 1 {
202 Ok(())
203 } else {
204 Err(context
205 .diagnostics()
206 .diagnostic(Severity::Error)
207 .with_message(::alloc::format!("invalid operation {}", op.name()))
208 .with_primary_label(
209 op.span(),
210 format!("incorrect number of operands, expected 1, got {}", op.num_operands()),
211 )
212 .with_help(
213 "this operator implements 'UnaryOp', which requires it to have exactly one \
214 operand",
215 )
216 .into_report())
217 }
218 }
219}
220
221/// Marker trait for binary ops, i.e. those which take two operands
222#[operation_trait]
223pub trait BinaryOp {
224 #[verifier]
225 fn is_binary_op(op: &Operation, context: &Context) -> Result<(), Report> {
226 if op.num_operands() == 2 {
227 Ok(())
228 } else {
229 Err(context
230 .diagnostics()
231 .diagnostic(Severity::Error)
232 .with_message(::alloc::format!("invalid operation {}", op.name()))
233 .with_primary_label(
234 op.span(),
235 format!("incorrect number of operands, expected 2, got {}", op.num_operands()),
236 )
237 .with_help(
238 "this operator implements 'BinaryOp', which requires it to have exactly two \
239 operands",
240 )
241 .into_report())
242 }
243 }
244}
245
246/// Op's regions have no arguments
247#[operation_trait]
248pub trait NoRegionArguments {
249 #[verifier]
250 fn no_region_arguments(op: &Operation, context: &Context) -> Result<(), Report> {
251 for region in op.regions().iter() {
252 if region.is_empty() {
253 continue;
254 }
255 if region.entry().has_arguments() {
256 return Err(context
257 .diagnostics()
258 .diagnostic(Severity::Error)
259 .with_message(::alloc::format!("invalid operation {}", op.name()))
260 .with_primary_label(
261 op.span(),
262 "this operation does not permit regions with arguments, but one was found",
263 )
264 .into_report());
265 }
266 }
267
268 Ok(())
269 }
270}
271
272/// Op's regions have a single block
273#[operation_trait]
274pub trait SingleBlock {
275 #[verifier]
276 fn has_only_single_block_regions(op: &Operation, context: &Context) -> Result<(), Report> {
277 for region in op.regions().iter() {
278 if region.body().iter().count() > 1 {
279 return Err(context
280 .diagnostics()
281 .diagnostic(Severity::Error)
282 .with_message(::alloc::format!("invalid operation {}", op.name()))
283 .with_primary_label(
284 op.span(),
285 "this operation requires single-block regions, but regions with multiple \
286 blocks were found",
287 )
288 .into_report());
289 }
290 }
291
292 Ok(())
293 }
294}
295
296// pub trait SingleBlockImplicitTerminator<T: Op + Default> {}
297
298/// Op has a single region
299#[operation_trait]
300pub trait SingleRegion {
301 #[verifier]
302 fn has_exactly_one_region(op: &Operation, context: &Context) -> Result<(), Report> {
303 let num_regions = op.num_regions();
304 if num_regions != 1 {
305 return Err(context
306 .diagnostics()
307 .diagnostic(Severity::Error)
308 .with_message(::alloc::format!("invalid operation {}", op.name()))
309 .with_primary_label(
310 op.span(),
311 format!("this operation requires exactly one region, but got {num_regions}"),
312 )
313 .into_report());
314 }
315
316 Ok(())
317 }
318}
319
320// pub trait HasParent<T> {}
321// pub trait ParentOneOf<(T,...)> {}
322
323/// Marker trait for ops which:
324///
325/// * Represent the attachment of metadata to values in the IR
326/// * Should not be considered as a "real" user for purposes of determining liveness of its operands
327/// * Should not be considered dead unless all of its operands are also dead
328/// * Does not result in any code being emitted during codegen
329///
330/// The goal of such operations is to attach important metadata, such as debug information, to
331/// values in the IR, ensuring that the metadata is preserved through transformations, while not
332/// interfering with optimizations that may make the original value dead except for the uses by
333/// transparent ops.
334#[operation_trait]
335pub trait Transparent {
336 #[verifier]
337 fn has_no_results(op: &Operation, context: &Context) -> Result<(), Report> {
338 if op.results().is_empty() {
339 Ok(())
340 } else {
341 Err(context
342 .diagnostics()
343 .diagnostic(Severity::Error)
344 .with_message(::alloc::format!("invalid operation {}", op.name()))
345 .with_primary_label(op.span(), "expected operation to have no results")
346 .with_help(
347 "this operator implements 'Transparent', which requires it to have no results",
348 )
349 .into_report())
350 }
351 }
352
353 #[verifier]
354 fn has_no_more_than_one_operand(op: &Operation, context: &Context) -> Result<(), Report> {
355 if op.num_operands() > 1 {
356 Err(context
357 .diagnostics()
358 .diagnostic(Severity::Error)
359 .with_message(::alloc::format!("invalid operation {}", op.name()))
360 .with_primary_label(
361 op.span(),
362 "expected operation to have no more than one operand",
363 )
364 .with_help(
365 "this operator implements 'Transparent', which requires it to have an arity < \
366 2",
367 )
368 .into_report())
369 } else {
370 Ok(())
371 }
372 }
373}