use crate::recompiler::decoder::{DecodedInstruction, Operand};
use anyhow::Result;
use bitvec::prelude::*;
use smallvec::SmallVec;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ControlFlowGraph {
pub nodes: Vec<BasicBlock>,
pub edges: Vec<Edge>,
pub entry_block: u32,
}
#[derive(Debug, Clone)]
#[repr(C)] pub struct BasicBlock {
pub id: u32,
pub start_address: u32,
pub end_address: u32,
pub instructions: Vec<DecodedInstruction>,
pub successors: SmallVec<[u32; 2]>,
pub predecessors: SmallVec<[u32; 2]>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)] pub struct Edge {
pub from: u32,
pub to: u32,
pub edge_type: EdgeType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)] pub enum EdgeType {
Unconditional = 0,
ConditionalTrue = 1,
ConditionalFalse = 2,
Call = 3,
Return = 4,
}
#[derive(Debug, Clone)]
pub struct Loop {
pub header: u32,
pub back_edges: SmallVec<[(u32, u32); 2]>,
pub body: BitVec<u32>,
pub exits: SmallVec<[u32; 2]>,
}
pub struct ControlFlowAnalyzer;
impl ControlFlowAnalyzer {
#[inline] pub fn build_cfg(
instructions: &[DecodedInstruction],
entry_address: u32,
) -> Result<ControlFlowGraph> {
let mut nodes: Vec<BasicBlock> = Vec::new();
let mut edges: Vec<Edge> = Vec::new();
let mut address_to_block: HashMap<u32, u32> = HashMap::new();
let mut block_id: u32 = 0u32;
let mut block_starts: std::collections::HashSet<u32> = std::collections::HashSet::new();
block_starts.insert(entry_address);
let mut current_address: u32 = entry_address;
for inst in instructions.iter() {
if let Some(target) = Self::get_branch_target(inst) {
block_starts.insert(target);
if current_address.wrapping_add(4) < u32::MAX {
block_starts.insert(current_address.wrapping_add(4));
}
}
current_address = current_address.wrapping_add(4); }
let mut current_block: Option<BasicBlock> = None;
let mut current_address: u32 = entry_address;
for inst in instructions.iter() {
if block_starts.contains(¤t_address) {
if let Some(block) = current_block.take() {
let block_idx: u32 = nodes.len() as u32;
address_to_block.insert(block.start_address, block_idx);
nodes.push(block);
}
current_block = Some(BasicBlock {
id: block_id,
start_address: current_address,
end_address: current_address,
instructions: vec![inst.clone()],
successors: SmallVec::new(),
predecessors: SmallVec::new(),
});
block_id = block_id.wrapping_add(1);
} else if let Some(ref mut block) = current_block {
block.instructions.push(inst.clone());
block.end_address = current_address;
}
current_address = current_address.wrapping_add(4); }
if let Some(block) = current_block {
let block_idx: u32 = nodes.len() as u32;
address_to_block.insert(block.start_address, block_idx);
nodes.push(block);
}
let mut successor_updates: Vec<(usize, u32)> = Vec::new();
let mut predecessor_updates: Vec<(usize, u32)> = Vec::new();
for (block_idx, block) in nodes.iter().enumerate() {
let block_idx_u32: u32 = block_idx as u32;
if let Some(last_inst) = block.instructions.last() {
if let Some(target) = Self::get_branch_target(last_inst) {
if let Some(&target_block) = address_to_block.get(&target) {
edges.push(Edge {
from: block_idx_u32,
to: target_block,
edge_type: EdgeType::Unconditional,
});
successor_updates.push((block_idx, target_block));
predecessor_updates.push((target_block as usize, block_idx_u32));
}
} else {
if block_idx + 1 < nodes.len() {
let next_block_id: u32 = (block_idx + 1) as u32;
edges.push(Edge {
from: block_idx_u32,
to: next_block_id,
edge_type: EdgeType::Unconditional,
});
successor_updates.push((block_idx, next_block_id));
predecessor_updates.push((block_idx + 1, block_idx_u32));
}
}
}
}
for (block_idx, successor) in successor_updates {
if let Some(block) = nodes.get_mut(block_idx) {
if !block.successors.contains(&successor) {
block.successors.push(successor);
}
}
}
for (block_idx, predecessor) in predecessor_updates {
if let Some(block) = nodes.get_mut(block_idx) {
if !block.predecessors.contains(&predecessor) {
block.predecessors.push(predecessor);
}
}
}
Ok(ControlFlowGraph {
nodes,
edges,
entry_block: 0u32,
})
}
#[inline] fn get_branch_target(inst: &DecodedInstruction) -> Option<u32> {
if matches!(inst.instruction.instruction_type, crate::recompiler::decoder::InstructionType::Branch) {
if let Some(Operand::Address(addr)) = inst.instruction.operands.first() {
return Some(*addr);
}
if let Some(Operand::Immediate32(imm)) = inst.instruction.operands.first() {
return None;
}
if let Some(Operand::Register(0)) = inst.instruction.operands.first() {
return None;
}
}
None
}
#[inline] pub fn detect_loops(cfg: &ControlFlowGraph) -> Vec<Loop> {
let mut loops: Vec<Loop> = Vec::new();
let mut visited: BitVec<u32> = bitvec![u32, Lsb0; 0; cfg.nodes.len()];
let mut in_stack: BitVec<u32> = bitvec![u32, Lsb0; 0; cfg.nodes.len()];
Self::dfs_loops(cfg, 0u32, &mut visited, &mut in_stack, &mut loops);
loops
}
#[inline] fn dfs_loops(
cfg: &ControlFlowGraph,
node: u32,
visited: &mut BitVec<u32>,
in_stack: &mut BitVec<u32>,
loops: &mut Vec<Loop>,
) {
let node_idx: usize = node as usize;
if node_idx >= visited.len() {
return;
}
visited.set(node_idx, true);
in_stack.set(node_idx, true);
if let Some(block) = cfg.nodes.get(node_idx) {
for &succ in block.successors.iter() {
let succ_idx: usize = succ as usize;
if succ_idx >= visited.len() {
continue;
}
if !visited[succ_idx] {
Self::dfs_loops(cfg, succ, visited, in_stack, loops);
} else if in_stack[succ_idx] {
let loop_header: u32 = succ;
let mut loop_body: BitVec<u32> = bitvec![u32, Lsb0; 0; cfg.nodes.len()];
loop_body.set(loop_header as usize, true);
loop_body.set(node_idx, true);
loops.push(Loop {
header: loop_header,
back_edges: SmallVec::from_slice(&[(node, loop_header)]),
body: loop_body,
exits: SmallVec::new(),
});
}
}
}
in_stack.set(node_idx, false);
}
#[inline] pub fn analyze_function_calls(cfg: &ControlFlowGraph) -> Vec<FunctionCall> {
let mut calls: Vec<FunctionCall> = Vec::new();
for block in cfg.nodes.iter() {
let mut instruction_address: u32 = block.start_address;
for inst in block.instructions.iter() {
if Self::is_function_call(inst) {
if let Some(target) = Self::get_branch_target(inst) {
calls.push(FunctionCall {
caller_block: block.id,
callee_address: target,
instruction_address,
});
}
}
instruction_address = instruction_address.wrapping_add(4); }
}
calls
}
#[inline] fn is_function_call(inst: &DecodedInstruction) -> bool {
matches!(inst.instruction.instruction_type, crate::recompiler::decoder::InstructionType::Branch)
&& (inst.raw & 1u32) != 0u32 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)] pub struct FunctionCall {
pub caller_block: u32,
pub callee_address: u32,
pub instruction_address: u32,
}