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 smallvec::SmallVec;
use std::collections::HashSet;
/// 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
/// `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.
@@ -134,6 +138,7 @@ impl SSABuilder {
results: Vec::new(),
side_effects: SideEffects::new(),
visited: Default::default(),
successors_can_optimize_var_lookup: Default::default(),
}
}
@@ -142,9 +147,11 @@ impl SSABuilder {
pub fn clear(&mut self) {
self.variables.clear();
self.ssa_blocks.clear();
self.successors_can_optimize_var_lookup.clear();
debug_assert!(self.calls.is_empty());
debug_assert!(self.results.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.
@@ -154,6 +161,7 @@ impl SSABuilder {
&& self.calls.is_empty()
&& self.results.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
// for the first block.
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;
}
self.visited.clear();
let mut current = block;
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);
// 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
// safe to optimize this line of lookups.
if predecessors.len() == 0 {
self.successors_can_optimize_var_lookup[block] = Some(true);
return true;
}
// We can stop the search here, the algorithm can handle these cases, even if they are
// in an undefined island.
if predecessors.len() > 1 {
self.successors_can_optimize_var_lookup[block] = Some(true);
return true;
}
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;
}
self.visited[current] = true;
current = next_current;
}
}