midenc_hir_analysis/
dense.rs

1mod backward;
2mod forward;
3mod lattice;
4
5use midenc_hir::{
6    cfg::Graph, dominance::DominanceInfo, loops::LoopForest, pass::AnalysisManager, Backward,
7    Block, BlockRef, CallOpInterface, EntityWithId, Forward, Operation, ProgramPoint,
8    RegionBranchOpInterface, RegionKindInterface, RegionRef, Report, Spanned, SymbolTable,
9};
10
11pub use self::{
12    backward::DenseBackwardDataFlowAnalysis, forward::DenseForwardDataFlowAnalysis,
13    lattice::DenseLattice,
14};
15use super::{AnalysisStrategy, DataFlowAnalysis, DataFlowSolver, Dense};
16use crate::analyses::{dce::CfgEdge, LoopAction, LoopState};
17
18/// This type provides an [AnalysisStrategy] for dense data-flow analyses.
19///
20/// In short, it implements the [DataFlowAnalysis] trait, and handles all of the boilerplate that
21/// any well-structured dense data-flow analysis requires. Analyses can make use of this strategy
22/// by implementing one of the dense data-flow analysis traits, which [DenseDataFlowAnalysis] will
23/// use to delegate analysis-specific details to the analysis implementation. The two traits are:
24///
25/// * [DenseForwardDataFlowAnalysis], for forward-propagating dense analyses
26/// * [DenseBackwardDataFlowAnalysis], for backward-propagating dense analyses
27///
28/// ## What is a dense analysis?
29///
30/// A dense data-flow analysis is one which associates analysis state with program points, in order
31/// to represent the evolution of some state as the program executes (in the case of forward
32/// analysis), or to derive facts about program points based on the possible paths that execution
33/// might take (backward analysis).
34///
35/// This is in contrast to sparse data-flow analysis, which associates state with SSA values at
36/// their definition, and thus the state does not evolve as the program executes. This is also where
37/// the distinction between _dense_ and _sparse_ comes from - program points are dense, while SSA
38/// value definitions are sparse (insofar as the state associated with an SSA value only ever occurs
39/// once, while states associated with program points are duplicated at each point).
40///
41/// Some examples of dense analyses:
42///
43/// * Dead code analysis - at the extreme, indicates whether every program point is executable, i.e.
44///   "live", or not. In practice, dead code analysis tends to only associate its state with
45///   specific control-flow edges, i.e. changes to the state only occur at block boundaries.
46/// * Reaching definition analysis - tracks, for each program point, the set of values whose
47///   definitions reach that point.
48/// * Dead store analysis - determines for each store instruction in a function, whether or not the
49///   stored value is ever read. For example, if you are initializing a struct, and set some field
50///   `foo` to `1`, and then set it to `2`, the first store is never observable, and so the store
51///   could be eliminated entirely.
52///
53/// ## Usage
54///
55/// This type is meant to be used indirectly, as an [AnalysisStrategy] implementation, rather than
56/// directly as a [DataFlowAnalysis] implementation, as shown below:
57///
58/// ```rust,ignore
59/// use midenc_hir::dataflow::*;
60///
61/// #[derive(Default)]
62/// pub struct MyAnalysis;
63/// impl BuildableDataFlowAnalysis for MyAnalysis {
64///     type Strategy = DenseDataFlowAnalysis<Self, Forward>;
65///
66///     fn new(_solver: &mut DataFlowSolver) -> Self {
67///         Self
68///     }
69/// }
70/// impl DenseForwardDataFlowAnalysis for MyAnalysis {
71///     type Lattice = Lattice<u32>;
72///
73///     //...
74/// }
75/// ```
76///
77/// The above permits us to load `MyAnalysis` into a `DataFlowSolver` without ever mentioning the
78/// `DenseDataFlowAnalysis` type at all, like so:
79///
80/// ```rust,ignore
81/// let mut solver = DataFlowSolver::default();
82/// solver.load::<MyAnalysis>();
83/// solver.initialize_and_run(&op, analysis_manager);
84/// ```
85pub struct DenseDataFlowAnalysis<T, D> {
86    analysis: T,
87    _direction: core::marker::PhantomData<D>,
88}
89
90impl<A: DenseForwardDataFlowAnalysis> AnalysisStrategy<A> for DenseDataFlowAnalysis<A, Forward> {
91    type Direction = Forward;
92    type Kind = Dense;
93
94    fn build(analysis: A, _solver: &mut DataFlowSolver) -> Self {
95        Self {
96            analysis,
97            _direction: core::marker::PhantomData,
98        }
99    }
100}
101
102impl<A: DenseBackwardDataFlowAnalysis> AnalysisStrategy<A> for DenseDataFlowAnalysis<A, Backward> {
103    type Direction = Backward;
104    type Kind = Dense;
105
106    fn build(analysis: A, _solver: &mut DataFlowSolver) -> Self {
107        Self {
108            analysis,
109            _direction: core::marker::PhantomData,
110        }
111    }
112}
113
114impl<A: DenseForwardDataFlowAnalysis> DataFlowAnalysis for DenseDataFlowAnalysis<A, Forward> {
115    #[inline]
116    fn debug_name(&self) -> &'static str {
117        self.analysis.debug_name()
118    }
119
120    fn analysis_id(&self) -> core::any::TypeId {
121        core::any::TypeId::of::<Self>()
122    }
123
124    /// Initialize the analysis by visiting every program point whose execution may modify the
125    /// program state; that is, every operation and block.
126    fn initialize(
127        &self,
128        top: &Operation,
129        solver: &mut DataFlowSolver,
130        analysis_manager: AnalysisManager,
131    ) -> Result<(), Report> {
132        log::debug!(
133            target: self.analysis.debug_name(),
134            "initializing analysis for {top}",
135        );
136
137        forward::process_operation(self, top, solver)?;
138
139        // If the op has SSACFG regions, use the dominator tree analysis, if available, to visit the
140        // CFG top-down. Otherwise, fall back to a naive iteration over the contents of each region.
141        //
142        // If we have a domtree, we don't bother visiting unreachable blocks (i.e. blocks that
143        // are not in the tree because they are unreachable via the CFG). If we don't have a domtree,
144        // then all blocks are visited, regardless of reachability.
145        if !top.has_regions() {
146            return Ok(());
147        }
148
149        let is_ssa_cfg = top
150            .as_trait::<dyn RegionKindInterface>()
151            .is_none_or(|rki| rki.has_ssa_dominance());
152        log::trace!(target: self.analysis.debug_name(), "visiting regions of op (is cfg={is_ssa_cfg})");
153        if is_ssa_cfg {
154            let dominfo = analysis_manager.get_analysis::<DominanceInfo>()?;
155            for (region_index, region) in top.regions().iter().enumerate() {
156                // Single-block regions do not require a dominance tree (and do not have one)
157                if region.has_one_block() {
158                    let block = region.entry();
159                    log::trace!(target: self.analysis.debug_name(), "initializing single-block region {region_index} from entry: {block}");
160                    forward::visit_block(self, &block, solver);
161                    log::trace!(target: self.analysis.debug_name(), "initializing {block} in pre-order");
162                    for op in block.body() {
163                        let child_analysis_manager = analysis_manager.nest(op.as_operation_ref());
164                        self.initialize(&op, solver, child_analysis_manager)?;
165                    }
166                } else {
167                    let entry_block = region.entry_block_ref().unwrap();
168                    log::trace!(target: self.analysis.debug_name(), "initializing multi-block region {region_index} with entry: {entry_block}");
169                    log::trace!(target: self.analysis.debug_name(), "computing region dominance tree");
170                    let domtree = dominfo.dominance(region.as_region_ref());
171                    log::trace!(target: self.analysis.debug_name(), "computing region loop forest forward");
172                    let loop_forest = LoopForest::new(&domtree);
173
174                    // Visit blocks in CFG reverse post-order
175                    log::trace!(
176                        target: self.analysis.debug_name(),
177                        "visiting region control flow graph in reverse post-order",
178                    );
179                    for node in domtree.reverse_postorder() {
180                        let Some(block) = node.block() else {
181                            continue;
182                        };
183                        log::trace!(target: self.analysis.debug_name(), "analyzing {block}");
184
185                        // Anchor the fact that a loop is being exited to the CfgEdge of the exit,
186                        // if applicable for this block
187                        if let Some(loop_info) = loop_forest.loop_for(block) {
188                            // This block can exit a loop
189                            if loop_info.is_loop_exiting(block) {
190                                log::trace!(target: self.analysis.debug_name(), "{block} is a loop exit");
191                                for succ in BlockRef::children(block) {
192                                    if !loop_info.contains_block(succ) {
193                                        log::trace!(target: self.analysis.debug_name(), "{block} can exit loop to {succ}");
194                                        let mut guard = solver.get_or_create_mut::<LoopState, _>(
195                                            CfgEdge::new(block, succ, block.span()),
196                                        );
197                                        guard.join(LoopAction::Exit);
198                                    }
199                                }
200                            }
201                        }
202
203                        let block = block.borrow();
204                        forward::visit_block(self, &block, solver);
205                        log::trace!(target: self.analysis.debug_name(), "initializing {block} in pre-order");
206                        for op in block.body() {
207                            let child_analysis_manager =
208                                analysis_manager.nest(op.as_operation_ref());
209                            self.initialize(&op, solver, child_analysis_manager)?;
210                        }
211                    }
212                }
213            }
214        } else {
215            for (region_index, region) in top.regions().iter().enumerate() {
216                for block in region.body() {
217                    log::trace!(target: self.analysis.debug_name(), "initializing {block} of region {region_index}");
218                    forward::visit_block(self, &block, solver);
219                    log::trace!(target: self.analysis.debug_name(), "initializing {block} in pre-order");
220                    for op in block.body() {
221                        let child_analysis_manager = analysis_manager.nest(op.as_operation_ref());
222                        self.initialize(&op, solver, child_analysis_manager)?;
223                    }
224                }
225            }
226        }
227
228        Ok(())
229    }
230
231    /// Visit a program point that modifies the state of the program.
232    ///
233    /// If the program point is at the beginning of a block, then the state is propagated from
234    /// control-flow predecessors or callsites. If the operation before the program point is a call
235    /// operation or region control-flow operation, then the state after the execution of the
236    /// operation is set by control-flow or the callgraph. Otherwise, this function invokes the
237    /// operation transfer function before the program point iterator.
238    fn visit(&self, point: &ProgramPoint, solver: &mut DataFlowSolver) -> Result<(), Report> {
239        if point.is_at_block_start() {
240            let block = point.block().expect("expected block");
241            forward::visit_block(self, &block.borrow(), solver);
242        } else {
243            let op = point.operation().expect("expected operation");
244            forward::process_operation(self, &op.borrow(), solver)?;
245        }
246
247        Ok(())
248    }
249}
250
251impl<A: DenseBackwardDataFlowAnalysis> DataFlowAnalysis for DenseDataFlowAnalysis<A, Backward> {
252    #[inline]
253    fn debug_name(&self) -> &'static str {
254        self.analysis.debug_name()
255    }
256
257    fn analysis_id(&self) -> core::any::TypeId {
258        core::any::TypeId::of::<Self>()
259    }
260
261    /// Initialize the analysis by visiting every program point whose execution may modify the
262    /// program state; that is, every operation and block.
263    fn initialize(
264        &self,
265        top: &Operation,
266        solver: &mut DataFlowSolver,
267        analysis_manager: AnalysisManager,
268    ) -> Result<(), Report> {
269        log::trace!(
270            target: self.analysis.debug_name(),
271            "initializing analysis for {top}",
272        );
273
274        backward::process_operation(self, top, solver)?;
275
276        // If the op has SSACFG regions, use the dominator tree analysis, if available, to visit the
277        // CFG in post-order. Otherwise, fall back to a naive iteration over the contents of each region.
278        //
279        // If we have a domtree, we don't bother visiting unreachable blocks (i.e. blocks that
280        // are not in the tree because they are unreachable via the CFG). If we don't have a domtree,
281        // then all blocks are visited, regardless of reachability.
282        if !top.has_regions() {
283            return Ok(());
284        }
285
286        let is_ssa_cfg = top
287            .as_trait::<dyn RegionKindInterface>()
288            .is_none_or(|rki| rki.has_ssa_dominance());
289        log::trace!(target: self.analysis.debug_name(), "visiting regions of op (is cfg={is_ssa_cfg})");
290        if is_ssa_cfg {
291            let dominfo = analysis_manager.get_analysis::<DominanceInfo>()?;
292            for (region_index, region) in top.regions().iter().enumerate() {
293                // Single-block regions do not require a dominance tree (and do not have one)
294                if region.has_one_block() {
295                    let block = region.entry();
296                    log::trace!(target: self.analysis.debug_name(), "initializing single-block region {region_index} from entry: {block}");
297                    backward::visit_block(self, &block, solver);
298                    log::trace!(target: self.analysis.debug_name(), "initializing {block} in post-order");
299                    for op in block.body().iter().rev() {
300                        let child_analysis_manager = analysis_manager.nest(op.as_operation_ref());
301                        self.initialize(&op, solver, child_analysis_manager)?;
302                    }
303                } else {
304                    let entry_block = region.entry_block_ref().unwrap();
305                    log::trace!(target: self.analysis.debug_name(), "initializing multi-block region {region_index} with entry: {entry_block}");
306                    log::trace!(target: self.analysis.debug_name(), "computing region dominance tree");
307                    let domtree = dominfo.dominance(region.as_region_ref());
308                    log::trace!(target: self.analysis.debug_name(), "computing region loop forest backward");
309                    let loop_forest = LoopForest::new(&domtree);
310
311                    // Visit blocks in CFG postorder
312                    log::trace!(
313                        target: self.analysis.debug_name(),
314                        "visiting region control flow graph in post-order",
315                    );
316                    for node in domtree.postorder() {
317                        let Some(block) = node.block() else {
318                            continue;
319                        };
320                        log::trace!(target: self.analysis.debug_name(), "analyzing {block}");
321
322                        // Anchor the fact that a loop is being exited to the CfgEdge of the exit,
323                        // if applicable for this block
324                        if let Some(loop_info) = loop_forest.loop_for(block) {
325                            // This block can exit a loop
326                            if loop_info.is_loop_exiting(block) {
327                                log::trace!(target: self.analysis.debug_name(), "{block} is a loop exit");
328                                for succ in BlockRef::children(block) {
329                                    if !loop_info.contains_block(succ) {
330                                        log::trace!(target: self.analysis.debug_name(), "{block} can exit loop to {succ}");
331                                        let mut guard = solver.get_or_create_mut::<LoopState, _>(
332                                            CfgEdge::new(block, succ, block.span()),
333                                        );
334                                        guard.join(LoopAction::Exit);
335                                    }
336                                }
337                            }
338                        }
339
340                        let block = block.borrow();
341                        backward::visit_block(self, &block, solver);
342                        log::trace!(target: self.analysis.debug_name(), "initializing {block} in post-order");
343                        for op in block.body().iter().rev() {
344                            let child_analysis_manager =
345                                analysis_manager.nest(op.as_operation_ref());
346                            self.initialize(&op, solver, child_analysis_manager)?;
347                        }
348                    }
349                }
350            }
351        } else {
352            for (region_index, region) in top.regions().iter().enumerate() {
353                for block in region.body().iter().rev() {
354                    log::trace!(target: self.analysis.debug_name(), "initializing {block} of region {region_index}");
355                    backward::visit_block(self, &block, solver);
356                    log::trace!(target: self.analysis.debug_name(), "initializing {block} in post-order");
357                    for op in block.body().iter().rev() {
358                        let child_analysis_manager = analysis_manager.nest(op.as_operation_ref());
359                        self.initialize(&op, solver, child_analysis_manager)?;
360                    }
361                }
362            }
363        }
364
365        Ok(())
366    }
367
368    /// Visit a program point that modifies the state of the program. If the program point is at the
369    /// beginning of a block, then the state is propagated from control-flow predecessors or
370    /// callsites.  If the operation before the program point is a call operation or region
371    /// control-flow operation, then the state after the execution of the operation is set by
372    /// control-flow or the callgraph. Otherwise, this function invokes the operation transfer
373    /// function before the program point.
374    fn visit(&self, point: &ProgramPoint, solver: &mut DataFlowSolver) -> Result<(), Report> {
375        if point.is_at_block_end() {
376            let block = point.block().expect("expected block");
377            backward::visit_block(self, &block.borrow(), solver);
378        } else {
379            let op = point.next_operation().expect("expected operation");
380            backward::process_operation(self, &op.borrow(), solver)?;
381        }
382
383        Ok(())
384    }
385}
386
387impl<A: DenseForwardDataFlowAnalysis> DenseForwardDataFlowAnalysis
388    for DenseDataFlowAnalysis<A, Forward>
389{
390    type Lattice = <A as DenseForwardDataFlowAnalysis>::Lattice;
391
392    fn debug_name(&self) -> &'static str {
393        <A as DenseForwardDataFlowAnalysis>::debug_name(&self.analysis)
394    }
395
396    fn visit_operation(
397        &self,
398        op: &Operation,
399        before: &Self::Lattice,
400        after: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>,
401        solver: &mut DataFlowSolver,
402    ) -> Result<(), Report> {
403        <A as DenseForwardDataFlowAnalysis>::visit_operation(
404            &self.analysis,
405            op,
406            before,
407            after,
408            solver,
409        )
410    }
411
412    fn set_to_entry_state(
413        &self,
414        lattice: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>,
415        solver: &mut DataFlowSolver,
416    ) {
417        <A as DenseForwardDataFlowAnalysis>::set_to_entry_state(&self.analysis, lattice, solver);
418    }
419
420    fn visit_branch_control_flow_transfer(
421        &self,
422        from: BlockRef,
423        to: &Block,
424        before: &Self::Lattice,
425        after: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>,
426        solver: &mut DataFlowSolver,
427    ) {
428        <A as DenseForwardDataFlowAnalysis>::visit_branch_control_flow_transfer(
429            &self.analysis,
430            from,
431            to,
432            before,
433            after,
434            solver,
435        );
436    }
437
438    fn visit_region_branch_control_flow_transfer(
439        &self,
440        branch: &dyn RegionBranchOpInterface,
441        region_from: Option<RegionRef>,
442        region_to: Option<RegionRef>,
443        before: &Self::Lattice,
444        after: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>,
445        solver: &mut DataFlowSolver,
446    ) {
447        <A as DenseForwardDataFlowAnalysis>::visit_region_branch_control_flow_transfer(
448            &self.analysis,
449            branch,
450            region_from,
451            region_to,
452            before,
453            after,
454            solver,
455        );
456    }
457
458    fn visit_call_control_flow_transfer(
459        &self,
460        call: &dyn CallOpInterface,
461        action: super::CallControlFlowAction,
462        before: &Self::Lattice,
463        after: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>,
464        solver: &mut DataFlowSolver,
465    ) {
466        <A as DenseForwardDataFlowAnalysis>::visit_call_control_flow_transfer(
467            &self.analysis,
468            call,
469            action,
470            before,
471            after,
472            solver,
473        );
474    }
475}
476
477impl<A: DenseBackwardDataFlowAnalysis> DenseBackwardDataFlowAnalysis
478    for DenseDataFlowAnalysis<A, Backward>
479{
480    type Lattice = <A as DenseBackwardDataFlowAnalysis>::Lattice;
481
482    fn debug_name(&self) -> &'static str {
483        <A as DenseBackwardDataFlowAnalysis>::debug_name(&self.analysis)
484    }
485
486    fn symbol_table(&self) -> Option<&dyn SymbolTable> {
487        <A as DenseBackwardDataFlowAnalysis>::symbol_table(&self.analysis)
488    }
489
490    fn set_to_exit_state(
491        &self,
492        lattice: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>,
493        solver: &mut DataFlowSolver,
494    ) {
495        <A as DenseBackwardDataFlowAnalysis>::set_to_exit_state(&self.analysis, lattice, solver)
496    }
497
498    fn visit_operation(
499        &self,
500        op: &Operation,
501        after: &Self::Lattice,
502        before: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>,
503        solver: &mut DataFlowSolver,
504    ) -> Result<(), Report> {
505        <A as DenseBackwardDataFlowAnalysis>::visit_operation(
506            &self.analysis,
507            op,
508            after,
509            before,
510            solver,
511        )
512    }
513
514    fn visit_branch_control_flow_transfer(
515        &self,
516        from: &Block,
517        to: BlockRef,
518        after: &Self::Lattice,
519        before: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>,
520        solver: &mut DataFlowSolver,
521    ) {
522        <A as DenseBackwardDataFlowAnalysis>::visit_branch_control_flow_transfer(
523            &self.analysis,
524            from,
525            to,
526            after,
527            before,
528            solver,
529        )
530    }
531
532    fn visit_region_branch_control_flow_transfer(
533        &self,
534        branch: &dyn RegionBranchOpInterface,
535        region_from: Option<RegionRef>,
536        region_to: Option<RegionRef>,
537        after: &Self::Lattice,
538        before: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>,
539        solver: &mut DataFlowSolver,
540    ) {
541        <A as DenseBackwardDataFlowAnalysis>::visit_region_branch_control_flow_transfer(
542            &self.analysis,
543            branch,
544            region_from,
545            region_to,
546            after,
547            before,
548            solver,
549        )
550    }
551
552    fn visit_call_control_flow_transfer(
553        &self,
554        call: &dyn CallOpInterface,
555        action: super::CallControlFlowAction,
556        after: &Self::Lattice,
557        before: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>,
558        solver: &mut DataFlowSolver,
559    ) {
560        <A as DenseBackwardDataFlowAnalysis>::visit_call_control_flow_transfer(
561            &self.analysis,
562            call,
563            action,
564            after,
565            before,
566            solver,
567        )
568    }
569}