1 // Copyright © 2023 Collabora, Ltd.
2 // SPDX-License-Identifier: MIT
3
4 use crate::bitset::BitSet;
5 use crate::ir::*;
6
7 use std::cell::RefCell;
8 use std::collections::HashMap;
9
10 struct Phi {
11 idx: u32,
12 orig: SSAValue,
13 dst: SSAValue,
14 srcs: HashMap<usize, SSAValue>,
15 }
16
17 struct DefTrackerBlock {
18 pred: Vec<usize>,
19 succ: Vec<usize>,
20 defs: RefCell<HashMap<SSAValue, SSAValue>>,
21 phis: RefCell<Vec<Phi>>,
22 }
23
get_ssa_or_phi( ssa_alloc: &mut SSAValueAllocator, phi_alloc: &mut PhiAllocator, blocks: &[DefTrackerBlock], needs_src: &mut BitSet, b_idx: usize, ssa: SSAValue, ) -> SSAValue24 fn get_ssa_or_phi(
25 ssa_alloc: &mut SSAValueAllocator,
26 phi_alloc: &mut PhiAllocator,
27 blocks: &[DefTrackerBlock],
28 needs_src: &mut BitSet,
29 b_idx: usize,
30 ssa: SSAValue,
31 ) -> SSAValue {
32 let b = &blocks[b_idx];
33 let mut b_defs = b.defs.borrow_mut();
34 if let Some(ssa) = b_defs.get(&ssa) {
35 return *ssa;
36 }
37
38 let mut pred_ssa = None;
39 let mut all_same = true;
40 for p_idx in &b.pred {
41 if *p_idx >= b_idx {
42 // This is a loop back-edge, add a phi just in case. We'll remove
43 // it later if it's not needed
44 all_same = false;
45 } else {
46 let p_ssa = get_ssa_or_phi(
47 ssa_alloc, phi_alloc, blocks, needs_src, *p_idx, ssa,
48 );
49 if *pred_ssa.get_or_insert(p_ssa) != p_ssa {
50 all_same = false;
51 }
52 }
53 }
54
55 if all_same {
56 let pred_ssa = pred_ssa.expect("Undefined value");
57 b_defs.insert(ssa, pred_ssa);
58 pred_ssa
59 } else {
60 let phi_idx = phi_alloc.alloc();
61 let phi_ssa = ssa_alloc.alloc(ssa.file());
62 let mut phi = Phi {
63 idx: phi_idx,
64 orig: ssa,
65 dst: phi_ssa,
66 srcs: HashMap::new(),
67 };
68 for p_idx in &b.pred {
69 if *p_idx >= b_idx {
70 needs_src.insert(*p_idx);
71 continue;
72 }
73 // The earlier recursive call ensured this exists
74 let p_ssa = *blocks[*p_idx].defs.borrow().get(&ssa).unwrap();
75 phi.srcs.insert(*p_idx, p_ssa);
76 }
77 b.phis.borrow_mut().push(phi);
78 b_defs.insert(ssa, phi_ssa);
79 phi_ssa
80 }
81 }
82
get_or_insert_phi_dsts(bb: &mut BasicBlock) -> &mut OpPhiDsts83 fn get_or_insert_phi_dsts(bb: &mut BasicBlock) -> &mut OpPhiDsts {
84 let has_phi = matches!(&bb.instrs[0].op, Op::PhiDsts(_));
85 if !has_phi {
86 bb.instrs.insert(0, Instr::new_boxed(OpPhiDsts::new()));
87 }
88 match &mut bb.instrs[0].op {
89 Op::PhiDsts(phi) => phi,
90 _ => panic!("Expected to find the phi we just inserted"),
91 }
92 }
93
get_or_insert_phi_srcs(bb: &mut BasicBlock) -> &mut OpPhiSrcs94 fn get_or_insert_phi_srcs(bb: &mut BasicBlock) -> &mut OpPhiSrcs {
95 let mut has_phi = false;
96 let mut ip = bb.instrs.len();
97 for (i, instr) in bb.instrs.iter_mut().enumerate().rev() {
98 match &mut instr.op {
99 Op::PhiSrcs(_) => {
100 ip = i;
101 has_phi = true;
102 break;
103 }
104 _ => {
105 if instr.is_branch() {
106 ip = i;
107 } else {
108 break;
109 }
110 }
111 }
112 }
113 if !has_phi {
114 bb.instrs.insert(ip, Instr::new_boxed(OpPhiSrcs::new()));
115 }
116 match &mut bb.instrs[ip].op {
117 Op::PhiSrcs(phi) => phi,
118 _ => panic!("Expected to find the phi we just inserted"),
119 }
120 }
121
122 impl Function {
123 /// Repairs SSA form
124 ///
125 /// Certain passes such as register spilling may produce a program that is
126 /// no longer in SSA form. This pass is able to repair SSA by inserting
127 /// phis as needed. Even though we do not require dominance or that each
128 /// value be defined once we do require that, for every use of an SSAValue
129 /// and for every path from the start of the program to that use, there must
130 /// be some definition of the value along that path.
131 ///
132 /// The algorithm implemented here is based on the one in "Simple and
133 /// Efficient Construction of Static Single Assignment Form" by Braun, et.
134 /// al. The primary difference between our implementation and the paper is
135 /// that we can't rewrite the IR on-the-fly. Instead, we store everything
136 /// in hash tables and handle removing redundant phis with back-edges as a
137 /// separate pass between figuring out where phis are needed and actually
138 /// constructing the phi instructions.
repair_ssa(&mut self)139 pub fn repair_ssa(&mut self) {
140 // First, count the number of defs for each SSA value. This will allow
141 // us to skip any SSA values which only have a single definition in
142 // later passes.
143 let mut has_mult_defs = false;
144 let mut num_defs = HashMap::new();
145 for b in &self.blocks {
146 for instr in &b.instrs {
147 instr.for_each_ssa_def(|ssa| {
148 num_defs
149 .entry(*ssa)
150 .and_modify(|e| {
151 has_mult_defs = true;
152 *e += 1;
153 })
154 .or_insert(1);
155 });
156 }
157 }
158
159 if !has_mult_defs {
160 return;
161 }
162
163 let cfg = &mut self.blocks;
164 let ssa_alloc = &mut self.ssa_alloc;
165 let phi_alloc = &mut self.phi_alloc;
166
167 let mut blocks = Vec::new();
168 let mut needs_src = BitSet::new();
169 for b_idx in 0..cfg.len() {
170 assert!(blocks.len() == b_idx);
171 blocks.push(DefTrackerBlock {
172 pred: cfg.pred_indices(b_idx).to_vec(),
173 succ: cfg.succ_indices(b_idx).to_vec(),
174 defs: RefCell::new(HashMap::new()),
175 phis: RefCell::new(Vec::new()),
176 });
177
178 for instr in &mut cfg[b_idx].instrs {
179 instr.for_each_ssa_use_mut(|ssa| {
180 if num_defs.get(ssa).cloned().unwrap_or(0) > 1 {
181 *ssa = get_ssa_or_phi(
182 ssa_alloc,
183 phi_alloc,
184 &blocks,
185 &mut needs_src,
186 b_idx,
187 *ssa,
188 );
189 }
190 });
191
192 instr.for_each_ssa_def_mut(|ssa| {
193 if num_defs.get(ssa).cloned().unwrap_or(0) > 1 {
194 let new_ssa = ssa_alloc.alloc(ssa.file());
195 blocks[b_idx].defs.borrow_mut().insert(*ssa, new_ssa);
196 *ssa = new_ssa;
197 }
198 });
199 }
200 }
201
202 // Populate phi sources for any back-edges
203 loop {
204 let Some(b_idx) = needs_src.next_set(0) else {
205 break;
206 };
207 needs_src.remove(b_idx);
208
209 for s_idx in &blocks[b_idx].succ {
210 if *s_idx <= b_idx {
211 let s = &blocks[*s_idx];
212
213 // We do a mutable borrow here. The algorithm is recursive
214 // and may insert phis into other blocks. However, because
215 // this is phi exists, its destination should be in the def
216 // set for s and so no new phis should need to be added.
217 // RefCell's dynamic borrow checks will assert this.
218 for phi in s.phis.borrow_mut().iter_mut() {
219 phi.srcs.entry(b_idx).or_insert_with(|| {
220 get_ssa_or_phi(
221 ssa_alloc,
222 phi_alloc,
223 &blocks,
224 &mut needs_src,
225 b_idx,
226 phi.orig,
227 )
228 });
229 }
230 }
231 }
232 }
233
234 // For loop back-edges, we inserted a phi whether we need one or not.
235 // We want to eliminate any redundant phis.
236 let mut ssa_map = HashMap::new();
237 if cfg.has_loop() {
238 let mut to_do = true;
239 while to_do {
240 to_do = false;
241 for b_idx in 0..cfg.len() {
242 let b = &blocks[b_idx];
243 b.phis.borrow_mut().retain_mut(|phi| {
244 let mut ssa = None;
245 for (_, p_ssa) in phi.srcs.iter_mut() {
246 // Apply the remap to the phi sources so that we
247 // pick up any remaps from previous loop iterations.
248 while let Some(new_ssa) = ssa_map.get(p_ssa) {
249 *p_ssa = *new_ssa;
250 }
251
252 if *p_ssa == phi.dst {
253 continue;
254 }
255 if *ssa.get_or_insert(*p_ssa) != *p_ssa {
256 // Multiple unique sources
257 return true;
258 }
259 }
260
261 // All sources are identical or the phi destination so
262 // we can delete this phi and add it to the remap
263 let ssa = ssa.expect("Circular SSA def");
264 ssa_map.insert(phi.dst, ssa);
265 to_do = true;
266 false
267 });
268 }
269 }
270 }
271
272 // Now we apply the remap to instruction sources and place the actual
273 // phis
274 for b_idx in 0..cfg.len() {
275 // Grab the successor index for inserting OpPhiSrc before we take a
276 // mutable reference to the CFG. There are no critical edges so we
277 // can only have an OpPhiSrc if there is a single successor.
278 let succ = cfg.succ_indices(b_idx);
279 let s_idx = if succ.len() == 1 {
280 Some(succ[0])
281 } else {
282 for s_idx in succ {
283 debug_assert!(blocks[*s_idx].phis.borrow().is_empty());
284 }
285 None
286 };
287
288 let bb = &mut cfg[b_idx];
289
290 // First we have phi destinations
291 let b_phis = blocks[b_idx].phis.borrow();
292 if !b_phis.is_empty() {
293 let phi_dst = get_or_insert_phi_dsts(bb);
294 for phi in b_phis.iter() {
295 phi_dst.dsts.push(phi.idx, phi.dst.into());
296 }
297 }
298
299 // Fix up any remapped SSA values in sources
300 if !ssa_map.is_empty() {
301 for instr in &mut bb.instrs {
302 instr.for_each_ssa_use_mut(|ssa| {
303 while let Some(new_ssa) = ssa_map.get(ssa) {
304 *ssa = *new_ssa;
305 }
306 });
307 }
308 }
309
310 if let Some(s_idx) = s_idx {
311 let s_phis = blocks[s_idx].phis.borrow();
312 if !s_phis.is_empty() {
313 let phi_src = get_or_insert_phi_srcs(bb);
314 for phi in s_phis.iter() {
315 let mut ssa = phi.srcs.get(&b_idx).unwrap();
316 while let Some(new_ssa) = ssa_map.get(ssa) {
317 ssa = new_ssa;
318 }
319 phi_src.srcs.push(phi.idx, (*ssa).into());
320 }
321 }
322 }
323 }
324 }
325 }
326