use crate::entity::EntityRef;
use crate::{cfg::CFGInfo, cfg::RPOIndex, entity::PerEntity, Block, FunctionBody, Value, ValueDef};
use fxhash::{FxHashMap, FxHashSet};
use smallvec::SmallVec;
use std::borrow::Cow;
use std::collections::{HashSet, VecDeque};
pub struct Reducifier<'a> {
body: &'a FunctionBody,
cfg: CFGInfo,
blocks: PerEntity<Block, BlockState>,
}
#[derive(Debug, Clone, Default)]
struct BlockState {
headers: FxHashSet<Block>,
is_header: bool,
}
impl<'a> Reducifier<'a> {
pub fn new(body: &'a FunctionBody) -> Reducifier<'a> {
let cfg = CFGInfo::new(body);
Reducifier {
body,
cfg,
blocks: PerEntity::default(),
}
}
pub fn run(&mut self) -> Cow<'a, FunctionBody> {
let cfg = CFGInfo::new(&self.body);
let mut has_irreducible = false;
for (rpo, &block) in cfg.rpo.entries() {
for &succ in &self.body.blocks[block].succs {
let succ_rpo = cfg.rpo_pos[succ].unwrap();
if succ_rpo.index() <= rpo.index() && !cfg.dominates(succ, block) {
has_irreducible = true;
}
}
}
if !has_irreducible {
return Cow::Borrowed(self.body);
}
for (rpo, &block) in cfg.rpo.entries() {
for &succ in &self.body.blocks[block].succs {
let succ_rpo = cfg.rpo_pos[succ].unwrap();
if succ_rpo.index() <= rpo.index() {
for i in succ_rpo.index()..=rpo.index() {
let b = cfg.rpo[RPOIndex::new(i)];
self.blocks[b].headers.insert(succ);
self.blocks[b].is_header = true;
}
}
}
}
let mut header_stack = vec![];
for &block in cfg.rpo.values() {
while let Some(innermost) = header_stack.last() {
if !self.blocks[block].headers.contains(innermost) {
header_stack.pop();
} else {
break;
}
}
if self.blocks[block].is_header {
header_stack.push(block);
}
for &header in &header_stack {
self.blocks[block].headers.insert(header);
}
}
let mut irreducible_headers: FxHashSet<Block> = FxHashSet::default();
for (block, data) in self.body.blocks.entries() {
let headers = &self.blocks[block].headers;
for &succ in &data.succs {
log::trace!("examining edge {} -> {}", block, succ);
for &succ_header in &self.blocks[succ].headers {
log::trace!(" successor {} has header {}", succ, succ_header);
if succ_header != succ && !headers.contains(&succ_header) {
log::trace!(" -> irreducible edge");
irreducible_headers.insert(succ_header);
}
}
}
}
if log::log_enabled!(log::Level::Trace) {
for block in self.body.blocks.iter() {
let mut headers = self.blocks[block]
.headers
.iter()
.cloned()
.collect::<Vec<_>>();
headers.sort();
log::trace!("* {}: header set {:?}", block, headers);
}
}
let mut cut_blocks = HashSet::default();
for (block, data) in self.body.blocks.entries() {
for &succ in &data.succs {
for header in &self.blocks[block].headers {
if !self.blocks[succ].headers.contains(header)
&& irreducible_headers.contains(header)
{
log::trace!("cut-block at loop exit: {}", succ);
cut_blocks.insert(succ);
}
}
for header in &self.blocks[succ].headers {
if !self.blocks[block].headers.contains(header) && *header != succ {
log::trace!("cut-block at loop side entry: {}", succ);
cut_blocks.insert(succ);
}
}
}
}
let mut new_body = self.body.clone();
let cfg = CFGInfo::new(&new_body);
crate::passes::resolve_aliases::run(&mut new_body);
crate::passes::maxssa::run(&mut new_body, Some(cut_blocks), &cfg);
crate::passes::resolve_aliases::run(&mut new_body);
log::trace!("after max-SSA run:\n{}\n", new_body.display("| ", None));
let mut context_map: FxHashMap<Vec<Block>, usize> = FxHashMap::default();
let mut contexts: Vec<Vec<Block>> = vec![vec![]];
context_map.insert(vec![], 0);
let mut block_map: FxHashMap<(usize, Block), Block> = FxHashMap::default();
let mut value_map: FxHashMap<(usize, Value), Value> = FxHashMap::default();
let mut cloned_blocks: Vec<(usize, Block)> = vec![];
let mut terminators: FxHashMap<Block, Vec<(usize, Block)>> = FxHashMap::default();
let mut queue: VecDeque<(usize, Block)> = VecDeque::new();
let mut visited: FxHashSet<(usize, Block)> = FxHashSet::default();
queue.push_back((0, new_body.entry));
visited.insert((0, new_body.entry));
while let Some((ctx, block)) = queue.pop_front() {
log::trace!(
"elaborate: block {} in context {} ({:?})",
block,
ctx,
contexts[ctx]
);
let new_block = if ctx != 0 {
log::trace!("cloning block {} in new context", block);
let new_block = new_body.add_block();
new_body.blocks[new_block].desc = format!("Cloned {}", block);
let params = new_body.blocks[block].params.clone();
for (ty, val) in params {
let blockparam = new_body.add_blockparam(new_block, ty);
value_map.insert((ctx, val), blockparam);
}
block_map.insert((ctx, block), new_block);
cloned_blocks.push((ctx, new_block));
let insts = new_body.blocks[block].insts.clone();
for value in insts {
let def = new_body.values[value].clone();
let new_value = new_body.values.push(def);
value_map.insert((ctx, value), new_value);
new_body.blocks[new_block].insts.push(new_value);
}
new_body.blocks[new_block].terminator = new_body.blocks[block].terminator.clone();
new_block
} else {
block
};
let term = terminators.entry(new_block).or_insert_with(|| vec![]);
let succs = new_body.blocks[block].succs.clone();
for succ in succs {
let mut ctx_blocks = self.blocks[succ]
.headers
.iter()
.cloned()
.collect::<Vec<_>>();
ctx_blocks.sort();
ctx_blocks.retain(|&header_block| {
header_block != succ
&& (contexts[ctx].contains(&header_block)
|| !self.blocks[block].headers.contains(&header_block))
});
let to_ctx = *context_map.entry(ctx_blocks.clone()).or_insert_with(|| {
let id = contexts.len();
contexts.push(ctx_blocks);
id
});
log::trace!(
"elaborate: edge {} -> {} from ctx {:?} goes to ctx {:?}",
block,
succ,
contexts[ctx],
contexts[to_ctx]
);
term.push((to_ctx, succ));
if visited.insert((to_ctx, succ)) {
log::trace!("enqueue block {} ctx {}", succ, to_ctx);
queue.push_back((to_ctx, succ));
}
}
}
for (ctx, new_block) in cloned_blocks {
for &inst in &new_body.blocks[new_block].insts {
match &mut new_body.values[inst] {
ValueDef::Operator(_, args, _) => {
let new_args = new_body.arg_pool[*args]
.iter()
.map(|&val| value_map.get(&(ctx, val)).cloned().unwrap_or(val))
.collect::<SmallVec<[Value; 4]>>();
let new_args = new_body.arg_pool.from_iter(new_args.into_iter());
*args = new_args;
}
ValueDef::PickOutput(val, _, _) | ValueDef::Alias(val) => {
*val = value_map.get(&(ctx, *val)).cloned().unwrap_or(*val);
}
_ => unreachable!(),
}
}
new_body.blocks[new_block]
.terminator
.update_uses(|u| *u = value_map.get(&(ctx, *u)).cloned().unwrap_or(*u));
}
for (block, block_def) in new_body.blocks.entries_mut() {
log::trace!("processing terminators for block {}", block);
let terms = match terminators.get(&block) {
Some(t) => t,
None => continue,
};
let mut terms = terms.iter();
block_def.terminator.update_targets(|target| {
let &(to_ctx, to_orig_block) = terms.next().unwrap();
target.block = block_map
.get(&(to_ctx, to_orig_block))
.cloned()
.unwrap_or(to_orig_block);
});
}
new_body.recompute_edges();
log::trace!("After duplication:\n{}\n", new_body.display("| ", None));
new_body.validate().unwrap();
new_body.verify_reducible().unwrap();
Cow::Owned(new_body)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
entity::EntityRef, BlockTarget, FuncDecl, Module, Operator, SignatureData, Terminator, Type,
};
#[test]
fn test_irreducible() {
let _ = env_logger::try_init();
let mut module = Module::empty();
let sig = module.signatures.push(SignatureData {
params: vec![Type::I32, Type::I64, Type::F64],
returns: vec![Type::I64],
});
let mut body = FunctionBody::new(&module, sig);
let block1 = body.entry;
let block2 = body.add_block();
let block3 = body.add_block();
let block4 = body.add_block();
let arg0 = body.blocks[block1].params[0].1;
let arg1 = body.blocks[block1].params[1].1;
let arg2 = body.blocks[block1].params[2].1;
body.set_terminator(
block1,
Terminator::CondBr {
cond: arg0,
if_true: BlockTarget {
block: block2,
args: vec![arg1],
},
if_false: BlockTarget {
block: block3,
args: vec![arg2],
},
},
);
let block2_param = body.add_blockparam(block2, Type::I64);
let block3_param = body.add_blockparam(block3, Type::F64);
let block2_param_cast = body.add_op(
block2,
Operator::F64ReinterpretI64,
&[block2_param],
&[Type::F64],
);
let block3_param_cast = body.add_op(
block3,
Operator::I64ReinterpretF64,
&[block3_param],
&[Type::I64],
);
body.set_terminator(
block2,
Terminator::Br {
target: BlockTarget {
block: block3,
args: vec![block2_param_cast],
},
},
);
body.set_terminator(
block3,
Terminator::CondBr {
cond: arg0,
if_true: BlockTarget {
block: block2,
args: vec![block3_param_cast],
},
if_false: BlockTarget {
block: block4,
args: vec![],
},
},
);
body.set_terminator(
block4,
Terminator::Return {
values: vec![block3_param_cast],
},
);
log::debug!("Body:\n{}", body.display("| ", Some(&module)));
body.validate().unwrap();
let mut reducifier = Reducifier::new(&body);
let new_body = reducifier.run();
new_body.validate().unwrap();
log::debug!("Reducified body:\n{}", body.display("| ", Some(&module)));
let cfg = CFGInfo::new(&new_body);
for (block, def) in new_body.blocks.entries() {
for &succ in &def.succs {
if cfg.rpo_pos[succ].unwrap().index() <= cfg.rpo_pos[block].unwrap().index() {
assert!(cfg.dominates(succ, block));
}
}
}
module
.funcs
.push(FuncDecl::Body(sig, "func0".to_string(), body));
let wasm = module.to_wasm_bytes().unwrap();
log::debug!("wasm bytes: {:?}", wasm);
}
}