1 //! A Constant-Phi-Node removal pass.
2 
3 use crate::dominator_tree::DominatorTree;
4 use crate::fx::FxHashMap;
5 use crate::fx::FxHashSet;
6 use crate::ir;
7 use crate::ir::Function;
8 use crate::ir::{Block, BlockCall, Inst, Value};
9 use crate::timing;
10 use arrayvec::ArrayVec;
11 use bumpalo::Bump;
12 use cranelift_entity::SecondaryMap;
13 use smallvec::SmallVec;
14 
15 // A note on notation.  For the sake of clarity, this file uses the phrase
16 // "formal parameters" to mean the `Value`s listed in the block head, and
17 // "actual parameters" to mean the `Value`s passed in a branch or a jump:
18 //
19 // block4(v16: i32, v18: i32):            <-- formal parameters
20 //   ...
21 //   brif v27, block7(v22, v24), block6   <-- actual parameters
22 
23 // This transformation pass (conceptually) partitions all values in the
24 // function into two groups:
25 //
26 // * Group A: values defined by block formal parameters, except for the entry block.
27 //
28 // * Group B: All other values: that is, values defined by instructions,
29 //   and the formals of the entry block.
30 //
31 // For each value in Group A, it attempts to establish whether it will have
32 // the value of exactly one member of Group B.  If so, the formal parameter is
33 // deleted, all corresponding actual parameters (in jumps/branches to the
34 // defining block) are deleted, and a rename is inserted.
35 //
36 // The entry block is special-cased because (1) we don't know what values flow
37 // to its formals and (2) in any case we can't change its formals.
38 //
39 // Work proceeds in three phases.
40 //
41 // * Phase 1: examine all instructions.  For each block, make up a useful
42 //   grab-bag of information, `BlockSummary`, that summarises the block's
43 //   formals and jump/branch instruction.  This is used by Phases 2 and 3.
44 //
45 // * Phase 2: for each value in Group A, try to find a single Group B value
46 //   that flows to it.  This is done using a classical iterative forward
47 //   dataflow analysis over a simple constant-propagation style lattice.  It
48 //   converges quickly in practice -- I have seen at most 4 iterations.  This
49 //   is relatively cheap because the iteration is done over the
50 //   `BlockSummary`s, and does not visit each instruction.  The resulting
51 //   fixed point is stored in a `SolverState`.
52 //
53 // * Phase 3: using the `SolverState` and `BlockSummary`, edit the function to
54 //   remove redundant formals and actuals, and to insert suitable renames.
55 //
56 // Note that the effectiveness of the analysis depends on on the fact that
57 // there are no copy instructions in Cranelift's IR.  If there were, the
58 // computation of `actual_absval` in Phase 2 would have to be extended to
59 // chase through such copies.
60 //
61 // For large functions, the analysis cost using the new AArch64 backend is about
62 // 0.6% of the non-optimising compile time, as measured by instruction counts.
63 // This transformation usually pays for itself several times over, though, by
64 // reducing the isel/regalloc cost downstream.  Gains of up to 7% have been
65 // seen for large functions.
66 
67 /// The `Value`s (Group B) that can flow to a formal parameter (Group A).
68 #[derive(Clone, Copy, Debug, PartialEq)]
69 enum AbstractValue {
70     /// Two or more values flow to this formal.
71     Many,
72 
73     /// Exactly one value, as stated, flows to this formal.  The `Value`s that
74     /// can appear here are exactly: `Value`s defined by `Inst`s, plus the
75     /// `Value`s defined by the formals of the entry block.  Note that this is
76     /// exactly the set of `Value`s that are *not* tracked in the solver below
77     /// (see `SolverState`).
78     One(Value /*Group B*/),
79 
80     /// No value flows to this formal.
81     None,
82 }
83 
84 impl AbstractValue {
85     fn join(self, other: AbstractValue) -> AbstractValue {
86         match (self, other) {
87             // Joining with `None` has no effect
88             (AbstractValue::None, p2) => p2,
89             (p1, AbstractValue::None) => p1,
90             // Joining with `Many` produces `Many`
91             (AbstractValue::Many, _p2) => AbstractValue::Many,
92             (_p1, AbstractValue::Many) => AbstractValue::Many,
93             // The only interesting case
94             (AbstractValue::One(v1), AbstractValue::One(v2)) => {
95                 if v1 == v2 {
96                     AbstractValue::One(v1)
97                 } else {
98                     AbstractValue::Many
99                 }
100             }
101         }
102     }
103 
104     fn is_one(self) -> bool {
105         matches!(self, AbstractValue::One(_))
106     }
107 }
108 
109 #[derive(Clone, Copy, Debug)]
110 struct OutEdge<'a> {
111     /// An instruction that transfers control.
112     inst: Inst,
113     /// The index into branch_destinations for this instruction that corresponds
114     /// to this edge.
115     branch_index: u32,
116     /// The block that control is transferred to.
117     block: Block,
118     /// The arguments to that block.
119     ///
120     /// These values can be from both groups A and B.
121     args: &'a [Value],
122 }
123 
124 impl<'a> OutEdge<'a> {
125     /// Construct a new `OutEdge` for the given instruction.
126     ///
127     /// Returns `None` if this is an edge without any block arguments, which
128     /// means we can ignore it for this analysis's purposes.
129     #[inline]
130     fn new(
131         bump: &'a Bump,
132         dfg: &ir::DataFlowGraph,
133         inst: Inst,
134         branch_index: usize,
135         block: BlockCall,
136     ) -> Option<Self> {
137         let inst_var_args = block.args_slice(&dfg.value_lists);
138 
139         // Skip edges without params.
140         if inst_var_args.is_empty() {
141             return None;
142         }
143 
144         Some(OutEdge {
145             inst,
146             branch_index: branch_index as u32,
147             block: block.block(&dfg.value_lists),
148             args: bump.alloc_slice_fill_iter(
149                 inst_var_args
150                     .iter()
151                     .map(|value| dfg.resolve_aliases(*value)),
152             ),
153         })
154     }
155 }
156 
157 /// For some block, a useful bundle of info.  The `Block` itself is not stored
158 /// here since it will be the key in the associated `FxHashMap` -- see
159 /// `summaries` below.  For the `SmallVec` tuning params: most blocks have
160 /// few parameters, hence `4`.  And almost all blocks have either one or two
161 /// successors, hence `2`.
162 #[derive(Clone, Debug, Default)]
163 struct BlockSummary<'a> {
164     /// Formal parameters for this `Block`.
165     ///
166     /// These values are from group A.
167     formals: &'a [Value],
168 
169     /// Each outgoing edge from this block.
170     ///
171     /// We don't bother to include transfers that pass zero parameters
172     /// since that makes more work for the solver for no purpose.
173     ///
174     /// Note that, because blocks used with `br_table`s cannot have block
175     /// arguments, there are at most two outgoing edges from these blocks.
176     dests: ArrayVec<OutEdge<'a>, 2>,
177 }
178 
179 impl<'a> BlockSummary<'a> {
180     /// Construct a new `BlockSummary`, using `values` as its backing storage.
181     #[inline]
182     fn new(bump: &'a Bump, formals: &[Value]) -> Self {
183         Self {
184             formals: bump.alloc_slice_copy(formals),
185             dests: Default::default(),
186         }
187     }
188 }
189 
190 /// Solver state.  This holds a AbstractValue for each formal parameter, except
191 /// for those from the entry block.
192 struct SolverState {
193     absvals: FxHashMap<Value /*Group A*/, AbstractValue>,
194 }
195 
196 impl SolverState {
197     fn new() -> Self {
198         Self {
199             absvals: FxHashMap::default(),
200         }
201     }
202 
203     fn get(&self, actual: Value) -> AbstractValue {
204         *self
205             .absvals
206             .get(&actual)
207             .unwrap_or_else(|| panic!("SolverState::get: formal param {:?} is untracked?!", actual))
208     }
209 
210     fn maybe_get(&self, actual: Value) -> Option<&AbstractValue> {
211         self.absvals.get(&actual)
212     }
213 
214     fn set(&mut self, actual: Value, lp: AbstractValue) {
215         match self.absvals.insert(actual, lp) {
216             Some(_old_lp) => {}
217             None => panic!("SolverState::set: formal param {:?} is untracked?!", actual),
218         }
219     }
220 }
221 
222 /// Detect phis in `func` that will only ever produce one value, using a
223 /// classic forward dataflow analysis.  Then remove them.
224 #[inline(never)]
225 pub fn do_remove_constant_phis(func: &mut Function, domtree: &mut DominatorTree) {
226     let _tt = timing::remove_constant_phis();
227     debug_assert!(domtree.is_valid());
228 
229     // Phase 1 of 3: for each block, make a summary containing all relevant
230     // info.  The solver will iterate over the summaries, rather than having
231     // to inspect each instruction in each block.
232     let bump =
233         Bump::with_capacity(domtree.cfg_postorder().len() * 4 * std::mem::size_of::<Value>());
234     let mut summaries =
235         SecondaryMap::<Block, BlockSummary>::with_capacity(domtree.cfg_postorder().len());
236 
237     for b in domtree.cfg_postorder().iter().rev().copied() {
238         let formals = func.dfg.block_params(b);
239         let mut summary = BlockSummary::new(&bump, formals);
240 
241         for inst in func.layout.block_insts(b) {
242             for (ix, dest) in func.dfg.insts[inst].branch_destination().iter().enumerate() {
243                 if let Some(edge) = OutEdge::new(&bump, &func.dfg, inst, ix, *dest) {
244                     summary.dests.push(edge);
245                 }
246             }
247         }
248 
249         // Ensure the invariant that all blocks (except for the entry) appear
250         // in the summary, *unless* they have neither formals nor any
251         // param-carrying branches/jumps.
252         if formals.len() > 0 || summary.dests.len() > 0 {
253             summaries[b] = summary;
254         }
255     }
256 
257     // Phase 2 of 3: iterate over the summaries in reverse postorder,
258     // computing new `AbstractValue`s for each tracked `Value`.  The set of
259     // tracked `Value`s is exactly Group A as described above.
260 
261     let entry_block = func
262         .layout
263         .entry_block()
264         .expect("remove_constant_phis: entry block unknown");
265 
266     // Set up initial solver state
267     let mut state = SolverState::new();
268 
269     for b in domtree.cfg_postorder().iter().rev().copied() {
270         // For each block, get the formals
271         if b == entry_block {
272             continue;
273         }
274         let formals = func.dfg.block_params(b);
275         for formal in formals {
276             let mb_old_absval = state.absvals.insert(*formal, AbstractValue::None);
277             assert!(mb_old_absval.is_none());
278         }
279     }
280 
281     // Solve: repeatedly traverse the blocks in reverse postorder, until there
282     // are no changes.
283     let mut iter_no = 0;
284     loop {
285         iter_no += 1;
286         let mut changed = false;
287 
288         for src in domtree.cfg_postorder().iter().rev().copied() {
289             let src_summary = &summaries[src];
290             for edge in &src_summary.dests {
291                 assert!(edge.block != entry_block);
292                 // By contrast, the dst block must have a summary.  Phase 1
293                 // will have only included an entry in `src_summary.dests` if
294                 // that branch/jump carried at least one parameter.  So the
295                 // dst block does take parameters, so it must have a summary.
296                 let dst_summary = &summaries[edge.block];
297                 let dst_formals = &dst_summary.formals;
298                 assert_eq!(edge.args.len(), dst_formals.len());
299                 for (formal, actual) in dst_formals.iter().zip(edge.args) {
300                     // Find the abstract value for `actual`.  If it is a block
301                     // formal parameter then the most recent abstract value is
302                     // to be found in the solver state.  If not, then it's a
303                     // real value defining point (not a phi), in which case
304                     // return it itself.
305                     let actual_absval = match state.maybe_get(*actual) {
306                         Some(pt) => *pt,
307                         None => AbstractValue::One(*actual),
308                     };
309 
310                     // And `join` the new value with the old.
311                     let formal_absval_old = state.get(*formal);
312                     let formal_absval_new = formal_absval_old.join(actual_absval);
313                     if formal_absval_new != formal_absval_old {
314                         changed = true;
315                         state.set(*formal, formal_absval_new);
316                     }
317                 }
318             }
319         }
320 
321         if !changed {
322             break;
323         }
324     }
325 
326     let mut n_consts = 0;
327     for absval in state.absvals.values() {
328         if absval.is_one() {
329             n_consts += 1;
330         }
331     }
332 
333     // Phase 3 of 3: edit the function to remove constant formals, using the
334     // summaries and the final solver state as a guide.
335 
336     // Make up a set of blocks that need editing.
337     let mut need_editing = FxHashSet::<Block>::default();
338     for (block, summary) in summaries.iter() {
339         if block == entry_block {
340             continue;
341         }
342         for formal in summary.formals {
343             let formal_absval = state.get(*formal);
344             if formal_absval.is_one() {
345                 need_editing.insert(block);
346                 break;
347             }
348         }
349     }
350 
351     // Firstly, deal with the formals.  For each formal which is redundant,
352     // remove it, and also add a reroute from it to the constant value which
353     // it we know it to be.
354     for b in &need_editing {
355         let mut del_these = SmallVec::<[(Value, Value); 32]>::new();
356         let formals: &[Value] = func.dfg.block_params(*b);
357         for formal in formals {
358             // The state must give an absval for `formal`.
359             if let AbstractValue::One(replacement_val) = state.get(*formal) {
360                 del_these.push((*formal, replacement_val));
361             }
362         }
363         // We can delete the formals in any order.  However,
364         // `remove_block_param` works by sliding backwards all arguments to
365         // the right of the value it is asked to delete.  Hence when removing more
366         // than one formal, it is significantly more efficient to ask it to
367         // remove the rightmost formal first, and hence this `rev()`.
368         for (redundant_formal, replacement_val) in del_these.into_iter().rev() {
369             func.dfg.remove_block_param(redundant_formal);
370             func.dfg.change_to_alias(redundant_formal, replacement_val);
371         }
372     }
373 
374     // Secondly, visit all branch insns.  If the destination has had its
375     // formals changed, change the actuals accordingly.  Don't scan all insns,
376     // rather just visit those as listed in the summaries we prepared earlier.
377     let mut old_actuals = alloc::vec::Vec::new();
378     for summary in summaries.values() {
379         for edge in &summary.dests {
380             if !need_editing.contains(&edge.block) {
381                 continue;
382             }
383 
384             let dfg = &mut func.dfg;
385             let block =
386                 &mut dfg.insts[edge.inst].branch_destination_mut()[edge.branch_index as usize];
387 
388             old_actuals.extend(block.args_slice(&dfg.value_lists));
389 
390             // Check that the numbers of arguments make sense.
391             let formals = &summaries[edge.block].formals;
392             assert_eq!(formals.len(), old_actuals.len());
393 
394             // Filter out redundant block arguments.
395             let mut formals = formals.iter();
396             old_actuals.retain(|_| {
397                 let formal_i = formals.next().unwrap();
398                 !state.get(*formal_i).is_one()
399             });
400 
401             // Replace the block with a new one that only includes the non-redundant arguments.
402             // This leaks the value list from the old block,
403             // https://github.com/bytecodealliance/wasmtime/issues/5451 for more information.
404             let destination = block.block(&dfg.value_lists);
405             *block = BlockCall::new(destination, &old_actuals, &mut dfg.value_lists);
406             old_actuals.clear();
407         }
408     }
409 
410     log::debug!(
411         "do_remove_constant_phis: done, {} iters.   {} formals, of which {} const.",
412         iter_no,
413         state.absvals.len(),
414         n_consts
415     );
416 }
417