Skip to main content

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}