react_compiler_optimization/
prune_maybe_throws.rs1use std::collections::HashMap;
14
15use react_compiler_diagnostics::{
16 CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, GENERATED_SOURCE,
17};
18use react_compiler_hir::{
19 BlockId, HirFunction, Instruction, InstructionValue, Terminal,
20};
21use react_compiler_lowering::{
22 get_reverse_postordered_blocks, mark_instruction_ids, remove_dead_do_while_statements,
23 remove_unnecessary_try_catch, remove_unreachable_for_updates,
24};
25
26use crate::merge_consecutive_blocks::merge_consecutive_blocks;
27
28pub fn prune_maybe_throws(
30 func: &mut HirFunction,
31 functions: &mut [HirFunction],
32) -> Result<(), CompilerDiagnostic> {
33 let terminal_mapping = prune_maybe_throws_impl(func);
34 if let Some(terminal_mapping) = terminal_mapping {
35 func.body.blocks = get_reverse_postordered_blocks(&func.body, &func.instructions);
38 remove_unreachable_for_updates(&mut func.body);
39 remove_dead_do_while_statements(&mut func.body);
40 remove_unnecessary_try_catch(&mut func.body);
41 mark_instruction_ids(&mut func.body, &mut func.instructions);
42 merge_consecutive_blocks(func, functions);
43
44 for block in func.body.blocks.values_mut() {
46 let preds = &block.preds;
47 let mut phi_updates: Vec<(usize, Vec<(BlockId, BlockId)>)> = Vec::new();
48
49 for (phi_idx, phi) in block.phis.iter().enumerate() {
50 let mut updates = Vec::new();
51 for (predecessor, _) in &phi.operands {
52 if !preds.contains(predecessor) {
53 let mapped_terminal =
54 terminal_mapping.get(predecessor).copied().ok_or_else(|| {
55 CompilerDiagnostic::new(
56 ErrorCategory::Invariant,
57 "Expected non-existing phi operand's predecessor to have been mapped to a new terminal",
58 Some(format!(
59 "Could not find mapping for predecessor bb{} in block bb{}",
60 predecessor.0, block.id.0,
61 )),
62 )
63 .with_detail(CompilerDiagnosticDetail::Error {
64 loc: GENERATED_SOURCE,
65 message: None,
66 identifier_name: None,
67 })
68 })?;
69 updates.push((*predecessor, mapped_terminal));
70 }
71 }
72 if !updates.is_empty() {
73 phi_updates.push((phi_idx, updates));
74 }
75 }
76
77 for (phi_idx, updates) in phi_updates {
78 for (old_pred, new_pred) in updates {
79 let operand = block.phis[phi_idx]
80 .operands
81 .shift_remove(&old_pred)
82 .unwrap();
83 block.phis[phi_idx].operands.insert(new_pred, operand);
84 }
85 }
86 }
87
88 }
89 Ok(())
90}
91
92fn prune_maybe_throws_impl(func: &mut HirFunction) -> Option<HashMap<BlockId, BlockId>> {
93 let mut terminal_mapping: HashMap<BlockId, BlockId> = HashMap::new();
94 let instructions = &func.instructions;
95
96 for block in func.body.blocks.values_mut() {
97 let continuation = match &block.terminal {
98 Terminal::MaybeThrow { continuation, .. } => *continuation,
99 _ => continue,
100 };
101
102 let can_throw = block
103 .instructions
104 .iter()
105 .any(|instr_id| instruction_may_throw(&instructions[instr_id.0 as usize]));
106
107 if !can_throw {
108 let source = terminal_mapping.get(&block.id).copied().unwrap_or(block.id);
109 terminal_mapping.insert(continuation, source);
110 if let Terminal::MaybeThrow { handler, .. } = &mut block.terminal {
115 *handler = None;
116 }
117 }
118 }
119
120 if terminal_mapping.is_empty() {
121 None
122 } else {
123 Some(terminal_mapping)
124 }
125}
126
127fn instruction_may_throw(instr: &Instruction) -> bool {
128 match &instr.value {
129 InstructionValue::Primitive { .. }
130 | InstructionValue::ArrayExpression { .. }
131 | InstructionValue::ObjectExpression { .. } => false,
132 _ => true,
133 }
134}