1use core::mem::take;
2
3use alloc::vec::Vec;
4use cubecl_ir::{Id, Instruction, Memory, Operation, Type, Value, ValueKind};
5use hashbrown::{HashMap, HashSet};
6use petgraph::visit::EdgeRef;
7
8use crate::{EdgeIndex, Function, GlobalState, NodeIndex};
9
10#[derive(Debug)]
12pub struct SsaState<'a> {
13 versions: HashMap<Id, Id>,
14 visited_blocks: &'a mut HashSet<NodeIndex>,
15 visited_edges: &'a mut HashSet<EdgeIndex>,
16}
17
18#[derive(Debug, Clone, PartialEq)]
21pub struct PhiEntry {
22 pub block: NodeIndex,
23 pub value: Value,
24}
25
26#[derive(Debug, Clone, PartialEq)]
58pub struct PhiInstruction {
59 pub out: Value,
61 pub entries: Vec<PhiEntry>,
63}
64
65impl Function {
66 pub(crate) fn version_program(&mut self, global_state: &GlobalState) {
68 let locals = self.destructurable_local_memories();
69 let versions: HashMap<_, _> = locals.keys().map(|key| (*key, 0)).collect();
70 let mut visited_blocks = HashSet::new();
71 let mut visited_edges = HashSet::new();
72 let initial_state = SsaState {
73 versions,
74 visited_blocks: &mut visited_blocks,
75 visited_edges: &mut visited_edges,
76 };
77 self.version_block(global_state, self.root, initial_state);
78 }
79
80 fn version_block(
81 &mut self,
82 global_state: &GlobalState,
83 block: NodeIndex,
84 mut state: SsaState<'_>,
85 ) {
86 self.version_block_ops(global_state, block, &mut state);
87
88 let edges: Vec<_> = self.edges(block).map(|it| (it.id(), it.target())).collect();
89 let state = &mut state;
90 for (edge_id, target) in edges {
91 let edge_visited = state.visited_edges.contains(&edge_id);
92 state.visited_edges.insert(edge_id);
93 let block_visited = state.visited_blocks.contains(&target);
94 state.visited_blocks.insert(block);
95
96 let new_state = SsaState {
97 versions: state.versions.clone(),
98 visited_blocks: state.visited_blocks,
99 visited_edges: state.visited_edges,
100 };
101
102 if !edge_visited {
103 self.version_phi(target, block, &new_state);
104 }
105 if !block_visited {
106 self.version_block(global_state, target, new_state);
107 }
108 }
109 }
110
111 fn version_phi(&mut self, target: NodeIndex, source: NodeIndex, state: &SsaState<'_>) {
113 let phi = self[target].phi_nodes.clone();
114 for node in phi.borrow_mut().iter_mut() {
115 let entry = node
116 .entries
117 .iter_mut()
118 .find(|it| it.block == source)
119 .unwrap();
120 if let Some((id, item)) = as_local(entry.value)
121 && self.destructurable_local_memories().contains_key(&id)
122 {
123 let id = state.versions[&id];
124 entry.value = Value::new(id, item);
125 }
126 }
127 }
128
129 fn version_block_ops(
131 &mut self,
132 global_state: &GlobalState,
133 block: NodeIndex,
134 state: &mut SsaState<'_>,
135 ) {
136 for phi in self[block].phi_nodes.borrow_mut().iter_mut() {
137 if let Some((id, item)) = as_local(phi.out)
138 && self.destructurable_local_memories().contains_key(&id)
139 {
140 let version = state.versions.get_mut(&id).unwrap();
141 let out = global_state.root_scope.create_value(item);
142 *version = out.id();
143 phi.out = out;
144 }
145 }
146
147 let ops = take(&mut *self[block].ops.borrow_mut());
148 let ops = ops.into_iter().flat_map(|(_, mut instruction)| {
149 self.version_loads(&mut instruction, state);
150 self.version_stores(&mut instruction, state, global_state);
151 if let Operation::DeclareVariable { .. } = &instruction.operation
152 && state.versions.contains_key(&instruction.out().id())
153 {
154 None
155 } else {
156 Some(instruction)
157 }
158 });
159 *self[block].ops.borrow_mut() = ops.collect();
160 }
161
162 fn version_loads(&mut self, inst: &mut Instruction, state: &mut SsaState<'_>) {
163 if let Operation::Memory(Memory::Load(ptr)) = inst.operation
164 && let Some(id) = state.versions.get(&ptr.id())
165 {
166 let new_val = Value::new(*id, inst.out().ty);
167 *inst = Instruction::new(Operation::Copy(new_val), inst.out())
168 }
169 }
170
171 fn version_stores(
172 &mut self,
173 inst: &mut Instruction,
174 state: &mut SsaState<'_>,
175 global_state: &GlobalState,
176 ) {
177 if let Operation::Memory(Memory::Store(store)) = &mut inst.operation
178 && let Some(version) = state.versions.get_mut(&store.ptr.id())
179 {
180 let new_val = global_state.root_scope.create_value(store.value.ty);
181 *version = new_val.id();
182 *inst = Instruction::new(Operation::Copy(store.value), new_val);
183 }
184 }
185}
186
187fn as_local(val: Value) -> Option<(Id, Type)> {
188 match val.kind {
189 ValueKind::Value { id } => Some((id, val.ty)),
190 _ => None,
191 }
192}