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