charon_lib/transform/merge_goto_chains.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
//! # Micro-pass: merge single-origin gotos into their parent to reduce CFG graph size.
use crate::ids::Vector;
use crate::transform::TransformCtx;
use crate::ullbc_ast::*;
use super::ctx::UllbcPass;
/// Set of antecedents of a given block. We only care about block ids if there's a single
/// antecedent.
enum Antecedents {
Zero,
One {
id: BlockId,
/// Whether the antecedent is a goto.
is_goto: bool,
},
Many,
}
impl Antecedents {
fn add(&mut self, id: BlockId, is_goto: bool) {
match self {
Antecedents::Zero => *self = Antecedents::One { id, is_goto },
Antecedents::One { .. } => *self = Antecedents::Many,
Antecedents::Many => {}
}
}
}
pub struct Transform;
impl UllbcPass for Transform {
fn transform_body(&self, ctx: &mut TransformCtx, body: &mut ExprBody) {
// Check the option which instructs to ignore this pass
if ctx.options.no_merge_goto_chains {
return;
}
// Compute for each block the set of blocks that points to it.
let mut antecedents: Vector<BlockId, Antecedents> =
body.body.map_ref(|_| Antecedents::Zero);
for (block_id, block) in body.body.iter_indexed() {
let is_goto = block.terminator.content.is_goto();
for target in block.targets() {
antecedents.get_mut(target).unwrap().add(block_id, is_goto);
}
}
// Merge blocks with a single antecedent into their antecedent.
for mut id in body.body.all_indices() {
// Go up the chain to find the first parent with zero (the start block) or multiple
// antecedents. This avoids quadratic behavior where we repeatedly copy a growing list
// of statements, since blocks may not be sorted..
while let Antecedents::One {
id: antecedent_id,
is_goto: true,
} = antecedents[id]
{
id = antecedent_id;
}
// While the current block is a straight goto, merge the target block back into this
// one.
while let Some(source) = body.body.get(id)
&& let RawTerminator::Goto { target } = source.terminator.content
&& let Antecedents::One { .. } = antecedents[target]
{
let mut target = body.body.remove(target).unwrap();
let source = &mut body.body[id];
source.statements.append(&mut target.statements);
source.terminator = target.terminator;
}
}
}
}