• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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