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}