@@ -5851,28 +5851,33 @@ impl Function {
58515851 }
58525852 }
58535853
5854-
5855- // TODO: Add a comment about the paper and where this function comes from, as well as how it's used in ZJIT
5856- // (It's used more individually and differently than the paper)
5857- // TODO: Update comments to consider block params rather than phi nodes and demarcate differences from the algorithm clearly
5858- // TODO: Fix input arguments. We need block params, not just the phi
5859- // If all possible phi values are the same, replace the phi with the value
5860- // TODO: Add Max's optimization about not checking the first block. Maybe do this by keeping track of all changing edges and using a worklist?
5861-
5862- /// Sometimes block params can only come from one place and safely removed as block params.
5863- /// Trivial block param removal increases the efficacy of CFG-based optimization passes.
5864- /// This function implements algorithm 2 from <https://c9x.me/compile/bib/braun13cc.pdf>.
5865- /// Light modifications are made to use block params instead of phis.
5854+ /// ZJIT uses block parameters in HIR SSA representation.
5855+ /// Sometimes, we can prove that a block param is only called with a single value.
5856+ /// This pass identifies such trivial block params and replaces them with the concretized value.
5857+ /// This produces a minimal SSA representation amenable to further optimizations.
5858+ /// The implementation is inspired from algorithm 2 in <https://c9x.me/compile/bib/braun13cc.pdf>.
58665859 fn remove_trivial_block_params(&mut self) {
5867- // Each block param corresponds to a ParamValue
5868- // This is the abstract domain used for abstract interpretation
5869- // If there are no predecessors or multiple predecessors to a param, no optimization can happen
5870- // However if there is a single unique predecessor, then the block param is trivial and we can replace with its concretized value
5871- #[derive(Clone)]
5860+ // Each block param is lifted to an abstract domain of ParamValues.
5861+ // The lattice is simple. None is Bottom, Multiple is Top, and One is between both.
5862+ // During analysis, all block params start with None.
5863+ // New values passed to the block transition up the lattice.
5864+ // Trivial block params have one unique value. This is the case we optimize away.
5865+ // Lattice structure taken from cranelift: <https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/codegen/src/remove_constant_phis.rs>
5866+ #[derive(Clone, Copy)]
58725867 enum ParamValue {
58735868 None,
58745869 One(InsnId),
5875- Multiple
5870+ Many
5871+ }
5872+
5873+ impl ParamValue {
5874+ fn update(&mut self, value: InsnId) {
5875+ *self = match *self {
5876+ ParamValue::None => ParamValue::One(value),
5877+ ParamValue::One(original) if original != value => ParamValue::Many,
5878+ other => other
5879+ };
5880+ }
58765881 }
58775882
58785883 // Helper function to remove selected indices from a vec in place
@@ -5892,61 +5897,59 @@ impl Function {
58925897 BranchEdge { target: edge.target, args }
58935898 }
58945899
5895- fn insn_passes_params(insn: Insn) -> bool {
5896- match insn {
5897- Insn::CondBranch {if_true, if_false, ..} => !if_true.args.is_empty() || !if_false.args.is_empty(),
5898- Insn::Jump(edge) => !edge.args.is_empty(),
5899- _ => false
5900- }
5901- }
5902-
5903- // Instantiate the domain for abstract interpretation
5904- // Outer index is block
5905- // Inner index is param index
5906- // Value is the state of the param. This is used to determine whether block param optimization can occur.
5900+ // Instantiate the domain for abstract interpretation.
5901+ // We store possible param values for each block
59075902 let mut predecessor_domain: Vec<Vec<ParamValue>> = vec![Vec::new(); self.blocks.len()];
59085903
5909- let mut updated = true ;
5904+ let blocks = self.reverse_post_order() ;
59105905
59115906 // Find blocks that terminate with Jump or CondBranch instructions that pass block params along.
59125907 // These terminators are later analyzed for trivial block params.
5913- let param_passing_blocks: Vec<BlockId> = self.reverse_post_order().into_iter()
5908+ let predecessor_blocks: Vec<BlockId> = blocks.iter().copied()
5909+ .filter(|&block_id|
5910+ // Match against the final instruction which terminates the basic block.
5911+ // If it has any non-empty edges, keep it.
5912+ match self.find(*self.blocks[block_id.0].insns().last().unwrap()) {
5913+ Insn::CondBranch {if_true, if_false, ..} => !if_true.args.is_empty() || !if_false.args.is_empty(),
5914+ Insn::Jump(edge) => !edge.args.is_empty(),
5915+ _ => false
5916+ })
5917+ .collect();
5918+
5919+ // We only need to update blocks that have params. (Blocks without params cannot be improved)
5920+ let param_blocks: Vec<BlockId> = blocks.into_iter()
59145921 .filter(|&block_id|
5915- insn_passes_params( self.find(*self. blocks[block_id.0].insns.last ().unwrap())) )
5922+ self.blocks[block_id.0].params ().len() != 0 )
59165923 .collect();
59175924
5918- while updated {
5925+ // NOTE: It is possible that once some block_params are removed, there will be no params.
5926+ // This means that predecessor_blocks or param_blocks could be pruned. This minor optimization can be added if desired.
5927+ // Importantly, we do not keep track of exactly which edges correspond to which blocks. While doing so
5928+ // would allow us to replace our "loop until fixpoint" with a "iterate through the worklist, only checking relevant edges",
5929+ // the construction of the mapping from predecessor edges to blocks seems expensive.
5930+
5931+ let mut changed = true;
5932+
5933+ while changed {
5934+ changed = false;
59195935
59205936 for (row, block) in predecessor_domain.iter_mut().zip(&self.blocks) {
59215937 row.resize(block.params.len(), ParamValue::None);
59225938 }
5923- updated = false;
59245939
5925- // TODO: Maybe move this outside the loop somehow? probably can't immediately, but we could keep track of a worklist of edges that change maybe?
5926- // And only use the changed ones like a worklist? And then instead of looping to fixpoint we use a worklist based approach?
5927- //
59285940 // Scan through each jump, collecting edges with params to analyze from CondBranch and Jump insns.
5929- for block_id in ¶m_passing_blocks {
5930- let insn_index = self.blocks[block_id.0].insns.len() - 1;
5931- let insn_id = self.blocks[block_id.0].insns[insn_index];
5932- let mut edges: Vec<BranchEdge> = vec![];
5941+ for block_id in &predecessor_blocks {
5942+ let insn_id = *self.blocks[block_id.0].insns.last().unwrap();
5943+
5944+ // Extract edges into a tuple for processing
5945+ let (first, second) = match self.find(insn_id) {
5946+ Insn::Jump(edge) => (Some(edge), None),
5947+ Insn::CondBranch { if_true, if_false, ..} => (Some(if_true), Some(if_false)),
5948+ _ => (None, None)
5949+ };
59335950
5934- match self.find(insn_id) {
5935- Insn::CondBranch { if_true, if_false, .. } => {
5936- if if_true.args.len() > 0 {
5937- edges.push(if_true);
5938- }
5939- if if_false.args.len() > 0 {
5940- edges.push(if_false);
5941- }
5942- }
5943- Insn::Jump(edge) => {
5944- if edge.args.len() > 0 {
5945- edges.push(edge);
5946- }
5947- }
5948- _ => {}
5949- }
5951+ // Keep all edges that pass params
5952+ let edges = first.into_iter().chain(second).filter(|edge| edge.args.len() > 0);
59505953
59515954 // Use the results of abstract interpretation to update the states
59525955 // Perform abstract interpretation
@@ -5957,18 +5960,7 @@ impl Function {
59575960 if param == self.find_id(self.blocks[block_id.0].params[i]) {
59585961 continue
59595962 }
5960- let state = &mut predecessor_domain[block_id.0][i];
5961- match *state {
5962- ParamValue::None => {
5963- *state = ParamValue::One(param);
5964- },
5965- ParamValue::One(value) => {
5966- if value != param {
5967- *state = ParamValue::Multiple;
5968- }
5969- }
5970- ParamValue::Multiple => {},
5971- }
5963+ predecessor_domain[block_id.0][i].update(param);
59725964 }
59735965 }
59745966 }
@@ -5978,18 +5970,8 @@ impl Function {
59785970 // 1. Replace uses of the trivial params with the concretized value
59795971 // 2. Remove trivial params from the basic block definition
59805972 // 3. Remove trivial params from each CondBranch and Jump that targets the basic block that was just updated
5981- for (block_id, block_preds) in predecessor_domain.iter().enumerate() {
5982- // If there are no block params, there is nothing to optimize
5983- // TODO: We scan predecessors and only keep track of blocks that pass params.
5984- // We don't do this for block_preds but we should. This requires it kind of becoming hash-mappy again :|
5985- // This conditional is a stop-gap to get most of the gains from such an optimization, though it should be removed
5986- // Maybe we can get around this easier by not iterating over the predecessor domain, but over a subset of indices we care about
5987- if block_preds.len() == 0 {
5988- continue
5989- }
5990-
5991- let block_id = BlockId(block_id);
5992-
5973+ for block_id in ¶m_blocks {
5974+ let block_preds = &predecessor_domain[block_id.0];
59935975 let trivial_indices: Vec<usize> = block_preds.iter().enumerate()
59945976 .filter_map(|(idx, state)|
59955977 matches!(state, ParamValue::One(_)).then_some(idx)
@@ -5999,31 +5981,31 @@ impl Function {
59995981 for param_index in &trivial_indices {
60005982 if let ParamValue::One(insn_id) = block_preds[*param_index] {
60015983 self.make_equal_to(self.blocks[block_id.0].params[*param_index], insn_id);
6002- updated = true;
5984+ changed = true;
60035985 }
60045986 }
60055987
60065988 // Update the block
60075989 prune_vec_by_indices(&mut self.blocks[block_id.0].params, &trivial_indices);
60085990
60095991 // Update the terminators (basic blocks can only branch at the terminator. This is where block params are passed)
6010- for jump_block_id in ¶m_passing_blocks {
5992+ for jump_block_id in &predecessor_blocks {
60115993 let index = self.blocks[jump_block_id.0].insns.len() - 1;
60125994 let cond_insn_id = self.blocks[jump_block_id.0].insns[index];
60135995 match self.find(cond_insn_id) {
60145996 Insn::Jump(edge) => {
6015- if edge.target == block_id {
5997+ if edge.target == * block_id {
60165998 let edge = prune_branch_edge(edge, &trivial_indices);
60175999 self.insns[cond_insn_id.0] = Insn::Jump(edge);
60186000 }
60196001 }
60206002 Insn::CondBranch { val, if_true, if_false } => {
6021- let if_true = if if_true.target == block_id {
6003+ let if_true = if if_true.target == * block_id {
60226004 prune_branch_edge(if_true, &trivial_indices)
60236005 } else {
60246006 if_true
60256007 };
6026- let if_false = if if_false.target == block_id {
6008+ let if_false = if if_false.target == * block_id {
60276009 prune_branch_edge(if_false, &trivial_indices)
60286010 } else {
60296011 if_false
0 commit comments