1#![no_std]
27#![allow(unknown_lints, unnecessary_transmutes)]
28
29extern crate alloc;
30
31#[cfg(any(feature = "std", test))]
32extern crate std;
33
34use core::{
35 cell::RefCell,
36 ops::{Deref, DerefMut},
37};
38
39use alloc::{boxed::Box, collections::vec_deque::VecDeque, rc::Rc, vec, vec::Vec};
40use analyses::{AnalysisCache, dominance::DomFrontiers, writes::LocalStores};
41use cubecl_core::{
42 CubeDim,
43 post_processing::{
44 analysis_helper::GlobalAnalyses,
45 constant_prop::{ConstEval, ConstOperandSimplify},
46 disaggregate::DisaggregateVisitor,
47 visitor::InstructionVisitor,
48 },
49};
50use cubecl_ir::{
51 self as ir, AddressSpace, Allocator, Branch, Id, Instruction, Operation, Processor, Scope,
52 Type, Value,
53};
54use gvn::GvnPass;
55use hashbrown::HashMap;
56use passes::{
57 EliminateConstBranches, EliminateDeadBlocks, EliminateDeadPhi, EliminateUnusedVariables,
58 EmptyBranchToSelect, InlineCopies, MergeBlocks, MergeSameExpressions, OptimizerPass,
59 ReduceStrength,
60};
61use petgraph::{Direction, prelude::StableDiGraph, visit::EdgeRef};
62
63mod analyses;
64mod block;
65mod control_flow;
66mod debug;
67mod gvn;
68mod instructions;
69mod passes;
70mod phi_frontiers;
71mod transformers;
72mod version;
73
74pub(crate) use cubecl_core::post_processing::util::AtomicCounter;
75
76pub use analyses::uniformity::Uniformity;
77pub use block::*;
78pub use control_flow::*;
79pub use petgraph::graph::{EdgeIndex, NodeIndex};
80pub use transformers::*;
81pub use version::PhiInstruction;
82
83pub use crate::analyses::liveness::MemoryLiveness;
84pub use crate::analyses::liveness::shared::SharedLiveness;
85use crate::{
86 analyses::{
87 dominance::Dominators,
88 liveness::{Captures, Liveness},
89 pointer_source::PointerSource,
90 },
91 passes::{CopyTransform, DisaggregateArray},
92};
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
95pub struct MemoryBlock {
96 pub address_space: AddressSpace,
97 pub value_ty: Type,
98 pub alignment: usize,
99 pub root_ptr: Value,
102}
103
104impl MemoryBlock {
105 pub fn size(&self) -> usize {
107 self.value_ty.size()
108 }
109}
110
111#[derive(Default, Debug, Clone)]
112pub struct Function {
113 pub explicit_params: Vec<Value>,
115 pub implicit_params: Vec<Value>,
117 pub memories: HashMap<Id, MemoryBlock>,
118 pub graph: StableDiGraph<BasicBlock, u32>,
119 pub root: NodeIndex,
120 pub ret: NodeIndex,
122 pub return_value: Option<Value>,
124
125 analysis_cache: Rc<AnalysisCache>,
127 current_block: Option<NodeIndex>,
129 loop_break: VecDeque<NodeIndex>,
131}
132
133impl Deref for Function {
134 type Target = StableDiGraph<BasicBlock, u32>;
135
136 fn deref(&self) -> &Self::Target {
137 &self.graph
138 }
139}
140
141impl DerefMut for Function {
142 fn deref_mut(&mut self) -> &mut Self::Target {
143 &mut self.graph
144 }
145}
146
147#[derive(Debug, Clone, Default)]
149pub struct Optimizer {
150 pub main: Function,
151 pub global_state: GlobalState,
153}
154
155#[derive(Debug, Clone)]
156pub struct GlobalState {
157 pub allocator: Allocator,
159 pub root_scope: Scope,
161 pub buffer_visibility: RefCell<Vec<BufferVisibility>>,
162 pub extra_functions: HashMap<Id, Function>,
163 #[allow(dead_code)]
165 pub(crate) cube_dim: CubeDim,
166 pub(crate) transformers: Vec<Rc<dyn IrTransformer>>,
167 pub(crate) processors: Rc<Vec<Box<dyn Processor>>>,
168 pub(crate) visitors: Rc<RefCell<Vec<Box<dyn InstructionVisitor>>>>,
169}
170
171#[derive(Debug, Clone, Default)]
172pub struct BufferVisibility {
173 pub readable: bool,
175 pub writable: bool,
177}
178
179unsafe impl Send for Optimizer {}
181unsafe impl Sync for Optimizer {}
182
183impl Default for GlobalState {
184 fn default() -> Self {
185 Self {
186 allocator: Default::default(),
187 root_scope: Scope::root(false),
188 buffer_visibility: Default::default(),
189 extra_functions: Default::default(),
190 cube_dim: CubeDim::new_1d(1),
191 transformers: Default::default(),
192 processors: Default::default(),
193 visitors: Default::default(),
194 }
195 }
196}
197
198impl Optimizer {
199 pub fn new(
202 expand: Scope,
203 cube_dim: CubeDim,
204 transformers: Vec<Rc<dyn IrTransformer>>,
205 visitors: Vec<Box<dyn InstructionVisitor>>,
206 processors: Vec<Box<dyn Processor>>,
207 ) -> Self {
208 let extra_funcs = expand.state().functions.clone();
209 let mut global_state = GlobalState {
210 allocator: expand.state().allocator.clone(),
211 root_scope: expand.clone(),
212 buffer_visibility: Default::default(),
213 cube_dim,
214 transformers,
215 processors: Rc::new(processors),
216 visitors: Rc::new(RefCell::new(visitors)),
217 extra_functions: Default::default(),
218 };
219 for (id, func) in extra_funcs.into_iter() {
220 let mut function = Function {
221 explicit_params: func.explicit_params,
222 return_value: func.scope.return_value,
223 ..Default::default()
224 };
225 function.run_opt(&global_state, func.scope);
226 global_state.extra_functions.insert(id, function);
227 }
228 let mut root_func = Function::default();
229 root_func.run_opt(&global_state, expand);
230
231 Self {
232 global_state,
233 main: root_func,
234 }
235 }
236
237 pub fn shared_only(expand: Scope, cube_dim: CubeDim) -> Self {
240 let extra_funcs = expand.state().functions.clone();
241 let disaggregate: Box<dyn InstructionVisitor> = Box::new(DisaggregateVisitor::default());
242 let mut global_state = GlobalState {
243 allocator: expand.state().allocator.clone(),
244 root_scope: expand.clone(),
245 buffer_visibility: Default::default(),
246 cube_dim,
247 transformers: Vec::new(),
248 processors: Rc::new(vec![]),
249 visitors: Rc::new(RefCell::new(vec![disaggregate])),
250 extra_functions: Default::default(),
251 };
252 for (id, func) in extra_funcs.into_iter() {
253 let mut function = Function {
254 explicit_params: func.explicit_params,
255 ..Default::default()
256 };
257 function.run_shared_only(&global_state, func.scope);
258 global_state.extra_functions.insert(id, function);
259 }
260 let mut root_func = Function::default();
261 root_func.run_shared_only(&global_state, expand);
262
263 Self {
264 global_state,
265 main: root_func,
266 }
267 }
268
269 pub fn entry(&self) -> NodeIndex {
271 self.main.root
272 }
273}
274
275impl GlobalState {
276 fn set_buffer_readable(&self, id: Id) {
277 let mut buffer_vis = self.buffer_visibility.borrow_mut();
278 let idx = id as usize;
279 if idx >= buffer_vis.len() {
280 buffer_vis.resize(idx + 1, Default::default());
281 }
282 buffer_vis[idx].readable = true;
283 }
284
285 fn set_buffer_writable(&self, id: Id) {
286 let mut buffer_vis = self.buffer_visibility.borrow_mut();
287 let idx = id as usize;
288 if idx >= buffer_vis.len() {
289 buffer_vis.resize(idx + 1, Default::default());
290 }
291 buffer_vis[idx].writable = true;
292 }
293}
294
295pub fn global_buffer_id(value: &ir::Value) -> Option<Id> {
296 match value.address_space() {
297 AddressSpace::Global(id) => Some(id),
298 _ => None,
299 }
300}
301
302impl Function {
303 fn parse_graph(&mut self, state: &GlobalState, scope: Scope) {
304 let entry = self.add_node(BasicBlock::default());
305 self.root = entry;
306 self.current_block = Some(entry);
307 self.ret = self.add_node(BasicBlock::default());
308 *self[self.ret].control_flow.borrow_mut() = ControlFlow::Return {
309 value: self.return_value,
310 };
311
312 self.parse_scope(state, scope);
313 if let Some(current_block) = self.current_block {
314 let ret = self.ret;
315 self.add_edge(current_block, ret, 0);
316 }
317 self.invalidate_structure();
320 }
321
322 pub fn parse_scope(&mut self, state: &GlobalState, scope: Scope) -> bool {
324 let global_analyses = GlobalAnalyses::default();
325 global_analyses.recalculate_pointer_source(&scope);
326 global_analyses.recalculate_used_values(&scope);
327 for visitor in state.visitors.borrow_mut().iter_mut() {
328 visitor.visit_scope(&scope, &global_analyses, &AtomicCounter::new(0));
329 }
330 let processed = scope.process(state.processors.iter().map(|it| &**it));
331
332 for global_arg in processed.global_state.borrow().global_args.iter() {
333 let address_space = global_arg.address_space();
334 if matches!(address_space, AddressSpace::Local) {
336 continue;
337 }
338 self.memories.insert(
339 global_arg.id(),
340 MemoryBlock {
341 address_space,
342 value_ty: global_arg.ty.unwrap_ptr(),
343 alignment: global_arg.ty.align(),
344 root_ptr: *global_arg,
345 },
346 );
347 }
348
349 let is_break = processed.instructions.contains(&Branch::Break.into());
350
351 for mut instruction in processed.instructions {
352 let mut removed = false;
353 for transform in state.transformers.iter() {
354 match transform.maybe_transform(&scope, &instruction) {
355 TransformAction::Ignore => {}
356 TransformAction::Replace(replacement) => {
357 self.current_block_mut()
358 .ops
359 .borrow_mut()
360 .extend(replacement);
361 removed = true;
362 break;
363 }
364 TransformAction::Remove => {
365 removed = true;
366 break;
367 }
368 }
369 }
370 if removed {
371 continue;
372 }
373 match &mut instruction.operation {
374 Operation::DeclareVariable {
375 value_ty,
376 addr_space,
377 alignment,
378 } => {
379 let out = instruction.out.unwrap();
380 self.memories.insert(
381 out.id(),
382 MemoryBlock {
383 address_space: *addr_space,
384 value_ty: *value_ty,
385 alignment: *alignment,
386 root_ptr: out,
387 },
388 );
389 self.current_block_mut().ops.borrow_mut().push(instruction);
390 }
391 Operation::Branch(branch) => match self.parse_control_flow(state, branch.clone()) {
392 ControlFlowAction::None => {}
393 ControlFlowAction::AbortBlock => {
394 break;
395 }
396 },
397 _ => {
398 self.current_block_mut().ops.borrow_mut().push(instruction);
399 }
400 }
401 }
402
403 is_break
404 }
405
406 pub(crate) fn current_block_mut(&mut self) -> &mut BasicBlock {
408 let current_block = self.current_block.unwrap();
409 &mut self[current_block]
410 }
411
412 pub fn predecessors(&self, block: NodeIndex) -> Vec<NodeIndex> {
414 self.edges_directed(block, Direction::Incoming)
415 .map(|it| it.source())
416 .filter(|it| !self.is_unreachable(*it))
417 .collect()
418 }
419
420 pub fn successors(&self, block: NodeIndex) -> Vec<NodeIndex> {
422 self.edges_directed(block, Direction::Outgoing)
423 .map(|it| it.target())
424 .collect()
425 }
426
427 pub fn breadth_first_dominators(&self) -> Vec<NodeIndex> {
431 self.analysis_cache
432 .try_get::<Dominators>()
433 .expect("Dominators should be present")
434 .breadth_first_nodes()
435 }
436
437 #[track_caller]
439 pub fn block(&self, block: NodeIndex) -> &BasicBlock {
440 &self[block]
441 }
442
443 #[track_caller]
445 pub fn block_mut(&mut self, block: NodeIndex) -> &mut BasicBlock {
446 &mut self[block]
447 }
448
449 pub fn is_unreachable(&self, block: NodeIndex) -> bool {
450 let control_flow = self[block].control_flow.borrow();
451 matches!(*control_flow, ControlFlow::Unreachable)
452 }
453
454 pub fn node_ids(&self) -> Vec<NodeIndex> {
456 self.node_indices().collect()
457 }
458
459 fn transform_ssa_and_merge_composites(&mut self, state: &GlobalState) {
460 self.ssa_transform(state);
461
462 let mut done = false;
463 while !done {
464 let changes = AtomicCounter::new(0);
465 if changes.get() > 0 {
466 self.ssa_transform(state);
467 } else {
468 done = true;
469 }
470 }
471 }
472
473 fn ssa_transform(&mut self, state: &GlobalState) {
474 InlineCopies.apply_pre_ssa(self, state, AtomicCounter::new(0));
475 self.place_phi_nodes(state);
476 self.version_program(state);
477 self.invalidate_analysis::<LocalStores>();
478 self.invalidate_analysis::<DomFrontiers>();
479 }
480
481 fn run_opt(&mut self, state: &GlobalState, scope: Scope) {
483 self.parse_graph(state, scope);
484 self.split_critical_edges();
485
486 self.transform_ssa_and_merge_composites(state);
487 self.analysis::<PointerSource>(state);
488 self.apply_post_ssa_passes(state);
489
490 let arrays_prop = AtomicCounter::new(0);
496 log::debug!("Applying {}", DisaggregateArray.name());
497 DisaggregateArray.apply_post_ssa(self, state, arrays_prop.clone());
498 if arrays_prop.get() > 0 {
499 InlineCopies.apply_post_ssa(self, state, AtomicCounter::new(0));
500 self.invalidate_analysis::<Liveness>();
501 self.transform_ssa_and_merge_composites(state);
502 self.apply_post_ssa_passes(state);
503 }
504
505 let gvn_count = AtomicCounter::new(0);
506 log::debug!("Applying {}", GvnPass.name());
507 GvnPass.apply_post_ssa(self, state, gvn_count.clone());
508 log::debug!("Applying {}", ReduceStrength.name());
509 ReduceStrength.apply_post_ssa(self, state, gvn_count.clone());
510 log::debug!("Applying {}", CopyTransform.name());
511 CopyTransform.apply_post_ssa(self, state, gvn_count.clone());
512
513 if gvn_count.get() > 0 {
514 self.apply_post_ssa_passes(state);
515 }
516
517 self.split_free();
518 self.analysis::<SharedLiveness>(state);
519
520 log::debug!("Applying {}", MergeBlocks.name());
521 MergeBlocks.apply_post_ssa(self, state, AtomicCounter::new(0));
522
523 log::debug!("Collecting captures");
524 let captures = self.analysis::<Captures>(state);
525 self.implicit_params = captures
526 .at_block(self.root)
527 .iter()
528 .copied()
529 .filter(|param| !self.explicit_params.contains(param))
530 .collect();
531
532 self.update_buffer_vis(state);
533 self.analysis::<Dominators>(state);
534 }
535
536 fn run_shared_only(&mut self, state: &GlobalState, scope: Scope) {
538 self.parse_graph(state, scope);
539 self.split_critical_edges();
540 self.transform_ssa_and_merge_composites(state);
541 self.split_free();
542 self.analysis::<PointerSource>(state);
543 self.analysis::<SharedLiveness>(state);
544 self.update_buffer_vis(state);
545 }
546
547 fn update_buffer_vis(&mut self, state: &GlobalState) {
548 self.visit_all(
549 state,
550 |_, val| {
551 if let Some(id) = global_buffer_id(val) {
552 state.set_buffer_readable(id);
553 }
554 },
555 |_, val| {
556 if let Some(id) = global_buffer_id(val) {
557 state.set_buffer_writable(id);
558 }
559 },
560 );
561 }
562
563 fn apply_post_ssa_passes(&mut self, state: &GlobalState) {
564 let mut passes: Vec<Box<dyn OptimizerPass>> = vec![
566 Box::new(InlineCopies),
567 Box::new(EliminateUnusedVariables),
568 Box::new(ConstOperandSimplify),
569 Box::new(MergeSameExpressions),
570 Box::new(ConstEval),
571 Box::new(EliminateConstBranches),
572 Box::new(EmptyBranchToSelect),
573 Box::new(EliminateDeadBlocks),
574 Box::new(EliminateDeadPhi),
575 ];
576
577 log::debug!("Applying post-SSA passes");
578 loop {
579 let counter = AtomicCounter::default();
580 for pass in &mut passes {
581 log::debug!("Applying {}", pass.name());
582 pass.apply_post_ssa(self, state, counter.clone());
583 }
584
585 if counter.get() == 0 {
586 break;
587 }
588 }
589 }
590
591 pub(crate) fn ret(&mut self) -> NodeIndex {
592 if self[self.ret].block_use.contains(&BlockUse::Merge) {
593 let ret = self.ret;
594 let new_ret = self.add_node(BasicBlock::default());
595 self.add_edge(new_ret, ret, 0);
596 self.ret = new_ret;
597 self.invalidate_structure();
598 new_ret
599 } else {
600 self.ret
601 }
602 }
603
604 pub fn all_params(&self) -> impl Iterator<Item = Value> {
605 self.explicit_params
606 .iter()
607 .copied()
608 .chain(self.implicit_params.iter().copied())
609 }
610
611 pub fn create_local_mut(&mut self, state: &GlobalState, value_ty: Type) -> Value {
612 let ty = Type::Pointer(value_ty.intern(), AddressSpace::Local);
613 let val = state.allocator.create_value(ty);
614 let root = self.root;
615 self[root].ops.borrow_mut().push(Instruction::new(
616 Operation::DeclareVariable {
617 value_ty,
618 addr_space: AddressSpace::Local,
619 alignment: value_ty.align(),
620 },
621 val,
622 ));
623 self.memories.insert(
624 val.id(),
625 MemoryBlock {
626 address_space: AddressSpace::Local,
627 value_ty,
628 alignment: value_ty.align(),
629 root_ptr: val,
630 },
631 );
632 val
633 }
634}
635
636pub fn visit_noop(_opt: &mut Function, _var: &mut Value) {}
638
639#[cfg(test)]
640mod test {
641 use alloc::vec;
642 use cubecl_core as cubecl;
643 use cubecl_core::prelude::*;
644 use cubecl_ir::{ElemType, Type, UIntKind};
645
646 use crate::Optimizer;
647
648 #[allow(unused)]
649 #[cube(launch)]
650 fn pre_kernel(x: u32, cond: u32, out: &mut [u32]) {
651 let mut y = 0;
652 let mut z = 0;
653 if cond == 0 {
654 y = x + 4;
655 }
656 z = x + 4;
657 out[0] = y;
658 out[1] = z;
659 }
660
661 #[test_log::test]
662 #[ignore = "no good way to assert opt is applied"]
663 fn test_pre() {
664 let ctx = Scope::root(false);
665 let u32 = Type::scalar(ElemType::UInt(UIntKind::U32));
666 let x = ctx.create_value(u32).into();
667 let cond = ctx.create_value(u32).into();
668 let mut arr = ctx.global(0, u32).into();
669
670 pre_kernel::expand(&ctx, x, cond, &mut arr);
671 let opt = Optimizer::new(ctx, CubeDim::new_1d(1), vec![], vec![], vec![]);
672 std::println!("{opt}")
673 }
674}