Memoize can_optimize_var_lookup (#4924)

* Memoize `can_optimize_var_lookup`

`can_optimize_var_lookup` can have quadratic behavior if there is a chain
of blocks each containing a `local.get` instruction because each run can
walk up the entire chain. This change memoizes the results of
`can_optimize_var_lookup` so that we can stop following the chain of
predecessors when we hit a block that has previously been handled
(making the operation linear again).
This commit is contained in:
Adam Bratschi-Kaye
2022-09-19 19:18:11 +02:00
committed by GitHub
parent b8fa068ca8
commit 562bb25360

View File

@@ -21,7 +21,6 @@ use cranelift_codegen::ir::{
}; };
use cranelift_codegen::packed_option::PackedOption; use cranelift_codegen::packed_option::PackedOption;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::collections::HashSet;
/// Structure containing the data relevant the construction of SSA for a given function. /// Structure containing the data relevant the construction of SSA for a given function.
/// ///
@@ -56,7 +55,12 @@ pub struct SSABuilder {
/// Reused allocation for blocks we've already visited in the /// Reused allocation for blocks we've already visited in the
/// `can_optimize_var_lookup` method. /// `can_optimize_var_lookup` method.
visited: HashSet<Block>, visited: SecondaryMap<Block, bool>,
/// If a block B has chain up single predecessors leading to a block B' in
/// this map, then the value in the map indicates whether variable lookups
/// can be optimized in block B.
successors_can_optimize_var_lookup: SecondaryMap<Block, Option<bool>>,
} }
/// Side effects of a `use_var` or a `seal_block` method call. /// Side effects of a `use_var` or a `seal_block` method call.
@@ -134,6 +138,7 @@ impl SSABuilder {
results: Vec::new(), results: Vec::new(),
side_effects: SideEffects::new(), side_effects: SideEffects::new(),
visited: Default::default(), visited: Default::default(),
successors_can_optimize_var_lookup: Default::default(),
} }
} }
@@ -142,9 +147,11 @@ impl SSABuilder {
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.variables.clear(); self.variables.clear();
self.ssa_blocks.clear(); self.ssa_blocks.clear();
self.successors_can_optimize_var_lookup.clear();
debug_assert!(self.calls.is_empty()); debug_assert!(self.calls.is_empty());
debug_assert!(self.results.is_empty()); debug_assert!(self.results.is_empty());
debug_assert!(self.side_effects.is_empty()); debug_assert!(self.side_effects.is_empty());
debug_assert!(self.successors_can_optimize_var_lookup.is_empty());
} }
/// Tests whether an `SSABuilder` is in a cleared state. /// Tests whether an `SSABuilder` is in a cleared state.
@@ -154,6 +161,7 @@ impl SSABuilder {
&& self.calls.is_empty() && self.calls.is_empty()
&& self.results.is_empty() && self.results.is_empty()
&& self.side_effects.is_empty() && self.side_effects.is_empty()
&& self.successors_can_optimize_var_lookup.is_empty()
} }
} }
@@ -291,31 +299,43 @@ impl SSABuilder {
// Check that the initial block only has one predecessor. This is only a requirement // Check that the initial block only has one predecessor. This is only a requirement
// for the first block. // for the first block.
if self.predecessors(block).len() != 1 { if self.predecessors(block).len() != 1 {
// Skip insertion into `successors_can_optimize_var_lookup` because
// the condition is different for the first block.
return false; return false;
} }
self.visited.clear(); self.visited.clear();
let mut current = block; let mut current = block;
loop { loop {
if let Some(can_optimize) = self.successors_can_optimize_var_lookup[current] {
self.successors_can_optimize_var_lookup[block] = Some(can_optimize);
return can_optimize;
}
let predecessors = self.predecessors(current); let predecessors = self.predecessors(current);
// We haven't found the original block and we have either reached the entry // We haven't found the original block and we have either reached the entry
// block, or we found the end of this line of dead blocks, either way we are // block, or we found the end of this line of dead blocks, either way we are
// safe to optimize this line of lookups. // safe to optimize this line of lookups.
if predecessors.len() == 0 { if predecessors.len() == 0 {
self.successors_can_optimize_var_lookup[block] = Some(true);
return true; return true;
} }
// We can stop the search here, the algorithm can handle these cases, even if they are // We can stop the search here, the algorithm can handle these cases, even if they are
// in an undefined island. // in an undefined island.
if predecessors.len() > 1 { if predecessors.len() > 1 {
self.successors_can_optimize_var_lookup[block] = Some(true);
return true; return true;
} }
let next_current = predecessors[0].block; let next_current = predecessors[0].block;
if !self.visited.insert(current) { if self.visited[current] {
self.successors_can_optimize_var_lookup[block] = Some(false);
return false; return false;
} }
self.visited[current] = true;
current = next_current; current = next_current;
} }
} }