Skip to main content

midenc_dialect_scf/
ops.rs

1use alloc::rc::Rc;
2
3use midenc_hir::{
4    derive::{EffectOpInterface, OpParser, OpPrinter, operation},
5    dialects::builtin::attributes::U32ArrayAttr,
6    effects::*,
7    parse::ParserExt,
8    patterns::RewritePatternSet,
9    print::AsmPrinter,
10    traits::*,
11    *,
12};
13
14use crate::ScfDialect;
15
16/// [If] is a structured control flow operation representing conditional execution.
17///
18/// An [If] takes a single condition as an argument, which chooses between one of its two regions
19/// based on the condition. If the condition is true, then the `then_body` region is executed,
20/// otherwise `else_body`.
21///
22/// Neither region allows any arguments, and both regions must be terminated with one of:
23///
24/// * [midenc_hir::dialects::builtin::Ret] to return from the enclosing function directly
25/// * `midenc_dialect_ub::Unreachable` to abort execution
26/// * [Yield] to return from the enclosing [If]
27#[derive(OpPrinter, OpParser)]
28#[operation(
29    dialect = ScfDialect,
30    traits(SingleBlock, NoRegionArguments, HasRecursiveMemoryEffects),
31    implements(RegionBranchOpInterface, OpPrinter)
32)]
33pub struct If {
34    #[operand]
35    condition: Bool,
36    #[region(name = "then")]
37    then_body: Region,
38    #[region(name = "else")]
39    else_body: Region,
40    #[results]
41    returns: AnyType,
42}
43
44impl If {
45    pub fn then_yield(&self) -> UnsafeIntrusiveEntityRef<Yield> {
46        let terminator = self.then_body().entry().terminator().unwrap();
47        terminator
48            .try_downcast_op::<Yield>()
49            .expect("invalid hir.if then terminator: expected yield")
50    }
51
52    pub fn else_yield(&self) -> UnsafeIntrusiveEntityRef<Yield> {
53        let terminator = self.else_body().entry().terminator().unwrap();
54        terminator
55            .try_downcast_op::<Yield>()
56            .expect("invalid hir.if else terminator: expected yield")
57    }
58}
59
60impl Canonicalizable for If {
61    fn get_canonicalization_patterns(rewrites: &mut RewritePatternSet, context: Rc<Context>) {
62        rewrites.push(crate::canonicalization::ConvertTrivialIfToSelect::new(context.clone()));
63        rewrites.push(crate::canonicalization::IfRemoveUnusedResults::new(context.clone()));
64        rewrites.push(crate::canonicalization::FoldRedundantYields::new(context));
65    }
66}
67
68impl RegionBranchOpInterface for If {
69    fn get_entry_successor_regions(
70        &self,
71        operands: &[Option<AttributeRef>],
72    ) -> RegionSuccessorIter<'_> {
73        let condition = operands[0].as_ref().and_then(|v| v.borrow().as_bool());
74        let has_then = condition.is_none_or(|v| v);
75        let else_possible = condition.is_none_or(|v| !v);
76        let has_else = else_possible && !self.else_body().is_empty();
77
78        let mut infos = SmallVec::<[RegionSuccessorInfo; 2]>::default();
79        if has_then {
80            infos.push(RegionSuccessorInfo::Entering(self.then_body().as_region_ref()));
81        }
82
83        if else_possible {
84            if has_else {
85                infos.push(RegionSuccessorInfo::Entering(self.else_body().as_region_ref()));
86            } else {
87                // Branching back to parent with `then` results
88                infos.push(RegionSuccessorInfo::Returning(
89                    self.results().all().iter().map(|v| v.borrow().as_value_ref()).collect(),
90                ));
91            }
92        }
93
94        RegionSuccessorIter::new(self.as_operation(), infos)
95    }
96
97    fn get_successor_regions(&self, point: RegionBranchPoint) -> RegionSuccessorIter<'_> {
98        match point {
99            RegionBranchPoint::Parent => {
100                // Either branch is reachable on entry (unless `else` is empty, as it is optional)
101                let mut infos: SmallVec<[_; 2]> =
102                    smallvec![RegionSuccessorInfo::Entering(self.then_body().as_region_ref())];
103                // Don't consider the else region if it is empty
104                if !self.else_body().is_empty() {
105                    infos.push(RegionSuccessorInfo::Entering(self.else_body().as_region_ref()));
106                }
107                RegionSuccessorIter::new(self.as_operation(), infos)
108            }
109            RegionBranchPoint::Child(_) => {
110                // Only the parent If is reachable from then_body/else_body
111                RegionSuccessorIter::new(
112                    self.as_operation(),
113                    [RegionSuccessorInfo::Returning(
114                        self.results().all().iter().map(|v| v.borrow().as_value_ref()).collect(),
115                    )],
116                )
117            }
118        }
119    }
120
121    fn get_region_invocation_bounds(
122        &self,
123        operands: &[Option<AttributeRef>],
124    ) -> SmallVec<[InvocationBounds; 1]> {
125        let condition = operands[0].as_ref().and_then(|v| v.borrow().as_bool());
126
127        if let Some(condition) = condition {
128            if condition {
129                smallvec![InvocationBounds::Exact(1), InvocationBounds::Never]
130            } else {
131                smallvec![InvocationBounds::Never, InvocationBounds::Exact(1)]
132            }
133        } else {
134            // Only one region is invoked, and no more than a single time
135            smallvec![InvocationBounds::NoMoreThan(1); 2]
136        }
137    }
138
139    #[inline(always)]
140    fn is_repetitive_region(&self, _index: usize) -> bool {
141        false
142    }
143
144    #[inline(always)]
145    fn has_loop(&self) -> bool {
146        false
147    }
148}
149
150/// A while is a loop structure composed of two regions: a "before" region, and an "after" region.
151///
152/// The "before" region's entry block parameters correspond to the operands expected by the
153/// operation, and can be used to compute the condition that determines whether the "after" body
154/// is executed or not, or simply forwarded to the "after" region. The "before" region must
155/// terminate with a [Condition] operation, which will be evaluated to determine whether or not
156/// to continue the loop.
157///
158/// The "after" region corresponds to the loop body, and must terminate with a [Yield] operation,
159/// whose operands must be of the same arity and type as the "before" region's argument list. In
160/// this way, the "after" body can feed back input to the "before" body to determine whether to
161/// continue the loop.
162#[derive(OpPrinter, OpParser)]
163#[operation(
164    dialect = ScfDialect,
165    traits(SingleBlock, HasRecursiveMemoryEffects),
166    implements(RegionBranchOpInterface, LoopLikeOpInterface, OpPrinter)
167)]
168pub struct While {
169    #[operands]
170    inits: AnyType,
171    #[region]
172    before: Region,
173    #[region]
174    after: Region,
175    #[results]
176    returns: AnyType,
177}
178
179impl While {
180    pub fn condition_op(&self) -> UnsafeIntrusiveEntityRef<Condition> {
181        let term = self
182            .before()
183            .entry()
184            .terminator()
185            .expect("expected before region to have a terminator");
186        term.try_downcast_op::<Condition>()
187            .expect("expected before region to terminate with hir.condition")
188    }
189
190    pub fn yield_op(&self) -> UnsafeIntrusiveEntityRef<Yield> {
191        let term = self
192            .after()
193            .entry()
194            .terminator()
195            .expect("expected after region to have a terminator");
196        term.try_downcast_op::<Yield>()
197            .expect("expected after region to terminate with hir.yield")
198    }
199}
200
201impl Canonicalizable for While {
202    fn get_canonicalization_patterns(rewrites: &mut RewritePatternSet, context: Rc<Context>) {
203        rewrites.push(crate::canonicalization::RemoveLoopInvariantArgsFromBeforeBlock::new(
204            context.clone(),
205        ));
206        //rewrites.push(crate::canonicalization::RemoveLoopInvariantValueYielded::new(context.clone()));
207        rewrites.push(crate::canonicalization::WhileConditionTruth::new(context.clone()));
208        rewrites.push(crate::canonicalization::WhileUnusedResult::new(context.clone()));
209        rewrites.push(crate::canonicalization::WhileRemoveDuplicatedResults::new(context.clone()));
210        rewrites.push(crate::canonicalization::WhileRemoveUnusedArgs::new(context.clone()));
211        //rewrites.push(crate::canonicalization::ConvertDoWhileToWhileTrue::new(context));
212    }
213}
214
215impl LoopLikeOpInterface for While {
216    fn get_region_iter_args(&self) -> Option<EntityRef<'_, [BlockArgumentRef]>> {
217        let entry = self.before().entry_block_ref()?;
218        Some(EntityRef::map(entry.borrow(), |block| block.arguments()))
219    }
220
221    fn get_loop_header_region(&self) -> RegionRef {
222        self.before().as_region_ref()
223    }
224
225    fn get_loop_regions(&self) -> SmallVec<[RegionRef; 2]> {
226        smallvec![self.before().as_region_ref(), self.after().as_region_ref()]
227    }
228
229    fn get_inits_mut(&mut self) -> OpOperandRangeMut<'_> {
230        self.inits_mut()
231    }
232
233    fn get_yielded_values_mut(&mut self) -> Option<EntityProjectionMut<'_, OpOperandRangeMut<'_>>> {
234        let mut yield_op = self
235            .after()
236            .entry()
237            .terminator()
238            .expect("invalid `while`: expected loop body to be terminated");
239
240        // The values which are yielded to each iteration
241        Some(EntityMut::project(yield_op.borrow_mut(), |op| op.operands_mut().group_mut(0)))
242    }
243}
244
245impl RegionBranchOpInterface for While {
246    #[inline]
247    fn get_entry_successor_operands(&self, _point: RegionBranchPoint) -> SuccessorOperandRange<'_> {
248        // Operands being forwarded to the `before` region from outside the loop
249        SuccessorOperandRange::forward(self.operands().all())
250    }
251
252    fn get_successor_regions(&self, point: RegionBranchPoint) -> RegionSuccessorIter<'_> {
253        match point {
254            RegionBranchPoint::Parent => {
255                // The only successor region when branching from outside the While op is the
256                // `before` region.
257                RegionSuccessorIter::new(
258                    self.as_operation(),
259                    [RegionSuccessorInfo::Entering(self.before().as_region_ref())],
260                )
261            }
262            RegionBranchPoint::Child(region) => {
263                let before_region = self.before().as_region_ref();
264                let after_region = self.after().as_region_ref();
265                assert!(region == before_region || region == after_region);
266
267                // When branching from `before`, the only successor is `after` or the While itself,
268                // otherwise, when branching from `after` the only successor is `before`.
269                if region == after_region {
270                    RegionSuccessorIter::new(
271                        self.as_operation(),
272                        [RegionSuccessorInfo::Entering(before_region)],
273                    )
274                } else {
275                    RegionSuccessorIter::new(
276                        self.as_operation(),
277                        [
278                            RegionSuccessorInfo::Returning(
279                                self.results()
280                                    .all()
281                                    .iter()
282                                    .map(|r| r.borrow().as_value_ref())
283                                    .collect(),
284                            ),
285                            RegionSuccessorInfo::Entering(after_region),
286                        ],
287                    )
288                }
289            }
290        }
291    }
292
293    #[inline]
294    fn get_region_invocation_bounds(
295        &self,
296        _operands: &[Option<AttributeRef>],
297    ) -> SmallVec<[InvocationBounds; 1]> {
298        smallvec![InvocationBounds::Unknown; self.num_regions()]
299    }
300
301    #[inline(always)]
302    fn is_repetitive_region(&self, _index: usize) -> bool {
303        // Both regions are in the loop (`before` -> `after` -> `before` -> `after`)
304        true
305    }
306
307    #[inline(always)]
308    fn has_loop(&self) -> bool {
309        true
310    }
311}
312
313/// The `hir.index_switch` is a control-flow operation that branches to one of the given regions
314/// based on the values of the argument and the cases. The argument is always of type `u32`.
315///
316/// The operation always has a "default" region and any number of case regions denoted by integer
317/// constants. Control-flow transfers to the case region whose constant value equals the value of
318/// the argument. If the argument does not equal any of the case values, control-flow transfer to
319/// the "default" region.
320///
321/// ## Example
322///
323/// ```text,ignore
324/// %0 = hir.index_switch %arg0 : u32 -> i32
325/// case 2 {
326///   %1 = hir.constant 10 : i32
327///   scf.yield %1 : i32
328/// }
329/// case 5 {
330///   %2 = hir.constant 20 : i32
331///   scf.yield %2 : i32
332/// }
333/// default {
334///   %3 = hir.constant 30 : i32
335///   scf.yield %3 : i32
336/// }
337/// ```
338#[operation(
339    dialect = ScfDialect,
340    traits(SingleBlock, HasRecursiveMemoryEffects),
341    implements(RegionBranchOpInterface, OpPrinter)
342)]
343pub struct IndexSwitch {
344    #[operand]
345    selector: UInt32,
346    #[attr]
347    cases: U32ArrayAttr,
348    #[region]
349    default_region: Region,
350}
351
352impl OpPrinter for IndexSwitch {
353    fn print(&self, printer: &mut AsmPrinter<'_>) {
354        use alloc::borrow::Cow;
355
356        use formatter::*;
357
358        printer.print_space();
359        printer.print_value_uses(ValueRange::<1>::Operands(&[self.selector().as_operand_ref()]));
360        printer.print_space();
361
362        for case in self.cases().iter() {
363            let index = self.get_case_index_for_selector(*case).unwrap();
364            let region = self.get_case_region(index);
365            *printer += nl() + const_text("case ") + display(*case) + const_text(" ");
366            printer.print_region(&region.borrow());
367        }
368
369        *printer += nl() + const_text("default ");
370        printer.print_region(&self.default_region());
371
372        if self.op.has_attributes() {
373            printer.print_space();
374            printer.print_attribute_dictionary(
375                self.op.attributes().iter().map(|attr| *attr.as_named_attribute()),
376            );
377        }
378
379        printer.print_space();
380        printer.print_colon_type_list(
381            self.results().iter().map(|r| Cow::Owned(r.borrow().ty().clone())),
382        );
383    }
384}
385
386impl OpParser for IndexSwitch {
387    fn parse(state: &mut OperationState, parser: &mut dyn OpAsmParser<'_>) -> ParseResult {
388        use alloc::{format, vec};
389
390        use midenc_hir::{
391            diagnostics::{LabeledSpan, RelatedError, Report, Severity, miette::diagnostic},
392            dialects::builtin::attributes::Array,
393            parse::ParserError,
394        };
395
396        let selector = parser.parse_operand(/*allow_result_number=*/ true)?;
397        let selector = parser.resolve_operand(selector, Type::U32)?;
398        state.add_operand(selector);
399
400        let mut cases = Array::<u32>::default();
401        let mut regions = SmallVec::<[RegionRef; 2]>::default();
402        while parser.parse_optional_custom_keyword("case")?.is_some() {
403            let case_value = parser.parse_decimal_integer::<u32>()?;
404            if cases.contains(&case_value) {
405                return Err(ParserError::Report(RelatedError::new(Report::from(diagnostic!(
406                    severity = Severity::Error,
407                    labels = vec![LabeledSpan::at(
408                        case_value.span(),
409                        "this case selector has already been used"
410                    )],
411                    "invalid scf.index_switch operation"
412                )))));
413            }
414
415            let region = parser.context().create_region();
416            parser.parse_region(region, &[], false)?;
417
418            cases.push(case_value.into_inner());
419            regions.push(region);
420        }
421
422        parser.parse_custom_keyword("default")?;
423        let fallback_region = parser.context().create_region();
424        parser.parse_region(fallback_region, &[], false)?;
425
426        state
427            .add_attribute("cases", parser.context_rc().create_attribute::<U32ArrayAttr, _>(cases));
428        // The default region is declared first on the operation; case regions follow it (see
429        // `get_case_region`).
430        state.add_region(fallback_region);
431        for region in regions {
432            state.add_region(region);
433        }
434
435        parser.parse_optional_attribute_dict(&mut state.attrs)?;
436        parser.parse_colon_type_list(&mut state.results)?;
437
438        Ok(())
439    }
440}
441
442impl IndexSwitch {
443    pub fn num_cases(&self) -> usize {
444        self.cases().len()
445    }
446
447    pub fn get_default_block(&self) -> BlockRef {
448        self.default_region().entry_block_ref().expect("default region has no blocks")
449    }
450
451    pub fn get_case_index_for_selector(&self, selector: u32) -> Option<usize> {
452        self.cases().iter().position(|case| *case == selector)
453    }
454
455    #[track_caller]
456    pub fn get_case_block(&self, index: usize) -> BlockRef {
457        let block_ref = self.get_case_region(index).borrow().entry_block_ref();
458        match block_ref {
459            None => panic!("region for case {index} has no blocks"),
460            Some(block) => block,
461        }
462    }
463
464    #[track_caller]
465    pub fn get_case_region(&self, mut index: usize) -> RegionRef {
466        let mut next_region = self.regions().front().as_pointer();
467        let mut current_index = 0;
468        // Shift the requested index up by one to account for default region
469        index += 1;
470        while let Some(region) = next_region.take() {
471            if index == current_index {
472                return region;
473            }
474            next_region = region.next();
475            current_index += 1;
476        }
477
478        panic!("invalid region index `{}`: out of bounds", index - 1)
479    }
480}
481
482impl RegionBranchOpInterface for IndexSwitch {
483    fn get_entry_successor_regions(
484        &self,
485        operands: &[Option<AttributeRef>],
486    ) -> RegionSuccessorIter<'_> {
487        let selector = operands[0].as_ref().and_then(|v| v.borrow().as_u32());
488        let selected = selector.map(|s| self.get_case_index_for_selector(s));
489
490        match selected {
491            None => {
492                // All regions are possible successors
493                let infos =
494                    self.regions().iter().map(|r| RegionSuccessorInfo::Entering(r.as_region_ref()));
495                RegionSuccessorIter::new(self.as_operation(), infos)
496            }
497            Some(Some(selected)) => {
498                // A specific case was selected
499                RegionSuccessorIter::new(
500                    self.as_operation(),
501                    [RegionSuccessorInfo::Entering(self.get_case_region(selected))],
502                )
503            }
504            Some(None) => {
505                // The fallback case should be used
506                RegionSuccessorIter::new(
507                    self.as_operation(),
508                    [RegionSuccessorInfo::Entering(self.default_region().as_region_ref())],
509                )
510            }
511        }
512    }
513
514    fn get_successor_regions(&self, point: RegionBranchPoint) -> RegionSuccessorIter<'_> {
515        match point {
516            RegionBranchPoint::Parent => {
517                // Any region is reachable on entry
518                let infos =
519                    self.regions().iter().map(|r| RegionSuccessorInfo::Entering(r.as_region_ref()));
520                RegionSuccessorIter::new(self.as_operation(), infos)
521            }
522            RegionBranchPoint::Child(_) => {
523                // Only the parent op is reachable from its regions
524                RegionSuccessorIter::new(
525                    self.as_operation(),
526                    [RegionSuccessorInfo::Returning(
527                        self.results().all().iter().map(|v| v.borrow().as_value_ref()).collect(),
528                    )],
529                )
530            }
531        }
532    }
533
534    fn get_region_invocation_bounds(
535        &self,
536        operands: &[Option<AttributeRef>],
537    ) -> SmallVec<[InvocationBounds; 1]> {
538        let selector = operands[0].as_ref().and_then(|v| v.borrow().as_u32());
539
540        if let Some(selector) = selector {
541            let mut bounds = smallvec![InvocationBounds::Never; self.num_cases()];
542            let selected =
543                self.get_case_index_for_selector(selector).map(|idx| idx + 1).unwrap_or(0);
544            bounds[selected] = InvocationBounds::Exact(1);
545            bounds
546        } else {
547            // Only one region is invoked, and no more than a single time
548            smallvec![InvocationBounds::NoMoreThan(1); self.num_cases()]
549        }
550    }
551
552    #[inline(always)]
553    fn is_repetitive_region(&self, _index: usize) -> bool {
554        false
555    }
556
557    #[inline(always)]
558    fn has_loop(&self) -> bool {
559        false
560    }
561}
562
563impl Canonicalizable for IndexSwitch {
564    fn get_canonicalization_patterns(rewrites: &mut RewritePatternSet, context: Rc<Context>) {
565        rewrites.push(crate::canonicalization::FoldConstantIndexSwitch::new(context.clone()));
566        rewrites.push(crate::canonicalization::FoldRedundantYields::new(context.clone()));
567        rewrites.push(crate::canonicalization::IndexSwitchRemoveUnusedResults::new(context));
568    }
569}
570
571/// The [Condition] op is used in conjunction with [While] as the terminator of its `before` region.
572///
573/// This op represents a choice between continuing the loop, or exiting the [While] loop and
574/// continuing execution after the loop.
575///
576/// NOTE: Attempting to use this op in any other context than the one described above is invalid,
577/// and the implementation of various interfaces by this op will panic if that assumption is
578/// violated.
579#[derive(EffectOpInterface, OpPrinter, OpParser)]
580#[operation(
581    dialect = ScfDialect,
582    traits(Terminator, ReturnLike),
583    implements(RegionBranchTerminatorOpInterface, MemoryEffectOpInterface, OpPrinter)
584)]
585pub struct Condition {
586    #[operand]
587    condition: Bool,
588    #[operands]
589    forwarded: AnyType,
590}
591
592impl RegionBranchTerminatorOpInterface for Condition {
593    #[inline]
594    fn get_successor_operands(&self, _point: RegionBranchPoint) -> SuccessorOperandRange<'_> {
595        SuccessorOperandRange::forward(self.forwarded())
596    }
597
598    #[inline]
599    fn get_mutable_successor_operands(
600        &mut self,
601        _point: RegionBranchPoint,
602    ) -> SuccessorOperandRangeMut<'_> {
603        SuccessorOperandRangeMut::forward(self.forwarded_mut())
604    }
605
606    fn get_successor_regions(
607        &self,
608        operands: &[Option<AttributeRef>],
609    ) -> SmallVec<[RegionSuccessorInfo; 2]> {
610        // A [While] loop has two regions: `before` (containing this op), and `after`, which this
611        // op branches to when the condition is true. If the condition is false, control is
612        // transferred back to the parent [While] operation, with the forwarded operands of the
613        // condition used as the results of the [While] operation.
614        //
615        // We can return a single statically-known region if we were given a constant condition
616        // value, otherwise we must return both possible regions.
617        let cond = operands[0].as_ref().and_then(|v| v.borrow().as_bool());
618        let mut regions = SmallVec::<[RegionSuccessorInfo; 2]>::default();
619
620        let parent_op = self.parent_op().unwrap();
621        let parent_op = parent_op.borrow();
622        let while_op = parent_op
623            .downcast_ref::<While>()
624            .expect("expected `Condition` op to be a child of a `While` op");
625        let after_region = while_op.after().as_region_ref();
626
627        // We can't know the condition until runtime, so both the parent `while` op and
628        match cond {
629            None => {
630                regions.push(RegionSuccessorInfo::Entering(after_region));
631                regions.push(RegionSuccessorInfo::Returning(
632                    while_op.results().all().iter().map(|r| r.borrow().as_value_ref()).collect(),
633                ));
634            }
635            Some(true) => {
636                regions.push(RegionSuccessorInfo::Entering(after_region));
637            }
638            Some(false) => {
639                regions.push(RegionSuccessorInfo::Returning(
640                    while_op.results().all().iter().map(|r| r.borrow().as_value_ref()).collect(),
641                ));
642            }
643        }
644
645        regions
646    }
647}
648
649/// The [Yield] op is used in conjunction with [If] and [While] ops as a return-like terminator.
650///
651/// * With [If], its regions must be terminated with either a [Yield] or an `Unreachable` op.
652/// * With [While], a [Yield] is only valid in the `after` region, and the yielded operands must
653///   match the region arguments of the `before` region. Thus to return values from the body of a
654///   loop, one must first yield them from the `after` region to the `before` region using [Yield],
655///   and then yield them from the `before` region by passsing them as forwarded operands of the
656///   [Condition] op.
657///
658/// Any number of operands can be yielded at the same time. However, when [Yield] is used in
659/// conjunction with [While], the arity and type of the operands must match the region arguments
660/// of the `before` region. When used in conjunction with [If], both the `if_true` and `if_false`
661/// regions must yield the same arity and types.
662#[derive(EffectOpInterface, OpPrinter, OpParser)]
663#[operation(
664    dialect = ScfDialect,
665    traits(Terminator, ReturnLike, Pure, AlwaysSpeculatable),
666    implements(
667        RegionBranchTerminatorOpInterface,
668        MemoryEffectOpInterface,
669        OperandRangeRequirementOpInterface,
670        ConditionallySpeculatable,
671        OpPrinter,
672    )
673)]
674pub struct Yield {
675    #[operands]
676    yielded: AnyType,
677}
678
679impl OperandRangeRequirementOpInterface for Yield {
680    fn operand_range_requirement(&self, _operand_index: usize) -> OperandRangeRequirement {
681        OperandRangeRequirement::None
682    }
683}
684
685impl RegionBranchTerminatorOpInterface for Yield {
686    #[inline]
687    fn get_successor_operands(&self, _point: RegionBranchPoint) -> SuccessorOperandRange<'_> {
688        SuccessorOperandRange::forward(self.yielded())
689    }
690
691    fn get_mutable_successor_operands(
692        &mut self,
693        _point: RegionBranchPoint,
694    ) -> SuccessorOperandRangeMut<'_> {
695        SuccessorOperandRangeMut::forward(self.yielded_mut())
696    }
697
698    fn get_successor_regions(
699        &self,
700        _operands: &[Option<AttributeRef>],
701    ) -> SmallVec<[RegionSuccessorInfo; 2]> {
702        // Depending on the type of operation containing this yield, the set of successor regions
703        // is always known.
704        //
705        // * [While] may only have a yield to its `before` region
706        // * [If] may only yield to its parent
707        // * [IndexSwitch] may only yield to its parent
708        let parent_op = self.parent_op().unwrap();
709        let parent_op = parent_op.borrow();
710        if parent_op.is::<If>() || parent_op.is::<IndexSwitch>() {
711            smallvec![RegionSuccessorInfo::Returning(
712                parent_op.results().all().iter().map(|v| v.borrow().as_value_ref()).collect()
713            )]
714        } else if let Some(while_op) = parent_op.downcast_ref::<While>() {
715            let before_region = while_op.before().as_region_ref();
716            smallvec![RegionSuccessorInfo::Entering(before_region)]
717        } else {
718            panic!("unsupported parent operation for '{}': '{}'", self.name(), parent_op.name())
719        }
720    }
721}
722
723impl ConditionallySpeculatable for Yield {
724    fn speculatability(&self) -> Speculatability {
725        Speculatability::Speculatable
726    }
727}
728
729#[cfg(test)]
730mod tests {
731    use midenc_expect_test::expect;
732    use midenc_hir::{
733        diagnostics::Report, dialects::builtin::Function, testing::parse_function_fixpoint,
734    };
735
736    use super::*;
737
738    /// Find the first operation of type `T` in the entry block of `function`.
739    fn find_op<T: OpRegistration>(function: &Function) -> UnsafeIntrusiveEntityRef<T> {
740        function
741            .body()
742            .entry()
743            .body()
744            .iter()
745            .find_map(|op| op.as_operation_ref().try_downcast_op::<T>().ok())
746            .unwrap_or_else(|| {
747                panic!("expected a {} op in the function body", <T as OpRegistration>::full_name())
748            })
749    }
750
751    /// The regions of an operation with operands must parse with no pre-bound entry arguments:
752    /// the `^block(...)` header the printer emits declares them. Also covers the variadic
753    /// forwarded operands of `scf.condition`, which parse as the trailing comma list following
754    /// the condition operand, and the `scf.while` result type signature.
755    #[test]
756    fn parse_scf_while_round_trips() -> Result<(), Report> {
757        let context = Rc::new(Context::default());
758        let source = "\
759builtin.function public extern(\"C\") @count_to(%n: u32) -> u32 {
760    %zero = arith.constant 0 : u32;
761    %count = scf.while %zero before {
762    ^head(%i: u32):
763        %continue = arith.lt %i, %n;
764        scf.condition %continue, %i : (i1, u32);
765    } after {
766    ^body(%j: u32):
767        %next = arith.incr %j;
768        scf.yield %next : (u32);
769    } : (u32) -> u32;
770    builtin.ret %count : (u32);
771};";
772        let (function, printed) = parse_function_fixpoint(&context, "parse_scf_while.hir", source)?;
773        expect![[r#"
774            builtin.function public extern("C") @count_to(%0: u32) -> u32 {
775                %1 = arith.constant 0 : u32;
776                %6 = scf.while %1 before {
777                ^block2(%2: u32):
778                    %3 = arith.lt %2, %0;
779                    scf.condition %3, %2 : (i1, u32);
780                } after {
781                ^block3(%4: u32):
782                    %5 = arith.incr %4;
783                    scf.yield %5 : (u32);
784                } : (u32) -> (u32);
785                builtin.ret %6 : (u32);
786            };"#]]
787        .assert_eq(&printed);
788
789        let function = function.borrow();
790        let while_op = find_op::<While>(&function);
791        let while_op = while_op.borrow();
792        assert_eq!(while_op.inits().len(), 1);
793        assert_eq!(while_op.num_results(), 1);
794        assert_eq!(while_op.before().entry().num_arguments(), 1);
795        let condition = while_op.condition_op();
796        let condition = condition.borrow();
797        assert_eq!(condition.forwarded().len(), 1);
798
799        Ok(())
800    }
801
802    /// The trailing forwarded-operand list of `scf.condition` may be empty, in which case the
803    /// printer omits it entirely; the parser must accept the bare form.
804    #[test]
805    fn parse_scf_condition_without_forwarded_operands() -> Result<(), Report> {
806        let context = Rc::new(Context::default());
807        let source = "\
808builtin.function public extern(\"C\") @spin(%n: u32) -> u32 {
809    %zero = arith.constant 0 : u32;
810    scf.while %zero before {
811    ^head(%i: u32):
812        %continue = arith.lt %i, %n;
813        scf.condition %continue : (i1);
814    } after {
815    ^body:
816        scf.yield %zero : (u32);
817    } : (u32) -> ();
818    builtin.ret %n : (u32);
819};";
820        let (function, printed) =
821            parse_function_fixpoint(&context, "parse_scf_condition_bare.hir", source)?;
822        expect![[r#"
823            builtin.function public extern("C") @spin(%0: u32) -> u32 {
824                %1 = arith.constant 0 : u32;
825                scf.while %1 before {
826                ^block2(%2: u32):
827                    %3 = arith.lt %2, %0;
828                    scf.condition %3 : (i1);
829                } after {
830                ^block3:
831                    scf.yield %1 : (u32);
832                } : (u32) -> ();
833                builtin.ret %0 : (u32);
834            };"#]]
835        .assert_eq(&printed);
836
837        let function = function.borrow();
838        let while_op = find_op::<While>(&function);
839        let while_op = while_op.borrow();
840        assert_eq!(while_op.num_results(), 0);
841        let condition = while_op.condition_op();
842        let condition = condition.borrow();
843        assert_eq!(condition.forwarded().len(), 0);
844
845        Ok(())
846    }
847
848    /// Multi-name result bindings (`%x, %y = scf.if ...`) map the parsed names onto the
849    /// flattened result list of a single variadic result group.
850    #[test]
851    fn parse_multi_name_result_bindings() -> Result<(), Report> {
852        let context = Rc::new(Context::default());
853        let source = "\
854builtin.function public extern(\"C\") @pick(%c: i1, %a: u32, %b: u32) -> u32 {
855    %x, %y = scf.if %c then {
856        scf.yield %a, %b : (u32, u32);
857    } else {
858        scf.yield %b, %a : (u32, u32);
859    } : (i1) -> (u32, u32);
860    %sum = arith.add %x, %y <{ overflow = #builtin.overflow<unchecked> }>;
861    builtin.ret %sum : (u32);
862};";
863        let (function, printed) =
864            parse_function_fixpoint(&context, "parse_multi_result.hir", source)?;
865        expect![[r#"
866            builtin.function public extern("C") @pick(%0: i1, %1: u32, %2: u32) -> u32 {
867                %3, %4 = scf.if %0 then {
868                    scf.yield %1, %2 : (u32, u32);
869                } else {
870                    scf.yield %2, %1 : (u32, u32);
871                } : (i1) -> (u32, u32);
872                %5 = arith.add %3, %4 <{ overflow = #builtin.overflow<unchecked> }>;
873                builtin.ret %5 : (u32);
874            };"#]]
875        .assert_eq(&printed);
876
877        let function = function.borrow();
878        let if_op = find_op::<If>(&function);
879        let if_op = if_op.borrow();
880        assert_eq!(if_op.num_results(), 2);
881        // Both bound names resolve to the if's results: the adder uses each exactly once.
882        for result in if_op.results().all().iter() {
883            assert_eq!(result.borrow().iter_uses().count(), 1);
884        }
885
886        Ok(())
887    }
888
889    /// `scf.index_switch` regions parse with the default region first, matching the accessors
890    /// (`default_region` is region 0, case regions follow in case order).
891    #[test]
892    fn parse_index_switch_region_order() -> Result<(), Report> {
893        let context = Rc::new(Context::default());
894        let source = "\
895builtin.function public extern(\"C\") @dispatch(%sel: u32, %a: u32, %b: u32) -> u32 {
896    %r = scf.index_switch %sel
897    case 1 {
898        scf.yield %a : (u32);
899    }
900    default {
901        scf.yield %b : (u32);
902    } : u32;
903    builtin.ret %r : (u32);
904};";
905        let (function, printed) =
906            parse_function_fixpoint(&context, "parse_index_switch.hir", source)?;
907        expect![[r#"
908            builtin.function public extern("C") @dispatch(%0: u32, %1: u32, %2: u32) -> u32 {
909                %3 = scf.index_switch %0 
910                case 1 {
911                    scf.yield %1 : (u32);
912                }
913                default {
914                    scf.yield %2 : (u32);
915                } : (u32);
916                builtin.ret %3 : (u32);
917            };"#]]
918        .assert_eq(&printed);
919
920        let function = function.borrow();
921        let body = function.body();
922        let entry = body.entry();
923        let arg_a = entry.arguments()[1] as ValueRef;
924        let arg_b = entry.arguments()[2] as ValueRef;
925
926        let yielded_value = |region: &Region| -> ValueRef {
927            let terminator = region.entry().terminator().unwrap();
928            let yield_op = terminator
929                .try_downcast_op::<Yield>()
930                .expect("expected region to terminate with scf.yield");
931            let yield_op = yield_op.borrow();
932            let yielded = yield_op.yielded();
933            let operand = yielded.iter().next().unwrap();
934            operand.borrow().as_value_ref()
935        };
936
937        let switch_op = find_op::<IndexSwitch>(&function);
938        let switch_op = switch_op.borrow();
939        // The default region yields %b and the `case 1` region yields %a; if the parser
940        // appended the default region last, the two would come back swapped.
941        assert_eq!(yielded_value(&switch_op.default_region()), arg_b);
942        let case_region = switch_op.get_case_region(0);
943        assert_eq!(yielded_value(&case_region.borrow()), arg_a);
944
945        Ok(())
946    }
947}