use crate::dominator_tree::DominatorTree;
use crate::entity::SecondaryMap;
use crate::inst_predicates::visit_block_succs;
use crate::ir::{Block, Function, Inst, Opcode};
use crate::{FxHashMap, FxHashSet};
use crate::{machinst::*, trace};
#[derive(Debug)]
pub struct BlockLoweringOrder {
lowered_order: Vec<LoweredBlock>,
lowered_succ_indices: Vec<BlockIndex>,
lowered_succ_ranges: Vec<(Option<Inst>, core::ops::Range<usize>)>,
blockindex_by_block: SecondaryMap<Block, BlockIndex>,
cold_blocks: FxHashSet<BlockIndex>,
indirect_branch_targets: FxHashSet<BlockIndex>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum LoweredBlock {
Orig {
block: Block,
},
CriticalEdge {
pred: Block,
succ: Block,
succ_idx: u32,
},
}
impl LoweredBlock {
pub fn orig_block(&self) -> Option<Block> {
match self {
&LoweredBlock::Orig { block } => Some(block),
&LoweredBlock::CriticalEdge { .. } => None,
}
}
#[cfg(test)]
pub fn in_edge(&self) -> Option<Block> {
match self {
&LoweredBlock::CriticalEdge { pred, .. } => Some(pred),
&LoweredBlock::Orig { .. } => None,
}
}
pub fn out_edge(&self) -> Option<Block> {
match self {
&LoweredBlock::CriticalEdge { succ, .. } => Some(succ),
&LoweredBlock::Orig { .. } => None,
}
}
}
impl BlockLoweringOrder {
pub fn new(
f: &Function,
domtree: &DominatorTree,
ctrl_plane: &mut ControlPlane,
) -> BlockLoweringOrder {
trace!("BlockLoweringOrder: function body {:?}", f);
let mut block_in_count = SecondaryMap::with_default(0);
let mut block_out_count = SecondaryMap::with_default(0);
let mut block_succs: SmallVec<[LoweredBlock; 128]> = SmallVec::new();
let mut block_succ_range = SecondaryMap::with_default(0..0);
let mut indirect_branch_target_clif_blocks = FxHashSet::default();
for block in f.layout.blocks() {
let start = block_succs.len();
visit_block_succs(f, block, |_, succ, from_table| {
block_out_count[block] += 1;
block_in_count[succ] += 1;
block_succs.push(LoweredBlock::Orig { block: succ });
if from_table {
indirect_branch_target_clif_blocks.insert(succ);
}
});
if let Some(inst) = f.layout.last_inst(block) {
match f.dfg.insts[inst].opcode() {
Opcode::BrTable | Opcode::TryCall | Opcode::TryCallIndirect => {
block_out_count[block] = block_out_count[block].max(2);
}
_ => {}
}
}
let end = block_succs.len();
block_succ_range[block] = start..end;
}
let mut lowered_order = Vec::new();
let mut blockindex_by_block = SecondaryMap::with_default(BlockIndex::invalid());
for &block in domtree.cfg_rpo() {
let idx = BlockIndex::new(lowered_order.len());
lowered_order.push(LoweredBlock::Orig { block });
blockindex_by_block[block] = idx;
if block_out_count[block] > 1 {
let range = block_succ_range[block].clone();
let succs = ctrl_plane.shuffled(block_succs[range].iter_mut().enumerate());
for (succ_ix, lb) in succs {
let succ = lb.orig_block().unwrap();
if block_in_count[succ] > 1 {
*lb = LoweredBlock::CriticalEdge {
pred: block,
succ,
succ_idx: succ_ix as u32,
};
lowered_order.push(*lb);
}
}
}
}
let lb_to_bindex = FxHashMap::from_iter(
lowered_order
.iter()
.enumerate()
.map(|(i, &lb)| (lb, BlockIndex::new(i))),
);
let mut lowered_succ_indices = Vec::new();
let mut cold_blocks = FxHashSet::default();
let mut indirect_branch_targets = FxHashSet::default();
let lowered_succ_ranges =
Vec::from_iter(lowered_order.iter().enumerate().map(|(ix, lb)| {
let bindex = BlockIndex::new(ix);
let start = lowered_succ_indices.len();
let opt_inst = match lb {
&LoweredBlock::Orig { block } => {
let range = block_succ_range[block].clone();
lowered_succ_indices
.extend(block_succs[range].iter().map(|lb| lb_to_bindex[lb]));
if f.layout.is_cold(block) {
cold_blocks.insert(bindex);
}
if indirect_branch_target_clif_blocks.contains(&block) {
indirect_branch_targets.insert(bindex);
}
let last = f.layout.last_inst(block).unwrap();
let opcode = f.dfg.insts[last].opcode();
assert!(opcode.is_terminator());
opcode.is_branch().then_some(last)
}
&LoweredBlock::CriticalEdge { succ, .. } => {
let succ_index = lb_to_bindex[&LoweredBlock::Orig { block: succ }];
lowered_succ_indices.push(succ_index);
if f.layout.is_cold(succ) {
cold_blocks.insert(bindex);
}
if indirect_branch_target_clif_blocks.contains(&succ) {
indirect_branch_targets.insert(bindex);
}
None
}
};
let end = lowered_succ_indices.len();
(opt_inst, start..end)
}));
let result = BlockLoweringOrder {
lowered_order,
lowered_succ_indices,
lowered_succ_ranges,
blockindex_by_block,
cold_blocks,
indirect_branch_targets,
};
trace!("BlockLoweringOrder: {:#?}", result);
result
}
pub fn lowered_order(&self) -> &[LoweredBlock] {
&self.lowered_order[..]
}
pub fn lowered_index_for_block(&self, block: Block) -> Option<BlockIndex> {
let idx = self.blockindex_by_block[block];
if idx.is_valid() { Some(idx) } else { None }
}
pub fn succ_indices(&self, block: BlockIndex) -> (Option<Inst>, &[BlockIndex]) {
let (opt_inst, range) = &self.lowered_succ_ranges[block.index()];
(*opt_inst, &self.lowered_succ_indices[range.clone()])
}
pub fn is_cold(&self, block: BlockIndex) -> bool {
self.cold_blocks.contains(&block)
}
pub fn is_indirect_branch_target(&self, block: BlockIndex) -> bool {
self.indirect_branch_targets.contains(&block)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::cursor::{Cursor, FuncCursor};
use crate::flowgraph::ControlFlowGraph;
use crate::ir::UserFuncName;
use crate::ir::types::*;
use crate::ir::{AbiParam, InstBuilder, Signature};
use crate::isa::CallConv;
fn build_test_func(n_blocks: usize, edges: &[(usize, usize)]) -> BlockLoweringOrder {
assert!(n_blocks > 0);
let name = UserFuncName::testcase("test0");
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(I32));
let mut func = Function::with_name_signature(name, sig);
let blocks = (0..n_blocks)
.map(|i| {
let bb = func.dfg.make_block();
assert!(bb.as_u32() == i as u32);
bb
})
.collect::<Vec<_>>();
let arg0 = func.dfg.append_block_param(blocks[0], I32);
let mut pos = FuncCursor::new(&mut func);
let mut edge = 0;
for i in 0..n_blocks {
pos.insert_block(blocks[i]);
let mut succs = vec![];
while edge < edges.len() && edges[edge].0 == i {
succs.push(edges[edge].1);
edge += 1;
}
if succs.len() == 0 {
pos.ins().return_(&[arg0]);
} else if succs.len() == 1 {
pos.ins().jump(blocks[succs[0]], &[]);
} else if succs.len() == 2 {
pos.ins()
.brif(arg0, blocks[succs[0]], &[], blocks[succs[1]], &[]);
} else {
panic!("Too many successors");
}
}
let mut cfg = ControlFlowGraph::new();
cfg.compute(&func);
let dom_tree = DominatorTree::with_function(&func, &cfg);
BlockLoweringOrder::new(&func, &dom_tree, &mut Default::default())
}
#[test]
fn test_blockorder_diamond() {
let order = build_test_func(4, &[(0, 1), (0, 2), (1, 3), (2, 3)]);
assert_eq!(order.lowered_order.len(), 4);
}
#[test]
fn test_blockorder_critedge() {
let order = build_test_func(
7,
&[
(0, 1),
(0, 2),
(1, 3),
(1, 4),
(2, 5),
(3, 5),
(3, 6),
(4, 6),
],
);
assert_eq!(order.lowered_order.len(), 9);
println!("ordered = {:?}", order.lowered_order);
assert_eq!(order.lowered_order[0].orig_block().unwrap().as_u32(), 0);
assert!(order.lowered_order[0].in_edge().is_none());
assert!(order.lowered_order[0].out_edge().is_none());
assert_eq!(order.lowered_order[1].orig_block().unwrap().as_u32(), 2);
assert!(order.lowered_order[1].in_edge().is_none());
assert!(order.lowered_order[1].out_edge().is_none());
assert_eq!(order.lowered_order[2].orig_block().unwrap().as_u32(), 1);
assert!(order.lowered_order[2].in_edge().is_none());
assert!(order.lowered_order[2].out_edge().is_none());
assert_eq!(order.lowered_order[3].orig_block().unwrap().as_u32(), 4);
assert!(order.lowered_order[3].in_edge().is_none());
assert!(order.lowered_order[3].out_edge().is_none());
assert_eq!(order.lowered_order[4].orig_block().unwrap().as_u32(), 3);
assert!(order.lowered_order[4].in_edge().is_none());
assert!(order.lowered_order[4].out_edge().is_none());
assert!(order.lowered_order[5].orig_block().is_none());
assert_eq!(order.lowered_order[5].in_edge().unwrap().as_u32(), 3);
assert_eq!(order.lowered_order[5].out_edge().unwrap().as_u32(), 5);
assert!(order.lowered_order[6].orig_block().is_none());
assert_eq!(order.lowered_order[6].in_edge().unwrap().as_u32(), 3);
assert_eq!(order.lowered_order[6].out_edge().unwrap().as_u32(), 6);
assert_eq!(order.lowered_order[7].orig_block().unwrap().as_u32(), 6);
assert!(order.lowered_order[7].in_edge().is_none());
assert!(order.lowered_order[7].out_edge().is_none());
assert_eq!(order.lowered_order[8].orig_block().unwrap().as_u32(), 5);
assert!(order.lowered_order[8].in_edge().is_none());
assert!(order.lowered_order[8].out_edge().is_none());
}
}