Skip to main content

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(&current_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}