1#![allow(missing_docs)]
2use crate::ast::AstFunction;
11use crate::errors::{AnalysisError, AnalysisResult};
12use petgraph::graph::{DiGraph, NodeIndex};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use tracing::{debug, info};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct BasicBlock {
20 pub id: usize,
22 pub statements: Vec<Statement>,
24 pub dominates: Vec<usize>,
26 pub dominated_by: Vec<usize>,
28 pub is_loop_header: bool,
30 pub is_exit: bool,
32}
33
34impl BasicBlock {
35 pub fn new(id: usize) -> Self {
37 Self {
38 id,
39 statements: Vec::new(),
40 dominates: Vec::new(),
41 dominated_by: Vec::new(),
42 is_loop_header: false,
43 is_exit: false,
44 }
45 }
46
47 pub fn add_statement(&mut self, stmt: Statement) {
49 self.statements.push(stmt);
50 }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub enum Statement {
56 Assignment { target: String, value: String },
58 Branch { condition: String },
60 StateMutation { variable: String, value: String },
62 FunctionCall { function: String, args: Vec<String> },
64 Return { value: Option<String> },
66 Generic { code: String },
68}
69
70#[derive(Debug, Clone)]
72pub struct ControlFlowGraph {
73 graph: DiGraph<BasicBlock, String>,
75 block_map: HashMap<usize, NodeIndex>,
77 entry_block: usize,
79 exit_blocks: Vec<usize>,
81 next_id: usize,
83}
84
85impl ControlFlowGraph {
86 pub fn new() -> Self {
88 let mut cfg = Self {
89 graph: DiGraph::new(),
90 block_map: HashMap::new(),
91 entry_block: 0,
92 exit_blocks: Vec::new(),
93 next_id: 0,
94 };
95
96 let entry = cfg.new_block();
98 cfg.entry_block = entry;
99
100 cfg
101 }
102
103 pub fn new_block(&mut self) -> usize {
105 let id = self.next_id;
106 self.next_id += 1;
107
108 let block = BasicBlock::new(id);
109 let node_idx = self.graph.add_node(block);
110 self.block_map.insert(id, node_idx);
111
112 id
113 }
114
115 pub fn add_edge(
117 &mut self,
118 from: usize,
119 to: usize,
120 label: impl Into<String>,
121 ) -> AnalysisResult<()> {
122 let from_idx = self
123 .block_map
124 .get(&from)
125 .copied()
126 .ok_or_else(|| AnalysisError::cfg("Block not found"))?;
127
128 let to_idx = self
129 .block_map
130 .get(&to)
131 .copied()
132 .ok_or_else(|| AnalysisError::cfg("Target block not found"))?;
133
134 self.graph.add_edge(from_idx, to_idx, label.into());
135 Ok(())
136 }
137
138 pub fn add_statement(&mut self, block_id: usize, stmt: Statement) -> AnalysisResult<()> {
140 let node_idx = self
141 .block_map
142 .get(&block_id)
143 .copied()
144 .ok_or_else(|| AnalysisError::cfg("Block not found"))?;
145
146 if let Some(block) = self.graph.node_weight_mut(node_idx) {
147 block.add_statement(stmt);
148 Ok(())
149 } else {
150 Err(AnalysisError::cfg("Failed to get block"))
151 }
152 }
153
154 pub fn mark_exit(&mut self, block_id: usize) -> AnalysisResult<()> {
156 let node_idx = self
157 .block_map
158 .get(&block_id)
159 .copied()
160 .ok_or_else(|| AnalysisError::cfg("Block not found"))?;
161
162 if let Some(block) = self.graph.node_weight_mut(node_idx) {
163 block.is_exit = true;
164 self.exit_blocks.push(block_id);
165 Ok(())
166 } else {
167 Err(AnalysisError::cfg("Failed to get block"))
168 }
169 }
170
171 pub fn compute_dominance(&mut self) -> AnalysisResult<()> {
173 info!("Computing dominance relationships for CFG");
174
175 self.compute_dominators()?;
177
178 let dominators: HashMap<usize, Vec<usize>> = self
180 .block_map
181 .iter()
182 .map(|(&id, &idx)| {
183 let dominates = self
184 .graph
185 .node_weight(idx)
186 .map(|b| b.dominates.clone())
187 .unwrap_or_default();
188 (id, dominates)
189 })
190 .collect();
191
192 for (dominator_id, dominated_blocks) in dominators {
193 for dominated_id in dominated_blocks {
194 let node_idx = self
195 .block_map
196 .get(&dominated_id)
197 .copied()
198 .ok_or_else(|| AnalysisError::cfg("Block not found"))?;
199
200 if let Some(block) = self.graph.node_weight_mut(node_idx) {
201 block.dominated_by.push(dominator_id);
202 }
203 }
204 }
205
206 Ok(())
207 }
208
209 fn compute_dominators(&mut self) -> AnalysisResult<()> {
211 let blocks: Vec<usize> = self.block_map.keys().copied().collect();
212
213 if blocks.is_empty() {
214 return Ok(());
215 }
216
217 let mut dominators: HashMap<usize, Vec<usize>> =
219 blocks.iter().map(|&id| (id, blocks.clone())).collect();
220
221 if let Some(entry_doms) = dominators.get_mut(&self.entry_block) {
223 entry_doms.clear();
224 entry_doms.push(self.entry_block);
225 }
226
227 let mut changed = true;
229 let mut iterations = 0;
230 const MAX_ITERATIONS: usize = 100;
231
232 while changed && iterations < MAX_ITERATIONS {
233 changed = false;
234 iterations += 1;
235
236 for &block_id in &blocks {
237 if block_id == self.entry_block {
238 continue;
239 }
240
241 let node_idx = self
243 .block_map
244 .get(&block_id)
245 .copied()
246 .ok_or_else(|| AnalysisError::cfg("Block not found"))?;
247
248 let mut pred_dominators: Option<Vec<usize>> = None;
249
250 for pred_idx in self
251 .graph
252 .neighbors_directed(node_idx, petgraph::Direction::Incoming)
253 {
254 let pred_id = self
255 .graph
256 .node_weight(pred_idx)
257 .map(|b| b.id)
258 .ok_or_else(|| AnalysisError::cfg("Predecessor not found"))?;
259
260 let pred_doms = dominators
261 .get(&pred_id)
262 .cloned()
263 .ok_or_else(|| AnalysisError::cfg("Predecessor dominators not found"))?;
264
265 pred_dominators = match pred_dominators {
266 None => Some(pred_doms),
267 Some(current) => {
268 let intersection: Vec<_> = current
269 .iter()
270 .filter(|d| pred_doms.contains(d))
271 .copied()
272 .collect();
273 Some(intersection)
274 }
275 };
276 }
277
278 if let Some(mut pred_doms) = pred_dominators {
280 pred_doms.push(block_id);
281 pred_doms.sort();
282 pred_doms.dedup();
283
284 if let Some(old_doms) = dominators.get_mut(&block_id) {
285 if *old_doms != pred_doms {
286 *old_doms = pred_doms;
287 changed = true;
288 }
289 }
290 }
291 }
292 }
293
294 debug!(
295 "Dominance computation converged after {} iterations",
296 iterations
297 );
298
299 for (block_id, doms) in dominators {
301 if let Some(node_idx) = self.block_map.get(&block_id).copied() {
302 if let Some(block) = self.graph.node_weight_mut(node_idx) {
303 block.dominates = doms.into_iter().filter(|&id| id != block_id).collect();
304 }
305 }
306 }
307
308 Ok(())
309 }
310
311 pub fn dominates(&self, a: usize, b: usize) -> bool {
313 self.block_map
314 .get(&a)
315 .and_then(|&idx| self.graph.node_weight(idx))
316 .map(|block| block.dominates.contains(&b))
317 .unwrap_or(false)
318 }
319
320 pub fn detect_loops(&mut self) -> AnalysisResult<Vec<Loop>> {
322 info!("Detecting loops in CFG");
323
324 let mut loops = Vec::new();
325
326 let blocks: Vec<usize> = self.block_map.keys().copied().collect();
328
329 for &block_id in &blocks {
330 let node_idx = self
331 .block_map
332 .get(&block_id)
333 .copied()
334 .ok_or_else(|| AnalysisError::cfg("Block not found"))?;
335
336 let successors: Vec<_> = self.graph.neighbors(node_idx).collect();
338
339 for succ_idx in successors {
340 let succ_id = self
341 .graph
342 .node_weight(succ_idx)
343 .map(|b| b.id)
344 .ok_or_else(|| AnalysisError::cfg("Successor not found"))?;
345
346 if self.dominates(succ_id, block_id) {
348 loops.push(Loop {
349 header: succ_id,
350 back_edges: vec![(block_id, succ_id)],
351 body_blocks: vec![succ_id],
352 });
353
354 if let Some(block) = self.graph.node_weight_mut(succ_idx) {
356 block.is_loop_header = true;
357 }
358 }
359 }
360 }
361
362 info!("Found {} loops", loops.len());
363 Ok(loops)
364 }
365
366 pub fn reachable(&self, from: usize) -> AnalysisResult<Vec<usize>> {
368 let start_idx = self
369 .block_map
370 .get(&from)
371 .copied()
372 .ok_or_else(|| AnalysisError::cfg("Block not found"))?;
373
374 let mut reachable = Vec::new();
375 let mut visited = std::collections::HashSet::new();
376 let mut queue = vec![start_idx];
377
378 while let Some(node_idx) = queue.pop() {
379 if !visited.insert(node_idx) {
380 continue;
381 }
382
383 if let Some(block) = self.graph.node_weight(node_idx) {
384 reachable.push(block.id);
385 }
386
387 for succ_idx in self.graph.neighbors(node_idx) {
388 queue.push(succ_idx);
389 }
390 }
391
392 Ok(reachable)
393 }
394
395 pub fn entry(&self) -> usize {
397 self.entry_block
398 }
399
400 pub fn exits(&self) -> Vec<usize> {
402 self.exit_blocks.clone()
403 }
404
405 pub fn block_count(&self) -> usize {
407 self.graph.node_count()
408 }
409}
410
411impl Default for ControlFlowGraph {
412 fn default() -> Self {
413 Self::new()
414 }
415}
416
417#[derive(Debug, Clone)]
419pub struct Loop {
420 pub header: usize,
422 pub back_edges: Vec<(usize, usize)>,
424 pub body_blocks: Vec<usize>,
426}
427
428pub struct CfgBuilder;
430
431impl CfgBuilder {
432 pub fn build(function: &AstFunction) -> AnalysisResult<ControlFlowGraph> {
434 debug!("Building CFG for function '{}'", function.name);
435
436 let mut cfg = ControlFlowGraph::new();
437
438 let entry = cfg.entry();
441 cfg.mark_exit(entry)?;
442
443 cfg.compute_dominance()?;
445
446 cfg.detect_loops()?;
448
449 Ok(cfg)
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456
457 #[test]
458 fn test_cfg_creation() {
459 let mut cfg = ControlFlowGraph::new();
460 let block1 = cfg.entry();
461 let block2 = cfg.new_block();
462 let block3 = cfg.new_block();
463
464 assert_eq!(cfg.block_count(), 3);
465 assert_eq!(block1, 0);
466 assert_eq!(block2, 1);
467 assert_eq!(block3, 2);
468 }
469
470 #[test]
471 fn test_cfg_edges() {
472 let mut cfg = ControlFlowGraph::new();
473 let b1 = cfg.entry();
474 let b2 = cfg.new_block();
475 let b3 = cfg.new_block();
476
477 cfg.add_edge(b1, b2, "true").ok();
478 cfg.add_edge(b1, b3, "false").ok();
479
480 assert_eq!(cfg.block_count(), 3);
481 }
482
483 #[test]
484 fn test_cfg_exit_marking() {
485 let mut cfg = ControlFlowGraph::new();
486 let entry = cfg.entry();
487
488 cfg.mark_exit(entry).ok();
489 assert!(cfg.exits().contains(&entry));
490 }
491}