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,...)> {}