1use crate::{
18 mir::{
19 BlockId, Function, InstKind, Terminator, Value, ValueId, utils::repair_reachability_phis,
20 },
21 pass::FunctionPass,
22};
23use solar_data_structures::map::{FxHashMap, FxHashSet};
24
25#[derive(Debug, Default, Clone)]
27pub struct JumpThreadingStats {
28 pub jumps_threaded: usize,
30 pub branches_threaded: usize,
32 pub switches_threaded: usize,
34 pub gas_saved: usize,
36}
37
38impl JumpThreadingStats {
39 #[must_use]
41 pub fn total_threaded(&self) -> usize {
42 self.jumps_threaded + self.branches_threaded + self.switches_threaded
43 }
44}
45
46#[derive(Debug, Default)]
48pub struct JumpThreader {
49 pub stats: JumpThreadingStats,
51}
52
53pub struct JumpThreadingPass;
55
56impl FunctionPass for JumpThreadingPass {
57 fn name(&self) -> &str {
58 "jump-threading"
59 }
60
61 fn run_on_function(&mut self, func: &mut Function) -> bool {
62 JumpThreader::new().run_to_fixpoint(func).total_threaded() != 0
63 }
64}
65
66impl JumpThreader {
67 #[must_use]
69 pub fn new() -> Self {
70 Self::default()
71 }
72
73 pub fn run(&mut self, func: &mut Function) -> usize {
76 self.stats = JumpThreadingStats::default();
77 let mut changed = 0;
78
79 let forwarders = self.find_forwarder_blocks(func);
81
82 if !forwarders.is_empty() {
83 let final_targets = self.resolve_final_targets(&forwarders);
85
86 self.thread_jumps(func, &final_targets);
88 changed += self.stats.total_threaded();
89 }
90
91 changed += self.thread_phi_constant_edges(func);
92
93 if changed == 0 {
94 return 0;
95 }
96
97 self.update_cfg_edges(func);
99 repair_reachability_phis(func);
100
101 changed
102 }
103
104 pub fn run_to_fixpoint(&mut self, func: &mut Function) -> JumpThreadingStats {
106 let mut total_stats = JumpThreadingStats::default();
107 loop {
108 let changed = self.run(func);
109 if changed == 0 {
110 break;
111 }
112 total_stats.jumps_threaded += self.stats.jumps_threaded;
113 total_stats.branches_threaded += self.stats.branches_threaded;
114 total_stats.switches_threaded += self.stats.switches_threaded;
115 total_stats.gas_saved += self.stats.gas_saved;
116 }
117 total_stats
118 }
119
120 fn find_forwarder_blocks(&self, func: &Function) -> FxHashMap<BlockId, BlockId> {
122 let mut forwarders = FxHashMap::default();
123
124 for (block_id, block) in func.blocks.iter_enumerated() {
125 if block_id == func.entry_block {
127 continue;
128 }
129
130 if !block.instructions.is_empty() {
133 continue;
134 }
135
136 if let Some(Terminator::Jump(target)) = &block.terminator {
138 if *target != block_id {
140 forwarders.insert(block_id, *target);
141 }
142 }
143 }
144
145 forwarders
146 }
147
148 fn resolve_final_targets(
150 &self,
151 forwarders: &FxHashMap<BlockId, BlockId>,
152 ) -> FxHashMap<BlockId, BlockId> {
153 let mut final_targets = FxHashMap::default();
154
155 for &block_id in forwarders.keys() {
156 let final_target = self.follow_chain(block_id, forwarders);
157 if final_target != block_id {
158 final_targets.insert(block_id, final_target);
159 }
160 }
161
162 final_targets
163 }
164
165 fn follow_chain(&self, start: BlockId, forwarders: &FxHashMap<BlockId, BlockId>) -> BlockId {
167 let mut visited = FxHashSet::default();
168 let mut current = start;
169
170 while let Some(&next) = forwarders.get(¤t) {
171 if !visited.insert(current) {
172 break;
173 }
174 current = next;
175 }
176
177 current
178 }
179
180 fn thread_jumps(&mut self, func: &mut Function, final_targets: &FxHashMap<BlockId, BlockId>) {
182 let block_ids: Vec<_> = func.blocks.indices().collect();
183 for block_id in block_ids {
184 let Some(mut term) = func.blocks[block_id].terminator.clone() else {
185 continue;
186 };
187 self.thread_terminator(func, &mut term, final_targets);
188 func.blocks[block_id].terminator = Some(term);
189 }
190 }
191
192 fn thread_terminator(
194 &mut self,
195 func: &Function,
196 term: &mut Terminator,
197 final_targets: &FxHashMap<BlockId, BlockId>,
198 ) {
199 match term {
200 Terminator::Jump(target) => {
201 if let Some(final_target) = Self::threaded_target(func, *target, final_targets) {
202 *target = final_target;
203 self.stats.jumps_threaded += 1;
204 self.stats.gas_saved += 8;
205 }
206 }
207
208 Terminator::Branch { then_block, else_block, .. } => {
209 let mut changed = false;
210 if let Some(final_target) = Self::threaded_target(func, *then_block, final_targets)
211 {
212 *then_block = final_target;
213 changed = true;
214 }
215 if let Some(final_target) = Self::threaded_target(func, *else_block, final_targets)
216 {
217 *else_block = final_target;
218 changed = true;
219 }
220 if changed {
221 self.stats.branches_threaded += 1;
222 self.stats.gas_saved += 8;
223 }
224 }
225
226 Terminator::Switch { default, cases, .. } => {
227 let mut changed = false;
228 if let Some(final_target) = Self::threaded_target(func, *default, final_targets) {
229 *default = final_target;
230 changed = true;
231 }
232 for (_, target) in cases.iter_mut() {
233 if let Some(final_target) = Self::threaded_target(func, *target, final_targets)
234 {
235 *target = final_target;
236 changed = true;
237 }
238 }
239 if changed {
240 self.stats.switches_threaded += 1;
241 self.stats.gas_saved += 8;
242 }
243 }
244
245 Terminator::Return { .. }
246 | Terminator::Revert { .. }
247 | Terminator::ReturnData { .. }
248 | Terminator::Stop
249 | Terminator::SelfDestruct { .. }
250 | Terminator::Invalid => {}
251 }
252 }
253
254 fn threaded_target(
255 func: &Function,
256 target: BlockId,
257 final_targets: &FxHashMap<BlockId, BlockId>,
258 ) -> Option<BlockId> {
259 let final_target = final_targets.get(&target).copied()?;
260 (!func.block_has_phi(final_target)).then_some(final_target)
261 }
262
263 fn block_phi_results_have_external_uses(func: &Function, block_id: BlockId) -> bool {
264 let phi_results = func.block_phi_results(block_id);
265 if phi_results.is_empty() {
266 return false;
267 }
268
269 for (other_block, block) in func.blocks.iter_enumerated() {
270 if other_block != block_id {
271 for &inst_id in &block.instructions {
272 if func.instructions[inst_id]
273 .kind
274 .operands()
275 .iter()
276 .any(|operand| phi_results.contains(operand))
277 {
278 return true;
279 }
280 }
281 }
282
283 if other_block == block_id {
284 continue;
285 }
286 if let Some(term) = &block.terminator
287 && term.operands().iter().any(|operand| phi_results.contains(operand))
288 {
289 return true;
290 }
291 }
292
293 false
294 }
295
296 fn thread_phi_constant_edges(&mut self, func: &mut Function) -> usize {
297 let mut rewrites = Vec::new();
298 let block_ids: Vec<_> = func.blocks.indices().collect();
299
300 for block_id in block_ids {
301 if !func.block_has_only_phis(block_id) {
302 continue;
303 }
304 if Self::block_phi_results_have_external_uses(func, block_id) {
305 continue;
306 }
307
308 let Some(term) = &func.blocks[block_id].terminator else {
309 continue;
310 };
311 let predecessors = func.blocks[block_id].predecessors.clone();
312 if predecessors.is_empty() {
313 continue;
314 }
315
316 for pred in predecessors {
317 if pred == block_id || Self::successor_count(func, pred, block_id) != 1 {
318 continue;
319 }
320 let Some(target) = self.phi_constant_target_for_pred(func, block_id, term, pred)
321 else {
322 continue;
323 };
324 if target == block_id || func.block_has_phi(target) {
325 continue;
326 }
327 rewrites.push((pred, block_id, target));
328 }
329 }
330
331 let mut threaded = 0;
332 for (pred, old_target, new_target) in rewrites {
333 if Self::replace_successor(func, pred, old_target, new_target) {
334 threaded += 1;
335 }
336 }
337
338 if threaded != 0 {
339 self.stats.branches_threaded += threaded;
340 self.stats.gas_saved += threaded * 8;
341 }
342
343 threaded
344 }
345
346 fn phi_constant_target_for_pred(
347 &self,
348 func: &Function,
349 block_id: BlockId,
350 term: &Terminator,
351 pred: BlockId,
352 ) -> Option<BlockId> {
353 match term {
354 Terminator::Branch { condition, then_block, else_block } => {
355 let incoming = Self::incoming_value_for_pred(func, block_id, *condition, pred)?;
356 let condition = func.value_u256(incoming)?;
357 Some(if condition.is_zero() { *else_block } else { *then_block })
358 }
359 Terminator::Switch { value, default, cases } => {
360 let incoming = Self::incoming_value_for_pred(func, block_id, *value, pred)?;
361 let value = func.value_u256(incoming)?;
362 for (case, target) in cases {
363 if func.value_u256(*case)? == value {
364 return Some(*target);
365 }
366 }
367 Some(*default)
368 }
369 _ => None,
370 }
371 }
372
373 fn incoming_value_for_pred(
374 func: &Function,
375 block_id: BlockId,
376 value: ValueId,
377 pred: BlockId,
378 ) -> Option<ValueId> {
379 let Value::Inst(inst_id) = func.value(value) else {
380 return Some(value);
381 };
382 if !func.blocks[block_id].instructions.contains(inst_id) {
383 return None;
384 }
385 let InstKind::Phi(incoming) = &func.instructions[*inst_id].kind else {
386 return None;
387 };
388 incoming.iter().find_map(|(incoming_block, incoming_value)| {
389 (*incoming_block == pred).then_some(*incoming_value)
390 })
391 }
392
393 fn successor_count(func: &Function, pred: BlockId, target: BlockId) -> usize {
394 func.blocks[pred]
395 .terminator
396 .as_ref()
397 .map(|term| term.successors().into_iter().filter(|&succ| succ == target).count())
398 .unwrap_or_default()
399 }
400
401 fn replace_successor(
402 func: &mut Function,
403 pred: BlockId,
404 old_target: BlockId,
405 new_target: BlockId,
406 ) -> bool {
407 let Some(term) = &mut func.blocks[pred].terminator else {
408 return false;
409 };
410 match term {
411 Terminator::Jump(target) => {
412 if *target == old_target {
413 *target = new_target;
414 true
415 } else {
416 false
417 }
418 }
419 Terminator::Branch { then_block, else_block, .. } => {
420 let mut changed = false;
421 if *then_block == old_target {
422 *then_block = new_target;
423 changed = true;
424 }
425 if *else_block == old_target {
426 *else_block = new_target;
427 changed = true;
428 }
429 changed
430 }
431 Terminator::Switch { default, cases, .. } => {
432 let mut changed = false;
433 if *default == old_target {
434 *default = new_target;
435 changed = true;
436 }
437 for (_, target) in cases {
438 if *target == old_target {
439 *target = new_target;
440 changed = true;
441 }
442 }
443 changed
444 }
445 Terminator::Return { .. }
446 | Terminator::Revert { .. }
447 | Terminator::ReturnData { .. }
448 | Terminator::Stop
449 | Terminator::SelfDestruct { .. }
450 | Terminator::Invalid => false,
451 }
452 }
453
454 fn update_cfg_edges(&self, func: &mut Function) {
456 let block_ids: Vec<_> = func.blocks.indices().collect();
457 for block_id in &block_ids {
458 func.blocks[*block_id].predecessors.clear();
459 }
460
461 for block_id in block_ids {
462 let successors = func.blocks[block_id]
463 .terminator
464 .as_ref()
465 .map(|t| t.successors())
466 .unwrap_or_default();
467
468 for succ in successors {
469 func.blocks[succ].predecessors.push(block_id);
470 }
471 }
472 }
473}