1use std::collections::{HashMap, HashSet};
18
19use indexmap::IndexMap;
20
21use react_compiler_diagnostics::{CompilerDiagnostic, ErrorCategory};
22use react_compiler_hir::environment::Environment;
23use react_compiler_hir::type_config::{ValueKind, ValueReason};
24use react_compiler_hir::visitors::{
25 each_instruction_value_lvalue, for_each_instruction_value_lvalue_mut,
26 for_each_instruction_value_operand_mut, for_each_terminal_operand_mut,
27};
28use react_compiler_hir::{
29 AliasingEffect, BlockId, Effect, EvaluationOrder, FunctionId, HirFunction, IdentifierId,
30 InstructionValue, MutationReason, Place, SourceLocation, is_jsx_type, is_primitive_type,
31};
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
38#[allow(dead_code)]
39enum MutationKind {
40 None = 0,
41 Conditional = 1,
42 Definite = 2,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50enum EdgeKind {
51 Capture,
52 Alias,
53 MaybeAlias,
54}
55
56#[derive(Debug, Clone)]
57struct Edge {
58 index: usize,
59 node: IdentifierId,
60 kind: EdgeKind,
61}
62
63#[derive(Debug, Clone)]
64struct MutationInfo {
65 kind: MutationKind,
66 loc: Option<SourceLocation>,
67}
68
69#[derive(Debug, Clone)]
70enum NodeValue {
71 Object,
72 Phi,
73 Function { function_id: FunctionId },
74}
75
76#[derive(Debug, Clone)]
77struct Node {
78 id: IdentifierId,
79 created_from: IndexMap<IdentifierId, usize>,
80 captures: IndexMap<IdentifierId, usize>,
81 aliases: IndexMap<IdentifierId, usize>,
82 maybe_aliases: IndexMap<IdentifierId, usize>,
83 edges: Vec<Edge>,
84 transitive: Option<MutationInfo>,
85 local: Option<MutationInfo>,
86 last_mutated: usize,
87 mutation_reason: Option<MutationReason>,
88 value: NodeValue,
89}
90
91impl Node {
92 fn new(id: IdentifierId, value: NodeValue) -> Self {
93 Node {
94 id,
95 created_from: IndexMap::new(),
96 captures: IndexMap::new(),
97 aliases: IndexMap::new(),
98 maybe_aliases: IndexMap::new(),
99 edges: Vec::new(),
100 transitive: None,
101 local: None,
102 last_mutated: 0,
103 mutation_reason: None,
104 value,
105 }
106 }
107}
108
109struct AliasingState {
110 nodes: IndexMap<IdentifierId, Node>,
111}
112
113impl AliasingState {
114 fn new() -> Self {
115 AliasingState {
116 nodes: IndexMap::new(),
117 }
118 }
119
120 fn create(&mut self, place: &Place, value: NodeValue) {
121 self.nodes
122 .insert(place.identifier, Node::new(place.identifier, value));
123 }
124
125 fn create_from(&mut self, index: usize, from: &Place, into: &Place) {
126 self.create(into, NodeValue::Object);
127 let from_id = from.identifier;
128 let into_id = into.identifier;
129 if let Some(from_node) = self.nodes.get_mut(&from_id) {
131 from_node.edges.push(Edge {
132 index,
133 node: into_id,
134 kind: EdgeKind::Alias,
135 });
136 }
137 if let Some(to_node) = self.nodes.get_mut(&into_id) {
139 to_node.created_from.entry(from_id).or_insert(index);
140 }
141 }
142
143 fn capture(&mut self, index: usize, from: &Place, into: &Place) {
144 let from_id = from.identifier;
145 let into_id = into.identifier;
146 if !self.nodes.contains_key(&from_id) || !self.nodes.contains_key(&into_id) {
147 return;
148 }
149 self.nodes.get_mut(&from_id).unwrap().edges.push(Edge {
150 index,
151 node: into_id,
152 kind: EdgeKind::Capture,
153 });
154 self.nodes.get_mut(&into_id).unwrap().captures.entry(from_id).or_insert(index);
155 }
156
157 fn assign(&mut self, index: usize, from: &Place, into: &Place) {
158 let from_id = from.identifier;
159 let into_id = into.identifier;
160 if !self.nodes.contains_key(&from_id) || !self.nodes.contains_key(&into_id) {
161 return;
162 }
163 self.nodes.get_mut(&from_id).unwrap().edges.push(Edge {
164 index,
165 node: into_id,
166 kind: EdgeKind::Alias,
167 });
168 self.nodes.get_mut(&into_id).unwrap().aliases.entry(from_id).or_insert(index);
169 }
170
171 fn maybe_alias(&mut self, index: usize, from: &Place, into: &Place) {
172 let from_id = from.identifier;
173 let into_id = into.identifier;
174 if !self.nodes.contains_key(&from_id) || !self.nodes.contains_key(&into_id) {
175 return;
176 }
177 self.nodes.get_mut(&from_id).unwrap().edges.push(Edge {
178 index,
179 node: into_id,
180 kind: EdgeKind::MaybeAlias,
181 });
182 self.nodes.get_mut(&into_id).unwrap().maybe_aliases.entry(from_id).or_insert(index);
183 }
184
185 fn render(&self, index: usize, start: IdentifierId, env: &mut Environment) {
186 let mut seen = HashSet::new();
187 let mut queue: Vec<IdentifierId> = vec![start];
188 while let Some(current) = queue.pop() {
189 if !seen.insert(current) {
190 continue;
191 }
192 let node = match self.nodes.get(¤t) {
193 Some(n) => n,
194 None => continue,
195 };
196 if node.transitive.is_some() || node.local.is_some() {
197 continue;
198 }
199 if let NodeValue::Function { function_id } = &node.value {
200 append_function_errors(env, *function_id);
201 }
202 for (&alias, &when) in &node.created_from {
203 if when >= index {
204 continue;
205 }
206 queue.push(alias);
207 }
208 for (&alias, &when) in &node.aliases {
209 if when >= index {
210 continue;
211 }
212 queue.push(alias);
213 }
214 for (&capture, &when) in &node.captures {
215 if when >= index {
216 continue;
217 }
218 queue.push(capture);
219 }
220 }
221 }
222
223 fn mutate(
224 &mut self,
225 index: usize,
226 start: IdentifierId,
227 end: Option<EvaluationOrder>, transitive: bool,
229 start_kind: MutationKind,
230 loc: Option<SourceLocation>,
231 reason: Option<MutationReason>,
232 env: &mut Environment,
233 should_record_errors: bool,
234 ) {
235 #[derive(Clone)]
236 struct QueueEntry {
237 place: IdentifierId,
238 transitive: bool,
239 direction: Direction,
240 kind: MutationKind,
241 }
242 #[derive(Clone, Copy, PartialEq)]
243 enum Direction {
244 Backwards,
245 Forwards,
246 }
247
248 let mut seen: HashMap<IdentifierId, MutationKind> = HashMap::new();
249 let mut queue: Vec<QueueEntry> = vec![QueueEntry {
250 place: start,
251 transitive,
252 direction: Direction::Backwards,
253 kind: start_kind,
254 }];
255
256 while let Some(entry) = queue.pop() {
257 let current = entry.place;
258 let previous_kind = seen.get(¤t).copied();
259 if let Some(prev) = previous_kind {
260 if prev >= entry.kind {
261 continue;
262 }
263 }
264 seen.insert(current, entry.kind);
265
266 let node = match self.nodes.get_mut(¤t) {
267 Some(n) => n,
268 None => continue,
269 };
270
271 if node.mutation_reason.is_none() {
272 node.mutation_reason = reason.clone();
273 }
274 node.last_mutated = node.last_mutated.max(index);
275
276 if let Some(end_val) = end {
277 let ident = &mut env.identifiers[node.id.0 as usize];
278 ident.mutable_range.end = EvaluationOrder(
279 ident.mutable_range.end.0.max(end_val.0),
280 );
281 }
282
283 if let NodeValue::Function { function_id } = &node.value {
284 if node.transitive.is_none() && node.local.is_none() {
285 if should_record_errors {
286 append_function_errors(env, *function_id);
287 }
288 }
289 }
290
291 if entry.transitive {
292 match &node.transitive {
293 None => {
294 node.transitive = Some(MutationInfo {
295 kind: entry.kind,
296 loc,
297 });
298 }
299 Some(existing) if existing.kind < entry.kind => {
300 node.transitive = Some(MutationInfo {
301 kind: entry.kind,
302 loc,
303 });
304 }
305 _ => {}
306 }
307 } else {
308 match &node.local {
309 None => {
310 node.local = Some(MutationInfo {
311 kind: entry.kind,
312 loc,
313 });
314 }
315 Some(existing) if existing.kind < entry.kind => {
316 node.local = Some(MutationInfo {
317 kind: entry.kind,
318 loc,
319 });
320 }
321 _ => {}
322 }
323 }
324
325 let edges: Vec<Edge> = node.edges.clone();
328 let node_value_kind = match &node.value {
329 NodeValue::Phi => "Phi",
330 _ => "Other",
331 };
332 let node_aliases: Vec<(IdentifierId, usize)> =
333 node.aliases.iter().map(|(&k, &v)| (k, v)).collect();
334 let node_maybe_aliases: Vec<(IdentifierId, usize)> =
335 node.maybe_aliases.iter().map(|(&k, &v)| (k, v)).collect();
336 let node_captures: Vec<(IdentifierId, usize)> =
337 node.captures.iter().map(|(&k, &v)| (k, v)).collect();
338 let node_created_from: Vec<(IdentifierId, usize)> =
339 node.created_from.iter().map(|(&k, &v)| (k, v)).collect();
340
341 for edge in &edges {
342 if edge.index >= index {
343 break;
344 }
345 queue.push(QueueEntry {
346 place: edge.node,
347 transitive: entry.transitive,
348 direction: Direction::Forwards,
349 kind: if edge.kind == EdgeKind::MaybeAlias {
351 MutationKind::Conditional
352 } else {
353 entry.kind
354 },
355 });
356 }
357
358 for (alias, when) in &node_created_from {
359 if *when >= index {
360 continue;
361 }
362 queue.push(QueueEntry {
363 place: *alias,
364 transitive: true,
365 direction: Direction::Backwards,
366 kind: entry.kind,
367 });
368 }
369
370 if entry.direction == Direction::Backwards || node_value_kind != "Phi" {
371 for (alias, when) in &node_aliases {
373 if *when >= index {
374 continue;
375 }
376 queue.push(QueueEntry {
377 place: *alias,
378 transitive: entry.transitive,
379 direction: Direction::Backwards,
380 kind: entry.kind,
381 });
382 }
383 for (alias, when) in &node_maybe_aliases {
385 if *when >= index {
386 continue;
387 }
388 queue.push(QueueEntry {
389 place: *alias,
390 transitive: entry.transitive,
391 direction: Direction::Backwards,
392 kind: MutationKind::Conditional,
393 });
394 }
395 }
396
397 if entry.transitive {
399 for (capture, when) in &node_captures {
400 if *when >= index {
401 continue;
402 }
403 queue.push(QueueEntry {
404 place: *capture,
405 transitive: entry.transitive,
406 direction: Direction::Backwards,
407 kind: entry.kind,
408 });
409 }
410 }
411 }
412 }
413}
414
415fn append_function_errors(env: &mut Environment, function_id: FunctionId) {
420 let func = &env.functions[function_id.0 as usize];
421 if let Some(ref effects) = func.aliasing_effects {
422 let errors: Vec<_> = effects
424 .iter()
425 .filter_map(|effect| match effect {
426 AliasingEffect::Impure { error, .. }
427 | AliasingEffect::MutateFrozen { error, .. }
428 | AliasingEffect::MutateGlobal { error, .. } => Some(error.clone()),
429 _ => None,
430 })
431 .collect();
432 for error in errors {
433 env.record_diagnostic(error);
434 }
435 }
436}
437
438pub fn infer_mutation_aliasing_ranges(
449 func: &mut HirFunction,
450 env: &mut Environment,
451 is_function_expression: bool,
452) -> Result<Vec<AliasingEffect>, CompilerDiagnostic> {
453 let mut function_effects: Vec<AliasingEffect> = Vec::new();
454
455 let mut state = AliasingState::new();
459
460 struct PendingPhiOperand {
461 from: Place,
462 into: Place,
463 index: usize,
464 }
465 let mut pending_phis: HashMap<BlockId, Vec<PendingPhiOperand>> = HashMap::new();
466
467 struct PendingMutation {
468 index: usize,
469 id: EvaluationOrder,
470 transitive: bool,
471 kind: MutationKind,
472 place: Place,
473 reason: Option<MutationReason>,
474 }
475 let mut mutations: Vec<PendingMutation> = Vec::new();
476
477 struct PendingRender {
478 index: usize,
479 place: Place,
480 }
481 let mut renders: Vec<PendingRender> = Vec::new();
482
483 let mut index: usize = 0;
484
485 let should_record_errors = !is_function_expression && env.enable_validations();
486
487 for param in &func.params {
489 let place = match param {
490 react_compiler_hir::ParamPattern::Place(p) => p,
491 react_compiler_hir::ParamPattern::Spread(s) => &s.place,
492 };
493 state.create(place, NodeValue::Object);
494 }
495 for ctx in &func.context {
496 state.create(ctx, NodeValue::Object);
497 }
498 state.create(&func.returns, NodeValue::Object);
499
500 let mut seen_blocks: HashSet<BlockId> = HashSet::new();
501
502 let block_order: Vec<BlockId> = func.body.blocks.keys().cloned().collect();
504
505 for &block_id in &block_order {
506 let block = &func.body.blocks[&block_id];
507
508 for phi in &block.phis {
510 state.create(&phi.place, NodeValue::Phi);
511 for (&pred, operand) in &phi.operands {
512 if !seen_blocks.contains(&pred) {
513 pending_phis
514 .entry(pred)
515 .or_insert_with(Vec::new)
516 .push(PendingPhiOperand {
517 from: operand.clone(),
518 into: phi.place.clone(),
519 index: index,
520 });
521 index += 1;
522 } else {
523 state.assign(index, operand, &phi.place);
524 index += 1;
525 }
526 }
527 }
528 seen_blocks.insert(block_id);
529
530 let instr_ids: Vec<_> = block.instructions.clone();
532 for instr_id in &instr_ids {
533 let instr = &func.instructions[instr_id.0 as usize];
534 let instr_eval_order = instr.id;
535 let effects = match &instr.effects {
536 Some(e) => e.clone(),
537 None => continue,
538 };
539 for effect in &effects {
540 match effect {
541 AliasingEffect::Create { into, .. } => {
542 state.create(into, NodeValue::Object);
543 }
544 AliasingEffect::CreateFunction {
545 into, function_id, ..
546 } => {
547 state.create(
548 into,
549 NodeValue::Function {
550 function_id: *function_id,
551 },
552 );
553 }
554 AliasingEffect::CreateFrom { from, into } => {
555 state.create_from(index, from, into);
556 index += 1;
557 }
558 AliasingEffect::Assign { from, into } => {
559 if !state.nodes.contains_key(&into.identifier) {
560 state.create(into, NodeValue::Object);
561 }
562 state.assign(index, from, into);
563 index += 1;
564 }
565 AliasingEffect::Alias { from, into } => {
566 state.assign(index, from, into);
567 index += 1;
568 }
569 AliasingEffect::MaybeAlias { from, into } => {
570 state.maybe_alias(index, from, into);
571 index += 1;
572 }
573 AliasingEffect::Capture { from, into } => {
574 state.capture(index, from, into);
575 index += 1;
576 }
577 AliasingEffect::MutateTransitive { value }
578 | AliasingEffect::MutateTransitiveConditionally { value } => {
579 let is_transitive_conditional = matches!(
580 effect,
581 AliasingEffect::MutateTransitiveConditionally { .. }
582 );
583 mutations.push(PendingMutation {
584 index: index,
585 id: instr_eval_order,
586 transitive: true,
587 kind: if is_transitive_conditional {
588 MutationKind::Conditional
589 } else {
590 MutationKind::Definite
591 },
592 reason: None,
593 place: value.clone(),
594 });
595 index += 1;
596 }
597 AliasingEffect::Mutate { value, reason } => {
598 mutations.push(PendingMutation {
599 index: index,
600 id: instr_eval_order,
601 transitive: false,
602 kind: MutationKind::Definite,
603 reason: reason.clone(),
604 place: value.clone(),
605 });
606 index += 1;
607 }
608 AliasingEffect::MutateConditionally { value } => {
609 mutations.push(PendingMutation {
610 index: index,
611 id: instr_eval_order,
612 transitive: false,
613 kind: MutationKind::Conditional,
614 reason: None,
615 place: value.clone(),
616 });
617 index += 1;
618 }
619 AliasingEffect::MutateFrozen { .. }
620 | AliasingEffect::MutateGlobal { .. }
621 | AliasingEffect::Impure { .. } => {
622 if should_record_errors {
623 match effect {
624 AliasingEffect::MutateFrozen { error, .. }
625 | AliasingEffect::MutateGlobal { error, .. }
626 | AliasingEffect::Impure { error, .. } => {
627 env.record_diagnostic(error.clone());
628 }
629 _ => unreachable!(),
630 }
631 }
632 function_effects.push(effect.clone());
633 }
634 AliasingEffect::Render { place } => {
635 renders.push(PendingRender {
636 index: index,
637 place: place.clone(),
638 });
639 index += 1;
640 function_effects.push(effect.clone());
641 }
642 _ => {}
644 }
645 }
646 }
647
648 let block = &func.body.blocks[&block_id];
650 if let Some(block_phis) = pending_phis.remove(&block_id) {
651 for pending in block_phis {
652 state.assign(pending.index, &pending.from, &pending.into);
653 }
654 }
655
656 let terminal = &block.terminal;
658 if let react_compiler_hir::Terminal::Return { value, .. } = terminal {
659 state.assign(index, value, &func.returns);
660 index += 1;
661 }
662
663 let terminal_effects = match terminal {
665 react_compiler_hir::Terminal::MaybeThrow { effects, .. }
666 | react_compiler_hir::Terminal::Return { effects, .. } => effects.clone(),
667 _ => None,
668 };
669 if let Some(effects) = terminal_effects {
670 for effect in &effects {
671 match effect {
672 AliasingEffect::Alias { from, into } => {
673 state.assign(index, from, into);
674 index += 1;
675 }
676 AliasingEffect::Freeze { .. } => {
677 }
679 _ => {
680 }
683 }
684 }
685 }
686 }
687
688 for mutation in &mutations {
690 state.mutate(
691 mutation.index,
692 mutation.place.identifier,
693 Some(EvaluationOrder(mutation.id.0 + 1)),
694 mutation.transitive,
695 mutation.kind,
696 mutation.place.loc,
697 mutation.reason.clone(),
698 env,
699 should_record_errors,
700 );
701 }
702
703 for render in &renders {
705 if should_record_errors {
706 state.render(render.index, render.place.identifier, env);
707 }
708 }
709
710 for ctx in &func.context {
713 collect_param_effects(&state, ctx, &mut function_effects);
714 }
715 for param in &func.params {
716 let place = match param {
717 react_compiler_hir::ParamPattern::Place(p) => p,
718 react_compiler_hir::ParamPattern::Spread(s) => &s.place,
719 };
720 collect_param_effects(&state, place, &mut function_effects);
721 }
722
723 let mut captured_params: HashSet<IdentifierId> = HashSet::new();
727 for param in &func.params {
728 let place = match param {
729 react_compiler_hir::ParamPattern::Place(p) => p,
730 react_compiler_hir::ParamPattern::Spread(s) => &s.place,
731 };
732 if let Some(node) = state.nodes.get(&place.identifier) {
733 if node.local.is_some() || node.transitive.is_some() {
734 captured_params.insert(place.identifier);
735 }
736 }
737 }
738 for ctx in &func.context {
739 if let Some(node) = state.nodes.get(&ctx.identifier) {
740 if node.local.is_some() || node.transitive.is_some() {
741 captured_params.insert(ctx.identifier);
742 }
743 }
744 }
745
746 for param in &mut func.params {
748 let place = match param {
749 react_compiler_hir::ParamPattern::Place(p) => p,
750 react_compiler_hir::ParamPattern::Spread(s) => &mut s.place,
751 };
752 if captured_params.contains(&place.identifier) {
753 place.effect = Effect::Capture;
754 }
755 }
756 for ctx in &mut func.context {
757 if captured_params.contains(&ctx.identifier) {
758 ctx.effect = Effect::Capture;
759 }
760 }
761
762 for &block_id in &block_order {
768 let block = &func.body.blocks[&block_id];
769
770 let phi_data: Vec<_> = block
772 .phis
773 .iter()
774 .map(|phi| {
775 let first_instr_id = block
776 .instructions
777 .first()
778 .map(|id| func.instructions[id.0 as usize].id)
779 .unwrap_or_else(|| block.terminal.evaluation_order());
780
781 let is_mutated_after_creation = env.identifiers[phi.place.identifier.0 as usize]
782 .mutable_range
783 .end
784 > first_instr_id;
785
786 (
787 phi.place.identifier,
788 phi.operands.values().map(|o| o.identifier).collect::<Vec<_>>(),
789 is_mutated_after_creation,
790 first_instr_id,
791 )
792 })
793 .collect();
794
795 for (phi_id, _operand_ids, is_mutated_after_creation, first_instr_id) in &phi_data {
796 let block = func.body.blocks.get_mut(&block_id).unwrap();
799 for phi in &mut block.phis {
800 if phi.place.identifier == *phi_id {
801 phi.place.effect = Effect::Store;
802 for operand in phi.operands.values_mut() {
803 operand.effect = if *is_mutated_after_creation {
804 Effect::Capture
805 } else {
806 Effect::Read
807 };
808 }
809 break;
810 }
811 }
812
813 if *is_mutated_after_creation {
814 let ident = &mut env.identifiers[phi_id.0 as usize];
815 if ident.mutable_range.start == EvaluationOrder(0) {
816 ident.mutable_range.start =
817 EvaluationOrder(first_instr_id.0.saturating_sub(1));
818 }
819 }
820 }
821
822 let block = &func.body.blocks[&block_id];
823 let instr_ids: Vec<_> = block.instructions.clone();
824
825 for instr_id in &instr_ids {
826 let instr = &func.instructions[instr_id.0 as usize];
827 let eval_order = instr.id;
828
829 let lvalue_id = instr.lvalue.identifier;
832 {
833 let ident = &mut env.identifiers[lvalue_id.0 as usize];
834 if ident.mutable_range.start == EvaluationOrder(0) {
835 ident.mutable_range.start = eval_order;
836 }
837 if ident.mutable_range.end == EvaluationOrder(0) {
838 ident.mutable_range.end = EvaluationOrder(
839 (eval_order.0 + 1).max(ident.mutable_range.end.0),
840 );
841 }
842 }
843 func.instructions[instr_id.0 as usize].lvalue.effect = Effect::ConditionallyMutate;
844
845 let value_lvalue_ids: Vec<IdentifierId> = each_instruction_value_lvalue(&func.instructions[instr_id.0 as usize].value)
847 .into_iter()
848 .map(|p| p.identifier)
849 .collect();
850 for vlid in &value_lvalue_ids {
851 let ident = &mut env.identifiers[vlid.0 as usize];
852 if ident.mutable_range.start == EvaluationOrder(0) {
853 ident.mutable_range.start = eval_order;
854 }
855 if ident.mutable_range.end == EvaluationOrder(0) {
856 ident.mutable_range.end = EvaluationOrder(
857 (eval_order.0 + 1).max(ident.mutable_range.end.0),
858 );
859 }
860 }
861 for_each_instruction_value_lvalue_mut(&mut func.instructions[instr_id.0 as usize].value, &mut |place| {
862 place.effect = Effect::ConditionallyMutate;
863 });
864
865 for_each_instruction_value_operand_mut(&mut func.instructions[instr_id.0 as usize].value, &mut |place| {
867 place.effect = Effect::Read;
868 });
869
870 let instr = &func.instructions[instr_id.0 as usize];
871 if instr.effects.is_none() {
872 continue;
873 }
874
875 let effects = instr.effects.as_ref().unwrap().clone();
877 let mut operand_effects: HashMap<IdentifierId, Effect> = HashMap::new();
878
879 for effect in &effects {
880 match effect {
881 AliasingEffect::Assign { from, into, .. }
882 | AliasingEffect::Alias { from, into }
883 | AliasingEffect::Capture { from, into }
884 | AliasingEffect::CreateFrom { from, into }
885 | AliasingEffect::MaybeAlias { from, into } => {
886 let is_mutated_or_reassigned = env.identifiers
887 [into.identifier.0 as usize]
888 .mutable_range
889 .end
890 > eval_order;
891 if is_mutated_or_reassigned {
892 operand_effects
893 .insert(from.identifier, Effect::Capture);
894 operand_effects.insert(into.identifier, Effect::Store);
895 } else {
896 operand_effects.insert(from.identifier, Effect::Read);
897 operand_effects.insert(into.identifier, Effect::Store);
898 }
899 }
900 AliasingEffect::CreateFunction { .. } | AliasingEffect::Create { .. } => {
901 }
903 AliasingEffect::Mutate { value, .. } => {
904 operand_effects.insert(value.identifier, Effect::Store);
905 }
906 AliasingEffect::Apply { .. } => {
907 return Err(CompilerDiagnostic::new(
908 ErrorCategory::Invariant,
909 "[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects",
910 None,
911 ));
912 }
913 AliasingEffect::MutateTransitive { value, .. }
914 | AliasingEffect::MutateConditionally { value }
915 | AliasingEffect::MutateTransitiveConditionally { value } => {
916 operand_effects
917 .insert(value.identifier, Effect::ConditionallyMutate);
918 }
919 AliasingEffect::Freeze { value, .. } => {
920 operand_effects.insert(value.identifier, Effect::Freeze);
921 }
922 AliasingEffect::ImmutableCapture { .. } => {
923 }
925 AliasingEffect::Impure { .. }
926 | AliasingEffect::Render { .. }
927 | AliasingEffect::MutateFrozen { .. }
928 | AliasingEffect::MutateGlobal { .. } => {
929 }
931 }
932 }
933
934 let instr = &mut func.instructions[instr_id.0 as usize];
936 let lvalue_id = instr.lvalue.identifier;
937 if let Some(&effect) = operand_effects.get(&lvalue_id) {
938 instr.lvalue.effect = effect;
939 }
940 for_each_instruction_value_lvalue_mut(&mut instr.value, &mut |place| {
942 if let Some(&effect) = operand_effects.get(&place.identifier) {
943 place.effect = effect;
944 }
945 });
946
947 {
949 let mut apply = |place: &mut Place| {
950 let ident = &env.identifiers[place.identifier.0 as usize];
952 if ident.mutable_range.end > eval_order
953 && ident.mutable_range.start == EvaluationOrder(0)
954 {
955 env.identifiers[place.identifier.0 as usize].mutable_range.start =
956 eval_order;
957 }
958 if let Some(&effect) = operand_effects.get(&place.identifier) {
960 place.effect = effect;
961 }
962 };
963 for_each_instruction_value_operand_mut(&mut instr.value, &mut apply);
964
965 if let InstructionValue::FunctionExpression { lowered_func, .. }
968 | InstructionValue::ObjectMethod { lowered_func, .. } = &instr.value
969 {
970 let func_id = lowered_func.func;
971 let ctx_ids: Vec<IdentifierId> = env.functions[func_id.0 as usize]
972 .context
973 .iter()
974 .map(|c| c.identifier)
975 .collect();
976 for ctx_id in &ctx_ids {
977 let ident = &env.identifiers[ctx_id.0 as usize];
978 if ident.mutable_range.end > eval_order
979 && ident.mutable_range.start == EvaluationOrder(0)
980 {
981 env.identifiers[ctx_id.0 as usize].mutable_range.start = eval_order;
982 }
983 let effect = operand_effects.get(ctx_id).copied().unwrap_or(Effect::Read);
984 let inner_func = &mut env.functions[func_id.0 as usize];
985 for ctx_place in &mut inner_func.context {
986 if ctx_place.identifier == *ctx_id {
987 ctx_place.effect = effect;
988 }
989 }
990 }
991 }
992 }
993
994 let instr = &func.instructions[instr_id.0 as usize];
996 if let InstructionValue::StoreContext { value, .. } = &instr.value {
997 let val_id = value.identifier;
998 let val_range_end = env.identifiers[val_id.0 as usize].mutable_range.end;
999 if val_range_end <= eval_order {
1000 env.identifiers[val_id.0 as usize].mutable_range.end =
1001 EvaluationOrder(eval_order.0 + 1);
1002 }
1003 }
1004 }
1005
1006 let block = func.body.blocks.get_mut(&block_id).unwrap();
1008 match &mut block.terminal {
1009 react_compiler_hir::Terminal::Return { value, .. } => {
1010 value.effect = if is_function_expression {
1011 Effect::Read
1012 } else {
1013 Effect::Freeze
1014 };
1015 }
1016 terminal => {
1017 for_each_terminal_operand_mut(terminal, &mut |place| {
1018 place.effect = Effect::Read;
1019 });
1020 }
1021 }
1022 }
1023
1024 let returns_id = func.returns.identifier;
1028 let returns_type_id = env.identifiers[returns_id.0 as usize].type_;
1029 let returns_type = &env.types[returns_type_id.0 as usize];
1030 let return_value_kind = if is_primitive_type(returns_type) {
1031 ValueKind::Primitive
1032 } else if is_jsx_type(returns_type) {
1033 ValueKind::Frozen
1034 } else {
1035 ValueKind::Mutable
1036 };
1037
1038 function_effects.push(AliasingEffect::Create {
1039 into: func.returns.clone(),
1040 value: return_value_kind,
1041 reason: ValueReason::KnownReturnSignature,
1042 });
1043
1044 let mut tracked: Vec<Place> = Vec::new();
1046 for param in &func.params {
1047 let place = match param {
1048 react_compiler_hir::ParamPattern::Place(p) => p.clone(),
1049 react_compiler_hir::ParamPattern::Spread(s) => s.place.clone(),
1050 };
1051 tracked.push(place);
1052 }
1053 for ctx in &func.context {
1054 tracked.push(ctx.clone());
1055 }
1056 tracked.push(func.returns.clone());
1057
1058 let returns_identifier_id = func.returns.identifier;
1059
1060 for i in 0..tracked.len() {
1061 let into = tracked[i].clone();
1062 let mutation_index = index;
1063 index += 1;
1064
1065 state.mutate(
1066 mutation_index,
1067 into.identifier,
1068 None, true,
1070 MutationKind::Conditional,
1071 into.loc,
1072 None,
1073 env,
1074 false, );
1076
1077 for j in 0..tracked.len() {
1078 let from = &tracked[j];
1079 if from.identifier == into.identifier
1080 || from.identifier == returns_identifier_id
1081 {
1082 continue;
1083 }
1084
1085 let from_node = state.nodes.get(&from.identifier);
1086 assert!(
1087 from_node.is_some(),
1088 "Expected a node to exist for all parameters and context variables"
1089 );
1090 let from_node = from_node.unwrap();
1091
1092 if from_node.last_mutated == mutation_index {
1093 if into.identifier == returns_identifier_id {
1094 function_effects.push(AliasingEffect::Alias {
1095 from: from.clone(),
1096 into: into.clone(),
1097 });
1098 } else {
1099 function_effects.push(AliasingEffect::Capture {
1100 from: from.clone(),
1101 into: into.clone(),
1102 });
1103 }
1104 }
1105 }
1106 }
1107
1108 Ok(function_effects)
1109}
1110
1111fn collect_param_effects(
1116 state: &AliasingState,
1117 place: &Place,
1118 function_effects: &mut Vec<AliasingEffect>,
1119) {
1120 let node = match state.nodes.get(&place.identifier) {
1121 Some(n) => n,
1122 None => return,
1123 };
1124
1125 if let Some(ref local) = node.local {
1126 match local.kind {
1127 MutationKind::Conditional => {
1128 function_effects.push(AliasingEffect::MutateConditionally {
1129 value: Place {
1130 loc: local.loc,
1131 ..place.clone()
1132 },
1133 });
1134 }
1135 MutationKind::Definite => {
1136 function_effects.push(AliasingEffect::Mutate {
1137 value: Place {
1138 loc: local.loc,
1139 ..place.clone()
1140 },
1141 reason: node.mutation_reason.clone(),
1142 });
1143 }
1144 MutationKind::None => {}
1145 }
1146 }
1147
1148 if let Some(ref transitive) = node.transitive {
1149 match transitive.kind {
1150 MutationKind::Conditional => {
1151 function_effects.push(AliasingEffect::MutateTransitiveConditionally {
1152 value: Place {
1153 loc: transitive.loc,
1154 ..place.clone()
1155 },
1156 });
1157 }
1158 MutationKind::Definite => {
1159 function_effects.push(AliasingEffect::MutateTransitive {
1160 value: Place {
1161 loc: transitive.loc,
1162 ..place.clone()
1163 },
1164 });
1165 }
1166 MutationKind::None => {}
1167 }
1168 }
1169}
1170