Skip to main content

cairo_lang_lowering/
reorganize_blocks.rs

1use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
2use itertools::Itertools;
3
4use crate::analysis::core::StatementLocation;
5use crate::analysis::{DataflowAnalyzer, DataflowBackAnalysis, Direction, Edge};
6use crate::blocks::BlocksBuilder;
7use crate::optimizations::remappings::{self, Context};
8use crate::utils::{Rebuilder, RebuilderEx};
9use crate::{
10    Block, BlockEnd, BlockId, Lowered, Statement, VarRemapping, VariableArena, VariableId,
11};
12
13/// Reorganizes the blocks in lowered function and removes unnecessary remappings.
14///
15/// Removes unreachable blocks.
16/// Blocks that are reachable only through goto are combined with the block that does the goto.
17pub fn reorganize_blocks<'db>(lowered: &mut Lowered<'db>) {
18    if lowered.blocks.is_empty() {
19        return;
20    }
21    let mut ctx = TopSortContext {
22        old_block_rev_order: Vec::with_capacity(lowered.blocks.len()),
23        incoming_gotos: vec![0; lowered.blocks.len()],
24        can_be_merged: vec![true; lowered.blocks.len()],
25        remappings_ctx: Context::new(lowered.variables.len()),
26    };
27
28    remappings::visit_remappings(lowered, |remapping| {
29        for (dst, src) in remapping.iter() {
30            ctx.remappings_ctx.dest_to_srcs[dst.index()].push(src.var_id);
31        }
32    });
33
34    DataflowBackAnalysis::new(lowered, &mut ctx).run();
35
36    // Keep only blocks that can't be merged or have more than 1 incoming
37    // goto.
38    // Note that unreachable blocks were not added to `ctx.old_block_rev_order` during
39    // the analysis above.
40    let mut old_block_rev_order = ctx
41        .old_block_rev_order
42        .into_iter()
43        .filter(|block_id| !ctx.can_be_merged[block_id.0] || ctx.incoming_gotos[block_id.0] > 1)
44        .collect_vec();
45
46    // Add the root block as it was filtered above.
47    old_block_rev_order.push(BlockId::root());
48
49    let n_visited_blocks = old_block_rev_order.len();
50
51    // Rebuild the blocks in the correct order.
52    let mut new_blocks = BlocksBuilder::with_capacity(n_visited_blocks);
53
54    let mut rebuilder = RebuildContext {
55        block_remapping: UnorderedHashMap::from_iter(
56            old_block_rev_order
57                .iter()
58                .enumerate()
59                .map(|(idx, block_id)| (*block_id, BlockId(n_visited_blocks - idx - 1))),
60        ),
61        remappings_ctx: ctx.remappings_ctx,
62    };
63
64    let mut var_reassigner = VarReassigner::new(&lowered.variables);
65    for param in lowered.parameters.iter_mut() {
66        *param = var_reassigner.map_var_id(*param);
67    }
68
69    for block_id in old_block_rev_order.into_iter().rev() {
70        let mut block = &lowered.blocks[block_id];
71        let mut statements = Vec::with_capacity(block.statements.len());
72        loop {
73            statements.extend(
74                block.statements.iter().map(|stmt| {
75                    var_reassigner.rebuild_statement(&rebuilder.rebuild_statement(stmt))
76                }),
77            );
78            if let BlockEnd::Goto(target_block_id, remappings) = &block.end
79                && !rebuilder.block_remapping.contains_key(target_block_id)
80            {
81                assert!(
82                    rebuilder.rebuild_remapping(remappings).is_empty(),
83                    "Remapping should be empty."
84                );
85                block = &lowered.blocks[*target_block_id];
86                continue;
87            }
88            break;
89        }
90
91        let end = var_reassigner.rebuild_end(&rebuilder.rebuild_end(&block.end));
92        new_blocks.alloc(Block { statements, end });
93    }
94
95    lowered.variables = var_reassigner.new_vars;
96    lowered.blocks = new_blocks.build().unwrap();
97}
98
99pub struct TopSortContext {
100    old_block_rev_order: Vec<BlockId>,
101    // The number of incoming gotos, indexed by block_id.
102    incoming_gotos: Vec<usize>,
103
104    // True if the block can be merged with the block that goes to it.
105    can_be_merged: Vec<bool>,
106
107    remappings_ctx: remappings::Context,
108}
109
110impl<'db, 'a> DataflowAnalyzer<'db, 'a> for TopSortContext {
111    type Info = ();
112
113    const DIRECTION: Direction = Direction::Backward;
114
115    fn initial_info(&mut self, _block_id: BlockId, block_end: &'a BlockEnd<'db>) -> Self::Info {
116        // For zero-arm matches (e.g. `never` type), no MatchArm edges are traversed,
117        // so mark match inputs as used here.
118        if let BlockEnd::Match { info } = block_end {
119            for var_usage in info.inputs() {
120                self.remappings_ctx.set_used(var_usage.var_id);
121            }
122        }
123    }
124
125    fn merge(
126        &mut self,
127        _lowered: &Lowered<'db>,
128        _statement_location: StatementLocation,
129        _info1: Self::Info,
130        _info2: Self::Info,
131    ) -> Self::Info {
132    }
133
134    fn visit_block_start(
135        &mut self,
136        _info: &mut Self::Info,
137        block_id: BlockId,
138        _block: &Block<'db>,
139    ) {
140        self.old_block_rev_order.push(block_id);
141    }
142
143    fn transfer_stmt(
144        &mut self,
145        _info: &mut Self::Info,
146        _statement_location: StatementLocation,
147        stmt: &'a Statement<'db>,
148    ) {
149        for var_usage in stmt.inputs() {
150            self.remappings_ctx.set_used(var_usage.var_id);
151        }
152    }
153
154    fn transfer_edge(&mut self, info: &Self::Info, edge: &Edge<'db, 'a>) -> Self::Info {
155        match edge {
156            Edge::Goto { target, remapping: _ } => {
157                // Note that the remappings of a goto are not considered a usage. Later usages
158                // (such as a merge) would catch them if used.
159                self.incoming_gotos[target.0] += 1;
160            }
161            Edge::MatchArm { arm, match_info } => {
162                self.can_be_merged[arm.block_id.0] = false;
163                // Mark match inputs as used.
164                for var_usage in match_info.inputs() {
165                    self.remappings_ctx.set_used(var_usage.var_id);
166                }
167            }
168            Edge::Return { vars, .. } => {
169                for var_usage in vars.iter() {
170                    self.remappings_ctx.set_used(var_usage.var_id);
171                }
172            }
173            Edge::Panic { var } => {
174                self.remappings_ctx.set_used(var.var_id);
175            }
176        }
177        *info
178    }
179}
180
181pub struct RebuildContext {
182    block_remapping: UnorderedHashMap<BlockId, BlockId>,
183    remappings_ctx: remappings::Context,
184}
185impl<'db> Rebuilder<'db> for RebuildContext {
186    fn map_block_id(&mut self, block: BlockId) -> BlockId {
187        self.block_remapping[&block]
188    }
189
190    fn map_var_id(&mut self, var: VariableId) -> VariableId {
191        self.remappings_ctx.map_var_id(var)
192    }
193
194    fn transform_remapping(&mut self, remapping: &mut VarRemapping<'db>) {
195        self.remappings_ctx.transform_remapping(remapping)
196    }
197}
198
199/// Helper class to reassign variable ids according to the rebuild order.
200///
201/// Note that it can't be integrated into the RebuildContext above because rebuild_remapping might
202/// call `map_var_id` on variables that are going to be removed.
203pub struct VarReassigner<'db, 'a> {
204    pub old_vars: &'a VariableArena<'db>,
205    pub new_vars: VariableArena<'db>,
206
207    // Maps old var_id to new_var_id
208    pub vars: Vec<Option<VariableId>>,
209}
210
211impl<'db, 'a> VarReassigner<'db, 'a> {
212    pub fn new(old_vars: &'a VariableArena<'db>) -> Self {
213        Self {
214            old_vars,
215            new_vars: VariableArena::with_capacity(old_vars.len()),
216            vars: vec![None; old_vars.len()],
217        }
218    }
219}
220
221impl<'db> Rebuilder<'db> for VarReassigner<'db, '_> {
222    fn map_var_id(&mut self, var: VariableId) -> VariableId {
223        *self.vars[var.index()]
224            .get_or_insert_with(|| self.new_vars.alloc(self.old_vars[var].clone()))
225    }
226}