cairo_lang_lowering/
reorganize_blocks.rs

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