midenc_hir/ir/region/interfaces.rs
1use super::*;
2use crate::{
3 AttributeRef, Op, SuccessorOperandRange, SuccessorOperandRangeMut, Type, traits::Terminator,
4};
5
6/// An op interface that indicates what types of regions it holds
7pub trait RegionKindInterface {
8 /// Get the [RegionKind] for this operation
9 fn kind(&self) -> RegionKind;
10 /// Returns true if the kind of this operation's regions requires SSA dominance
11 #[inline]
12 fn has_ssa_dominance(&self) -> bool {
13 matches!(self.kind(), RegionKind::SSA)
14 }
15 #[inline]
16 fn has_graph_regions(&self) -> bool {
17 matches!(self.kind(), RegionKind::Graph)
18 }
19}
20
21// TODO(pauls): Implement verifier
22/// This interface provides information for region operations that exhibit branching behavior
23/// between held regions. I.e., this interface allows for expressing control flow information for
24/// region holding operations.
25///
26/// This interface is meant to model well-defined cases of control-flow and value propagation,
27/// where what occurs along control-flow edges is assumed to be side-effect free.
28///
29/// A "region branch point" indicates a point from which a branch originates. It can indicate either
30/// a region of this op or [RegionBranchPoint::Parent]. In the latter case, the branch originates
31/// from outside of the op, i.e., when first executing this op.
32///
33/// A "region successor" indicates the target of a branch. It can indicate either a region of this
34/// op or this op. In the former case, the region successor is a region pointer and a range of block
35/// arguments to which the "successor operands" are forwarded to. In the latter case, the control
36/// flow leaves this op and the region successor is a range of results of this op to which the
37/// successor operands are forwarded to.
38///
39/// By default, successor operands and successor block arguments/successor results must have the
40/// same type. `areTypesCompatible` can be implemented to allow non-equal types.
41///
42/// ## Example
43///
44/// ```hir,ignore
45/// %r = scf.for %iv = %lb to %ub step %step iter_args(%a = %b)
46/// -> tensor<5xf32> {
47/// ...
48/// scf.yield %c : tensor<5xf32>
49/// }
50/// ```
51///
52/// `scf.for` has one region. The region has two region successors: the region itself and the
53/// `scf.for` op. `%b` is an entry successor operand. `%c` is a successor operand. `%a` is a
54/// successor block argument. `%r` is a successor result.
55pub trait RegionBranchOpInterface: Op {
56 /// Returns the operands of this operation that are forwarded to the region successor's block
57 /// arguments or this operation's results when branching to `point`. `point` is guaranteed to
58 /// be among the successors that are returned by `get_entry_succcessor_regions` or
59 /// `get_successor_regions(parent_op())`.
60 ///
61 /// ## Example
62 ///
63 /// In the example in the top-level docs of this trait, this function returns the operand `%b`
64 /// of the `scf.for` op, regardless of the value of `point`, i.e. this op always forwards the
65 /// same operands, regardless of whether the loop has 0 or more iterations.
66 #[inline]
67 #[allow(unused_variables)]
68 fn get_entry_successor_operands(&self, point: RegionBranchPoint) -> SuccessorOperandRange<'_> {
69 crate::SuccessorOperandRange::empty()
70 }
71 /// Returns the potential region successors when first executing the op.
72 ///
73 /// Unlike [Self::get_successor_regions], this method also passes along the constant operands of
74 /// this op. Based on these, the implementation may filter out certain successors. By default, it
75 /// simply dispatches to `get_successor_regions`. `operands` contains an entry for every operand
76 /// of this op, with `None` representing if the operand is non-constant.
77 ///
78 /// NOTE: The control flow does not necessarily have to enter any region of this op.
79 ///
80 /// ## Example
81 ///
82 /// In the example in the top-level docs of this trait, this function may return two region
83 /// successors: the single region of the `scf.for` op and the `scf.for` operation (that
84 /// implements this interface). If `%lb`, `%ub`, `%step` are constants and it can be determined
85 /// the loop does not have any iterations, this function may choose to return only this
86 /// operation. Similarly, if it can be determined that the loop has at least one iteration, this
87 /// function may choose to return only the region of the loop.
88 #[inline]
89 #[allow(unused_variables)]
90 fn get_entry_successor_regions(
91 &self,
92 operands: &[Option<AttributeRef>],
93 ) -> RegionSuccessorIter<'_> {
94 self.get_successor_regions(RegionBranchPoint::Parent)
95 }
96 /// Returns the potential region successors when branching from `point`.
97 ///
98 /// These are the regions that may be selected during the flow of control.
99 ///
100 /// When `point` is [RegionBranchPoint::Parent], this function returns the region successors
101 /// when entering the operation. Otherwise, this method returns the successor regions when
102 /// branching from the region indicated by `point`.
103 ///
104 /// ## Example
105 ///
106 /// In the example in the top-level docs of this trait, this function returns the region of the
107 /// `scf.for` and this operation for either region branch point (`parent` and the region of the
108 /// `scf.for`). An implementation may choose to filter out region successors when it is
109 /// statically known (e.g., by examining the operands of this op) that those successors are not
110 /// branched to.
111 fn get_successor_regions(&self, point: RegionBranchPoint) -> RegionSuccessorIter<'_>;
112 /// Returns a set of invocation bounds, representing the minimum and maximum number of times
113 /// this operation will invoke each attached region (assuming the regions yield normally, i.e.
114 /// do not abort or invoke an infinite loop). The minimum number of invocations is at least 0.
115 /// If the maximum number of invocations cannot be statically determined, then it will be set to
116 /// [InvocationBounds::Unknown].
117 ///
118 /// This function also passes along the constant operands of this op. `operands` contains an
119 /// entry for every operand of this op, with `None` representing if the operand is non-constant.
120 ///
121 /// This function may be called speculatively on operations where the provided operands are not
122 /// necessarily the same as the operation's current operands. This may occur in analyses that
123 /// wish to determine "what would be the region invocations if these were the operands?"
124 #[inline]
125 #[allow(unused_variables)]
126 fn get_region_invocation_bounds(
127 &self,
128 operands: &[Option<AttributeRef>],
129 ) -> SmallVec<[InvocationBounds; 1]> {
130 use smallvec::smallvec;
131
132 smallvec![InvocationBounds::Unknown; self.num_regions()]
133 }
134 /// This function is called to compare types along control-flow edges.
135 ///
136 /// By default, the types are check for exact equality.
137 #[inline]
138 fn are_types_compatible(&self, lhs: &Type, rhs: &Type) -> bool {
139 lhs == rhs
140 }
141 /// Returns `true` if control flow originating from the region at `index` may eventually branch
142 /// back to the same region, either from itself, or after passing through other regions first.
143 fn is_repetitive_region(&self, index: usize) -> bool {
144 self.region(index).is_repetitive_region()
145 }
146 /// Returns `true` if there is a loop in the region branching graph.
147 ///
148 /// Only reachable regions (starting from the entry region) are considered.
149 fn has_loop(&self) -> bool {
150 self.get_successor_regions(RegionBranchPoint::Parent)
151 .filter_map(|entry| entry.into_successor())
152 .any(|region| {
153 Region::traverse_region_graph(®ion.borrow(), |r, visited| {
154 // Interrupted traversal if the region was already visited
155 visited.contains(&r.as_region_ref())
156 })
157 })
158 }
159}
160
161// TODO(pauls): Implement verifier (should have no results and no successors)
162/// This interface provides information for branching terminator operations in the presence of a
163/// parent [RegionBranchOpInterface] implementation. It specifies which operands are passed to which
164/// successor region.
165pub trait RegionBranchTerminatorOpInterface: Op + Terminator {
166 /// Get a range of operands corresponding to values that are semantically "returned" by passing
167 /// them to the region successor indicated by `point`.
168 fn get_successor_operands(&self, point: RegionBranchPoint) -> SuccessorOperandRange<'_>;
169 /// Get a mutable range of operands corresponding to values that are semantically "returned" by
170 /// passing them to the region successor indicated by `point`.
171 fn get_mutable_successor_operands(
172 &mut self,
173 point: RegionBranchPoint,
174 ) -> SuccessorOperandRangeMut<'_>;
175 /// Returns the potential region successors that are branched to after this terminator based on
176 /// the given constant operands.
177 ///
178 /// This method also passes along the constant operands of this op. `operands` contains an entry
179 /// for every operand of this op, with `None` representing non-constant values.
180 ///
181 /// The default implementation simply dispatches to the parent `RegionBranchOpInterface`'s
182 /// `get_successor_regions` implementation.
183 #[allow(unused_variables)]
184 fn get_successor_regions(
185 &self,
186 operands: &[Option<AttributeRef>],
187 ) -> SmallVec<[RegionSuccessorInfo; 2]> {
188 let parent_region =
189 self.parent_region().expect("expected operation to have a parent region");
190 let parent_op = parent_region.parent().expect("expected operation to have a parent op");
191 parent_op
192 .borrow()
193 .as_trait::<dyn RegionBranchOpInterface>()
194 .expect("invalid region terminator parent: must implement RegionBranchOpInterface")
195 .get_successor_regions(RegionBranchPoint::Child(parent_region))
196 .into_successor_infos()
197 }
198}
199
200/// This trait is implemented by operations which have loop-like semantics.
201///
202/// It provides useful helpers and access to properties of the loop represented, and is used in
203/// order to perform transformations on the loop. Implementors will be considered by loop-invariant
204/// code motion.
205///
206/// Loop-carried variables can be exposed through this interface. There are 3 components to a
207/// loop-carried variable:
208///
209/// - The "region iter_arg" is the block argument of the entry block that represents the loop-
210/// carried variable in each iteration.
211/// - The "init value" is an operand of the loop op that serves as the initial region iter_arg value
212/// for the first iteration (if any).
213/// - The "yielded" value is the value that is forwarded from one iteration to serve as the region
214/// iter_arg of the next iteration.
215///
216/// If one of the respective interface methods is implemented, so must the other two. The interface
217/// verifier ensures that the number of types of the region iter_args, init values and yielded
218/// values match.
219///
220/// Optionally, "loop results" can be exposed through this interface. These are the values that are
221/// returned from the loop op when there are no more iterations. The number and types of the loop
222/// results must match with the region iter_args. Note: Loop results are optional because some loops
223/// (e.g., `scf.while`) may produce results that do match 1-to-1 with the region iter_args.
224#[allow(unused_variables)]
225#[allow(clippy::result_unit_err)]
226pub trait LoopLikeOpInterface: Op {
227 /// Returns true if the given value is defined outside of the loop.
228 ///
229 /// A sensible implementation could be to check whether the value's defining operation lies
230 /// outside of the loops body region. If the loop uses explicit capture of dependencies, an
231 /// implementation could check whether the value corresponds to a captured dependency.
232 fn is_defined_outside_of_loop(&self, value: ValueRef) -> bool {
233 let value = value.borrow();
234 if let Some(defining_op) = value.get_defining_op() {
235 self.as_operation().is_ancestor_of(&defining_op.borrow())
236 } else {
237 let block_arg = value
238 .downcast_ref::<BlockArgument>()
239 .expect("invalid value reference: defining op is orphaned");
240 let defining_region = block_arg.parent_region().unwrap();
241 let defining_op = defining_region.parent().unwrap();
242 self.as_operation().is_ancestor_of(&defining_op.borrow())
243 }
244 }
245
246 /// Returns the entry region for this loop, which is expected to also play the role of loop
247 /// header.
248 ///
249 /// NOTE: It is expected that if the loop has iteration arguments, that the values returned
250 /// from `Self::get_region_iter_args` correspond to block arguments of the header region.
251 /// Additionally, it is presumed that initialization variables expected by the op are provided
252 /// to the loop body via block arguments of this region.
253 fn get_loop_header_region(&self) -> RegionRef;
254
255 /// Returns the regions that make up the body of the loop, and should be inspected for loop-
256 /// invariant operations.
257 fn get_loop_regions(&self) -> SmallVec<[RegionRef; 2]>;
258
259 /// Moves the given loop-invariant operation out of the loop.
260 fn move_out_of_loop(&mut self, mut op: OperationRef) {
261 op.borrow_mut().move_to(crate::ProgramPoint::before(self.as_operation()));
262 }
263
264 /// Promotes the loop body to its containing block if the loop is known to have a single
265 /// iteration.
266 ///
267 /// Returns `Ok` if the promotion was successful
268 fn promote_if_single_iteration(
269 &mut self,
270 rewriter: &mut dyn crate::Rewriter,
271 ) -> Result<(), ()> {
272 Err(())
273 }
274
275 /// Return all induction variables, if they exist.
276 ///
277 /// If the op has no notion of induction variable, then return `None`. If it does have a notion
278 /// but an instance doesn't have induction variables, then return an empty vector.
279 fn get_loop_induction_vars(&self) -> Option<SmallVec<[ValueRef; 2]>> {
280 None
281 }
282
283 /// Return all lower bounds, if they exist.
284 ///
285 /// If the op has no notion of lower bounds, then return `None`. If it does have a notion but an
286 /// instance doesn't have lower bounds, then return an empty vector.
287 fn get_loop_lower_bounds(&self) -> Option<SmallVec<[OpFoldResult; 2]>> {
288 None
289 }
290
291 /// Return all upper bounds, if they exist.
292 ///
293 /// If the op has no notion of upper bounds, then return `None`. If it does have a notion but an
294 /// instance doesn't have upper bounds, then return an empty vector.
295 fn get_loop_upper_bounds(&self) -> Option<SmallVec<[OpFoldResult; 2]>> {
296 None
297 }
298
299 /// Return all steps, if they exist.
300 ///
301 /// If the op has no notion of steps, then return `None`. If it does have a notion but an
302 /// instance doesn't have steps, then return an empty vector.
303 fn get_loop_steps(&self) -> Option<SmallVec<[OpFoldResult; 2]>> {
304 None
305 }
306
307 /// Return the mutable "init" operands that are used as initialization values for the region
308 /// "iter_args" of this loop.
309 fn get_inits_mut(&mut self) -> OpOperandRangeMut<'_> {
310 self.operands_mut().empty_mut()
311 }
312
313 /// Return the region "iter_args" (block arguments) that correspond to the "init" operands.
314 ///
315 /// If the op has multiple regions, return the corresponding block arguments of the entry region.
316 fn get_region_iter_args(&self) -> Option<EntityRef<'_, [BlockArgumentRef]>> {
317 None
318 }
319
320 /// Return the mutable operand range of values that are yielded to the next iteration by the
321 /// loop terminator.
322 ///
323 /// For loop operations that dont yield a value, this should return `None`.
324 fn get_yielded_values_mut(&mut self) -> Option<EntityProjectionMut<'_, OpOperandRangeMut<'_>>> {
325 None
326 }
327
328 /// Return the range of results that are return from this loop and correspond to the "init"
329 /// operands.
330 ///
331 /// Note: This interface method is optional. If loop results are not exposed via this interface,
332 /// `None` should be returned.
333 ///
334 /// Otherwise, the number and types of results must match with the region iter_args, inits and
335 /// yielded values that are exposed via this interface. If loop results are exposed but this
336 /// loop op has no loop-carried variables, an empty result range (and not `None`) should be
337 /// returned.
338 fn get_loop_results(&self) -> Option<OpResultRange<'_>> {
339 None
340 }
341}
342
343impl dyn LoopLikeOpInterface {
344 /// If there is a single induction variable return it, otherwise return `None`
345 pub fn get_single_induction_var(&self) -> Option<ValueRef> {
346 let vars = self.get_loop_induction_vars();
347 if let Some([var]) = vars.as_deref() {
348 return Some(*var);
349 }
350 None
351 }
352
353 /// Return the single lower bound value or attribute if it exists, otherwise return `None`
354 pub fn get_single_lower_bound(&self) -> Option<OpFoldResult> {
355 let mut lower_bounds = self.get_loop_lower_bounds()?;
356 if lower_bounds.len() == 1 {
357 lower_bounds.pop()
358 } else {
359 None
360 }
361 }
362
363 /// Return the single upper bound value or attribute if it exists, otherwise return `None`
364 pub fn get_single_upper_bound(&self) -> Option<OpFoldResult> {
365 let mut upper_bounds = self.get_loop_upper_bounds()?;
366 if upper_bounds.len() == 1 {
367 upper_bounds.pop()
368 } else {
369 None
370 }
371 }
372
373 /// Return the single step value or attribute if it exists, otherwise return `None`
374 pub fn get_single_step(&self) -> Option<OpFoldResult> {
375 let mut steps = self.get_loop_steps()?;
376 if steps.len() == 1 { steps.pop() } else { None }
377 }
378}