midenc_hir/ir/
region.rs

1mod branch_point;
2mod interfaces;
3mod invocation_bounds;
4mod kind;
5mod successor;
6mod transforms;
7
8use smallvec::{smallvec, SmallVec};
9
10pub use self::{
11    branch_point::RegionBranchPoint,
12    interfaces::{
13        LoopLikeOpInterface, RegionBranchOpInterface, RegionBranchTerminatorOpInterface,
14        RegionKindInterface,
15    },
16    invocation_bounds::InvocationBounds,
17    kind::RegionKind,
18    successor::{RegionSuccessor, RegionSuccessorInfo, RegionSuccessorIter},
19    transforms::RegionTransformFailed,
20};
21use super::*;
22use crate::{
23    adt::SmallSet,
24    patterns::RegionSimplificationLevel,
25    traits::{SingleBlock, SingleRegion},
26    Forward,
27};
28
29pub type RegionRef = UnsafeIntrusiveEntityRef<Region>;
30/// An intrusive, doubly-linked list of [Region]s
31pub type RegionList = EntityList<Region>;
32/// A cursor in a [RegionList]
33pub type RegionCursor<'a> = EntityCursor<'a, Region>;
34/// A mutable cursor in a [RegionList]
35pub type RegionCursorMut<'a> = EntityCursorMut<'a, Region>;
36
37/// A region is a container for [Block], in one of two forms:
38///
39/// * Graph-like, in which the region consists of a single block, and the order of operations in
40///   that block does not dictate any specific control flow semantics. It is up to the containing
41///   operation to define.
42/// * SSA-form, in which the region consists of one or more blocks that must obey the usual rules
43///   of SSA dominance, and where operations in a block reflect the order in which those operations
44///   are to be executed. Values defined by an operation must dominate any uses of those values in
45///   the region.
46///
47/// The first block in a region is the _entry_ block, and its argument list corresponds to the
48/// arguments expected by the region itself.
49///
50/// A region is only valid when it is attached to an [Operation], whereas the inverse is not true,
51/// i.e. an operation without a parent region is a top-level operation, e.g. `Module`.
52#[derive(Default)]
53pub struct Region {
54    /// The list of [Block]s that comprise this region
55    body: BlockList,
56}
57
58impl Entity for Region {}
59
60impl EntityWithParent for Region {
61    type Parent = Operation;
62}
63
64impl EntityListItem for Region {}
65
66impl EntityParent<Block> for Region {
67    fn offset() -> usize {
68        core::mem::offset_of!(Region, body)
69    }
70}
71
72impl core::fmt::Debug for Region {
73    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
74        core::fmt::Display::fmt(self, f)
75    }
76}
77impl core::fmt::Display for Region {
78    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
79        let region_number = self.region_number();
80        write!(f, "region({region_number})")
81    }
82}
83
84impl cfg::Graph for Region {
85    type ChildEdgeIter = block::BlockSuccessorEdgesIter;
86    type ChildIter = block::BlockSuccessorIter;
87    type Edge = BlockOperandRef;
88    type Node = BlockRef;
89
90    fn is_empty(&self) -> bool {
91        self.body.is_empty()
92    }
93
94    fn size(&self) -> usize {
95        self.body.len()
96    }
97
98    fn children(parent: Self::Node) -> Self::ChildIter {
99        block::BlockSuccessorIter::new(parent)
100    }
101
102    fn children_edges(parent: Self::Node) -> Self::ChildEdgeIter {
103        block::BlockSuccessorEdgesIter::new(parent)
104    }
105
106    fn edge_dest(edge: Self::Edge) -> Self::Node {
107        edge.parent().unwrap()
108    }
109
110    fn entry_node(&self) -> Self::Node {
111        self.body.front().as_pointer().expect("empty region")
112    }
113}
114
115impl<'a> cfg::InvertibleGraph for &'a Region {
116    type Inverse = cfg::Inverse<&'a Region>;
117    type InvertibleChildEdgeIter = block::BlockPredecessorEdgesIter;
118    type InvertibleChildIter = block::BlockPredecessorIter;
119
120    fn inverse(self) -> Self::Inverse {
121        cfg::Inverse::new(self)
122    }
123
124    fn inverse_children(parent: Self::Node) -> Self::InvertibleChildIter {
125        block::BlockPredecessorIter::new(parent)
126    }
127
128    fn inverse_children_edges(parent: Self::Node) -> Self::InvertibleChildEdgeIter {
129        block::BlockPredecessorEdgesIter::new(parent)
130    }
131}
132
133/// Blocks
134impl Region {
135    /// Returns true if this region is empty (has no blocks)
136    pub fn is_empty(&self) -> bool {
137        self.body.is_empty()
138    }
139
140    /// Get a handle to the entry block for this region
141    pub fn entry(&self) -> EntityRef<'_, Block> {
142        self.body.front().into_borrow().unwrap()
143    }
144
145    /// Get a mutable handle to the entry block for this region
146    pub fn entry_mut(&mut self) -> EntityMut<'_, Block> {
147        self.body.front_mut().into_borrow_mut().unwrap()
148    }
149
150    /// Get the [BlockRef] of the entry block of this region, if it has one
151    #[inline]
152    pub fn entry_block_ref(&self) -> Option<BlockRef> {
153        self.body.front().as_pointer()
154    }
155
156    /// Get the list of blocks comprising the body of this region
157    pub fn body(&self) -> &BlockList {
158        &self.body
159    }
160
161    /// Get a mutable reference to the list of blocks comprising the body of this region
162    pub fn body_mut(&mut self) -> &mut BlockList {
163        &mut self.body
164    }
165}
166
167/// Traversal
168impl Region {
169    pub fn prewalk_all<F>(&self, callback: F)
170    where
171        F: FnMut(&Operation),
172    {
173        Walk::<Operation>::prewalk_all::<Forward, _>(self, callback);
174    }
175
176    pub fn prewalk<F, B>(&self, callback: F) -> WalkResult<B>
177    where
178        F: FnMut(&Operation) -> WalkResult<B>,
179    {
180        Walk::<Operation>::prewalk::<Forward, _, _>(self, callback)
181    }
182
183    pub fn postwalk_all<F>(&self, callback: F)
184    where
185        F: FnMut(&Operation),
186    {
187        Walk::<Operation>::postwalk_all::<Forward, _>(self, callback);
188    }
189
190    pub fn postwalk<F, B>(&self, callback: F) -> WalkResult<B>
191    where
192        F: FnMut(&Operation) -> WalkResult<B>,
193    {
194        Walk::<Operation>::postwalk::<Forward, _, _>(self, callback)
195    }
196}
197
198/// Metadata
199impl Region {
200    #[inline]
201    pub fn as_region_ref(&self) -> RegionRef {
202        unsafe { RegionRef::from_raw(self) }
203    }
204
205    pub fn region_number(&self) -> usize {
206        let op = self.parent().unwrap().borrow();
207        op.regions()
208            .iter()
209            .position(|r| core::ptr::addr_eq(self, &*r))
210            .expect("invalid region parent")
211    }
212
213    /// Returns true if this region is an ancestor of `other`, i.e. it contains it.
214    ///
215    /// NOTE: This returns true if `self == other`, see [Self::is_proper_ancestor] if you do not
216    /// want this behavior.
217    pub fn is_ancestor(&self, other: &RegionRef) -> bool {
218        let this = self.as_region_ref();
219        &this == other || Self::is_proper_ancestor_of(&this, other)
220    }
221
222    /// Returns true if this region is a proper ancestor of `other`, i.e. `other` is contained by it
223    ///
224    /// NOTE: This returns false if `self == other`, see [Self::is_ancestor] if you do not want this
225    /// behavior.
226    pub fn is_proper_ancestor(&self, other: &RegionRef) -> bool {
227        let this = self.as_region_ref();
228        Self::is_proper_ancestor_of(&this, other)
229    }
230
231    fn is_proper_ancestor_of(this: &RegionRef, other: &RegionRef) -> bool {
232        if this == other {
233            return false;
234        }
235
236        let mut parent = other.borrow().parent_region();
237        while let Some(parent_region) = parent.take() {
238            if this == &parent_region {
239                return true;
240            }
241            parent = parent_region.borrow().parent_region();
242        }
243
244        false
245    }
246
247    /// Returns true if this region may be a graph region without SSA dominance
248    pub fn may_be_graph_region(&self) -> bool {
249        if let Some(owner) = self.parent() {
250            owner
251                .borrow()
252                .as_trait::<dyn RegionKindInterface>()
253                .is_some_and(|rki| rki.has_graph_regions())
254        } else {
255            true
256        }
257    }
258
259    /// Returns true if this region has only one block
260    pub fn has_one_block(&self) -> bool {
261        !self.body.is_empty()
262            && BlockRef::ptr_eq(
263                &self.body.front().as_pointer().unwrap(),
264                &self.body.back().as_pointer().unwrap(),
265            )
266    }
267
268    /// Get the defining [Operation] for this region, if the region is attached to one.
269    pub fn parent(&self) -> Option<OperationRef> {
270        self.as_region_ref().parent()
271    }
272
273    /// Get the region which contains the parent operation of this region, if there is one.
274    pub fn parent_region(&self) -> Option<RegionRef> {
275        self.parent().and_then(|op| op.grandparent())
276    }
277}
278
279/// Region Graph
280impl Region {
281    /// Traverse the region graph starting at `begin`.
282    ///
283    /// The traversal is interrupted if `stop` evaluates to `true` for a successor region. The
284    /// first argument given to the callback is the successor region, and the second argument is
285    /// the set of successor regions visited thus far.
286    ///
287    /// Returns `true` if traversal was interrupted, otherwise false.
288    ///
289    /// # Panics
290    ///
291    /// This function will panic if `begin` is a region that does not belong to an operation, or
292    /// if that operation does not implement `RegionBranchOpInterface`.
293    pub fn traverse_region_graph<F>(begin: &Self, mut stop: F) -> bool
294    where
295        F: FnMut(&Region, &SmallSet<RegionRef, 4>) -> bool,
296    {
297        let op = begin.parent().expect("cannot traverse an orphaned region");
298        let op = op.borrow();
299        let branch = op
300            .as_trait::<dyn RegionBranchOpInterface>()
301            .expect("expected parent op to implement RegionBranchOpInterface");
302
303        let mut visited = SmallSet::<RegionRef, 4>::default();
304        visited.insert(begin.as_region_ref());
305
306        let mut worklist = SmallVec::<[RegionRef; 4]>::default();
307        for successor in
308            branch.get_successor_regions(RegionBranchPoint::Child(begin.as_region_ref()))
309        {
310            if let Some(successor) = successor.into_successor() {
311                worklist.push(successor);
312            }
313        }
314
315        while let Some(next_region_ref) = worklist.pop() {
316            let next_region = next_region_ref.borrow();
317
318            if stop(&next_region, &visited) {
319                return true;
320            }
321
322            if visited.insert(next_region_ref) {
323                for successor in
324                    branch.get_successor_regions(RegionBranchPoint::Child(next_region_ref))
325                {
326                    if let Some(successor) = successor.into_successor() {
327                        worklist.push(successor);
328                    }
329                }
330            }
331        }
332
333        false
334    }
335
336    /// Returns true if `self` is reachable from `begin` in the region graph of the containing
337    /// operation, which must implement the `RegionBranchOpInterface` trait.
338    pub fn is_reachable_from(&self, begin: &Region) -> bool {
339        assert_eq!(self.parent(), begin.parent(), "expected both regions to belong to the same op");
340        // We interrupted the traversal if we find `self` in the region graph
341        Self::traverse_region_graph(begin, |region, _| core::ptr::addr_eq(self, region))
342    }
343
344    /// Returns true if `self` is reachable from itself in the region graph of the containing
345    /// operation, which must implement the `RegionBranchOpInterface` trait.
346    ///
347    /// The implication of this returning `true`, is that the region graph contains a loop, and
348    /// `self` participates in that loop.
349    pub fn is_repetitive_region(&self) -> bool {
350        Self::traverse_region_graph(self, |region, _| core::ptr::addr_eq(self, region))
351    }
352
353    /// Returns a vector of regions in the region graph rooted at `begin`, following a post-order
354    /// traversal of the graph, i.e. successors appear before their predecessors.
355    ///
356    /// NOTE: Backedges encountered during the traversal are ignored.
357    ///
358    /// Like [Self::traverse_region_graph], this requires the parent op to implement
359    /// [RegionBranchOpInterface].
360    pub fn postorder_region_graph(begin: &Self) -> SmallVec<[RegionRef; 4]> {
361        struct RegionNode {
362            region: RegionRef,
363            children: SmallVec<[RegionRef; 2]>,
364        }
365        impl RegionNode {
366            pub fn new(region: RegionRef, branch: &dyn RegionBranchOpInterface) -> Self {
367                // Collect unvisited children
368                let children = branch
369                    .get_successor_regions(RegionBranchPoint::Child(region))
370                    .filter_map(|s| s.into_successor())
371                    .collect();
372                Self { region, children }
373            }
374        }
375
376        let op = begin.parent().expect("cannot traverse an orphaned region");
377        let op = op.borrow();
378        let branch = op
379            .as_trait::<dyn RegionBranchOpInterface>()
380            .expect("expected parent op to implement RegionBranchOpInterface");
381
382        let mut postorder = SmallVec::<[RegionRef; 4]>::default();
383        let mut visited = SmallSet::<RegionRef, 4>::default();
384        let mut worklist = SmallVec::<[(RegionNode, usize); 4]>::default();
385
386        let root = begin.as_region_ref();
387        visited.insert(root);
388        let root = RegionNode::new(root, branch);
389        worklist.push((root, 0));
390
391        while let Some((node, child_index)) = worklist.last_mut() {
392            // If we visited all of the children of this node, "recurse" back up the stack
393            if *child_index >= node.children.len() {
394                postorder.push(node.region);
395                worklist.pop();
396            } else {
397                // Otherwise, recursively visit the given child
398                let index = *child_index;
399                *child_index += 1;
400                let child = RegionNode::new(node.children[index], branch);
401                if worklist.iter().any(|(node, _)| node.region == child.region) {
402                    // `child` forms a backedge to a node we're still visiting, so ignore it
403                    continue;
404                } else if visited.insert(child.region) {
405                    worklist.push((child, 0));
406                }
407            }
408        }
409
410        postorder
411    }
412
413    /// Returns a vector of regions in the region graph rooted at `root`, following a post-order
414    /// traversal of the graph, i.e. successors appear before their predecessors.
415    ///
416    /// NOTE: Backedges encountered during the traversal are ignored.
417    pub fn postorder_region_graph_for(
418        root: &dyn RegionBranchOpInterface,
419    ) -> SmallVec<[RegionRef; 4]> {
420        struct RegionNode {
421            region: RegionRef,
422            children: SmallVec<[RegionRef; 2]>,
423        }
424        impl RegionNode {
425            pub fn new(region: RegionRef, branch: &dyn RegionBranchOpInterface) -> Self {
426                // Collect unvisited children
427                let children = branch
428                    .get_successor_regions(RegionBranchPoint::Child(region))
429                    .filter_map(|s| s.into_successor())
430                    .collect();
431                Self { region, children }
432            }
433        }
434
435        let mut postorder = SmallVec::<[RegionRef; 4]>::default();
436        let mut visited = SmallSet::<RegionRef, 4>::default();
437        let mut worklist = SmallVec::<[(RegionNode, usize); 4]>::default();
438
439        for succ in root.get_successor_regions(RegionBranchPoint::Parent) {
440            let Some(region) = succ.into_successor() else {
441                continue;
442            };
443
444            if visited.insert(region) {
445                worklist.push((RegionNode::new(region, root), 0));
446            }
447        }
448
449        while let Some((node, child_index)) = worklist.last_mut() {
450            // If we visited all of the children of this node, "recurse" back up the stack
451            if *child_index >= node.children.len() {
452                postorder.push(node.region);
453                worklist.pop();
454            } else {
455                // Otherwise, recursively visit the given child
456                let index = *child_index;
457                *child_index += 1;
458                let child = RegionNode::new(node.children[index], root);
459                if worklist.iter().any(|(node, _)| node.region == child.region) {
460                    // `child` forms a backedge to a node we're still visiting, so ignore it
461                    continue;
462                } else if visited.insert(child.region) {
463                    worklist.push((child, 0));
464                }
465            }
466        }
467
468        postorder
469    }
470}
471
472/// Mutation
473impl Region {
474    /// Push `block` to the start of this region
475    #[inline]
476    pub fn push_front(&mut self, block: BlockRef) {
477        self.body.push_front(block);
478    }
479
480    /// Push `block` to the end of this region
481    #[inline]
482    pub fn push_back(&mut self, block: BlockRef) {
483        self.body.push_back(block);
484    }
485
486    pub fn take_body(&mut self, mut from_region: RegionRef) {
487        self.drop_all_references();
488        self.body.clear();
489
490        // Take blocks from `from_region`, update the parent of all the blocks, then splice to the
491        // end of this region's body
492        let blocks = from_region.borrow_mut().body_mut().take();
493        self.body.back_mut().splice_after(blocks);
494    }
495
496    /// Drop all operand uses from operations within this region, which is an essential step
497    /// in breaking cyclic dependencies between references when they are to be deleted.
498    pub fn drop_all_references(&mut self) {
499        let mut cursor = self.body_mut().front_mut();
500        while let Some(mut op) = cursor.as_pointer() {
501            op.borrow_mut().drop_all_references();
502            cursor.move_next();
503        }
504    }
505}
506
507/// Values
508impl Region {
509    /// Check if every value in `values` is defined above this region, i.e. they are defined in a
510    /// region which is a proper ancestor of `self`.
511    pub fn values_are_defined_above(&self, values: &[ValueRef]) -> bool {
512        let this = self.as_region_ref();
513        for value in values {
514            if !value
515                .borrow()
516                .parent_region()
517                .is_some_and(|value_region| Self::is_proper_ancestor_of(&value_region, &this))
518            {
519                return false;
520            }
521        }
522        true
523    }
524
525    /// Replace all uses of `value` with `replacement`, within this region.
526    pub fn replace_all_uses_in_region_with(&mut self, _value: ValueRef, _replacement: ValueRef) {
527        todo!("RegionUtils.h")
528    }
529
530    /// Visit each use of a value in this region (and its descendants), where that value was defined
531    /// in an ancestor of `limit`.
532    pub fn visit_used_values_defined_above<F>(&self, _limit: &RegionRef, _callback: F)
533    where
534        F: FnMut(OpOperand),
535    {
536        todo!("RegionUtils.h")
537    }
538
539    /// Visit each use of a value in any of the provided regions (or their descendants), where that
540    /// value was defined in an ancestor of that region.
541    pub fn visit_used_values_defined_above_any<F>(_regions: &[RegionRef], _callback: F)
542    where
543        F: FnMut(OpOperand),
544    {
545        todo!("RegionUtils.h")
546    }
547
548    /// Return a vector of values used in this region (and its descendants), and defined in an
549    /// ancestor of the `limit` region.
550    pub fn get_used_values_defined_above(&self, _limit: &RegionRef) -> SmallVec<[ValueRef; 1]> {
551        todo!("RegionUtils.h")
552    }
553
554    /// Return a vector of values used in any of the provided regions, but defined in an ancestor.
555    pub fn get_used_values_defined_above_any(_regions: &[RegionRef]) -> SmallVec<[ValueRef; 1]> {
556        todo!("RegionUtils.h")
557    }
558
559    /// Make this region isolated from above.
560    ///
561    /// * Capture the values that are defined above the region and used within it.
562    /// * Append block arguments to the entry block that represent each captured value.
563    /// * Replace all uses of the captured values within the region, with the new block arguments
564    /// * `clone_into_region` is called with the defining op of a captured value. If it returns
565    ///   true, it indicates that the op needs to be cloned into the region. As a result, the
566    ///   operands of that operation become part of the captured value set (unless the operations
567    ///   that define the operand values themselves are to be cloned). The cloned operations are
568    ///   added to the entry block of the region.
569    ///
570    /// Returns the set of captured values.
571    pub fn make_isolated_from_above<R, F>(
572        &mut self,
573        _rewriter: &mut R,
574        _clone_into_region: F,
575    ) -> SmallVec<[ValueRef; 1]>
576    where
577        R: crate::Rewriter,
578        F: Fn(&Operation) -> bool,
579    {
580        todo!("RegionUtils.h")
581    }
582}
583
584/// Queries
585impl Region {
586    pub fn find_common_ancestor(ops: &[OperationRef]) -> Option<RegionRef> {
587        use bitvec::prelude::*;
588
589        match ops.len() {
590            0 => None,
591            1 => unsafe { ops.get_unchecked(0) }.borrow().parent_region(),
592            num_ops => {
593                let (first, rest) = unsafe { ops.split_first().unwrap_unchecked() };
594                let mut region = first.borrow().parent_region();
595                let mut remaining_ops = bitvec![1; num_ops - 1];
596                while let Some(r) = region.take() {
597                    while let Some(index) = remaining_ops.first_one() {
598                        // Is this op contained in `region`?
599                        if r.borrow().find_ancestor_op(rest[index]).is_some() {
600                            unsafe {
601                                remaining_ops.set_unchecked(index, false);
602                            }
603                        }
604                    }
605                    if remaining_ops.not_any() {
606                        break;
607                    }
608                    region = r.borrow().parent_region();
609                }
610                region
611            }
612        }
613    }
614
615    /// Returns `block` if `block` lies in this region, or otherwise finds the ancestor of `block`
616    /// that lies in this region.
617    ///
618    /// Returns `None` if the latter fails.
619    pub fn find_ancestor_block(&self, block: BlockRef) -> Option<BlockRef> {
620        let this = self.as_region_ref();
621        let mut current = Some(block);
622        while let Some(current_block) = current.take() {
623            let parent = current_block.parent()?;
624            if parent == this {
625                return Some(current_block);
626            }
627            current = parent.grandparent();
628        }
629        current
630    }
631
632    /// Returns `op` if `op` lies in this region, or otherwise finds the ancestor of `op` that lies
633    /// in this region.
634    ///
635    /// Returns `None` if the latter fails.
636    pub fn find_ancestor_op(&self, op: OperationRef) -> Option<OperationRef> {
637        let this = self.as_region_ref();
638        let mut current = Some(op);
639        while let Some(current_op) = current.take() {
640            let parent = current_op.borrow().parent_region()?;
641            if parent == this {
642                return Some(current_op);
643            }
644            current = parent.parent();
645        }
646        current
647    }
648}
649
650/// Transforms
651impl Region {
652    /// Run a set of structural simplifications over the regions in `regions`.
653    ///
654    /// This includes transformations like unreachable block elimination, dead argument elimination,
655    /// as well as some other DCE.
656    ///
657    /// This function returns `Ok` if any of the regions were simplified, `Err` otherwise.
658    ///
659    /// The provided rewriter is used to notify callers of operation and block deletion.
660    ///
661    /// The provided [RegionSimplificationLevel] will be used to determine whether to apply more
662    /// aggressive simplifications, namely block merging. Note that when block merging is enabled,
663    /// this can lead to merged blocks with extra arguments.
664    pub fn simplify_all(
665        regions: &[RegionRef],
666        rewriter: &mut dyn crate::Rewriter,
667        simplification_level: RegionSimplificationLevel,
668    ) -> Result<(), RegionTransformFailed> {
669        let merge_blocks = matches!(simplification_level, RegionSimplificationLevel::Aggressive);
670
671        log::debug!(target: "region-simplify", "running region simplification on {} regions", regions.len());
672        log::debug!(target: "region-simplify", "  simplification level = {simplification_level:?}");
673        log::debug!(target: "region-simplify", "  merge_blocks         = {merge_blocks}");
674
675        let eliminated_blocks = Self::erase_unreachable_blocks(regions, rewriter).is_ok();
676        let eliminated_ops_or_args = Self::dead_code_elimination(regions, rewriter).is_ok();
677
678        let mut merged_identical_blocks = false;
679        let mut dropped_redundant_arguments = false;
680        if merge_blocks {
681            merged_identical_blocks = Self::merge_identical_blocks(regions, rewriter).is_ok();
682            dropped_redundant_arguments = Self::drop_redundant_arguments(regions, rewriter).is_ok();
683        }
684
685        if eliminated_blocks
686            || eliminated_ops_or_args
687            || merged_identical_blocks
688            || dropped_redundant_arguments
689        {
690            Ok(())
691        } else {
692            Err(RegionTransformFailed)
693        }
694    }
695}
696
697/// Printing
698impl Region {
699    pub fn print(&self, flags: &OpPrintingFlags) -> crate::formatter::Document {
700        use crate::formatter::PrettyPrint;
701
702        let printer = RegionPrinter {
703            region: self,
704            flags,
705        };
706        printer.render()
707    }
708}
709
710struct RegionPrinter<'a> {
711    region: &'a Region,
712    flags: &'a OpPrintingFlags,
713}
714
715impl crate::formatter::PrettyPrint for RegionPrinter<'_> {
716    fn render(&self) -> crate::formatter::Document {
717        use crate::formatter::*;
718
719        if self.region.is_empty() {
720            return const_text("{ }");
721        }
722
723        let is_parent_op_single_block_single_region = self.region.parent().is_some_and(|op| {
724            let op = op.borrow();
725            op.implements::<dyn SingleBlock>() && op.implements::<dyn SingleRegion>()
726        });
727        self.region.body.iter().fold(Document::Empty, |acc, block| {
728            if acc.is_empty() {
729                if is_parent_op_single_block_single_region || !self.flags.print_entry_block_headers
730                {
731                    const_text("{") + indent(4, nl() + block.print(self.flags))
732                } else {
733                    const_text("{") + nl() + block.print(self.flags)
734                }
735            } else {
736                acc + nl() + block.print(self.flags)
737            }
738        }) + nl()
739            + const_text("}")
740    }
741}
742
743impl crate::formatter::PrettyPrint for Region {
744    fn render(&self) -> crate::formatter::Document {
745        let flags = OpPrintingFlags::default();
746
747        self.print(&flags)
748    }
749}