[compiler] Preserve Create effects, guarantee effects initialize once (#33558)
Ensures that effects are well-formed with respect to the rules: * For a given instruction, each place is only initialized once (w one of Create, CreateFrom, Assign) * Ensures that Alias targets are already initialized within the same instruction (should have a Create before them) * Preserves Create and similar instructions * Avoids duplicate instructions when inferring effects of function expressions --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33558). * #33571 * __->__ #33558 * #33547
Joseph Savona committed
Jun 18, 2025 at 16:00 UTC
d37faa041bce86c1cbb05fdbc839440c9d9de9cf
2 files changed
+88
-28
compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts
+13
-1
@@ -20,10 +20,11 @@ import {inferReactiveScopeVariables} from '../ReactiveScopes';
20
import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
21
import {inferMutableRanges} from './InferMutableRanges';
22
import inferReferenceEffects from './InferReferenceEffects';
23
-import {assertExhaustive} from '../Utils/utils';
23
+import {assertExhaustive, retainWhere} from '../Utils/utils';
24
import {inferMutationAliasingEffects} from './InferMutationAliasingEffects';
25
import {inferFunctionExpressionAliasingEffectsSignature} from './InferFunctionExpressionAliasingEffectsSignature';
26
import {inferMutationAliasingRanges} from './InferMutationAliasingRanges';
27
+import {hashEffect} from './AliasingEffects';
28
29
export default function analyseFunctions(func: HIRFunction): void {
30
for (const [_, block] of func.body.blocks) {
@@ -81,6 +82,17 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
82
fn.aliasingEffects ??= [];
83
fn.aliasingEffects?.push(...effects);
84
}
85
+ if (fn.aliasingEffects != null) {
86
+ const seen = new Set<string>();
87
+ retainWhere(fn.aliasingEffects, effect => {
88
+ const hash = hashEffect(effect);
89
+ if (seen.has(hash)) {
90
+ return false;
91
+ }
92
+ seen.add(hash);
93
+ return true;
94
+ });
95
+ }
96
97
/**
98
* Phase 2: populate the Effect of each context variable to use in inferring
compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts
+75
-27
@@ -362,6 +362,11 @@ function inferBlock(
362
} else if (terminal.kind === 'maybe-throw') {
363
const handlerParam = context.catchHandlers.get(terminal.handler);
364
if (handlerParam != null) {
365
+ CompilerError.invariant(state.kind(handlerParam) != null, {
366
+ reason:
367
+ 'Expected catch binding to be intialized with a DeclareLocal Catch instruction',
368
+ loc: terminal.loc,
369
+ });
370
const effects: Array<AliasingEffect> = [];
371
for (const instr of block.instructions) {
372
if (
@@ -476,14 +481,14 @@ function applySignature(
481
* Track which values we've already aliased once, so that we can switch to
482
* appendAlias() for subsequent aliases into the same value
483
*/
479
- const aliased = new Set<IdentifierId>();
484
+ const initialized = new Set<IdentifierId>();
485
486
if (DEBUG) {
487
console.log(printInstruction(instruction));
488
}
489
490
for (const effect of signature.effects) {
486
- applyEffect(context, state, effect, aliased, effects);
491
+ applyEffect(context, state, effect, initialized, effects);
492
}
493
if (DEBUG) {
494
console.log(
@@ -508,7 +513,7 @@ function applyEffect(
513
context: Context,
514
state: InferenceState,
515
_effect: AliasingEffect,
511
- aliased: Set<IdentifierId>,
516
+ initialized: Set<IdentifierId>,
517
effects: Array<AliasingEffect>,
518
): void {
519
const effect = context.internEffect(_effect);
@@ -524,6 +529,13 @@ function applyEffect(
529
break;
530
}
531
case 'Create': {
532
+ CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
533
+ reason: `Cannot re-initialize variable within an instruction`,
534
+ description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
535
+ loc: effect.into.loc,
536
+ });
537
+ initialized.add(effect.into.identifier.id);
538
+
539
let value = context.effectInstructionValueCache.get(effect);
540
if (value == null) {
541
value = {
@@ -538,6 +550,7 @@ function applyEffect(
550
reason: new Set([effect.reason]),
551
});
552
state.define(effect.into, value);
553
+ effects.push(effect);
554
break;
555
}
556
case 'ImmutableCapture': {
@@ -555,6 +568,13 @@ function applyEffect(
568
break;
569
}
570
case 'CreateFrom': {
571
+ CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
572
+ reason: `Cannot re-initialize variable within an instruction`,
573
+ description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
574
+ loc: effect.into.loc,
575
+ });
576
+ initialized.add(effect.into.identifier.id);
577
+
578
const fromValue = state.kind(effect.from);
579
let value = context.effectInstructionValueCache.get(effect);
580
if (value == null) {
@@ -573,10 +593,21 @@ function applyEffect(
593
switch (fromValue.kind) {
594
case ValueKind.Primitive:
595
case ValueKind.Global: {
576
- // no need to track this data flow
596
+ effects.push({
597
+ kind: 'Create',
598
+ value: fromValue.kind,
599
+ into: effect.into,
600
+ reason: [...fromValue.reason][0] ?? ValueReason.Other,
601
+ });
602
break;
603
}
604
case ValueKind.Frozen: {
605
+ effects.push({
606
+ kind: 'Create',
607
+ value: fromValue.kind,
608
+ into: effect.into,
609
+ reason: [...fromValue.reason][0] ?? ValueReason.Other,
610
+ });
611
applyEffect(
612
context,
613
state,
@@ -585,7 +616,7 @@ function applyEffect(
616
from: effect.from,
617
into: effect.into,
618
},
588
- aliased,
619
+ initialized,
620
effects,
621
);
622
break;
@@ -597,6 +628,13 @@ function applyEffect(
628
break;
629
}
630
case 'CreateFunction': {
631
+ CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
632
+ reason: `Cannot re-initialize variable within an instruction`,
633
+ description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
634
+ loc: effect.into.loc,
635
+ });
636
+ initialized.add(effect.into.identifier.id);
637
+
638
effects.push(effect);
639
/**
640
* We consider the function mutable if it has any mutable context variables or
@@ -653,7 +691,7 @@ function applyEffect(
691
from: capture,
692
into: effect.into,
693
},
656
- aliased,
694
+ initialized,
695
effects,
696
);
697
}
@@ -661,6 +699,14 @@ function applyEffect(
699
}
700
case 'Alias':
701
case 'Capture': {
702
+ CompilerError.invariant(
703
+ effect.kind === 'Capture' || initialized.has(effect.into.identifier.id),
704
+ {
705
+ reason: `Expected destination value to already be initialized within this instruction for Alias effect`,
706
+ description: `Destination ${printPlace(effect.into)} is not initialized in this instruction`,
707
+ loc: effect.into.loc,
708
+ },
709
+ );
710
/*
711
* Capture describes potential information flow: storing a pointer to one value
712
* within another. If the destination is not mutable, or the source value has
@@ -698,7 +744,7 @@ function applyEffect(
744
from: effect.from,
745
into: effect.into,
746
},
701
- aliased,
747
+ initialized,
748
effects,
749
);
750
break;
@@ -714,6 +760,13 @@ function applyEffect(
760
break;
761
}
762
case 'Assign': {
763
+ CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
764
+ reason: `Cannot re-initialize variable within an instruction`,
765
+ description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
766
+ loc: effect.into.loc,
767
+ });
768
+ initialized.add(effect.into.identifier.id);
769
+
770
/*
771
* Alias represents potential pointer aliasing. If the type is a global,
772
* a primitive (copy-on-write semantics) then we can prune the effect
@@ -730,7 +783,7 @@ function applyEffect(
783
from: effect.from,
784
into: effect.into,
785
},
733
- aliased,
786
+ initialized,
787
effects,
788
);
789
let value = context.effectInstructionValueCache.get(effect);
@@ -768,12 +821,7 @@ function applyEffect(
821
break;
822
}
823
default: {
771
- if (aliased.has(effect.into.identifier.id)) {
772
- state.appendAlias(effect.into, effect.from);
773
- } else {
774
- aliased.add(effect.into.identifier.id);
775
- state.alias(effect.into, effect.from);
776
- }
824
+ state.assign(effect.into, effect.from);
825
effects.push(effect);
826
break;
827
}
@@ -826,11 +874,11 @@ function applyEffect(
874
context,
875
state,
876
{kind: 'MutateTransitiveConditionally', value: effect.function},
829
- aliased,
877
+ initialized,
878
effects,
879
);
880
for (const signatureEffect of signatureEffects) {
833
- applyEffect(context, state, signatureEffect, aliased, effects);
881
+ applyEffect(context, state, signatureEffect, initialized, effects);
882
}
883
break;
884
}
@@ -858,7 +906,7 @@ function applyEffect(
906
console.log('apply aliasing signature effects');
907
}
908
for (const signatureEffect of signatureEffects) {
861
- applyEffect(context, state, signatureEffect, aliased, effects);
909
+ applyEffect(context, state, signatureEffect, initialized, effects);
910
}
911
} else if (effect.signature != null) {
912
if (DEBUG) {
@@ -873,7 +921,7 @@ function applyEffect(
921
effect.loc,
922
);
923
for (const legacyEffect of legacyEffects) {
876
- applyEffect(context, state, legacyEffect, aliased, effects);
924
+ applyEffect(context, state, legacyEffect, initialized, effects);
925
}
926
} else {
927
if (DEBUG) {
@@ -888,7 +936,7 @@ function applyEffect(
936
value: ValueKind.Mutable,
937
reason: ValueReason.Other,
938
},
891
- aliased,
939
+ initialized,
940
effects,
941
);
942
/*
@@ -911,21 +959,21 @@ function applyEffect(
959
kind: 'MutateTransitiveConditionally',
960
value: operand,
961
},
914
- aliased,
962
+ initialized,
963
effects,
964
);
965
}
966
const mutateIterator =
967
arg.kind === 'Spread' ? conditionallyMutateIterator(operand) : null;
968
if (mutateIterator) {
921
- applyEffect(context, state, mutateIterator, aliased, effects);
969
+ applyEffect(context, state, mutateIterator, initialized, effects);
970
}
971
applyEffect(
972
context,
973
state,
974
// OK: recording information flow
975
{kind: 'Alias', from: operand, into: effect.into},
928
- aliased,
976
+ initialized,
977
effects,
978
);
979
for (const otherArg of [
@@ -953,7 +1001,7 @@ function applyEffect(
1001
from: operand,
1002
into: other,
1003
},
956
- aliased,
1004
+ initialized,
1005
effects,
1006
);
1007
}
@@ -1009,7 +1057,7 @@ function applyEffect(
1057
suggestions: null,
1058
},
1059
},
1012
- aliased,
1060
+ initialized,
1061
effects,
1062
);
1063
}
@@ -1028,7 +1076,7 @@ function applyEffect(
1076
suggestions: null,
1077
},
1078
},
1031
- aliased,
1079
+ initialized,
1080
effects,
1081
);
1082
} else {
@@ -1059,7 +1107,7 @@ function applyEffect(
1107
suggestions: null,
1108
},
1109
},
1062
- aliased,
1110
+ initialized,
1111
effects,
1112
);
1113
}
@@ -1166,7 +1214,7 @@ class InferenceState {
1214
}
1215
1216
// Updates the value at @param place to point to the same value as @param value.
1169
- alias(place: Place, value: Place): void {
1217
+ assign(place: Place, value: Place): void {
1218
const values = this.#variables.get(value.identifier.id);
1219
CompilerError.invariant(values != null, {
1220
reason: `[InferMutationAliasingEffects] Expected value for identifier to be initialized`,