react_compiler_optimization/
inline_iifes.rs1use std::collections::{HashMap, HashSet};
44
45use react_compiler_hir::environment::Environment;
46use react_compiler_hir::visitors;
47use react_compiler_hir::{
48 BasicBlock, BlockId, BlockKind, EvaluationOrder, FunctionId, GENERATED_SOURCE, GotoVariant,
49 HirFunction, IdentifierId, IdentifierName, Instruction, InstructionId, InstructionKind,
50 InstructionValue, LValue, Place, Terminal,
51};
52use react_compiler_lowering::{
53 create_temporary_place, get_reverse_postordered_blocks, mark_instruction_ids, mark_predecessors,
54};
55
56use crate::merge_consecutive_blocks::merge_consecutive_blocks;
57
58pub fn inline_immediately_invoked_function_expressions(
61 func: &mut HirFunction,
62 env: &mut Environment,
63) {
64 let mut functions: HashMap<IdentifierId, FunctionId> = HashMap::new();
66 let mut inlined_functions: HashSet<IdentifierId> = HashSet::new();
68
69 let mut queue: Vec<BlockId> = func.body.blocks.keys().copied().collect();
75 let mut queue_idx = 0;
76
77 'queue: while queue_idx < queue.len() {
78 let block_id = queue[queue_idx];
79 queue_idx += 1;
80
81 let block = match func.body.blocks.get(&block_id) {
82 Some(b) => b,
83 None => continue,
84 };
85
86 if !is_statement_block_kind(block.kind) {
89 continue;
90 }
91
92 let num_instructions = block.instructions.len();
93 for ii in 0..num_instructions {
94 let instr_id = func.body.blocks[&block_id].instructions[ii];
95 let instr = &func.instructions[instr_id.0 as usize];
96
97 match &instr.value {
98 InstructionValue::FunctionExpression { lowered_func, .. } => {
99 let identifier_id = instr.lvalue.identifier;
100 if env.identifiers[identifier_id.0 as usize].name.is_none() {
101 functions.insert(identifier_id, lowered_func.func);
102 }
103 continue;
104 }
105 InstructionValue::CallExpression { callee, args, .. } => {
106 if !args.is_empty() {
107 continue;
109 }
110
111 let callee_id = callee.identifier;
112 let inner_func_id = match functions.get(&callee_id) {
113 Some(id) => *id,
114 None => continue, };
116
117 let inner_func = &env.functions[inner_func_id.0 as usize];
118 if !inner_func.params.is_empty() || inner_func.is_async || inner_func.generator
119 {
120 continue;
122 }
123
124 inlined_functions.insert(callee_id);
126
127 let call_lvalue = func.instructions[instr_id.0 as usize].lvalue.clone();
129 let block_terminal_id = func.body.blocks[&block_id].terminal.evaluation_order();
130 let block_terminal_loc = func.body.blocks[&block_id].terminal.loc().cloned();
131 let block_kind = func.body.blocks[&block_id].kind;
132
133 let continuation_block_id = env.next_block_id();
135 let continuation_instructions: Vec<InstructionId> =
136 func.body.blocks[&block_id].instructions[ii + 1..].to_vec();
137 let continuation_terminal = func.body.blocks[&block_id].terminal.clone();
138 let continuation_block = BasicBlock {
139 id: continuation_block_id,
140 instructions: continuation_instructions,
141 kind: block_kind,
142 phis: Vec::new(),
143 preds: indexmap::IndexSet::new(),
144 terminal: continuation_terminal,
145 };
146 func.body
147 .blocks
148 .insert(continuation_block_id, continuation_block);
149
150 func.body
153 .blocks
154 .get_mut(&block_id)
155 .unwrap()
156 .instructions
157 .truncate(ii);
158
159 let has_single_return =
160 has_single_exit_return_terminal(&env.functions[inner_func_id.0 as usize]);
161 let inner_entry = env.functions[inner_func_id.0 as usize].body.entry;
162
163 if has_single_return {
164 func.body.blocks.get_mut(&block_id).unwrap().terminal = Terminal::Goto {
166 block: inner_entry,
167 id: block_terminal_id,
168 loc: block_terminal_loc,
169 variant: GotoVariant::Break,
170 };
171
172 let inner_func = &mut env.functions[inner_func_id.0 as usize];
174 let inner_blocks: Vec<(BlockId, BasicBlock)> =
175 inner_func.body.blocks.drain(..).collect();
176 let inner_instructions: Vec<Instruction> =
177 inner_func.instructions.drain(..).collect();
178
179 let instr_offset = func.instructions.len() as u32;
181 func.instructions.extend(inner_instructions);
182
183 for (_, mut inner_block) in inner_blocks {
184 for iid in &mut inner_block.instructions {
186 *iid = InstructionId(iid.0 + instr_offset);
187 }
188 inner_block.preds.clear();
189
190 if let Terminal::Return {
191 value,
192 id: ret_id,
193 loc: ret_loc,
194 ..
195 } = &inner_block.terminal
196 {
197 let load_instr = Instruction {
199 id: EvaluationOrder(0),
200 loc: ret_loc.clone(),
201 lvalue: call_lvalue.clone(),
202 value: InstructionValue::LoadLocal {
203 place: value.clone(),
204 loc: ret_loc.clone(),
205 },
206 effects: None,
207 };
208 let load_instr_id = InstructionId(func.instructions.len() as u32);
209 func.instructions.push(load_instr);
210 inner_block.instructions.push(load_instr_id);
211
212 let ret_id = *ret_id;
213 let ret_loc = ret_loc.clone();
214 inner_block.terminal = Terminal::Goto {
215 block: continuation_block_id,
216 id: ret_id,
217 loc: ret_loc,
218 variant: GotoVariant::Break,
219 };
220 }
221
222 func.body.blocks.insert(inner_block.id, inner_block);
223 }
224 } else {
225 let result = call_lvalue.clone();
227
228 func.body.blocks.get_mut(&block_id).unwrap().terminal = Terminal::Label {
230 block: inner_entry,
231 id: EvaluationOrder(0),
232 fallthrough: continuation_block_id,
233 loc: block_terminal_loc,
234 };
235
236 declare_temporary(env, func, block_id, &result);
238
239 let identifier_id = result.identifier;
241 if env.identifiers[identifier_id.0 as usize].name.is_none() {
242 promote_temporary(env, identifier_id);
243 }
244
245 let inner_func = &mut env.functions[inner_func_id.0 as usize];
247 let inner_blocks: Vec<(BlockId, BasicBlock)> =
248 inner_func.body.blocks.drain(..).collect();
249 let inner_instructions: Vec<Instruction> =
250 inner_func.instructions.drain(..).collect();
251
252 let instr_offset = func.instructions.len() as u32;
254 func.instructions.extend(inner_instructions);
255
256 for (_, mut inner_block) in inner_blocks {
257 for iid in &mut inner_block.instructions {
258 *iid = InstructionId(iid.0 + instr_offset);
259 }
260 inner_block.preds.clear();
261
262 if matches!(inner_block.terminal, Terminal::Return { .. }) {
264 rewrite_block(
265 env,
266 &mut func.instructions,
267 &mut inner_block,
268 continuation_block_id,
269 &result,
270 );
271 }
272
273 func.body.blocks.insert(inner_block.id, inner_block);
274 }
275 }
276
277 queue.push(continuation_block_id);
280 continue 'queue;
281 }
282 _ => {
283 for id in visitors::each_instruction_value_operand_ids(&instr.value, env) {
285 functions.remove(&id);
286 }
287 }
288 }
289 }
290 }
291
292 if !inlined_functions.is_empty() {
293 for block in func.body.blocks.values_mut() {
295 block.instructions.retain(|instr_id| {
296 let instr = &func.instructions[instr_id.0 as usize];
297 !inlined_functions.contains(&instr.lvalue.identifier)
298 });
299 }
300
301 func.body.blocks = get_reverse_postordered_blocks(&func.body, &func.instructions);
304 mark_instruction_ids(&mut func.body, &mut func.instructions);
305 mark_predecessors(&mut func.body);
306 merge_consecutive_blocks(func, &mut env.functions);
307 }
308}
309
310fn is_statement_block_kind(kind: BlockKind) -> bool {
313 matches!(kind, BlockKind::Block | BlockKind::Catch)
314}
315
316fn has_single_exit_return_terminal(func: &HirFunction) -> bool {
318 let mut has_return = false;
319 let mut exit_count = 0;
320 for block in func.body.blocks.values() {
321 match &block.terminal {
322 Terminal::Return { .. } => {
323 has_return = true;
324 exit_count += 1;
325 }
326 Terminal::Throw { .. } => {
327 exit_count += 1;
328 }
329 _ => {}
330 }
331 }
332 exit_count == 1 && has_return
333}
334
335fn rewrite_block(
339 env: &mut Environment,
340 instructions: &mut Vec<Instruction>,
341 block: &mut BasicBlock,
342 return_target: BlockId,
343 return_value: &Place,
344) {
345 if let Terminal::Return {
346 value,
347 loc: ret_loc,
348 ..
349 } = &block.terminal
350 {
351 let store_lvalue = create_temporary_place(env, ret_loc.clone());
352 let store_instr = Instruction {
353 id: EvaluationOrder(0),
354 loc: ret_loc.clone(),
355 lvalue: store_lvalue,
356 value: InstructionValue::StoreLocal {
357 lvalue: LValue {
358 kind: InstructionKind::Reassign,
359 place: return_value.clone(),
360 },
361 value: value.clone(),
362 type_annotation: None,
363 loc: ret_loc.clone(),
364 },
365 effects: None,
366 };
367 let store_instr_id = InstructionId(instructions.len() as u32);
368 instructions.push(store_instr);
369 block.instructions.push(store_instr_id);
370
371 let ret_loc = ret_loc.clone();
372 block.terminal = Terminal::Goto {
373 block: return_target,
374 id: EvaluationOrder(0),
375 variant: GotoVariant::Break,
376 loc: ret_loc,
377 };
378 }
379}
380
381fn declare_temporary(
383 env: &mut Environment,
384 func: &mut HirFunction,
385 block_id: BlockId,
386 result: &Place,
387) {
388 let declare_lvalue = create_temporary_place(env, result.loc.clone());
389 let declare_instr = Instruction {
390 id: EvaluationOrder(0),
391 loc: GENERATED_SOURCE,
392 lvalue: declare_lvalue,
393 value: InstructionValue::DeclareLocal {
394 lvalue: LValue {
395 place: result.clone(),
396 kind: InstructionKind::Let,
397 },
398 type_annotation: None,
399 loc: result.loc.clone(),
400 },
401 effects: None,
402 };
403 let instr_id = InstructionId(func.instructions.len() as u32);
404 func.instructions.push(declare_instr);
405 func.body
406 .blocks
407 .get_mut(&block_id)
408 .unwrap()
409 .instructions
410 .push(instr_id);
411}
412
413fn promote_temporary(env: &mut Environment, identifier_id: IdentifierId) {
415 let decl_id = env.identifiers[identifier_id.0 as usize].declaration_id;
416 env.identifiers[identifier_id.0 as usize].name =
417 Some(IdentifierName::Promoted(format!("#t{}", decl_id.0)));
418}
419