Skip to main content

pliron/irbuild/
match_rewrite.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) The pliron contributors
3
4//! Utilites for matching operations and rewriting them.
5//! Similar in spirit to MLIR's pattern matching rewrites.
6
7use alloc::collections::VecDeque;
8
9use crate::{
10    context::{Context, Ptr},
11    graph::walkers::{IRNode, WalkConfig, uninterruptible::immutable::walk_op},
12    irbuild::{
13        IRStatus,
14        inserter::{Inserter, OpInsertionPoint},
15        listener::{Recorder, RecorderEvent},
16        rewriter::{IRRewriter, Rewriter},
17    },
18    operation::Operation,
19    pass::{AnalysisManager, Pass, PassResult},
20    result::Result,
21    utils::table::HSet,
22};
23
24/// A rewriter that uses the [Recorder] listener.
25pub type MatchRewriter = IRRewriter<Recorder>;
26
27/// Interface for matching and rewriting operations.
28pub trait MatchRewrite {
29    /// Should operation be rewritten?
30    fn r#match(&mut self, ctx: &Context, op: Ptr<Operation>) -> bool;
31    /// Rewrite the matched operation.
32    /// Insertion point is set to be before the operation being rewritten.
33    fn rewrite(
34        &mut self,
35        ctx: &mut Context,
36        rewriter: &mut MatchRewriter,
37        op: Ptr<Operation>,
38    ) -> Result<()>;
39}
40
41/// Should new operations that match be enqueued to the front or back of the queue?
42#[derive(Clone, Copy, Debug, Default)]
43pub enum EnqueueOrder {
44    EnqueFront,
45    #[default]
46    EnqueBack,
47}
48
49/// Configuration for the order of collecting and enqueuing operations.
50#[derive(Clone, Debug, Default)]
51pub struct RewriterOrder {
52    /// Order of initial collection of operations.
53    pub collect: WalkConfig,
54    /// Order of enqueuing new operations that match.
55    pub enque: EnqueueOrder,
56}
57
58/// Collects all operations (recursively) that match a given pattern
59/// and then applies a rewrite to them.
60pub fn apply_match_rewrite<M: MatchRewrite>(
61    ctx: &mut Context,
62    match_rewrite: &mut M,
63    order: RewriterOrder,
64    op: Ptr<Operation>,
65) -> Result<IRStatus> {
66    let mut to_rewrite = VecDeque::new();
67
68    // Collect all operations that match.
69    struct WalkerState<'a, M> {
70        match_rewrite: &'a mut M,
71        to_rewrite: &'a mut VecDeque<Ptr<Operation>>,
72    }
73    let mut state = WalkerState {
74        match_rewrite,
75        to_rewrite: &mut to_rewrite,
76    };
77    // A callback for the walker.
78    fn walker_callback<M: MatchRewrite>(ctx: &Context, state: &mut WalkerState<M>, node: IRNode) {
79        if let IRNode::Operation(op) = node
80            && state.match_rewrite.r#match(ctx, op)
81        {
82            state.to_rewrite.push_back(op);
83        }
84    }
85    // Walk the operation tree.
86    walk_op(ctx, &mut state, &order.collect, op, walker_callback);
87
88    let mut erased = HSet::<Ptr<Operation>>::default();
89    let mut rewriter = MatchRewriter::default();
90    rewriter.set_listener(Recorder::default());
91
92    // Rewrite collected and newly added operations that match.
93    while !to_rewrite.is_empty() {
94        let op = to_rewrite.pop_front().unwrap();
95        if erased.contains(&op) {
96            continue;
97        }
98        rewriter.set_insertion_point(OpInsertionPoint::BeforeOperation(op));
99        match_rewrite.rewrite(ctx, &mut rewriter, op)?;
100        let listener = rewriter.get_listener_mut();
101        // First process all erased operations to avoid dereferencing them.
102        for event in &listener.events {
103            if let RecorderEvent::ErasedOperation(erased_op) = event {
104                erased.insert(*erased_op);
105            }
106        }
107        // Then process all other events.
108        for event in &listener.events {
109            match event {
110                RecorderEvent::ErasedOperation(_) => {
111                    // Already processed above.
112                }
113                RecorderEvent::InsertedOperation(new_op) => {
114                    // Check if the newly inserted operation also matches.
115                    if !erased.contains(new_op) && match_rewrite.r#match(ctx, *new_op) {
116                        match order.enque {
117                            EnqueueOrder::EnqueFront => to_rewrite.push_front(*new_op),
118                            EnqueueOrder::EnqueBack => to_rewrite.push_back(*new_op),
119                        }
120                    }
121                }
122                RecorderEvent::ReplacedValueUses { .. } => {
123                    // No action needed for value use replacements.
124                }
125                RecorderEvent::InsertedBlock(_) => {
126                    // No action needed for block insertions.
127                }
128                RecorderEvent::ErasedBlock(_) => {
129                    // No action needed for block erasures.
130                    // Operations inside the block will have triggered operation erasure events.
131                    // and we only care about operations here.
132                }
133                RecorderEvent::ErasedRegion(_) => {
134                    // No action needed for region erasures.
135                    // Operations inside the region will have triggered operation erasure events.
136                    // and we only care about operations here.
137                }
138                RecorderEvent::ValueTypeChanged { .. } => {
139                    // No action needed for type changes.
140                }
141                RecorderEvent::UnlinkedOperation(_op, _prev_position) => {
142                    // No action needed for operation unlinking.
143                }
144                RecorderEvent::UnlinkedBlock(_block, _prev_position) => {
145                    // No action needed for block unlinking.
146                }
147            }
148        }
149        listener.clear();
150    }
151    Ok(rewriter.is_modified().into())
152}
153
154/// Make [MatchRewrite] into a [Pass]
155pub struct PassWrapper<M: MatchRewrite> {
156    name: &'static str,
157    match_rewrite: M,
158    rewrite_order: RewriterOrder,
159}
160
161impl<M: MatchRewrite> PassWrapper<M> {
162    /// Create a new [PassWrapper] with the given name and [MatchRewrite].
163    pub fn new(name: &'static str, match_rewrite: M) -> Self {
164        Self {
165            name,
166            match_rewrite,
167            rewrite_order: RewriterOrder::default(),
168        }
169    }
170
171    /// Set the rewrite order for the pass.
172    pub fn set_rewrite_order(mut self, order: RewriterOrder) -> Self {
173        self.rewrite_order = order;
174        self
175    }
176}
177
178impl<M: MatchRewrite> Pass for PassWrapper<M> {
179    fn run(
180        &mut self,
181        op: Ptr<Operation>,
182        ctx: &mut Context,
183        _analyses: &mut AnalysisManager,
184    ) -> Result<PassResult> {
185        let mut pass_result = PassResult::default();
186        pass_result.ir_changed |=
187            apply_match_rewrite(ctx, &mut self.match_rewrite, self.rewrite_order.clone(), op)?;
188        Ok(pass_result)
189    }
190
191    fn name(&self) -> &str {
192        self.name
193    }
194}