1use alloc::collections::vec_deque::VecDeque;
2use cubecl_ir::{AddressSpace, Id, Value, ValueKind};
3use hashbrown::{HashMap, HashSet};
4use petgraph::graph::NodeIndex;
5
6use crate::{Function, GlobalState, analyses::post_order::PostOrder};
7
8use super::Analysis;
9
10type LivePredicate = fn(&Function, &Value) -> Option<Id>;
11
12pub struct Liveness {
13 live_vars: HashMap<NodeIndex, HashSet<Id>>,
14}
15
16#[derive(Debug, Clone)]
17struct BlockSets {
18 generated: HashSet<Id>,
19 kill: HashSet<Id>,
20}
21
22struct State {
23 worklist: VecDeque<NodeIndex>,
24 block_sets: HashMap<NodeIndex, BlockSets>,
25}
26
27impl Analysis for Liveness {
28 fn init(func: &mut Function, state: &GlobalState) -> Self {
29 Self {
30 live_vars: compute_liveness(func, state, Function::local_variable_id),
31 }
32 }
33}
34
35impl Liveness {
36 pub fn empty(func: &Function) -> Self {
37 let live_vars = func
38 .node_ids()
39 .iter()
40 .map(|it| (*it, HashSet::new()))
41 .collect();
42 Self { live_vars }
43 }
44
45 pub fn at_block(&self, block: NodeIndex) -> &HashSet<Id> {
46 &self.live_vars[&block]
47 }
48
49 pub fn is_dead(&self, node: NodeIndex, var: Id) -> bool {
50 !self.at_block(node).contains(&var)
51 }
52}
53
54pub struct MemoryLiveness {
61 live_vars: HashMap<NodeIndex, HashSet<Id>>,
62}
63
64impl Analysis for MemoryLiveness {
65 fn init(func: &mut Function, state: &GlobalState) -> Self {
66 Self {
67 live_vars: compute_liveness(func, state, Function::local_memory_id),
68 }
69 }
70}
71
72impl MemoryLiveness {
73 pub fn empty(func: &Function) -> Self {
74 let live_vars = func
75 .node_ids()
76 .iter()
77 .map(|it| (*it, HashSet::new()))
78 .collect();
79 Self { live_vars }
80 }
81
82 pub fn at_block(&self, block: NodeIndex) -> &HashSet<Id> {
83 &self.live_vars[&block]
84 }
85
86 pub fn is_dead(&self, node: NodeIndex, var: Id) -> bool {
87 !self.at_block(node).contains(&var)
88 }
89}
90
91fn compute_liveness(
93 func: &mut Function,
94 global_state: &GlobalState,
95 pred: LivePredicate,
96) -> HashMap<NodeIndex, HashSet<Id>> {
97 let mut live_vars: HashMap<NodeIndex, HashSet<Id>> = func
98 .node_ids()
99 .iter()
100 .map(|it| (*it, HashSet::new()))
101 .collect();
102 let mut state = State {
103 worklist: VecDeque::from(func.analysis::<PostOrder>(global_state).forward()),
104 block_sets: HashMap::new(),
105 };
106 while let Some(block) = state.worklist.pop_front() {
107 analyze_block(func, global_state, block, &mut state, &mut live_vars, pred);
108 }
109 live_vars
110}
111
112fn analyze_block(
113 func: &mut Function,
114 global_state: &GlobalState,
115 block: NodeIndex,
116 state: &mut State,
117 live_vars: &mut HashMap<NodeIndex, HashSet<Id>>,
118 pred: LivePredicate,
119) {
120 let BlockSets { generated, kill } = block_sets(func, global_state, block, state, pred);
121
122 let mut block_live = generated.clone();
123
124 for successor in func.successors(block) {
125 let successor = &live_vars[&successor];
126 block_live.extend(successor.difference(kill));
127 }
128
129 if block_live != live_vars[&block] {
130 state.worklist.extend(func.predecessors(block));
131 live_vars.insert(block, block_live);
132 }
133}
134
135fn block_sets<'a>(
136 func: &mut Function,
137 global_state: &GlobalState,
138 block: NodeIndex,
139 state: &'a mut State,
140 pred: LivePredicate,
141) -> &'a BlockSets {
142 let block_sets = state.block_sets.entry(block);
143 block_sets.or_insert_with(|| calculate_block_sets(func, global_state, block, pred))
144}
145
146fn calculate_block_sets(
147 func: &mut Function,
148 state: &GlobalState,
149 block: NodeIndex,
150 pred: LivePredicate,
151) -> BlockSets {
152 let mut generated = HashSet::new();
153 let mut kill = HashSet::new();
154
155 let ops = func[block].ops.clone();
156
157 let control_flow = func[block].control_flow.clone();
158 func.visit_control_flow(&mut control_flow.borrow_mut(), |func, val| {
159 if let Some(id) = pred(func, val) {
160 generated.insert(id);
161 }
162 });
163 let mut ops = ops.borrow().clone();
164 for op in ops.values_mut().rev() {
165 func.visit_out(&mut op.out, |func, val| {
167 if let Some(id) = pred(func, val) {
168 kill.insert(id);
169 generated.remove(&id);
170 }
171 });
172 func.visit_operation(state, &mut op.operation, |func, val| {
173 if let Some(id) = pred(func, val) {
174 generated.insert(id);
175 }
176 });
177 }
178
179 BlockSets { generated, kill }
180}
181
182impl Function {
183 pub fn local_variable_id(&self, value: &Value) -> Option<Id> {
186 match value.kind {
187 ValueKind::Value { id } if self.destructurable_local_memories().contains_key(&id) => {
188 Some(id)
189 }
190 _ => None,
191 }
192 }
193
194 pub fn local_memory_id(&self, value: &Value) -> Option<Id> {
199 match value.kind {
200 ValueKind::Value { id } => match self.memories.get(&id) {
201 Some(mem)
202 if matches!(mem.address_space, AddressSpace::Local)
203 && !mem.value_ty.is_atomic() =>
204 {
205 Some(id)
206 }
207 _ => None,
208 },
209 _ => None,
210 }
211 }
212}
213
214pub mod shared {
216 use alloc::vec::Vec;
217 use cubecl_ir::{AddressSpace, Marker, Operation, Type, Value, ValueKind};
218
219 use crate::{MemoryBlock, Uniformity};
220
221 use super::*;
222
223 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
225 pub struct SmemAllocation {
226 pub id: Id,
227 pub smem: MemoryBlock,
229 pub offset: usize,
231 }
232
233 #[derive(Default, Clone)]
241 pub struct SharedLiveness {
242 live_vars: HashMap<NodeIndex, HashSet<Id>>,
243 pub shared_memories: HashMap<Id, MemoryBlock>,
246 pub allocations: HashMap<Id, SmemAllocation>,
249 }
250
251 impl Analysis for SharedLiveness {
252 fn init(func: &mut Function, state: &GlobalState) -> Self {
253 let mut this = Self::empty(func);
254 this.analyze_liveness(func, state);
255 this.uniformize_liveness(func, state);
256 this.allocate_slices(func);
257 this
258 }
259 }
260
261 impl SharedLiveness {
262 pub fn empty(func: &Function) -> Self {
263 let live_vars = func
264 .node_ids()
265 .iter()
266 .map(|it| (*it, HashSet::new()))
267 .collect();
268 Self {
269 live_vars,
270 shared_memories: Default::default(),
271 allocations: Default::default(),
272 }
273 }
274
275 pub fn at_block(&self, block: NodeIndex) -> &HashSet<Id> {
276 &self.live_vars[&block]
277 }
278
279 fn is_live(&self, node: NodeIndex, var: Id) -> bool {
280 self.at_block(node).contains(&var)
281 }
282
283 fn analyze_liveness(&mut self, func: &mut Function, global_state: &GlobalState) {
285 let mut state = State {
286 worklist: VecDeque::from(func.analysis::<PostOrder>(global_state).reverse()),
287 block_sets: HashMap::new(),
288 };
289 while let Some(block) = state.worklist.pop_front() {
290 self.analyze_block(func, global_state, block, &mut state);
291 }
292 }
293
294 fn uniformize_liveness(&mut self, func: &mut Function, global_state: &GlobalState) {
297 let mut state = State {
298 worklist: VecDeque::from(func.analysis::<PostOrder>(global_state).forward()),
299 block_sets: HashMap::new(),
300 };
301 while let Some(block) = state.worklist.pop_front() {
302 self.uniformize_block(func, global_state, block, &mut state);
303 }
304 }
305
306 fn allocate_slices(&mut self, func: &mut Function) {
309 for block in func.node_ids() {
310 for live_smem in self.at_block(block).clone() {
311 if !self.allocations.contains_key(&live_smem) {
312 let smem = self.shared_memories[&live_smem];
313 let offset = self.allocate_slice(block, smem.size(), smem.alignment);
314 self.allocations.insert(
315 live_smem,
316 SmemAllocation {
317 id: live_smem,
318 smem,
319 offset,
320 },
321 );
322 }
323 }
324 }
325 }
326
327 fn allocate_slice(&mut self, block: NodeIndex, size: usize, align: usize) -> usize {
336 let live_slices = self.live_slices(block);
337 if live_slices.is_empty() {
338 return 0;
339 }
340
341 for i in 0..live_slices.len() - 1 {
342 let slice_0 = &live_slices[i];
343 let slice_1 = &live_slices[i + 1];
344 let end_0 = (slice_0.offset + slice_0.smem.size()).next_multiple_of(align);
345 let gap = slice_1.offset.saturating_sub(end_0);
346 if gap >= size {
347 return end_0;
348 }
349 }
350 let last_slice = &live_slices[live_slices.len() - 1];
351 (last_slice.offset + last_slice.smem.size()).next_multiple_of(align)
352 }
353
354 fn live_slices(&mut self, block: NodeIndex) -> Vec<SmemAllocation> {
356 let mut live_slices = self
357 .allocations
358 .iter()
359 .filter(|(k, _)| self.is_live(block, **k))
360 .map(|it| *it.1)
361 .collect::<Vec<_>>();
362 live_slices.sort_by_key(|it| it.offset);
363 live_slices
364 }
365
366 fn analyze_block(
367 &mut self,
368 func: &mut Function,
369 global_state: &GlobalState,
370 block: NodeIndex,
371 state: &mut State,
372 ) {
373 let BlockSets { generated, kill } = self.block_sets(func, global_state, block, state);
374
375 let mut live_vars = generated.clone();
376
377 for predecessor in func.predecessors(block) {
378 let predecessor = &self.live_vars[&predecessor];
379 live_vars.extend(predecessor.difference(kill));
380 }
381
382 if live_vars != self.live_vars[&block] {
383 state.worklist.extend(func.successors(block));
384 self.live_vars.insert(block, live_vars);
385 }
386 }
387
388 fn uniformize_block(
389 &mut self,
390 func: &mut Function,
391 global_state: &GlobalState,
392 block: NodeIndex,
393 state: &mut State,
394 ) {
395 let mut live_vars = self.live_vars[&block].clone();
396 let uniformity = func.analysis::<Uniformity>(global_state);
397
398 for successor in func.successors(block) {
399 if !uniformity.is_block_uniform(successor) {
400 let successor = &self.live_vars[&successor];
401 live_vars.extend(successor);
402 }
403 }
404
405 if live_vars != self.live_vars[&block] {
406 state.worklist.extend(func.predecessors(block));
407 self.live_vars.insert(block, live_vars);
408 }
409 }
410
411 fn block_sets<'a>(
412 &mut self,
413 func: &mut Function,
414 global_state: &GlobalState,
415 block: NodeIndex,
416 state: &'a mut State,
417 ) -> &'a BlockSets {
418 let block_sets = state.block_sets.entry(block);
419 block_sets.or_insert_with(|| self.calculate_block_sets(func, global_state, block))
420 }
421
422 fn calculate_block_sets(
425 &mut self,
426 func: &mut Function,
427 state: &GlobalState,
428 block: NodeIndex,
429 ) -> BlockSets {
430 let mut generated = HashSet::new();
431 let mut kill = HashSet::new();
432
433 let ops = func[block].ops.clone();
434
435 for op in ops.borrow_mut().values_mut() {
436 func.visit_out(&mut op.out, |func, var| {
437 if let Some((id, smem)) = shared_memory(func, var) {
438 generated.insert(id);
439 self.shared_memories.insert(id, smem);
440 }
441 });
442 func.visit_operation(state, &mut op.operation, |func, var| {
443 if let Some((id, smem)) = shared_memory(func, var) {
444 generated.insert(id);
445 self.shared_memories.insert(id, smem);
446 }
447 });
448
449 if let Operation::Marker(Marker::Free(Value {
450 ty: Type::Pointer(_, AddressSpace::Shared),
451 kind: ValueKind::Value { id, .. },
452 ..
453 })) = &op.operation
454 {
455 kill.insert(*id);
456 generated.remove(id);
457 }
458 }
459
460 BlockSets { generated, kill }
461 }
462 }
463
464 fn shared_memory(func: &Function, var: &Value) -> Option<(Id, MemoryBlock)> {
465 match var.kind {
466 ValueKind::Value { id } => {
467 if let Some(mem) = func.memories.get(&id)
468 && matches!(mem.address_space, AddressSpace::Shared)
469 {
470 Some((id, *mem))
471 } else {
472 None
473 }
474 }
475 _ => None,
476 }
477 }
478}
479
480mod captures {
481 use cubecl_ir::Value;
482
483 use super::*;
484
485 pub struct Captures {
486 live_vars: HashMap<NodeIndex, HashSet<Value>>,
487 }
488
489 #[derive(Clone)]
490 struct BlockSets {
491 generated: HashSet<Value>,
492 kill: HashSet<Value>,
493 }
494
495 struct State {
496 worklist: VecDeque<NodeIndex>,
497 block_sets: HashMap<NodeIndex, BlockSets>,
498 }
499
500 impl Analysis for Captures {
501 fn init(func: &mut Function, state: &GlobalState) -> Self {
502 let mut this = Self::empty(func);
503 this.analyze_liveness(func, state);
504 this
505 }
506 }
507
508 impl Captures {
509 pub fn empty(func: &Function) -> Self {
510 let live_vars = func
511 .node_ids()
512 .iter()
513 .map(|it| (*it, HashSet::new()))
514 .collect();
515 Self { live_vars }
516 }
517
518 pub fn at_block(&self, block: NodeIndex) -> &HashSet<Value> {
519 &self.live_vars[&block]
520 }
521
522 pub fn analyze_liveness(&mut self, func: &mut Function, global_state: &GlobalState) {
524 let mut state = State {
525 worklist: VecDeque::from(func.analysis::<PostOrder>(global_state).forward()),
526 block_sets: HashMap::new(),
527 };
528 while let Some(block) = state.worklist.pop_front() {
529 self.analyze_block(func, global_state, block, &mut state);
530 }
531 }
532
533 fn analyze_block(
534 &mut self,
535 func: &mut Function,
536 global_state: &GlobalState,
537 block: NodeIndex,
538 state: &mut State,
539 ) {
540 let BlockSets { generated, kill } = block_sets(func, global_state, block, state);
541
542 let mut live_vars = generated.clone();
543
544 for successor in func.successors(block) {
545 let successor = &self.live_vars[&successor];
546 live_vars.extend(successor.difference(kill));
547 }
548
549 if live_vars != self.live_vars[&block] {
550 state.worklist.extend(func.predecessors(block));
551 self.live_vars.insert(block, live_vars);
552 }
553 }
554 }
555
556 fn block_sets<'a>(
557 func: &mut Function,
558 global_state: &GlobalState,
559 block: NodeIndex,
560 state: &'a mut State,
561 ) -> &'a BlockSets {
562 let block_sets = state.block_sets.entry(block);
563 block_sets.or_insert_with(|| calculate_block_sets(func, global_state, block))
564 }
565
566 fn calculate_block_sets(
567 func: &mut Function,
568 state: &GlobalState,
569 block: NodeIndex,
570 ) -> BlockSets {
571 let mut generated = HashSet::new();
572 let mut kill = HashSet::new();
573
574 let ops = func[block].ops.clone();
575
576 let control_flow = func[block].control_flow.clone();
577 func.visit_control_flow(&mut control_flow.borrow_mut(), |_, var| {
578 generated.insert(*var);
579 });
580 for inst in ops.borrow_mut().values_mut().rev() {
581 func.visit_out(&mut inst.out, |_, var| {
583 kill.insert(*var);
584 generated.remove(var);
585 });
586 func.visit_operation(state, &mut inst.operation, |_, var| {
587 generated.insert(*var);
588 });
589 }
590
591 BlockSets { generated, kill }
592 }
593}
594
595pub use captures::Captures;