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