@samitouri / QOS-React-1 / commits / d37faa041b

[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`,