Skip to main content

torsh_jit/graph/
control_flow.rs

1//! Control flow analysis for computation graphs
2
3use crate::graph::core::{ComputationGraph, NodeId};
4use crate::graph::operations::Operation;
5use crate::JitResult;
6use std::collections::{HashMap, HashSet, VecDeque};
7
8/// Control flow analysis for identifying loops, conditions, and dominance relationships
9#[derive(Debug, Clone)]
10pub struct ControlFlowAnalysis {
11    /// Dominator tree: each node maps to its immediate dominator
12    pub dominators: HashMap<NodeId, Option<NodeId>>,
13
14    /// Dominated nodes: each node maps to the set of nodes it dominates
15    pub dominated: HashMap<NodeId, HashSet<NodeId>>,
16
17    /// Loop information
18    pub loops: Vec<LoopInfo>,
19
20    /// Conditional blocks
21    pub conditionals: Vec<ConditionalInfo>,
22
23    /// Statistics about the control flow
24    pub stats: ControlFlowStats,
25}
26
27impl ControlFlowAnalysis {
28    /// Create a new control flow analysis
29    pub fn new() -> Self {
30        Self {
31            dominators: HashMap::new(),
32            dominated: HashMap::new(),
33            loops: Vec::new(),
34            conditionals: Vec::new(),
35            stats: ControlFlowStats::default(),
36        }
37    }
38
39    /// Analyze a computation graph for control flow patterns
40    pub fn analyze(graph: &ComputationGraph) -> JitResult<Self> {
41        let mut analysis = Self::new();
42
43        // Compute dominator tree
44        analysis.compute_dominators(graph)?;
45
46        // Detect loops
47        analysis.detect_loops(graph)?;
48
49        // Detect conditionals
50        analysis.detect_conditionals(graph)?;
51
52        // Compute statistics
53        analysis.compute_statistics(graph);
54
55        Ok(analysis)
56    }
57
58    /// Compute dominator relationships
59    fn compute_dominators(&mut self, graph: &ComputationGraph) -> JitResult<()> {
60        // Simple dominator computation - in practice would use more sophisticated algorithms
61        let nodes: Vec<NodeId> = graph.nodes().map(|(id, _)| id).collect();
62
63        // Initialize dominators
64        for &node in &nodes {
65            self.dominators.insert(node, None);
66            self.dominated.insert(node, HashSet::new());
67        }
68
69        // For each node, find nodes that must be traversed to reach it from any input
70        for &node in &nodes {
71            let mut dominates = HashSet::new();
72
73            // Simple approximation: a node dominates another if all paths to the second
74            // node must pass through the first node
75            for &other_node in &nodes {
76                if node != other_node && self.dominates_node(graph, node, other_node) {
77                    dominates.insert(other_node);
78
79                    // Set immediate dominator if none exists or this is closer
80                    if self
81                        .dominators
82                        .get(&other_node)
83                        .expect("dominator entry should exist")
84                        .is_none()
85                    {
86                        self.dominators.insert(other_node, Some(node));
87                    }
88                }
89            }
90
91            self.dominated.insert(node, dominates);
92        }
93
94        Ok(())
95    }
96
97    /// Check if one node dominates another (simplified check)
98    fn dominates_node(&self, graph: &ComputationGraph, dominator: NodeId, node: NodeId) -> bool {
99        // This is a simplified domination check
100        // In practice, would use proper dominator tree algorithms
101
102        if dominator == node {
103            return true;
104        }
105
106        // Check if dominator is on all paths from inputs to node
107        let inputs = &graph.inputs;
108        if inputs.is_empty() {
109            return false;
110        }
111
112        for &input in inputs {
113            if !self.path_contains_node(graph, input, node, dominator) {
114                return false;
115            }
116        }
117
118        true
119    }
120
121    /// Check if a path from start to end contains a specific node
122    fn path_contains_node(
123        &self,
124        graph: &ComputationGraph,
125        start: NodeId,
126        end: NodeId,
127        check_node: NodeId,
128    ) -> bool {
129        if start == end {
130            return start == check_node;
131        }
132
133        let mut visited = HashSet::new();
134        let mut queue = VecDeque::new();
135        queue.push_back(start);
136
137        while let Some(current) = queue.pop_front() {
138            if visited.contains(&current) {
139                continue;
140            }
141            visited.insert(current);
142
143            if current == end {
144                return visited.contains(&check_node);
145            }
146
147            for neighbor in graph.get_node_outputs(current) {
148                if !visited.contains(&neighbor) {
149                    queue.push_back(neighbor);
150                }
151            }
152        }
153
154        false
155    }
156
157    /// Detect loop structures in the graph
158    fn detect_loops(&mut self, graph: &ComputationGraph) -> JitResult<()> {
159        let nodes: Vec<NodeId> = graph.nodes().map(|(id, _)| id).collect();
160
161        for &node in &nodes {
162            if let Some(node_data) = graph.get_node(node) {
163                match &node_data.operation {
164                    Operation::While(while_info) => {
165                        let loop_info = LoopInfo {
166                            header: node,
167                            condition: while_info.condition,
168                            body_nodes: self.find_loop_body_nodes(graph, while_info.body),
169                            loop_type: LoopType::While,
170                            max_iterations: while_info.max_iterations,
171                        };
172                        self.loops.push(loop_info);
173                    }
174                    Operation::For(for_info) => {
175                        let loop_info = LoopInfo {
176                            header: node,
177                            condition: for_info.start, // Simplified
178                            body_nodes: self.find_loop_body_nodes(graph, for_info.body),
179                            loop_type: LoopType::For,
180                            max_iterations: None, // Could be computed from for loop bounds
181                        };
182                        self.loops.push(loop_info);
183                    }
184                    _ => {}
185                }
186            }
187        }
188
189        Ok(())
190    }
191
192    /// Find all nodes that belong to a loop body
193    fn find_loop_body_nodes(
194        &self,
195        graph: &ComputationGraph,
196        body_start: NodeId,
197    ) -> HashSet<NodeId> {
198        let mut body_nodes = HashSet::new();
199        let mut queue = VecDeque::new();
200        queue.push_back(body_start);
201
202        while let Some(node) = queue.pop_front() {
203            if body_nodes.contains(&node) {
204                continue;
205            }
206            body_nodes.insert(node);
207
208            // Add successors that are part of the loop body
209            for successor in graph.get_node_outputs(node) {
210                if let Some(successor_data) = graph.get_node(successor) {
211                    match &successor_data.operation {
212                        Operation::Break | Operation::Continue => {
213                            // Don't traverse beyond loop control statements
214                            body_nodes.insert(successor);
215                        }
216                        _ => {
217                            if !body_nodes.contains(&successor) {
218                                queue.push_back(successor);
219                            }
220                        }
221                    }
222                }
223            }
224        }
225
226        body_nodes
227    }
228
229    /// Detect conditional structures in the graph
230    fn detect_conditionals(&mut self, graph: &ComputationGraph) -> JitResult<()> {
231        let nodes: Vec<NodeId> = graph.nodes().map(|(id, _)| id).collect();
232
233        for &node in &nodes {
234            if let Some(node_data) = graph.get_node(node) {
235                if let Operation::If(if_info) = &node_data.operation {
236                    let then_nodes = self.find_branch_nodes(graph, if_info.then_block);
237                    let else_nodes = if let Some(else_block) = if_info.else_block {
238                        self.find_branch_nodes(graph, else_block)
239                    } else {
240                        HashSet::new()
241                    };
242
243                    let conditional_info = ConditionalInfo {
244                        condition_node: if_info.condition,
245                        then_nodes,
246                        else_nodes,
247                        merge_point: if_info.merge_point,
248                    };
249                    self.conditionals.push(conditional_info);
250                }
251            }
252        }
253
254        Ok(())
255    }
256
257    /// Find all nodes that belong to a conditional branch
258    fn find_branch_nodes(&self, graph: &ComputationGraph, branch_start: NodeId) -> HashSet<NodeId> {
259        let mut branch_nodes = HashSet::new();
260        let mut queue = VecDeque::new();
261        queue.push_back(branch_start);
262
263        while let Some(node) = queue.pop_front() {
264            if branch_nodes.contains(&node) {
265                continue;
266            }
267            branch_nodes.insert(node);
268
269            // Add successors until we reach a merge point or loop back
270            for successor in graph.get_node_outputs(node) {
271                if let Some(successor_data) = graph.get_node(successor) {
272                    match &successor_data.operation {
273                        Operation::Merge(_) => {
274                            // Stop at merge points
275                            break;
276                        }
277                        _ => {
278                            if !branch_nodes.contains(&successor) {
279                                queue.push_back(successor);
280                            }
281                        }
282                    }
283                }
284            }
285        }
286
287        branch_nodes
288    }
289
290    /// Compute control flow statistics
291    fn compute_statistics(&mut self, graph: &ComputationGraph) {
292        let mut loop_count = 0;
293        let mut conditional_count = 0;
294        let mut block_count = 0;
295
296        for (_, node) in graph.nodes() {
297            match &node.operation {
298                Operation::While(_) | Operation::For(_) => loop_count += 1,
299                Operation::If(_) => conditional_count += 1,
300                Operation::Block(_) => block_count += 1,
301                _ => {}
302            }
303        }
304
305        self.stats = ControlFlowStats {
306            total_nodes: graph.node_count(),
307            loop_count,
308            conditional_count,
309            block_count,
310            max_loop_depth: self.compute_max_loop_depth(),
311            max_conditional_depth: self.compute_max_conditional_depth(),
312        };
313    }
314
315    /// Compute maximum loop nesting depth
316    fn compute_max_loop_depth(&self) -> usize {
317        // Simplified computation - would need more sophisticated analysis for nested loops
318        if self.loops.is_empty() {
319            0
320        } else {
321            1 // For now, assume max depth of 1
322        }
323    }
324
325    /// Compute maximum conditional nesting depth
326    fn compute_max_conditional_depth(&self) -> usize {
327        // Simplified computation - would need more sophisticated analysis for nested conditionals
328        if self.conditionals.is_empty() {
329            0
330        } else {
331            1 // For now, assume max depth of 1
332        }
333    }
334
335    /// Check if a node is inside a loop
336    pub fn is_in_loop(&self, node: NodeId) -> bool {
337        self.loops
338            .iter()
339            .any(|loop_info| loop_info.body_nodes.contains(&node))
340    }
341
342    /// Check if a node is inside a conditional branch
343    pub fn is_in_conditional(&self, node: NodeId) -> bool {
344        self.conditionals.iter().any(|cond_info| {
345            cond_info.then_nodes.contains(&node) || cond_info.else_nodes.contains(&node)
346        })
347    }
348
349    /// Get the loop that contains a given node
350    pub fn containing_loop(&self, node: NodeId) -> Option<&LoopInfo> {
351        self.loops
352            .iter()
353            .find(|loop_info| loop_info.body_nodes.contains(&node))
354    }
355
356    /// Get the conditional that contains a given node
357    pub fn containing_conditional(&self, node: NodeId) -> Option<&ConditionalInfo> {
358        self.conditionals.iter().find(|cond_info| {
359            cond_info.then_nodes.contains(&node) || cond_info.else_nodes.contains(&node)
360        })
361    }
362}
363
364impl Default for ControlFlowAnalysis {
365    fn default() -> Self {
366        Self::new()
367    }
368}
369
370/// Information about a loop in the control flow
371#[derive(Debug, Clone)]
372pub struct LoopInfo {
373    /// Header node of the loop
374    pub header: NodeId,
375    /// Condition node
376    pub condition: NodeId,
377    /// Nodes that are part of the loop body
378    pub body_nodes: HashSet<NodeId>,
379    /// Type of loop
380    pub loop_type: LoopType,
381    /// Maximum number of iterations (if known)
382    pub max_iterations: Option<usize>,
383}
384
385/// Types of loops
386#[derive(Debug, Clone, PartialEq, Eq)]
387pub enum LoopType {
388    While,
389    For,
390    DoWhile,
391}
392
393/// Information about a conditional structure
394#[derive(Debug, Clone)]
395pub struct ConditionalInfo {
396    /// The condition node
397    pub condition_node: NodeId,
398    /// Nodes in the 'then' branch
399    pub then_nodes: HashSet<NodeId>,
400    /// Nodes in the 'else' branch (if any)
401    pub else_nodes: HashSet<NodeId>,
402    /// Merge point where branches reconverge
403    pub merge_point: Option<NodeId>,
404}
405
406/// Statistics about control flow in the graph
407#[derive(Debug, Clone, Default)]
408pub struct ControlFlowStats {
409    /// Total number of nodes in the graph
410    pub total_nodes: usize,
411    /// Number of loops
412    pub loop_count: usize,
413    /// Number of conditionals
414    pub conditional_count: usize,
415    /// Number of block operations
416    pub block_count: usize,
417    /// Maximum loop nesting depth
418    pub max_loop_depth: usize,
419    /// Maximum conditional nesting depth
420    pub max_conditional_depth: usize,
421}