cairo_lang_lowering/
reorganize_blocks.rs1use 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
14pub 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 let mut new_blocks = BlocksBuilder::default();
42
43 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 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 incoming_gotos: Vec<usize>,
108
109 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 _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
202pub struct VarReassigner<'db, 'a> {
207 pub old_vars: &'a VariableArena<'db>,
208 pub new_vars: VariableArena<'db>,
209
210 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}