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