Skip to main content

cairo_lang_lowering/analysis/
backward.rs

1//! This module introduces the BackAnalysis utility that allows writing analyzers that go backwards
2//! in the flow of the program, on a Lowered representation.
3
4use std::collections::HashMap;
5
6use crate::analysis::Analyzer;
7use crate::{BlockEnd, BlockId, Lowered};
8
9/// Main analysis type that allows traversing the flow backwards.
10pub struct BackAnalysis<'db, 'a, TAnalyzer: Analyzer<'db, 'a>> {
11    lowered: &'a Lowered<'db>,
12    pub analyzer: TAnalyzer,
13    block_info: HashMap<BlockId, TAnalyzer::Info>,
14}
15impl<'db, 'a, TAnalyzer: Analyzer<'db, 'a>> BackAnalysis<'db, 'a, TAnalyzer> {
16    /// Creates a new BackAnalysis instance.
17    pub fn new(lowered: &'a Lowered<'db>, analyzer: TAnalyzer) -> Self {
18        Self { lowered, analyzer, block_info: Default::default() }
19    }
20    /// Gets the analysis info for the entire function.
21    pub fn get_root_info(&mut self) -> TAnalyzer::Info {
22        let mut dfs_stack = vec![BlockId::root()];
23        while let Some(block_id) = dfs_stack.last() {
24            let end = &self.lowered.blocks[*block_id].end;
25            if !self.add_missing_dependency_blocks(&mut dfs_stack, end) {
26                self.calc_block_info(dfs_stack.pop().unwrap());
27            }
28        }
29        self.block_info.remove(&BlockId::root()).unwrap()
30    }
31
32    /// Gets the analysis info from the start of a block.
33    fn calc_block_info(&mut self, block_id: BlockId) {
34        let mut info = self.get_end_info(block_id);
35
36        // Go through statements backwards, and update info.
37        for (i, stmt) in self.lowered.blocks[block_id].statements.iter().enumerate().rev() {
38            let statement_location = (block_id, i);
39            self.analyzer.visit_stmt(&mut info, statement_location, stmt);
40        }
41
42        self.analyzer.visit_block_start(&mut info, block_id, &self.lowered.blocks[block_id]);
43
44        // Store result.
45        self.block_info.insert(block_id, info);
46    }
47
48    /// Adds to the DFS stack the dependent blocks that are not yet in cache - returns whether if
49    /// there are any such blocks.
50    fn add_missing_dependency_blocks(
51        &self,
52        dfs_stack: &mut Vec<BlockId>,
53        block_end: &'a BlockEnd<'_>,
54    ) -> bool {
55        match block_end {
56            BlockEnd::NotSet => unreachable!(),
57            BlockEnd::Goto(target_block_id, _)
58                if !self.block_info.contains_key(target_block_id) =>
59            {
60                dfs_stack.push(*target_block_id);
61                true
62            }
63            BlockEnd::Goto(_, _) | BlockEnd::Return(..) | BlockEnd::Panic(_) => false,
64            BlockEnd::Match { info } => {
65                let mut missing_cache = false;
66                for arm in info.arms() {
67                    if !self.block_info.contains_key(&arm.block_id) {
68                        dfs_stack.push(arm.block_id);
69                        missing_cache = true;
70                    }
71                }
72                missing_cache
73            }
74        }
75    }
76
77    /// Gets the analysis info from the block's end onwards.
78    fn get_end_info(&mut self, block_id: BlockId) -> TAnalyzer::Info {
79        let block_end = &self.lowered.blocks[block_id].end;
80        let statement_location = (block_id, self.lowered.blocks[block_id].statements.len());
81        match block_end {
82            BlockEnd::NotSet => unreachable!(),
83            BlockEnd::Goto(target_block_id, remapping) => {
84                let mut info = self.block_info[target_block_id].clone();
85                self.analyzer.visit_goto(
86                    &mut info,
87                    statement_location,
88                    *target_block_id,
89                    remapping,
90                );
91                info
92            }
93            BlockEnd::Return(vars, _location) => {
94                self.analyzer.info_from_return(statement_location, vars)
95            }
96            BlockEnd::Panic(data) => self.analyzer.info_from_panic(statement_location, data),
97            BlockEnd::Match { info } => {
98                // Can remove the block since match blocks do not merge.
99                let arm_infos =
100                    info.arms().iter().map(|arm| self.block_info.remove(&arm.block_id).unwrap());
101                self.analyzer.merge_match(statement_location, info, arm_infos)
102            }
103        }
104    }
105}