@samitouri / QOS-React-1 / commits / 6bcf0d20da

[compiler] Empty dep arrays for globals/module-scoped values/imports (#31666)

Any LoadGlobal in the "infer deps" position can safely use an empty dep array. Globals have no reactive deps! I just keep messing up sapling. This is the revised version of #31662

Jordan Brown committed Dec 3, 2024 at 13:54 UTC 6bcf0d20dae349bba428d8f73dbcf0284b0acb10
2 files changed +42 -32
compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts
+41 -31
@@ -55,6 +55,8 @@ export function inferEffectDependencies(fn: HIRFunction): void {
55 {pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean}
56 >();
57
58 + const loadGlobals = new Set<IdentifierId>();
59 +
60 /**
61 * When inserting LoadLocals, we need to retain the reactivity of the base
62 * identifier, as later passes e.g. PruneNonReactiveDeps take the reactivity of
@@ -87,26 +89,23 @@ export function inferEffectDependencies(fn: HIRFunction): void {
89 lvalue.identifier.id,
90 instr as TInstruction<FunctionExpression>,
91 );
90 - } else if (
91 - value.kind === 'LoadGlobal' &&
92 - value.binding.kind === 'ImportSpecifier'
93 - ) {
94 - const moduleTargets = autodepFnConfigs.get(value.binding.module);
95 - if (moduleTargets != null) {
96 - const numRequiredArgs = moduleTargets.get(value.binding.imported);
97 - if (numRequiredArgs != null) {
98 - autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs);
99 - }
100 - }
101 - } else if (
102 - value.kind === 'LoadGlobal' &&
103 - value.binding.kind === 'ImportDefault'
104 - ) {
105 - const moduleTargets = autodepFnConfigs.get(value.binding.module);
106 - if (moduleTargets != null) {
107 - const numRequiredArgs = moduleTargets.get(DEFAULT_EXPORT);
108 - if (numRequiredArgs != null) {
109 - autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs);
92 + } else if (value.kind === 'LoadGlobal') {
93 + loadGlobals.add(lvalue.identifier.id);
94 +
95 + if (
96 + value.binding.kind === 'ImportSpecifier' ||
97 + value.binding.kind === 'ImportDefault'
98 + ) {
99 + const moduleTargets = autodepFnConfigs.get(value.binding.module);
100 + if (moduleTargets != null) {
101 + const importSpecifierName =
102 + value.binding.kind === 'ImportSpecifier'
103 + ? value.binding.imported
104 + : DEFAULT_EXPORT;
105 + const numRequiredArgs = moduleTargets.get(importSpecifierName);
106 + if (numRequiredArgs != null) {
107 + autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs);
108 + }
109 }
110 }
111 } else if (
@@ -117,8 +116,19 @@ export function inferEffectDependencies(fn: HIRFunction): void {
116 autodepFnLoads.get(value.callee.identifier.id) === value.args.length &&
117 value.args[0].kind === 'Identifier'
118 ) {
119 + const effectDeps: Array<Place> = [];
120 + const newInstructions: Array<Instruction> = [];
121 + const deps: ArrayExpression = {
122 + kind: 'ArrayExpression',
123 + elements: effectDeps,
124 + loc: GeneratedSource,
125 + };
126 + const depsPlace = createTemporaryPlace(fn.env, GeneratedSource);
127 + depsPlace.effect = Effect.Read;
128 +
129 const fnExpr = fnExpressions.get(value.args[0].identifier.id);
130 if (fnExpr != null) {
131 + // We have a function expression, so we can infer its dependencies
132 const scopeInfo =
133 fnExpr.lvalue.identifier.scope != null
134 ? scopeInfos.get(fnExpr.lvalue.identifier.scope.id)
@@ -140,14 +150,12 @@ export function inferEffectDependencies(fn: HIRFunction): void {
150 }
151
152 /**
143 - * Step 1: write new instructions to insert a dependency array
153 + * Step 1: push dependencies to the effect deps array
154 *
155 * Note that it's invalid to prune non-reactive deps in this pass, see
156 * the `infer-effect-deps/pruned-nonreactive-obj` fixture for an
157 * explanation.
158 */
149 - const effectDeps: Array<Place> = [];
150 - const newInstructions: Array<Instruction> = [];
159 for (const dep of scopeInfo.deps) {
160 const {place, instructions} = writeDependencyToInstructions(
161 dep,
@@ -158,14 +166,6 @@ export function inferEffectDependencies(fn: HIRFunction): void {
166 newInstructions.push(...instructions);
167 effectDeps.push(place);
168 }
161 - const deps: ArrayExpression = {
162 - kind: 'ArrayExpression',
163 - elements: effectDeps,
164 - loc: GeneratedSource,
165 - };
166 -
167 - const depsPlace = createTemporaryPlace(fn.env, GeneratedSource);
168 - depsPlace.effect = Effect.Read;
169
170 newInstructions.push({
171 id: makeInstructionId(0),
@@ -177,6 +177,16 @@ export function inferEffectDependencies(fn: HIRFunction): void {
177 // Step 2: push the inferred deps array as an argument of the useEffect
178 value.args.push({...depsPlace, effect: Effect.Freeze});
179 rewriteInstrs.set(instr.id, newInstructions);
180 + } else if (loadGlobals.has(value.args[0].identifier.id)) {
181 + // Global functions have no reactive dependencies, so we can insert an empty array
182 + newInstructions.push({
183 + id: makeInstructionId(0),
184 + loc: GeneratedSource,
185 + lvalue: {...depsPlace, effect: Effect.Mutate},
186 + value: deps,
187 + });
188 + value.args.push({...depsPlace, effect: Effect.Freeze});
189 + rewriteInstrs.set(instr.id, newInstructions);
190 }
191 }
192 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.expect.md
+1 -1
@@ -34,7 +34,7 @@ import { print } from "shared-runtime";
34 * before OutlineFunctions
35 */
36 function OutlinedFunctionInEffect() {
37 - useEffect(_temp);
37 + useEffect(_temp, []);
38 }
39 function _temp() {
40 return print("hello world!");