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