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