cairo_lang_lowering/
reorganize_blocks.rs1use 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
13pub 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 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 old_block_rev_order.push(BlockId::root());
48
49 let n_visited_blocks = old_block_rev_order.len();
50
51 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 incoming_gotos: Vec<usize>,
103
104 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 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 self.incoming_gotos[target.0] += 1;
160 }
161 Edge::MatchArm { arm, match_info } => {
162 self.can_be_merged[arm.block_id.0] = false;
163 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
199pub struct VarReassigner<'db, 'a> {
204 pub old_vars: &'a VariableArena<'db>,
205 pub new_vars: VariableArena<'db>,
206
207 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}