main
rs 531 lines 18.5 KB
Raw
1 use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
2
3 use indexmap::IndexMap;
4 use react_compiler_diagnostics::{CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory};
5 use react_compiler_hir::environment::Environment;
6 use react_compiler_hir::visitors;
7 use react_compiler_hir::*;
8
9 // =============================================================================
10 // SSABuilder
11 // =============================================================================
12
13 struct IncompletePhi {
14 old_place: Place,
15 new_place: Place,
16 }
17
18 struct State {
19 defs: FxHashMap<IdentifierId, IdentifierId>,
20 incomplete_phis: Vec<IncompletePhi>,
21 }
22
23 struct SSABuilder {
24 states: FxHashMap<BlockId, State>,
25 current: Option<BlockId>,
26 unsealed_preds: FxHashMap<BlockId, u32>,
27 block_preds: FxHashMap<BlockId, Vec<BlockId>>,
28 unknown: FxHashSet<IdentifierId>,
29 context: FxHashSet<IdentifierId>,
30 pending_phis: FxHashMap<BlockId, Vec<Phi>>,
31 processed_functions: Vec<FunctionId>,
32 }
33
34 impl SSABuilder {
35 fn new(blocks: &IndexMap<BlockId, BasicBlock, FxBuildHasher>) -> Self {
36 let mut block_preds = FxHashMap::default();
37 for (id, block) in blocks {
38 block_preds.insert(*id, block.preds.iter().copied().collect());
39 }
40 SSABuilder {
41 states: FxHashMap::default(),
42 current: None,
43 unsealed_preds: FxHashMap::default(),
44 block_preds,
45 unknown: FxHashSet::default(),
46 context: FxHashSet::default(),
47 pending_phis: FxHashMap::default(),
48 processed_functions: Vec::new(),
49 }
50 }
51
52 fn define_function(&mut self, func: &HirFunction) {
53 for (id, block) in &func.body.blocks {
54 self.block_preds
55 .insert(*id, block.preds.iter().copied().collect());
56 }
57 }
58
59 fn state_mut(&mut self) -> &mut State {
60 let current = self
61 .current
62 .expect("we need to be in a block to access state!");
63 self.states
64 .get_mut(&current)
65 .expect("state not found for current block")
66 }
67
68 fn make_id(&mut self, old_id: IdentifierId, env: &mut Environment) -> IdentifierId {
69 let new_id = env.next_identifier_id();
70 let old = &env.identifiers[old_id.0 as usize];
71 let declaration_id = old.declaration_id;
72 let name = old.name.clone();
73 let loc = old.loc;
74 let new_ident = &mut env.identifiers[new_id.0 as usize];
75 new_ident.declaration_id = declaration_id;
76 new_ident.name = name;
77 new_ident.loc = loc;
78 new_id
79 }
80
81 fn define_place(
82 &mut self,
83 old_place: &Place,
84 env: &mut Environment,
85 ) -> Result<Place, CompilerDiagnostic> {
86 let old_id = old_place.identifier;
87
88 if self.unknown.contains(&old_id) {
89 let ident = &env.identifiers[old_id.0 as usize];
90 let name = match &ident.name {
91 Some(name) => format!("{}${}", name.value(), old_id.0),
92 None => format!("${}", old_id.0),
93 };
94 return Err(CompilerDiagnostic::new(
95 ErrorCategory::Todo,
96 "[hoisting] EnterSSA: Expected identifier to be defined before being used",
97 Some(format!("Identifier {} is undefined", name)),
98 )
99 .with_detail(CompilerDiagnosticDetail::Error {
100 loc: old_place.loc,
101 message: None,
102 identifier_name: None,
103 }));
104 }
105
106 // Do not redefine context references.
107 if self.context.contains(&old_id) {
108 return Ok(self.get_place(old_place, env));
109 }
110
111 let new_id = self.make_id(old_id, env);
112 self.state_mut().defs.insert(old_id, new_id);
113 Ok(Place {
114 identifier: new_id,
115 effect: old_place.effect,
116 reactive: old_place.reactive,
117 loc: old_place.loc,
118 })
119 }
120
121 #[allow(dead_code)]
122 fn define_context(
123 &mut self,
124 old_place: &Place,
125 env: &mut Environment,
126 ) -> Result<Place, CompilerDiagnostic> {
127 let old_id = old_place.identifier;
128 let new_place = self.define_place(old_place, env)?;
129 self.context.insert(old_id);
130 Ok(new_place)
131 }
132
133 /// A function's context places capture a *binding*, not a value: the
134 /// variable is only read when the function is later called, so a context
135 /// place may reference a binding that is declared after the function
136 /// expression itself (eg `const colgroup = useMemo(() => <colgroup>...)`,
137 /// where the JSX tag name resolves to the variable being assigned). Unmark
138 /// such identifiers so the later declaration doesn't error; if the function
139 /// body actually *reads* the variable before it is defined, visiting the
140 /// body re-marks it and the hoisting bailout in define_place still applies.
141 fn unmark_unknown(&mut self, id: IdentifierId) {
142 self.unknown.remove(&id);
143 }
144
145 fn get_place(&mut self, old_place: &Place, env: &mut Environment) -> Place {
146 let current_id = self.current.expect("must be in a block");
147 let new_id = self.get_id_at(old_place, current_id, env);
148 Place {
149 identifier: new_id,
150 effect: old_place.effect,
151 reactive: old_place.reactive,
152 loc: old_place.loc,
153 }
154 }
155
156 fn get_id_at(
157 &mut self,
158 old_place: &Place,
159 block_id: BlockId,
160 env: &mut Environment,
161 ) -> IdentifierId {
162 if let Some(state) = self.states.get(&block_id) {
163 if let Some(&new_id) = state.defs.get(&old_place.identifier) {
164 return new_id;
165 }
166 }
167
168 let preds = self.block_preds.get(&block_id).cloned().unwrap_or_default();
169
170 if preds.is_empty() {
171 self.unknown.insert(old_place.identifier);
172 return old_place.identifier;
173 }
174
175 let unsealed = self.unsealed_preds.get(&block_id).copied().unwrap_or(0);
176 if unsealed > 0 {
177 let new_id = self.make_id(old_place.identifier, env);
178 let new_place = Place {
179 identifier: new_id,
180 effect: old_place.effect,
181 reactive: old_place.reactive,
182 loc: old_place.loc,
183 };
184 let state = self.states.get_mut(&block_id).unwrap();
185 state.incomplete_phis.push(IncompletePhi {
186 old_place: old_place.clone(),
187 new_place,
188 });
189 state.defs.insert(old_place.identifier, new_id);
190 return new_id;
191 }
192
193 if preds.len() == 1 {
194 let pred = preds[0];
195 let new_id = self.get_id_at(old_place, pred, env);
196 self.states
197 .get_mut(&block_id)
198 .unwrap()
199 .defs
200 .insert(old_place.identifier, new_id);
201 return new_id;
202 }
203
204 let new_id = self.make_id(old_place.identifier, env);
205 self.states
206 .get_mut(&block_id)
207 .unwrap()
208 .defs
209 .insert(old_place.identifier, new_id);
210 let new_place = Place {
211 identifier: new_id,
212 effect: old_place.effect,
213 reactive: old_place.reactive,
214 loc: old_place.loc,
215 };
216 self.add_phi(block_id, old_place, &new_place, env);
217 new_id
218 }
219
220 fn add_phi(
221 &mut self,
222 block_id: BlockId,
223 old_place: &Place,
224 new_place: &Place,
225 env: &mut Environment,
226 ) {
227 let preds = self.block_preds.get(&block_id).cloned().unwrap_or_default();
228
229 let mut pred_defs: IndexMap<BlockId, Place, FxBuildHasher> = IndexMap::default();
230 for pred_block_id in &preds {
231 let pred_id = self.get_id_at(old_place, *pred_block_id, env);
232 pred_defs.insert(
233 *pred_block_id,
234 Place {
235 identifier: pred_id,
236 effect: old_place.effect,
237 reactive: old_place.reactive,
238 loc: old_place.loc,
239 },
240 );
241 }
242
243 let phi = Phi {
244 place: new_place.clone(),
245 operands: pred_defs,
246 };
247
248 self.pending_phis.entry(block_id).or_default().push(phi);
249 }
250
251 fn fix_incomplete_phis(&mut self, block_id: BlockId, env: &mut Environment) {
252 let incomplete_phis: Vec<IncompletePhi> = self
253 .states
254 .get_mut(&block_id)
255 .unwrap()
256 .incomplete_phis
257 .drain(..)
258 .collect();
259 for phi in &incomplete_phis {
260 self.add_phi(block_id, &phi.old_place, &phi.new_place, env);
261 }
262 }
263
264 fn start_block(&mut self, block_id: BlockId) {
265 self.current = Some(block_id);
266 self.states.insert(
267 block_id,
268 State {
269 defs: FxHashMap::default(),
270 incomplete_phis: Vec::new(),
271 },
272 );
273 }
274 }
275
276 // =============================================================================
277 // Public entry point
278 // =============================================================================
279
280 pub fn enter_ssa(func: &mut HirFunction, env: &mut Environment) -> Result<(), CompilerDiagnostic> {
281 let mut builder = SSABuilder::new(&func.body.blocks);
282 let root_entry = func.body.entry;
283 enter_ssa_impl(func, &mut builder, env, root_entry)?;
284
285 // Apply all pending phis to the actual blocks
286 apply_pending_phis(func, env, &mut builder);
287
288 Ok(())
289 }
290
291 fn apply_pending_phis(func: &mut HirFunction, env: &mut Environment, builder: &mut SSABuilder) {
292 for (block_id, block) in func.body.blocks.iter_mut() {
293 if let Some(phis) = builder.pending_phis.remove(block_id) {
294 block.phis.extend(phis);
295 }
296 }
297 for fid in &builder.processed_functions.clone() {
298 let inner_func = &mut env.functions[fid.0 as usize];
299 for (block_id, block) in inner_func.body.blocks.iter_mut() {
300 if let Some(phis) = builder.pending_phis.remove(block_id) {
301 block.phis.extend(phis);
302 }
303 }
304 }
305 }
306
307 fn enter_ssa_impl(
308 func: &mut HirFunction,
309 builder: &mut SSABuilder,
310 env: &mut Environment,
311 root_entry: BlockId,
312 ) -> Result<(), CompilerDiagnostic> {
313 let mut visited_blocks: FxHashSet<BlockId> = FxHashSet::default();
314 let block_ids: Vec<BlockId> = func.body.blocks.keys().copied().collect();
315
316 for block_id in &block_ids {
317 let block_id = *block_id;
318
319 if visited_blocks.contains(&block_id) {
320 return Err(CompilerDiagnostic::new(
321 ErrorCategory::Invariant,
322 format!("found a cycle! visiting bb{} again", block_id.0),
323 None,
324 ));
325 }
326
327 visited_blocks.insert(block_id);
328 builder.start_block(block_id);
329
330 // Handle params at the root entry
331 if block_id == root_entry {
332 if !func.context.is_empty() {
333 return Err(CompilerDiagnostic::new(
334 ErrorCategory::Invariant,
335 "Expected function context to be empty for outer function declarations",
336 None,
337 ));
338 }
339 let params = std::mem::take(&mut func.params);
340 let mut new_params = Vec::with_capacity(params.len());
341 for param in params {
342 new_params.push(match param {
343 ParamPattern::Place(p) => ParamPattern::Place(builder.define_place(&p, env)?),
344 ParamPattern::Spread(s) => ParamPattern::Spread(SpreadPattern {
345 place: builder.define_place(&s.place, env)?,
346 }),
347 });
348 }
349 func.params = new_params;
350 }
351
352 // Process instructions
353 let instruction_ids: Vec<InstructionId> = func
354 .body
355 .blocks
356 .get(&block_id)
357 .unwrap()
358 .instructions
359 .clone();
360
361 for instr_id in &instruction_ids {
362 let instr_idx = instr_id.0 as usize;
363 let instr = &mut func.instructions[instr_idx];
364
365 // For FunctionExpression/ObjectMethod, we need to handle context
366 // mapping specially because env.functions is borrowed by the closure.
367 // First, check if this is a FunctionExpression/ObjectMethod and handle
368 // context mapping separately.
369 let func_expr_id = match &instr.value {
370 InstructionValue::FunctionExpression { lowered_func, .. }
371 | InstructionValue::ObjectMethod { lowered_func, .. } => Some(lowered_func.func),
372 _ => None,
373 };
374
375 // Map context places for function expressions before other operands
376 if let Some(fid) = func_expr_id {
377 let context = std::mem::take(&mut env.functions[fid.0 as usize].context);
378 env.functions[fid.0 as usize].context = context
379 .into_iter()
380 .map(|place| builder.get_place(&place, env))
381 .collect();
382 }
383
384 // Map non-context operands
385 visitors::for_each_instruction_value_operand_mut(&mut instr.value, &mut |place| {
386 *place = builder.get_place(place, env);
387 });
388
389 // Map lvalues (skip DeclareContext/StoreContext — context variables
390 // don't participate in SSA renaming)
391 let instr = &mut func.instructions[instr_idx];
392 let mut lvalue_err: Option<CompilerDiagnostic> = None;
393 visitors::for_each_instruction_lvalue_mut(instr, &mut |place| {
394 if lvalue_err.is_none() {
395 match builder.define_place(place, env) {
396 Ok(new_place) => *place = new_place,
397 Err(e) => lvalue_err = Some(e),
398 }
399 }
400 });
401 if let Some(e) = lvalue_err {
402 return Err(e);
403 }
404
405 // Handle inner function SSA
406 if let Some(fid) = func_expr_id {
407 let context_ids: Vec<IdentifierId> = env.functions[fid.0 as usize]
408 .context
409 .iter()
410 .map(|place| place.identifier)
411 .collect();
412 for id in context_ids {
413 builder.unmark_unknown(id);
414 }
415 builder.processed_functions.push(fid);
416 let inner_func = &mut env.functions[fid.0 as usize];
417 let inner_entry = inner_func.body.entry;
418 let entry_block = inner_func.body.blocks.get_mut(&inner_entry).unwrap();
419
420 if !entry_block.preds.is_empty() {
421 return Err(CompilerDiagnostic::new(
422 ErrorCategory::Invariant,
423 "Expected function expression entry block to have zero predecessors",
424 None,
425 ));
426 }
427 entry_block.preds.insert(block_id);
428
429 builder.define_function(inner_func);
430
431 let saved_current = builder.current;
432
433 // Map inner function params
434 let inner_params = std::mem::take(&mut env.functions[fid.0 as usize].params);
435 let mut new_inner_params = Vec::with_capacity(inner_params.len());
436 for param in inner_params {
437 new_inner_params.push(match param {
438 ParamPattern::Place(p) => {
439 ParamPattern::Place(builder.define_place(&p, env)?)
440 }
441 ParamPattern::Spread(s) => ParamPattern::Spread(SpreadPattern {
442 place: builder.define_place(&s.place, env)?,
443 }),
444 });
445 }
446 env.functions[fid.0 as usize].params = new_inner_params;
447
448 // Take the inner function out of the arena to process it
449 let mut inner_func =
450 std::mem::replace(&mut env.functions[fid.0 as usize], placeholder_function());
451
452 enter_ssa_impl(&mut inner_func, builder, env, root_entry)?;
453
454 // Put it back
455 env.functions[fid.0 as usize] = inner_func;
456
457 builder.current = saved_current;
458
459 // Clear entry preds
460 env.functions[fid.0 as usize]
461 .body
462 .blocks
463 .get_mut(&inner_entry)
464 .unwrap()
465 .preds
466 .clear();
467 builder.block_preds.insert(inner_entry, Vec::new());
468 }
469 }
470
471 // Map terminal operands
472 let terminal = &mut func.body.blocks.get_mut(&block_id).unwrap().terminal;
473 visitors::for_each_terminal_operand_mut(terminal, &mut |place| {
474 *place = builder.get_place(place, env);
475 });
476
477 // Handle successors
478 let terminal_ref = &func.body.blocks.get(&block_id).unwrap().terminal;
479 let successors = visitors::each_terminal_successor(terminal_ref);
480 for output_id in successors {
481 let output_preds_len = builder
482 .block_preds
483 .get(&output_id)
484 .map(|p| p.len() as u32)
485 .unwrap_or(0);
486
487 let count = if builder.unsealed_preds.contains_key(&output_id) {
488 builder.unsealed_preds[&output_id] - 1
489 } else {
490 output_preds_len - 1
491 };
492 builder.unsealed_preds.insert(output_id, count);
493
494 if count == 0 && visited_blocks.contains(&output_id) {
495 builder.fix_incomplete_phis(output_id, env);
496 }
497 }
498 }
499
500 Ok(())
501 }
502
503 /// Create a placeholder HirFunction for temporarily swapping an inner function
504 /// out of `env.functions` via `std::mem::replace`. The placeholder is never
505 /// read — the real function is swapped back immediately after processing.
506 pub fn placeholder_function() -> HirFunction {
507 HirFunction {
508 loc: None,
509 id: None,
510 name_hint: None,
511 fn_type: ReactFunctionType::Other,
512 params: Vec::new(),
513 return_type_annotation: None,
514 returns: Place {
515 identifier: IdentifierId(0),
516 effect: Effect::Unknown,
517 reactive: false,
518 loc: None,
519 },
520 context: Vec::new(),
521 body: HIR {
522 entry: BlockId(0),
523 blocks: IndexMap::default(),
524 },
525 instructions: Vec::new(),
526 generator: false,
527 is_async: false,
528 directives: Vec::new(),
529 aliasing_effects: None,
530 }
531 }