cairo_lang_lowering/
reorganize_blocks.rs

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