gcrecomp_core/recompiler/analysis/control_flow.rs
1//! Control Flow Analysis
2//!
3//! This module provides control flow graph (CFG) construction and analysis for PowerPC instructions.
4//! The CFG is essential for optimizations like dead code elimination, loop detection, and register allocation.
5//!
6//! # Memory Optimizations
7//! - `EdgeType` uses `#[repr(u8)]` to save 3 bytes per edge
8//! - `BasicBlock.successors` and `predecessors` use `SmallVec<[u32; 2]>` (most blocks have ≤2)
9//! - `Loop.body` uses `BitVec` for efficient membership testing (instead of `HashSet<usize>`)
10//! - Block IDs use `u32` instead of `usize` to save 4 bytes on 64-bit systems
11//! - Edge indices use `u32` for consistency
12//!
13//! # CFG Construction Algorithm
14//! 1. **Identify block boundaries**: Entry points, branch targets, and fall-through points
15//! 2. **Build basic blocks**: Linear sequences of instructions with single entry/exit
16//! 3. **Identify edges**: Connect blocks based on branch targets and fall-through
17//!
18//! # Loop Detection Algorithm
19//! Uses depth-first search (DFS) to find back edges, which indicate loops.
20//! A back edge is an edge from a node to an ancestor in the DFS tree.
21
22use crate::recompiler::decoder::{DecodedInstruction, Operand};
23use anyhow::Result;
24use bitvec::prelude::*;
25use smallvec::SmallVec;
26use std::collections::HashMap;
27
28/// Control flow graph representation.
29///
30/// # Memory Layout
31/// - `nodes`: Vector of basic blocks (heap allocation appropriate for large graphs)
32/// - `edges`: Vector of edges (heap allocation appropriate for large graphs)
33/// - `entry_block`: Entry point block ID (u32 for consistency)
34///
35/// # Graph Properties
36/// - Directed graph (edges have direction)
37/// - May contain cycles (loops)
38/// - Single entry point (function entry)
39#[derive(Debug, Clone)]
40pub struct ControlFlowGraph {
41 /// Basic blocks in the control flow graph
42 pub nodes: Vec<BasicBlock>,
43 /// Edges between basic blocks
44 pub edges: Vec<Edge>,
45 /// Entry block ID (function entry point)
46 pub entry_block: u32,
47}
48
49/// Basic block in the control flow graph.
50///
51/// # Memory Optimization
52/// - `id`: Uses `u32` instead of `usize` to save 4 bytes on 64-bit systems
53/// - `successors`: Uses `SmallVec<[u32; 2]>` - most blocks have ≤2 successors
54/// - `predecessors`: Uses `SmallVec<[u32; 2]>` - most blocks have ≤2 predecessors
55///
56/// # Basic Block Properties
57/// A basic block is a maximal sequence of instructions with:
58/// - Single entry point (first instruction)
59/// - Single exit point (last instruction is a branch/return)
60/// - No internal control flow (linear execution)
61#[derive(Debug, Clone)]
62#[repr(C)] // Ensure C-compatible layout
63pub struct BasicBlock {
64 /// Basic block identifier (unique within function)
65 /// Uses u32 instead of usize to save 4 bytes on 64-bit systems
66 pub id: u32,
67 /// Start address of this basic block in original binary
68 pub start_address: u32,
69 /// End address of this basic block (inclusive)
70 pub end_address: u32,
71 /// Instructions in this basic block (in execution order)
72 pub instructions: Vec<DecodedInstruction>,
73 /// Successor basic block IDs (targets of branches)
74 /// Uses SmallVec with inline capacity for 2 successors (most blocks have ≤2)
75 /// Typical cases: if-then-else (2 successors), loop (1-2 successors), fall-through (1 successor)
76 pub successors: SmallVec<[u32; 2]>,
77 /// Predecessor basic block IDs (blocks that branch to this block)
78 /// Uses SmallVec with inline capacity for 2 predecessors (most blocks have ≤2)
79 pub predecessors: SmallVec<[u32; 2]>,
80}
81
82/// Edge in the control flow graph.
83///
84/// # Memory Optimization
85/// - `from` and `to`: Use `u32` instead of `usize` to save 4 bytes each on 64-bit systems
86/// - `edge_type`: Uses `#[repr(u8)]` enum (1 byte instead of 4-8 bytes)
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88#[repr(C)] // Ensure C-compatible layout
89pub struct Edge {
90 /// Source basic block ID
91 pub from: u32,
92 /// Target basic block ID
93 pub to: u32,
94 /// Type of edge (determines control flow semantics)
95 pub edge_type: EdgeType,
96}
97
98/// Type of control flow edge.
99///
100/// # Memory Optimization
101/// Uses `#[repr(u8)]` to reduce size from default enum size (4-8 bytes) to 1 byte.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103#[repr(u8)] // Save 3-7 bytes per enum
104pub enum EdgeType {
105 /// Unconditional branch (always taken)
106 Unconditional = 0,
107 /// Conditional branch - true path (condition is true)
108 ConditionalTrue = 1,
109 /// Conditional branch - false path (condition is false)
110 ConditionalFalse = 2,
111 /// Function call edge (caller -> callee)
112 Call = 3,
113 /// Return edge (callee -> caller)
114 Return = 4,
115}
116
117/// Loop information in the control flow graph.
118///
119/// # Memory Optimization
120/// - `header`: Uses `u32` for block ID
121/// - `back_edges`: Uses `SmallVec` for small loops (most loops have 1-2 back edges)
122/// - `body`: Uses `BitVec` for efficient membership testing (instead of `HashSet<usize>`)
123/// - Saves memory: 1 bit per block instead of 8 bytes (pointer) + overhead
124/// - Faster membership tests: O(1) bit access vs O(1) hash lookup (but better cache locality)
125/// - `exits`: Uses `SmallVec` for small exit lists (most loops have 1-2 exits)
126#[derive(Debug, Clone)]
127pub struct Loop {
128 /// Loop header block ID (entry point of loop)
129 pub header: u32,
130 /// Back edges (edges from loop body to header)
131 /// Uses SmallVec - most loops have 1-2 back edges
132 pub back_edges: SmallVec<[(u32, u32); 2]>,
133 /// Loop body (set of blocks in the loop)
134 /// Uses BitVec for efficient membership testing and memory savings
135 /// 1 bit per block instead of 8 bytes (pointer) + hash table overhead
136 pub body: BitVec<u32>,
137 /// Loop exit blocks (blocks that exit the loop)
138 /// Uses SmallVec - most loops have 1-2 exits
139 pub exits: SmallVec<[u32; 2]>,
140}
141
142/// Control flow analyzer for building and analyzing CFGs.
143pub struct ControlFlowAnalyzer;
144
145impl ControlFlowAnalyzer {
146 /// Build a control flow graph from a sequence of instructions.
147 ///
148 /// # Algorithm
149 /// 1. **Identify block boundaries**: Entry points, branch targets, and instructions after branches
150 /// 2. **Build basic blocks**: Group instructions into blocks with single entry/exit
151 /// 3. **Identify edges**: Connect blocks based on branch targets and fall-through
152 ///
153 /// # Arguments
154 /// * `instructions` - Sequence of decoded PowerPC instructions
155 /// * `entry_address` - Entry address of the function (first instruction address)
156 ///
157 /// # Returns
158 /// `Result<ControlFlowGraph>` - Constructed control flow graph
159 ///
160 /// # Errors
161 /// Returns error if CFG construction fails (invalid addresses, malformed instructions)
162 ///
163 /// # Examples
164 /// ```rust
165 /// let instructions = vec![/* decoded instructions */];
166 /// let cfg = ControlFlowAnalyzer::build_cfg(&instructions, 0x80000000)?;
167 /// ```
168 #[inline] // May be called frequently, but function is large - let compiler decide
169 pub fn build_cfg(
170 instructions: &[DecodedInstruction],
171 entry_address: u32,
172 ) -> Result<ControlFlowGraph> {
173 let mut nodes: Vec<BasicBlock> = Vec::new();
174 let mut edges: Vec<Edge> = Vec::new();
175 let mut address_to_block: HashMap<u32, u32> = HashMap::new();
176 let mut block_id: u32 = 0u32;
177
178 // First pass: identify basic block boundaries
179 // Block boundaries occur at:
180 // 1. Function entry point
181 // 2. Branch targets
182 // 3. Instructions immediately after branches (fall-through)
183 let mut block_starts: std::collections::HashSet<u32> = std::collections::HashSet::new();
184 block_starts.insert(entry_address);
185
186 let mut current_address: u32 = entry_address;
187 for inst in instructions.iter() {
188 // Branch targets start new blocks
189 if let Some(target) = Self::get_branch_target(inst) {
190 block_starts.insert(target);
191 // Next instruction after branch also starts new block (fall-through)
192 if current_address.wrapping_add(4) < u32::MAX {
193 block_starts.insert(current_address.wrapping_add(4));
194 }
195 }
196 current_address = current_address.wrapping_add(4); // PowerPC instructions are 4 bytes
197 }
198
199 // Second pass: build basic blocks
200 let mut current_block: Option<BasicBlock> = None;
201 let mut current_address: u32 = entry_address;
202
203 for inst in instructions.iter() {
204 if block_starts.contains(¤t_address) {
205 // Start new block
206 if let Some(block) = current_block.take() {
207 let block_idx: u32 = nodes.len() as u32;
208 address_to_block.insert(block.start_address, block_idx);
209 nodes.push(block);
210 }
211 current_block = Some(BasicBlock {
212 id: block_id,
213 start_address: current_address,
214 end_address: current_address,
215 instructions: vec![inst.clone()],
216 successors: SmallVec::new(),
217 predecessors: SmallVec::new(),
218 });
219 block_id = block_id.wrapping_add(1);
220 } else if let Some(ref mut block) = current_block {
221 block.instructions.push(inst.clone());
222 block.end_address = current_address;
223 }
224
225 current_address = current_address.wrapping_add(4); // PowerPC instructions are 4 bytes
226 }
227
228 // Add final block if exists
229 if let Some(block) = current_block {
230 let block_idx: u32 = nodes.len() as u32;
231 address_to_block.insert(block.start_address, block_idx);
232 nodes.push(block);
233 }
234
235 // Third pass: identify edges
236 // Collect updates to apply after iteration (avoids borrow checker issues)
237 let mut successor_updates: Vec<(usize, u32)> = Vec::new();
238 let mut predecessor_updates: Vec<(usize, u32)> = Vec::new();
239
240 for (block_idx, block) in nodes.iter().enumerate() {
241 let block_idx_u32: u32 = block_idx as u32;
242 if let Some(last_inst) = block.instructions.last() {
243 if let Some(target) = Self::get_branch_target(last_inst) {
244 if let Some(&target_block) = address_to_block.get(&target) {
245 edges.push(Edge {
246 from: block_idx_u32,
247 to: target_block,
248 edge_type: EdgeType::Unconditional,
249 });
250 // Queue updates for successors and predecessors
251 successor_updates.push((block_idx, target_block));
252 predecessor_updates.push((target_block as usize, block_idx_u32));
253 }
254 } else {
255 // Fall-through edge (no explicit branch, continue to next block)
256 if block_idx + 1 < nodes.len() {
257 let next_block_id: u32 = (block_idx + 1) as u32;
258 edges.push(Edge {
259 from: block_idx_u32,
260 to: next_block_id,
261 edge_type: EdgeType::Unconditional,
262 });
263 successor_updates.push((block_idx, next_block_id));
264 predecessor_updates.push((block_idx + 1, block_idx_u32));
265 }
266 }
267 }
268 }
269
270 // Apply deferred updates
271 for (block_idx, successor) in successor_updates {
272 if let Some(block) = nodes.get_mut(block_idx) {
273 if !block.successors.contains(&successor) {
274 block.successors.push(successor);
275 }
276 }
277 }
278 for (block_idx, predecessor) in predecessor_updates {
279 if let Some(block) = nodes.get_mut(block_idx) {
280 if !block.predecessors.contains(&predecessor) {
281 block.predecessors.push(predecessor);
282 }
283 }
284 }
285
286 Ok(ControlFlowGraph {
287 nodes,
288 edges,
289 entry_block: 0u32,
290 })
291 }
292
293 /// Extract branch target address from a branch instruction.
294 ///
295 /// # Arguments
296 /// * `inst` - Decoded instruction (should be a branch instruction)
297 ///
298 /// # Returns
299 /// `Option<u32>` - Branch target address if instruction is a branch, None otherwise
300 ///
301 /// # Algorithm
302 /// Checks if instruction is a branch and extracts target from operands:
303 /// - Address operand: direct address
304 /// - Immediate32 operand: relative offset (would need current PC to compute absolute)
305 /// - Register operand: indirect branch (would need register value)
306 #[inline] // Hot path - called for every branch instruction
307 fn get_branch_target(inst: &DecodedInstruction) -> Option<u32> {
308 // Extract branch target from instruction
309 if matches!(inst.instruction.instruction_type, crate::recompiler::decoder::InstructionType::Branch) {
310 if let Some(Operand::Address(addr)) = inst.instruction.operands.first() {
311 return Some(*addr);
312 }
313 if let Some(Operand::Immediate32(imm)) = inst.instruction.operands.first() {
314 // Relative branch - would need current PC to compute absolute address
315 // For now, return None (caller should track PC)
316 return None;
317 }
318 if let Some(Operand::Register(0)) = inst.instruction.operands.first() {
319 // Branch to link register - would need LR value
320 return None;
321 }
322 }
323 None
324 }
325
326 /// Detect loops in the control flow graph using depth-first search.
327 ///
328 /// # Algorithm
329 /// Uses DFS to find back edges, which indicate loops.
330 /// A back edge is an edge from a node to an ancestor in the DFS tree.
331 ///
332 /// # Arguments
333 /// * `cfg` - Control flow graph to analyze
334 ///
335 /// # Returns
336 /// `Vec<Loop>` - List of detected loops with header, body, and back edges
337 ///
338 /// # Examples
339 /// ```rust
340 /// let loops = ControlFlowAnalyzer::detect_loops(&cfg);
341 /// for loop_info in loops {
342 /// println!("Loop header: block {}", loop_info.header);
343 /// }
344 /// ```
345 #[inline] // May be called frequently
346 pub fn detect_loops(cfg: &ControlFlowGraph) -> Vec<Loop> {
347 let mut loops: Vec<Loop> = Vec::new();
348 let mut visited: BitVec<u32> = bitvec![u32, Lsb0; 0; cfg.nodes.len()];
349 let mut in_stack: BitVec<u32> = bitvec![u32, Lsb0; 0; cfg.nodes.len()];
350
351 // Use DFS to find back edges (indicates loops)
352 Self::dfs_loops(cfg, 0u32, &mut visited, &mut in_stack, &mut loops);
353
354 loops
355 }
356
357 /// Depth-first search helper for loop detection.
358 ///
359 /// # Algorithm
360 /// Performs DFS and identifies back edges (edges to ancestors in DFS tree).
361 /// When a back edge is found, a loop is detected.
362 ///
363 /// # Arguments
364 /// * `cfg` - Control flow graph
365 /// * `node` - Current node being visited
366 /// * `visited` - Bit vector of visited nodes (for efficiency)
367 /// * `in_stack` - Bit vector of nodes in current DFS path (for back edge detection)
368 /// * `loops` - Output vector of detected loops
369 #[inline] // Recursive function - let compiler decide inlining
370 fn dfs_loops(
371 cfg: &ControlFlowGraph,
372 node: u32,
373 visited: &mut BitVec<u32>,
374 in_stack: &mut BitVec<u32>,
375 loops: &mut Vec<Loop>,
376 ) {
377 let node_idx: usize = node as usize;
378 if node_idx >= visited.len() {
379 return;
380 }
381
382 visited.set(node_idx, true);
383 in_stack.set(node_idx, true);
384
385 if let Some(block) = cfg.nodes.get(node_idx) {
386 for &succ in block.successors.iter() {
387 let succ_idx: usize = succ as usize;
388 if succ_idx >= visited.len() {
389 continue;
390 }
391
392 if !visited[succ_idx] {
393 Self::dfs_loops(cfg, succ, visited, in_stack, loops);
394 } else if in_stack[succ_idx] {
395 // Back edge found - loop detected
396 let loop_header: u32 = succ;
397 let mut loop_body: BitVec<u32> = bitvec![u32, Lsb0; 0; cfg.nodes.len()];
398 loop_body.set(loop_header as usize, true);
399 loop_body.set(node_idx, true);
400
401 loops.push(Loop {
402 header: loop_header,
403 back_edges: SmallVec::from_slice(&[(node, loop_header)]),
404 body: loop_body,
405 exits: SmallVec::new(),
406 });
407 }
408 }
409 }
410
411 in_stack.set(node_idx, false);
412 }
413
414 /// Analyze function calls in the control flow graph.
415 ///
416 /// # Algorithm
417 /// Scans all instructions in all blocks for branch-with-link instructions (bl, bla),
418 /// which indicate function calls.
419 ///
420 /// # Arguments
421 /// * `cfg` - Control flow graph to analyze
422 ///
423 /// # Returns
424 /// `Vec<FunctionCall>` - List of function calls with caller, callee, and location
425 ///
426 /// # Examples
427 /// ```rust
428 /// let calls = ControlFlowAnalyzer::analyze_function_calls(&cfg);
429 /// for call in calls {
430 /// println!("Call from block {} to 0x{:08X}", call.caller_block, call.callee_address);
431 /// }
432 /// ```
433 #[inline] // May be called frequently
434 pub fn analyze_function_calls(cfg: &ControlFlowGraph) -> Vec<FunctionCall> {
435 let mut calls: Vec<FunctionCall> = Vec::new();
436
437 for block in cfg.nodes.iter() {
438 let mut instruction_address: u32 = block.start_address;
439 for inst in block.instructions.iter() {
440 if Self::is_function_call(inst) {
441 if let Some(target) = Self::get_branch_target(inst) {
442 calls.push(FunctionCall {
443 caller_block: block.id,
444 callee_address: target,
445 instruction_address,
446 });
447 }
448 }
449 instruction_address = instruction_address.wrapping_add(4); // PowerPC instructions are 4 bytes
450 }
451 }
452
453 calls
454 }
455
456 /// Check if an instruction is a function call.
457 ///
458 /// # Algorithm
459 /// A function call is a branch instruction with the link bit set (bl, bla).
460 /// The link bit causes the return address to be saved in the link register (LR).
461 ///
462 /// # Arguments
463 /// * `inst` - Decoded instruction to check
464 ///
465 /// # Returns
466 /// `bool` - True if instruction is a function call, false otherwise
467 #[inline] // Hot path - called for every instruction
468 fn is_function_call(inst: &DecodedInstruction) -> bool {
469 // Check if instruction is a branch with link (bl, bla)
470 matches!(inst.instruction.instruction_type, crate::recompiler::decoder::InstructionType::Branch)
471 && (inst.raw & 1u32) != 0u32 // Link bit set
472 }
473}
474
475/// Function call information.
476///
477/// # Memory Optimization
478/// - `caller_block`: Uses `u32` instead of `usize` to save 4 bytes on 64-bit systems
479/// - `callee_address` and `instruction_address`: Already `u32` (optimal)
480#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
481#[repr(C)] // Ensure C-compatible layout
482pub struct FunctionCall {
483 /// Caller basic block ID
484 pub caller_block: u32,
485 /// Callee function address
486 pub callee_address: u32,
487 /// Instruction address where call occurs
488 pub instruction_address: u32,
489}