Skip to main content

cairo_lang_lowering/analysis/
core.rs

1//! Core dataflow analysis traits.
2//!
3//! This module provides a unified trait for both forward and backward dataflow analysis.
4//!
5//! # Design
6//!
7//! The `DataflowAnalyzer` trait provides a layered API:
8//!
9//! 1. **Core methods** (must implement): `initial_info`, `merge`
10//! 2. **Transfer granularity** (choose one):
11//!    - Block-level: override `transfer_block` for coarse-grained analysis
12//!    - Statement-level: override `transfer_stmt` (default `transfer_block` iterates statements)
13//! 3. **Variable tracking** (optional): `transfer_edge` - override if tracking per-variable state
14//!
15//! The Runner's (backward/forward/etc) should handle control flow mechanics automatically:
16//! - Goto with remapping/Match split: calls `transfer_edge`
17//! - Call transfer block for each block in need of processing
18
19use crate::ids::LocationId;
20use crate::{
21    Block, BlockEnd, BlockId, Lowered, MatchArm, MatchInfo, Statement, VarRemapping, VarUsage,
22};
23
24/// Location of a lowering statement inside a block.
25pub type StatementLocation = (BlockId, usize);
26
27/// The direction of dataflow analysis.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum Direction {
30    Forward,
31    Backward,
32}
33
34/// Represents an edge in the control flow graph.
35///
36/// Each variant captures the specific information needed for that edge type,
37/// enabling analyzers to handle variable introductions and remappings.
38#[derive(Debug)]
39pub enum Edge<'db, 'a> {
40    /// A goto edge with variable remapping.
41    Goto { target: BlockId, remapping: &'a VarRemapping<'db> },
42    /// A match arm edge with the arm's introduced variables.
43    MatchArm { arm: &'a MatchArm<'db>, match_info: &'a MatchInfo<'db> },
44    /// A return edge (terminal).
45    Return { vars: &'a [VarUsage<'db>], location: LocationId<'db> },
46    /// A panic edge (terminal).
47    Panic { var: VarUsage<'db> },
48}
49
50/// Unified analyzer trait for dataflow analysis.
51///
52/// Implementors specify the direction via `DIRECTION` and implement the core methods.
53/// The framework specifies the "behaviour" of the dataflow (essentially updates to lattice state
54/// for lattice analysis). Running an analysis is done by a runner (backward/forward/etc) which will
55/// handle control flow and dataflow mechanics.
56///
57/// # Transfer Granularity
58///
59/// You can work at either block or statement level:
60/// - **Block-level**: Override `transfer_block` for coarse-grained analysis
61/// - **Statement-level**: Override `transfer_stmt`; default `transfer_block` iterates statements
62///
63/// # Example (block-level analysis)
64/// ```ignore
65/// impl<'db, 'a> DataflowAnalyzer<'db, 'a> for BlockCounter {
66///     type Info = usize;
67///     const DIRECTION: Direction = Direction::Forward;
68///
69///     fn initial_info(&mut self) -> Self::Info { 0 }
70///     fn transfer_block(&mut self, info: &mut Self::Info, block_id: BlockId, block: &Block<'db>) {
71///         *info += 1;  // Just count blocks
72///     }
73///     fn merge(&mut self, _loc: StatementLocation, _end_loc: &LocationId, infos: ..) -> Self::Info {
74///         infos.map(|(_, i)| i).max().unwrap_or(0)
75///     }
76/// }
77/// ```
78pub trait DataflowAnalyzer<'db, 'a> {
79    /// The analysis state/info type.
80    type Info: Clone;
81
82    /// The direction of this analysis.
83    const DIRECTION: Direction;
84
85    /// Create the initial analysis state at a terminal block.
86    ///
87    /// - Backward: called at return/panic blocks (what we know at function exit)
88    /// - Forward: called at function entry
89    ///
90    /// For backward analysis, `block_end` provides access to return variables or panic data.
91    /// For forward analysis, this is typically called once at the root block.
92    fn initial_info(&mut self, block_id: BlockId, block_end: &'a BlockEnd<'db>) -> Self::Info;
93
94    /// Merge/join states from multiple control flow paths.
95    /// Called at join points (match merge for backward, block entry for forward).
96    ///
97    /// - `statement_location`: where the merge occurs in the CFG.
98    /// - `info1`: first info from an incoming path.
99    /// - `info2`: second info from another incoming path.
100    fn merge(
101        &mut self,
102        lowered: &Lowered<'db>,
103        statement_location: StatementLocation,
104        info1: Self::Info,
105        info2: Self::Info,
106    ) -> Self::Info;
107
108    /// Transfer function for an entire block.
109    /// - Backward: transforms post-block state to pre-block state
110    /// - Forward: transforms pre-block state to post-block state
111    ///
112    /// Default implementation iterates statements and calls `transfer_stmt`.
113    /// Override this for block-level analysis (ignoring individual statements).
114    fn transfer_block(&mut self, info: &mut Self::Info, block_id: BlockId, block: &'a Block<'db>) {
115        match Self::DIRECTION {
116            Direction::Forward => {
117                for (i, stmt) in block.statements.iter().enumerate() {
118                    self.transfer_stmt(info, (block_id, i), stmt);
119                }
120            }
121            Direction::Backward => {
122                for (i, stmt) in block.statements.iter().enumerate().rev() {
123                    self.transfer_stmt(info, (block_id, i), stmt);
124                }
125            }
126        }
127    }
128
129    /// Transfer function for a single statement.
130    /// - Backward: transforms post-state to pre-state
131    /// - Forward: transforms pre-state to post-state
132    ///
133    /// Default is no-op. Override this for statement-level analysis.
134    fn transfer_stmt(
135        &mut self,
136        _info: &mut Self::Info,
137        _statement_location: StatementLocation,
138        _stmt: &'a Statement<'db>,
139    ) {
140    }
141
142    /// Transfer state along a CFG edge.
143    /// Called when traversing between blocks via control flow edges.
144    ///
145    /// - `info`: the state to transfer
146    /// - `edge`: the edge being traversed, containing all relevant information
147    ///
148    /// Default implementation clones the state.
149    /// Override to modify state based on edge properties (e.g., variable remapping,
150    /// introduced variables in match arms).
151    fn transfer_edge(&mut self, info: &Self::Info, _edge: &Edge<'db, 'a>) -> Self::Info {
152        info.clone()
153    }
154
155    /// Called when entering a block during traversal (before transfer_block).
156    fn visit_block_start(
157        &mut self,
158        _info: &mut Self::Info,
159        _block_id: BlockId,
160        _block: &Block<'db>,
161    ) {
162    }
163
164    /// Block entry location.
165    /// For Backward it is the merge location.
166    /// For Forward it is the block entry location, if there is a statement.
167    /// If not statement exists it will look at block end. This might recurse to next block if we
168    /// have a goto with no remappings.
169    fn block_entry_location(&self, lowered: &Lowered<'db>, block_id: BlockId) -> LocationId<'db> {
170        match Self::DIRECTION {
171            Direction::Backward => {
172                if let BlockEnd::Match { info } = &lowered.blocks[block_id].end {
173                    *info.location()
174                } else {
175                    unreachable!("In a backward analysis a merge should always be a match end.")
176                }
177            }
178            Direction::Forward => lowered.blocks[block_id]
179                .statements
180                .iter()
181                .find_map(|s| s.location())
182                .or(lowered.blocks[block_id].end.location())
183                .unwrap_or_else(|| {
184                    let BlockEnd::Goto(next, _) = &lowered.blocks[block_id].end else {
185                        unreachable!("Only goto end has no location")
186                    };
187                    self.block_entry_location(lowered, *next)
188                }),
189        }
190    }
191}