use std::{
collections::{BTreeMap, BTreeSet},
marker::Sized,
};
use vm::file_format::{Bytecode, CodeOffset};
type Map<K, V> = BTreeMap<K, V>;
type Set<V> = BTreeSet<V>;
pub type BlockId = CodeOffset;
pub trait ControlFlowGraph: Sized {
fn new(code: &[Bytecode]) -> Self;
fn reachable_from(&self, block_id: BlockId) -> Vec<&BasicBlock>;
fn block_id_of_offset(&self, code_offset: CodeOffset) -> Option<BlockId>;
fn block_of_id(&self, block_id: BlockId) -> Option<&BasicBlock>;
fn num_blocks(&self) -> u16;
fn entry_block_id(&self) -> BlockId;
}
pub struct BasicBlock {
pub entry: CodeOffset,
pub exit: CodeOffset,
pub successors: Vec<BlockId>,
}
pub struct VMControlFlowGraph {
pub blocks: Map<BlockId, BasicBlock>,
}
impl BasicBlock {
pub fn display(&self) {
println!("+=======================+");
println!("| Enter: {} |", self.entry);
println!("+-----------------------+");
println!("==> Children: {:?}", self.successors);
println!("+-----------------------+");
println!("| Exit: {} |", self.exit);
println!("+=======================+");
}
}
impl VMControlFlowGraph {
pub fn display(&self) {
for block in self.blocks.values() {
block.display();
}
}
fn is_end_of_block(pc: CodeOffset, code: &[Bytecode], block_ids: &Set<BlockId>) -> bool {
pc + 1 == (code.len() as CodeOffset) || block_ids.contains(&(pc + 1))
}
fn record_block_ids(pc: CodeOffset, code: &[Bytecode], block_ids: &mut Set<BlockId>) {
let bytecode = &code[pc as usize];
if let Some(offset) = bytecode.offset() {
block_ids.insert(*offset);
}
if bytecode.is_branch() && pc + 1 < (code.len() as CodeOffset) {
block_ids.insert(pc + 1);
}
}
fn traverse_by(
&self,
get_targets: fn(&BasicBlock) -> &Vec<BlockId>,
block_id: BlockId,
) -> Vec<&BasicBlock> {
let mut ret = Vec::new();
let mut index = 0;
let mut seen = Set::new();
let block = &self.blocks[&block_id];
ret.push(block);
seen.insert(&block_id);
while index < ret.len() {
let block = ret[index];
index += 1;
let successors = get_targets(&block);
for block_id in successors.iter() {
if !seen.contains(&block_id) {
ret.push(&self.blocks[&block_id]);
seen.insert(block_id);
}
}
}
ret
}
}
const ENTRY_BLOCK_ID: BlockId = 0;
impl ControlFlowGraph for VMControlFlowGraph {
fn num_blocks(&self) -> u16 {
self.blocks.len() as u16
}
fn block_id_of_offset(&self, code_offset: CodeOffset) -> Option<BlockId> {
let mut index = None;
for (block_id, block) in &self.blocks {
if block.entry >= code_offset && block.exit <= code_offset {
index = Some(*block_id);
}
}
index
}
fn block_of_id(&self, block_id: BlockId) -> Option<&BasicBlock> {
if self.blocks.contains_key(&block_id) {
Some(&self.blocks[&block_id])
} else {
None
}
}
fn reachable_from(&self, block_id: BlockId) -> Vec<&BasicBlock> {
self.traverse_by(|block: &BasicBlock| &block.successors, block_id)
}
fn entry_block_id(&self) -> BlockId {
ENTRY_BLOCK_ID
}
fn new(code: &[Bytecode]) -> Self {
let mut block_ids = Set::new();
block_ids.insert(ENTRY_BLOCK_ID);
for pc in 0..code.len() {
VMControlFlowGraph::record_block_ids(pc as CodeOffset, code, &mut block_ids);
}
let mut cfg = VMControlFlowGraph { blocks: Map::new() };
let mut entry = 0;
for pc in 0..code.len() {
let co_pc: CodeOffset = pc as CodeOffset;
if VMControlFlowGraph::is_end_of_block(co_pc, code, &block_ids) {
let successors = Bytecode::get_successors(co_pc, code);
let bb = BasicBlock {
entry,
exit: co_pc,
successors,
};
cfg.blocks.insert(entry, bb);
entry = co_pc + 1;
}
}
assert!(entry == code.len() as CodeOffset);
cfg
}
}