Convert use_var and predecessors_lookup into a state machine to avoid recursion.

This commit is contained in:
Dan Gohman
2017-09-20 21:35:34 -07:00
parent 9cda4eacde
commit 22b769b716

View File

@@ -43,6 +43,12 @@ where
blocks: PrimaryMap<Block, BlockData<Variable>>,
// Records the basic blocks at the beginning of the `Ebb`s.
ebb_headers: EntityMap<Ebb, PackedOption<Block>>,
// Call and result stacks for use in the `use_var`/`predecessors_lookup` state machine.
calls: Vec<Call>,
results: Vec<Value>,
// Side effects accumulated in the `use_var`/`predecessors_lookup` state machine.
side_effects: SideEffects,
}
/// Side effects of a `use_var` or a `seal_ebb_header_block` method call.
@@ -65,11 +71,8 @@ impl SideEffects {
}
}
fn append(&mut self, mut more: SideEffects) {
self.split_ebbs_created.append(&mut more.split_ebbs_created);
self.instructions_added_to_ebbs.append(
&mut more.instructions_added_to_ebbs,
);
fn is_empty(&self) -> bool {
self.split_ebbs_created.is_empty() && self.instructions_added_to_ebbs.is_empty()
}
}
@@ -149,6 +152,9 @@ where
variables: EntityMap::with_default(EntityMap::new()),
blocks: PrimaryMap::new(),
ebb_headers: EntityMap::new(),
calls: Vec::new(),
results: Vec::new(),
side_effects: SideEffects::new(),
}
}
@@ -158,6 +164,9 @@ where
self.variables.clear();
self.blocks.clear();
self.ebb_headers.clear();
debug_assert!(self.calls.is_empty());
debug_assert!(self.results.is_empty());
debug_assert!(self.side_effects.is_empty());
}
}
@@ -176,6 +185,13 @@ enum UseVarCases {
SealedMultiplePredecessors(Value, Ebb),
}
// States for the `use_var`/`predecessors_lookup` state machine.
enum Call {
UseVar(Block),
FinishSealedOnePredecessor(Block),
FinishPredecessorsLookup(Value, Ebb),
}
/// The following methods are the API of the SSA builder. Here is how it should be used when
/// translating to Cretonne IL:
///
@@ -230,18 +246,19 @@ where
}
// Otherwise, we have to do a non-local lookup.
self.use_var_nonlocal(func, var, ty, block)
debug_assert!(self.calls.is_empty());
debug_assert!(self.results.is_empty());
debug_assert!(self.side_effects.is_empty());
self.use_var_nonlocal(func, var, ty, block);
(
self.run_state_machine(func, var, ty),
mem::replace(&mut self.side_effects, SideEffects::new()),
)
}
// The non-local case of use_var. Query each predecessor for a value and add branch
// arguments as needed to satisfy the use.
fn use_var_nonlocal(
&mut self,
func: &mut Function,
var: Variable,
ty: Type,
block: Block,
) -> (Value, SideEffects) {
/// Resolve a use of `var` in `block` in the case where there's no prior def
/// in `block`.
fn use_var_nonlocal(&mut self, func: &mut Function, var: Variable, ty: Type, block: Block) {
let case = match self.blocks[block] {
BlockData::EbbHeader(ref mut data) => {
// The block has multiple predecessors so we append an Ebb argument that
@@ -262,29 +279,34 @@ where
}
BlockData::EbbBody { predecessor: pred } => UseVarCases::SealedOnePredecessor(pred),
};
// TODO: avoid recursion for the calls to use_var and predecessors_lookup.
match case {
// The block has a single predecessor, we look into it.
UseVarCases::SealedOnePredecessor(pred) => {
let (val, mids) = self.use_var(func, var, ty, pred);
self.def_var(var, val, block);
(val, mids)
self.calls.push(Call::FinishSealedOnePredecessor(block));
self.calls.push(Call::UseVar(pred));
}
// The block has multiple predecessors, we register the ebb argument as the current
// definition for the variable.
UseVarCases::Unsealed(val) => {
self.def_var(var, val, block);
(val, SideEffects::new())
self.results.push(val);
}
UseVarCases::SealedMultiplePredecessors(val, ebb) => {
// If multiple predecessor we look up a use_var in each of them:
// if they all yield the same value no need for an Ebb argument
self.def_var(var, val, block);
self.predecessors_lookup(func, val, var, ty, ebb)
self.begin_predecessors_lookup(val, ebb);
}
}
}
/// For blocks with a single predecessor, once we've determined the value,
/// record a local def for it for future queries to find.
fn finish_sealed_one_predecessor(&mut self, var: Variable, block: Block) {
let val = *self.results.last().unwrap();
self.def_var(var, val, block);
}
/// Declares a new basic block belonging to the body of a certain `Ebb` and having `pred`
/// as a predecessor. `pred` is the only predecessor of the block and the block is sealed
/// at creation.
@@ -363,16 +385,14 @@ where
}
};
let mut side_effects = SideEffects::new();
// For each undef var we look up values in the predecessors and create an Ebb argument
// only if necessary.
for (var, val) in undef_vars {
let ty = func.dfg.value_type(val);
let (_, local_side_effects) = self.predecessors_lookup(func, val, var, ty, ebb);
side_effects.append(local_side_effects);
self.predecessors_lookup(func, val, var, ty, ebb);
}
self.mark_ebb_header_block_sealed(block);
side_effects
mem::replace(&mut self.side_effects, SideEffects::new())
}
/// Set the `sealed` flag for `block`.
@@ -398,18 +418,46 @@ where
temp_arg_var: Variable,
ty: Type,
dest_ebb: Ebb,
) -> (Value, SideEffects) {
let mut pred_values: ZeroOneOrMore<Value> = ZeroOneOrMore::Zero();
let mut side_effects = SideEffects::new();
) -> Value {
debug_assert!(self.calls.is_empty());
debug_assert!(self.results.is_empty());
// self.side_effects may be non-empty here so that callers can
// accumulate side effects over multiple calls.
self.begin_predecessors_lookup(temp_arg_val, dest_ebb);
self.run_state_machine(func, temp_arg_var, ty)
}
// Iterate over the predecessors. To avoid borrowing `self` for the whole loop,
// temporarily detach the predecessors list and replace it with an empty list.
// `use_var`'s traversal won't revisit these predecesors.
let mut preds = mem::replace(self.predecessors_mut(dest_ebb), Vec::new());
for &(pred, _) in &preds {
/// Initiate use lookups in all predecessors of `dest_ebb`, and arrange for a call
/// to `finish_predecessors_lookup` once they complete.
fn begin_predecessors_lookup(&mut self, temp_arg_val: Value, dest_ebb: Ebb) {
self.calls.push(Call::FinishPredecessorsLookup(
temp_arg_val,
dest_ebb,
));
// Iterate over the predecessors.
let mut calls = mem::replace(&mut self.calls, Vec::new());
calls.extend(self.predecessors(dest_ebb).iter().rev().map(|&(pred, _)| {
Call::UseVar(pred)
}));
self.calls = calls;
}
/// Examine the values from the predecessors and compute a result value, creating
/// block arguments as needed.
fn finish_predecessors_lookup(
&mut self,
func: &mut Function,
temp_arg_val: Value,
temp_arg_var: Variable,
dest_ebb: Ebb,
) {
let mut pred_values: ZeroOneOrMore<Value> = ZeroOneOrMore::Zero();
// Iterate over the predecessors.
for _ in 0..self.predecessors(dest_ebb).len() {
// For each predecessor, we query what is the local SSA value corresponding
// to var and we put it as an argument of the branch instruction.
let (pred_val, local_side_effects) = self.use_var(func, temp_arg_var, ty, pred);
let pred_val = self.results.pop().unwrap();
match pred_values {
ZeroOneOrMore::Zero() => {
if pred_val != temp_arg_val {
@@ -423,10 +471,7 @@ where
}
ZeroOneOrMore::More() => {}
};
side_effects.append(local_side_effects);
}
debug_assert!(self.predecessors(dest_ebb).is_empty());
let result_val = match pred_values {
ZeroOneOrMore::Zero() => {
// The variable is used but never defined before. This is an irregularity in the
@@ -446,7 +491,7 @@ where
} else {
panic!("value used but never declared and initialization not supported")
};
side_effects.instructions_added_to_ebbs.push(dest_ebb);
self.side_effects.instructions_added_to_ebbs.push(dest_ebb);
val
}
ZeroOneOrMore::One(pred_val) => {
@@ -460,7 +505,9 @@ where
}
ZeroOneOrMore::More() => {
// There is disagreement in the predecessors on which value to use so we have
// to keep the ebb argument.
// to keep the ebb argument. To avoid borrowing `self` for the whole loop,
// temporarily detach the predecessors list and replace it with an empty list.
let mut preds = mem::replace(self.predecessors_mut(dest_ebb), Vec::new());
for &mut (ref mut pred_block, ref mut last_inst) in &mut preds {
// We already did a full `use_var` above, so we can do just the fast path.
let pred_val = self.variables
@@ -481,19 +528,18 @@ where
{
*pred_block = middle_block;
*last_inst = middle_jump_inst;
side_effects.split_ebbs_created.push(middle_ebb);
self.side_effects.split_ebbs_created.push(middle_ebb);
}
}
debug_assert!(self.predecessors(dest_ebb).is_empty());
temp_arg_val
}
};
// Now that we're done, move the predecessors list back.
debug_assert!(self.predecessors(dest_ebb).is_empty());
*self.predecessors_mut(dest_ebb) = preds;
(result_val, side_effects)
temp_arg_val
}
};
self.results.push(result_val);
}
/// Appends a jump argument to a jump instruction, returns ebb created in case of
@@ -564,6 +610,37 @@ where
BlockData::EbbHeader(ref data) => data.sealed,
}
}
/// The main algorithm is naturally recursive: when there's a `use_var` in a
/// block with no correspondin local defs, it recurses and performs a
/// `use_var` in each predecessor. To avoid risking running out of callstack
/// space, we keep an explicit stack and use a small state machine rather
/// than literal recursion.
fn run_state_machine(&mut self, func: &mut Function, var: Variable, ty: Type) -> Value {
// Process the calls scheduled in `self.calls` until it is empty.
while let Some(call) = self.calls.pop() {
match call {
Call::UseVar(block) => {
// First we lookup for the current definition of the variable in this block
if let Some(var_defs) = self.variables.get(var) {
if let Some(val) = var_defs[block].expand() {
self.results.push(val);
continue;
}
}
self.use_var_nonlocal(func, var, ty, block);
}
Call::FinishSealedOnePredecessor(block) => {
self.finish_sealed_one_predecessor(var, block);
}
Call::FinishPredecessorsLookup(temp_arg_val, dest_ebb) => {
self.finish_predecessors_lookup(func, temp_arg_val, var, dest_ebb);
}
}
}
debug_assert_eq!(self.results.len(), 1);
self.results.pop().unwrap()
}
}
#[cfg(test)]