1use std::collections::{HashMap, HashSet};
12
13use indexmap::IndexMap;
14use react_compiler_hir::environment::Environment;
15use react_compiler_hir::{
16 BasicBlock, BlockId, BlockKind, EvaluationOrder, HirFunction, HIR, IdentifierId, Instruction,
17 InstructionId, InstructionKind, InstructionValue, JsxAttribute, JsxTag,
18 NonLocalBinding, ObjectProperty, ObjectPropertyKey, ObjectPropertyOrSpread,
19 ObjectPropertyType, ObjectPattern, ParamPattern, Pattern, Place, ReactFunctionType,
20 Terminal, ReturnVariant, IdentifierName, LValuePattern, FunctionId,
21};
22
23pub fn outline_jsx(func: &mut HirFunction, env: &mut Environment) {
27 let mut outlined_fns: Vec<HirFunction> = Vec::new();
28 outline_jsx_impl(func, env, &mut outlined_fns);
29
30 for outlined_fn in outlined_fns {
31 env.outline_function(outlined_fn, Some(ReactFunctionType::Component));
32 }
33}
34
35struct JsxInstrInfo {
37 instr_idx: usize, #[allow(dead_code)]
39 instr_id: InstructionId, lvalue_id: IdentifierId,
41 eval_order: EvaluationOrder,
42}
43
44struct OutlinedJsxAttribute {
45 original_name: String,
46 new_name: String,
47 place: Place,
48}
49
50struct OutlinedResult {
51 instrs: Vec<Instruction>,
52 func: HirFunction,
53}
54
55fn outline_jsx_impl(
56 func: &mut HirFunction,
57 env: &mut Environment,
58 outlined_fns: &mut Vec<HirFunction>,
59) {
60 let mut globals: HashMap<IdentifierId, usize> = HashMap::new(); let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect();
65 for block_id in &block_ids {
66 let block = &func.body.blocks[block_id];
67 let instr_ids = block.instructions.clone();
68
69 let mut rewrite_instr: HashMap<EvaluationOrder, Vec<Instruction>> = HashMap::new();
70 let mut jsx_group: Vec<JsxInstrInfo> = Vec::new();
71 let mut children_ids: HashSet<IdentifierId> = HashSet::new();
72
73 enum InstrAction {
75 LoadGlobal { lvalue_id: IdentifierId, instr_idx: usize },
76 FunctionExpr { func_id: FunctionId },
77 JsxExpr {
78 lvalue_id: IdentifierId,
79 instr_idx: usize,
80 eval_order: EvaluationOrder,
81 child_ids: Vec<IdentifierId>,
82 },
83 Other,
84 }
85
86 let mut actions: Vec<InstrAction> = Vec::new();
87 for i in (0..instr_ids.len()).rev() {
88 let iid = instr_ids[i];
89 let instr = &func.instructions[iid.0 as usize];
90 let lvalue_id = instr.lvalue.identifier;
91
92 match &instr.value {
93 InstructionValue::LoadGlobal { .. } => {
94 actions.push(InstrAction::LoadGlobal { lvalue_id, instr_idx: iid.0 as usize });
95 }
96 InstructionValue::FunctionExpression { lowered_func, .. } => {
97 actions.push(InstrAction::FunctionExpr { func_id: lowered_func.func });
98 }
99 InstructionValue::JsxExpression { children, .. } => {
100 let child_ids = children.as_ref()
101 .map(|kids| kids.iter().map(|c| c.identifier).collect())
102 .unwrap_or_default();
103 actions.push(InstrAction::JsxExpr {
104 lvalue_id,
105 instr_idx: iid.0 as usize,
106 eval_order: instr.id,
107 child_ids,
108 });
109 }
110 _ => {
111 actions.push(InstrAction::Other);
112 }
113 }
114 }
115
116 for action in actions {
118 match action {
119 InstrAction::LoadGlobal { lvalue_id, instr_idx } => {
120 globals.insert(lvalue_id, instr_idx);
121 }
122 InstrAction::FunctionExpr { func_id } => {
123 let mut inner_func = std::mem::replace(
124 &mut env.functions[func_id.0 as usize],
125 react_compiler_ssa::enter_ssa::placeholder_function(),
126 );
127 outline_jsx_impl(&mut inner_func, env, outlined_fns);
128 env.functions[func_id.0 as usize] = inner_func;
129 }
130 InstrAction::JsxExpr { lvalue_id, instr_idx, eval_order, child_ids } => {
131 if !children_ids.contains(&lvalue_id) {
132 process_and_outline_jsx(
133 func,
134 env,
135 &mut jsx_group,
136 &globals,
137 &mut rewrite_instr,
138 outlined_fns,
139 );
140 jsx_group.clear();
141 children_ids.clear();
142 }
143 jsx_group.push(JsxInstrInfo {
144 instr_idx,
145 instr_id: InstructionId(instr_idx as u32),
146 lvalue_id,
147 eval_order,
148 });
149 for child_id in child_ids {
150 children_ids.insert(child_id);
151 }
152 }
153 InstrAction::Other => {}
154 }
155 }
156 process_and_outline_jsx(
158 func,
159 env,
160 &mut jsx_group,
161 &globals,
162 &mut rewrite_instr,
163 outlined_fns,
164 );
165 if !rewrite_instr.is_empty() {
166 let block = func.body.blocks.get_mut(block_id).unwrap();
167 let old_instr_ids = block.instructions.clone();
168 let mut new_instr_ids = Vec::new();
169 for &iid in &old_instr_ids {
170 let eval_order = func.instructions[iid.0 as usize].id;
171 if let Some(replacement_instrs) = rewrite_instr.get(&eval_order) {
172 for new_instr in replacement_instrs {
174 let new_idx = func.instructions.len();
175 func.instructions.push(new_instr.clone());
176 new_instr_ids.push(InstructionId(new_idx as u32));
177 }
178 } else {
179 new_instr_ids.push(iid);
180 }
181 }
182 let block = func.body.blocks.get_mut(block_id).unwrap();
183 block.instructions = new_instr_ids;
184
185 super::dead_code_elimination(func, env);
187 }
188 }
189}
190
191fn process_and_outline_jsx(
192 func: &mut HirFunction,
193 env: &mut Environment,
194 jsx_group: &mut Vec<JsxInstrInfo>,
195 globals: &HashMap<IdentifierId, usize>,
196 rewrite_instr: &mut HashMap<EvaluationOrder, Vec<Instruction>>,
197 outlined_fns: &mut Vec<HirFunction>,
198) {
199 if jsx_group.len() <= 1 {
200 return;
201 }
202 jsx_group.sort_by_key(|j| j.eval_order);
204
205 let result = process_jsx_group(func, env, jsx_group, globals);
206 if let Some(result) = result {
207 outlined_fns.push(result.func);
208 let last_eval_order = jsx_group.last().unwrap().eval_order;
213 rewrite_instr.insert(last_eval_order, result.instrs);
214 }
215}
216
217fn process_jsx_group(
218 func: &HirFunction,
219 env: &mut Environment,
220 jsx_group: &[JsxInstrInfo],
221 globals: &HashMap<IdentifierId, usize>,
222) -> Option<OutlinedResult> {
223 if func.fn_type == ReactFunctionType::Component {
225 return None;
226 }
227
228 let props = collect_props(func, env, jsx_group)?;
229
230 let outlined_tag = env.generate_globally_unique_identifier_name(None);
231 let new_instrs = emit_outlined_jsx(func, env, jsx_group, &props, &outlined_tag)?;
232 let outlined_fn = emit_outlined_fn(func, env, jsx_group, &props, globals)?;
233
234 let mut outlined_fn = outlined_fn;
236 outlined_fn.id = Some(outlined_tag);
237
238 Some(OutlinedResult {
239 instrs: new_instrs,
240 func: outlined_fn,
241 })
242}
243
244fn collect_props(
245 func: &HirFunction,
246 env: &mut Environment,
247 jsx_group: &[JsxInstrInfo],
248) -> Option<Vec<OutlinedJsxAttribute>> {
249 let mut id_counter = 1u32;
250 let mut seen: HashSet<String> = HashSet::new();
251 let mut attributes = Vec::new();
252 let jsx_ids: HashSet<IdentifierId> = jsx_group.iter().map(|j| j.lvalue_id).collect();
253
254 let mut generate_name = |old_name: &str, _env: &mut Environment| -> String {
255 let mut new_name = old_name.to_string();
256 while seen.contains(&new_name) {
257 new_name = format!("{}{}", old_name, id_counter);
258 id_counter += 1;
259 }
260 seen.insert(new_name.clone());
261 new_name
264 };
265
266 for info in jsx_group {
267 let instr = &func.instructions[info.instr_idx];
268 if let InstructionValue::JsxExpression { props, children, .. } = &instr.value {
269 for attr in props {
270 match attr {
271 JsxAttribute::SpreadAttribute { .. } => return None,
272 JsxAttribute::Attribute { name, place } => {
273 let new_name = generate_name(name, env);
274 attributes.push(OutlinedJsxAttribute {
275 original_name: name.clone(),
276 new_name,
277 place: place.clone(),
278 });
279 }
280 }
281 }
282
283 if let Some(kids) = children {
284 for child in kids {
285 if jsx_ids.contains(&child.identifier) {
286 continue;
287 }
288 let child_id = child.identifier;
290 let decl_id = env.identifiers[child_id.0 as usize].declaration_id;
291 if env.identifiers[child_id.0 as usize].name.is_none() {
292 env.identifiers[child_id.0 as usize].name =
293 Some(IdentifierName::Promoted(format!("#t{}", decl_id.0)));
294 }
295
296 let child_name = match &env.identifiers[child_id.0 as usize].name {
297 Some(IdentifierName::Named(n)) => n.clone(),
298 Some(IdentifierName::Promoted(n)) => n.clone(),
299 None => format!("#t{}", decl_id.0),
300 };
301 let new_name = generate_name("t", env);
302 attributes.push(OutlinedJsxAttribute {
303 original_name: child_name,
304 new_name,
305 place: child.clone(),
306 });
307 }
308 }
309 }
310 }
311
312 Some(attributes)
313}
314
315fn emit_outlined_jsx(
316 func: &HirFunction,
317 env: &mut Environment,
318 jsx_group: &[JsxInstrInfo],
319 outlined_props: &[OutlinedJsxAttribute],
320 outlined_tag: &str,
321) -> Option<Vec<Instruction>> {
322 let props: Vec<JsxAttribute> = outlined_props
323 .iter()
324 .map(|p| JsxAttribute::Attribute {
325 name: p.new_name.clone(),
326 place: p.place.clone(),
327 })
328 .collect();
329
330 let load_id = env.next_identifier_id();
332 let decl_id = env.identifiers[load_id.0 as usize].declaration_id;
334 env.identifiers[load_id.0 as usize].name =
335 Some(IdentifierName::Promoted(format!("#T{}", decl_id.0)));
336
337 let load_place = Place {
338 identifier: load_id,
339 effect: react_compiler_hir::Effect::Unknown,
340 reactive: false,
341 loc: None,
342 };
343
344 let load_jsx = Instruction {
345 id: EvaluationOrder(0),
346 lvalue: load_place.clone(),
347 value: InstructionValue::LoadGlobal {
348 binding: NonLocalBinding::ModuleLocal {
349 name: outlined_tag.to_string(),
350 },
351 loc: None,
352 },
353 loc: None,
354 effects: None,
355 };
356
357 let last_info = jsx_group.last().unwrap();
359 let last_instr = &func.instructions[last_info.instr_idx];
360 let jsx_expr = Instruction {
361 id: EvaluationOrder(0),
362 lvalue: last_instr.lvalue.clone(),
363 value: InstructionValue::JsxExpression {
364 tag: JsxTag::Place(load_place),
365 props,
366 children: None,
367 loc: None,
368 opening_loc: None,
369 closing_loc: None,
370 },
371 loc: None,
372 effects: None,
373 };
374
375 Some(vec![load_jsx, jsx_expr])
376}
377
378fn emit_outlined_fn(
379 func: &HirFunction,
380 env: &mut Environment,
381 jsx_group: &[JsxInstrInfo],
382 old_props: &[OutlinedJsxAttribute],
383 globals: &HashMap<IdentifierId, usize>,
384) -> Option<HirFunction> {
385 let old_to_new_props = create_old_to_new_props_mapping(env, old_props);
386
387 let props_obj_id = env.next_identifier_id();
389 let decl_id = env.identifiers[props_obj_id.0 as usize].declaration_id;
390 env.identifiers[props_obj_id.0 as usize].name =
391 Some(IdentifierName::Promoted(format!("#t{}", decl_id.0)));
392 let props_obj = Place {
393 identifier: props_obj_id,
394 effect: react_compiler_hir::Effect::Unknown,
395 reactive: false,
396 loc: None,
397 };
398
399 let destructure_instr = emit_destructure_props(env, &props_obj, &old_to_new_props);
401
402 let load_global_instrs = emit_load_globals(func, jsx_group, globals)?;
404
405 let updated_jsx_instrs = emit_updated_jsx(func, jsx_group, &old_to_new_props);
407
408 let mut instructions = Vec::new();
410 instructions.push(destructure_instr);
411 instructions.extend(load_global_instrs);
412 instructions.extend(updated_jsx_instrs);
413
414 let mut instr_table = Vec::new();
416 let mut instr_ids = Vec::new();
417 for instr in instructions {
418 let idx = instr_table.len();
419 instr_table.push(instr);
420 instr_ids.push(InstructionId(idx as u32));
421 }
422
423 let last_lvalue = instr_table.last().unwrap().lvalue.clone();
425
426 let returns_id = env.next_identifier_id();
428 let returns_place = Place {
429 identifier: returns_id,
430 effect: react_compiler_hir::Effect::Unknown,
431 reactive: false,
432 loc: None,
433 };
434
435 let block = BasicBlock {
436 kind: BlockKind::Block,
437 id: BlockId(0),
438 instructions: instr_ids,
439 preds: indexmap::IndexSet::new(),
440 terminal: Terminal::Return {
441 value: last_lvalue,
442 return_variant: ReturnVariant::Explicit,
443 id: EvaluationOrder(0),
444 loc: None,
445 effects: None,
446 },
447 phis: Vec::new(),
448 };
449
450 let mut blocks = IndexMap::new();
451 blocks.insert(BlockId(0), block);
452
453 let outlined_fn = HirFunction {
454 id: None,
455 name_hint: None,
456 fn_type: ReactFunctionType::Other,
457 params: vec![ParamPattern::Place(props_obj)],
458 return_type_annotation: None,
459 returns: returns_place,
460 context: Vec::new(),
461 body: HIR {
462 entry: BlockId(0),
463 blocks,
464 },
465 instructions: instr_table,
466 generator: false,
467 is_async: false,
468 directives: Vec::new(),
469 aliasing_effects: Some(vec![]),
470 loc: None,
471 };
472
473 Some(outlined_fn)
474}
475
476fn emit_load_globals(
477 func: &HirFunction,
478 jsx_group: &[JsxInstrInfo],
479 globals: &HashMap<IdentifierId, usize>,
480) -> Option<Vec<Instruction>> {
481 let mut instructions = Vec::new();
482 for info in jsx_group {
483 let instr = &func.instructions[info.instr_idx];
484 if let InstructionValue::JsxExpression { tag, .. } = &instr.value {
485 if let JsxTag::Place(tag_place) = tag {
486 let global_instr_idx = globals.get(&tag_place.identifier)?;
487 instructions.push(func.instructions[*global_instr_idx].clone());
488 }
489 }
490 }
491 Some(instructions)
492}
493
494fn emit_updated_jsx(
495 func: &HirFunction,
496 jsx_group: &[JsxInstrInfo],
497 old_to_new_props: &IndexMap<IdentifierId, OutlinedJsxAttribute>,
498) -> Vec<Instruction> {
499 let jsx_ids: HashSet<IdentifierId> = jsx_group.iter().map(|j| j.lvalue_id).collect();
500 let mut new_instrs = Vec::new();
501
502 for info in jsx_group {
503 let instr = &func.instructions[info.instr_idx];
504 if let InstructionValue::JsxExpression {
505 tag,
506 props,
507 children,
508 loc,
509 opening_loc,
510 closing_loc,
511 } = &instr.value
512 {
513 let mut new_props = Vec::new();
514 for prop in props {
515 let (name, place) = match prop {
518 JsxAttribute::Attribute { name, place } => (name, place),
519 JsxAttribute::SpreadAttribute { .. } => {
520 unreachable!("Expected only JsxAttribute, not spread")
521 }
522 };
523 if name == "key" {
524 continue;
525 }
526 let new_prop = old_to_new_props
528 .get(&place.identifier)
529 .expect("Expected a new property for identifier");
530 new_props.push(JsxAttribute::Attribute {
531 name: new_prop.original_name.clone(),
532 place: new_prop.place.clone(),
533 });
534 }
535
536 let new_children = children.as_ref().map(|kids| {
537 kids.iter()
538 .map(|child| {
539 if jsx_ids.contains(&child.identifier) {
540 child.clone()
541 } else {
542 let new_prop = old_to_new_props
544 .get(&child.identifier)
545 .expect("Expected a new prop for child identifier");
546 new_prop.place.clone()
547 }
548 })
549 .collect()
550 });
551
552 new_instrs.push(Instruction {
553 id: instr.id,
554 lvalue: instr.lvalue.clone(),
555 value: InstructionValue::JsxExpression {
556 tag: tag.clone(),
557 props: new_props,
558 children: new_children,
559 loc: *loc,
560 opening_loc: *opening_loc,
561 closing_loc: *closing_loc,
562 },
563 loc: instr.loc,
564 effects: instr.effects.clone(),
565 });
566 }
567 }
568
569 new_instrs
570}
571
572fn create_old_to_new_props_mapping(
573 env: &mut Environment,
574 old_props: &[OutlinedJsxAttribute],
575) -> IndexMap<IdentifierId, OutlinedJsxAttribute> {
576 let mut old_to_new = IndexMap::new();
577
578 for old_prop in old_props {
579 if old_prop.original_name == "key" {
580 continue;
581 }
582
583 let new_id = env.next_identifier_id();
584 env.identifiers[new_id.0 as usize].name =
585 Some(IdentifierName::Named(old_prop.new_name.clone()));
586
587 let new_place = Place {
588 identifier: new_id,
589 effect: react_compiler_hir::Effect::Unknown,
590 reactive: false,
591 loc: None,
592 };
593
594 old_to_new.insert(
595 old_prop.place.identifier,
596 OutlinedJsxAttribute {
597 original_name: old_prop.original_name.clone(),
598 new_name: old_prop.new_name.clone(),
599 place: new_place,
600 },
601 );
602 }
603
604 old_to_new
605}
606
607fn emit_destructure_props(
608 env: &mut Environment,
609 props_obj: &Place,
610 old_to_new_props: &IndexMap<IdentifierId, OutlinedJsxAttribute>,
611) -> Instruction {
612 let mut properties = Vec::new();
613 for prop in old_to_new_props.values() {
614 properties.push(ObjectPropertyOrSpread::Property(ObjectProperty {
615 key: ObjectPropertyKey::String {
616 name: prop.new_name.clone(),
617 },
618 property_type: ObjectPropertyType::Property,
619 place: prop.place.clone(),
620 }));
621 }
622
623 let lvalue_id = env.next_identifier_id();
624 let lvalue = Place {
625 identifier: lvalue_id,
626 effect: react_compiler_hir::Effect::Unknown,
627 reactive: false,
628 loc: None,
629 };
630
631 Instruction {
632 id: EvaluationOrder(0),
633 lvalue,
634 value: InstructionValue::Destructure {
635 lvalue: LValuePattern {
636 pattern: Pattern::Object(ObjectPattern {
637 properties,
638 loc: None,
639 }),
640 kind: InstructionKind::Let,
641 },
642 value: props_obj.clone(),
643 loc: None,
644 },
645 loc: None,
646 effects: None,
647 }
648}