Skip to main content

cubecl_core/post_processing/
visitor.rs

1use core::fmt::Debug;
2
3use alloc::vec::Vec;
4
5use cubecl_ir::{
6    Branch, CoopMma, GlobalState, Instruction, Marker, NonSemantic, Operation, OperationReflect,
7    Operator, Scope, TensorIndexingOps, TmaOps, Value,
8};
9use derive_more::{Deref, DerefMut};
10
11use crate::post_processing::{analysis_helper::GlobalAnalyses, util::AtomicCounter};
12
13/// Visitor that operates on an instruction level. Useful for passes that only need to recursively
14/// traverse the scopes and don't care about control flow.
15///
16/// The `changes` counter should be incremented on any change, unless the pass is a unique one-time
17/// pass. It's used to determine when to end a fixed-point optimization loop.
18pub trait InstructionVisitor: Debug {
19    fn visit_instruction(
20        &mut self,
21        instruction: Instruction,
22        global_state: &GlobalState,
23        analyses: &GlobalAnalyses,
24        changes: &AtomicCounter,
25    ) -> Vec<Instruction>;
26
27    fn visit_scope(&mut self, scope: &Scope, analyses: &GlobalAnalyses, changes: &AtomicCounter) {
28        visit_scope(self, scope, analyses, changes);
29    }
30}
31
32pub fn visit_scope<T: InstructionVisitor + ?Sized>(
33    visitor: &mut T,
34    scope: &Scope,
35    analyses: &GlobalAnalyses,
36    changes: &AtomicCounter,
37) {
38    let instructions = scope.take_instructions();
39    let mut new_instructions = Vec::with_capacity(instructions.len());
40    for inst in instructions {
41        if let Operation::Branch(branch) = &inst.operation {
42            match branch {
43                Branch::If(op) => {
44                    visitor.visit_scope(&op.scope, analyses, changes);
45                }
46                Branch::IfElse(op) => {
47                    visitor.visit_scope(&op.scope_if, analyses, changes);
48                    visitor.visit_scope(&op.scope_else, analyses, changes);
49                }
50                Branch::Switch(op) => {
51                    for (_, case) in &op.cases {
52                        visitor.visit_scope(case, analyses, changes);
53                    }
54                    visitor.visit_scope(&op.scope_default, analyses, changes);
55                }
56                Branch::RangeLoop(op) => {
57                    visitor.visit_scope(&op.scope, analyses, changes);
58                }
59                Branch::Loop(op) => {
60                    visitor.visit_scope(&op.scope, analyses, changes);
61                }
62                _ => {}
63            }
64        }
65
66        new_instructions.extend(visitor.visit_instruction(
67            inst,
68            &scope.global_state,
69            analyses,
70            changes,
71        ));
72    }
73
74    scope.register_all(new_instructions);
75}
76
77#[derive(Deref, DerefMut)]
78pub struct Visitor<T>(pub T);
79
80impl<T> Visitor<T> {
81    pub fn visit_instruction(
82        &mut self,
83        inst: &mut Instruction,
84        analyses: &GlobalAnalyses,
85        visit_read: impl FnMut(&mut T, &mut Value),
86        mut visit_write: impl FnMut(&mut T, &mut Value),
87    ) {
88        self.visit_operation(&mut inst.operation, analyses, visit_read);
89
90        for ptr in inst.operation.write_pointers() {
91            if let Some(source) = analyses.ptr_source().get_mut(&ptr.kind) {
92                visit_write(self, source);
93            }
94        }
95
96        self.visit_out(&mut inst.out, visit_write);
97    }
98
99    pub fn visit_out(
100        &mut self,
101        out: &mut Option<Value>,
102        mut visit_write: impl FnMut(&mut T, &mut Value),
103    ) {
104        if let Some(out) = out {
105            visit_write(self, out)
106        }
107    }
108
109    /// Visit an operation with a set of read and write visitors. Each visitor will be called with
110    /// each read or written to variable.
111    pub fn visit_operation(
112        &mut self,
113        op: &mut Operation,
114        analyses: &GlobalAnalyses,
115        mut visit_read: impl FnMut(&mut T, &mut Value),
116    ) {
117        for ptr in op.read_pointers() {
118            if let Some(source) = analyses.ptr_source().get_mut(&ptr.kind) {
119                visit_read(self, source);
120            }
121        }
122
123        match op {
124            Operation::Marker(Marker::Free(_)) => {}
125            Operation::CoopMma(coop_mma) => self.visit_cmma(coop_mma, visit_read),
126            Operation::Branch(branch) => self.visit_branch(branch, visit_read),
127            Operation::Tma(tma_ops) => self.visit_tma(tma_ops, visit_read),
128            Operation::TensorIndexing(tensor_ops) => self.visit_tensor_ops(tensor_ops, visit_read),
129            Operation::NonSemantic(non_semantic) => {
130                self.visit_nonsemantic(non_semantic, visit_read)
131            }
132            Operation::DeclareVariable { .. } => {}
133            Operation::Operator(Operator::ReadBuiltin(_)) => {}
134            op => {
135                if let Some(args) = op.args_mut() {
136                    for arg in args {
137                        visit_read(self, arg);
138                    }
139                } else {
140                    panic!("Found op {op:?} which doesn't reflect. Needs special handling.");
141                }
142            }
143        }
144    }
145
146    /// Visit a control flow finisher with a set of read and write visitors. Each visitor will be called with
147    /// each read or written to variable.
148    pub fn visit_branch(
149        &mut self,
150        op: &mut Branch,
151        mut visit_read: impl FnMut(&mut T, &mut Value),
152    ) {
153        match op {
154            Branch::If(if_) => visit_read(self, &mut if_.cond),
155            Branch::IfElse(if_else) => visit_read(self, &mut if_else.cond),
156            Branch::Switch(switch) => visit_read(self, &mut switch.value),
157            Branch::RangeLoop(range_loop) => {
158                visit_read(self, &mut range_loop.start);
159                visit_read(self, &mut range_loop.end);
160            }
161            Branch::Loop(_) | Branch::Return | Branch::Break | Branch::Unreachable => {}
162        }
163    }
164
165    fn visit_cmma(&mut self, cmma: &mut CoopMma, mut visit_read: impl FnMut(&mut T, &mut Value)) {
166        match cmma {
167            CoopMma::Fill { value } => {
168                visit_read(self, value);
169            }
170            CoopMma::Load {
171                ptr,
172                stride,
173                layout: _,
174            } => {
175                visit_read(self, ptr);
176                visit_read(self, stride);
177            }
178            CoopMma::LoadTensor {
179                buffer,
180                layout,
181                view,
182            } => {
183                visit_read(self, buffer);
184                visit_read(self, layout);
185                if let Some(view) = view {
186                    visit_read(self, view);
187                }
188            }
189            CoopMma::Execute {
190                mat_a,
191                mat_b,
192                mat_c,
193            } => {
194                visit_read(self, mat_a);
195                visit_read(self, mat_b);
196                visit_read(self, mat_c);
197            }
198            CoopMma::Store {
199                mat,
200                stride,
201                destination,
202                layout: _,
203            } => {
204                visit_read(self, mat);
205                visit_read(self, stride);
206                visit_read(self, destination);
207            }
208            CoopMma::StoreTensor { mat, layout, view } => {
209                visit_read(self, mat);
210                visit_read(self, layout);
211                if let Some(view) = view {
212                    visit_read(self, view);
213                }
214            }
215            CoopMma::Cast { input } => {
216                visit_read(self, input);
217            }
218            CoopMma::RowIndex { lane_id, i, .. } => {
219                visit_read(self, lane_id);
220                visit_read(self, i);
221            }
222            CoopMma::ColIndex { lane_id, i, .. } => {
223                visit_read(self, lane_id);
224                visit_read(self, i);
225            }
226            CoopMma::LoadMatrix { ptr, .. } => {
227                visit_read(self, ptr);
228            }
229            CoopMma::StoreMatrix {
230                registers,
231                destination,
232                ..
233            } => {
234                visit_read(self, registers);
235                visit_read(self, destination);
236            }
237            CoopMma::ExecuteManual {
238                registers_a,
239                registers_b,
240                registers_c,
241                ..
242            } => {
243                visit_read(self, registers_a);
244                visit_read(self, registers_b);
245                visit_read(self, registers_c);
246            }
247            CoopMma::ExecuteScaled {
248                registers_a,
249                registers_b,
250                registers_c,
251                scales_a,
252                scales_b,
253                ..
254            } => {
255                visit_read(self, registers_a);
256                visit_read(self, registers_b);
257                visit_read(self, registers_c);
258                visit_read(self, scales_a);
259                visit_read(self, scales_b);
260            }
261            CoopMma::ExecuteElementwise { matrix, .. } => {
262                visit_read(self, matrix);
263            }
264        }
265    }
266
267    fn visit_tma(&mut self, tma_ops: &mut TmaOps, mut visit_read: impl FnMut(&mut T, &mut Value)) {
268        match tma_ops {
269            TmaOps::TmaStore {
270                source,
271                coordinates,
272            } => {
273                visit_read(self, source);
274                for coord in coordinates {
275                    visit_read(self, coord)
276                }
277            }
278            TmaOps::CommitGroup | TmaOps::WaitGroup { .. } | TmaOps::WaitGroupRead { .. } => {}
279        }
280    }
281
282    fn visit_tensor_ops(
283        &mut self,
284        tensor_ops: &mut TensorIndexingOps,
285        mut visit_read: impl FnMut(&mut T, &mut Value),
286    ) {
287        match tensor_ops {
288            TensorIndexingOps::CreateLayout {
289                shape,
290                strides,
291                clamp_mode: _,
292            } => {
293                for s in shape {
294                    visit_read(self, s);
295                }
296                for s in strides.iter_mut().flatten() {
297                    visit_read(self, s);
298                }
299            }
300            TensorIndexingOps::CreateView => {}
301            TensorIndexingOps::Slice {
302                layout,
303                offsets,
304                shape,
305            } => {
306                visit_read(self, layout);
307                for o in offsets {
308                    visit_read(self, o);
309                }
310                for s in shape {
311                    visit_read(self, s);
312                }
313            }
314        }
315    }
316
317    fn visit_nonsemantic(
318        &mut self,
319        non_semantic: &mut NonSemantic,
320        mut visit_read: impl FnMut(&mut T, &mut Value),
321    ) {
322        match non_semantic {
323            NonSemantic::Comment { .. }
324            | NonSemantic::EnterDebugScope
325            | NonSemantic::ExitDebugScope => {}
326            NonSemantic::Print { args, .. } => {
327                for arg in args {
328                    visit_read(self, arg);
329                }
330            }
331        }
332    }
333}