main
ts 604 lines 17.1 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 import {NodePath} from '@babel/traverse';
9 import * as t from '@babel/types';
10 import prettyFormat from 'pretty-format';
11 import {CompilerOutputMode, Logger, ProgramContext} from '.';
12 import {CompilerError} from '../CompilerError';
13 import {Err, Ok, Result} from '../Utils/Result';
14 import {
15 HIRFunction,
16 ReactiveFunction,
17 assertConsistentIdentifiers,
18 assertTerminalPredsExist,
19 assertTerminalSuccessorsExist,
20 assertValidBlockNesting,
21 assertValidMutableRanges,
22 buildReactiveScopeTerminalsHIR,
23 lower,
24 mergeConsecutiveBlocks,
25 mergeOverlappingReactiveScopesHIR,
26 pruneUnusedLabelsHIR,
27 } from '../HIR';
28 import {
29 Environment,
30 EnvironmentConfig,
31 ReactFunctionType,
32 } from '../HIR/Environment';
33 import {findContextIdentifiers} from '../HIR/FindContextIdentifiers';
34 import {
35 analyseFunctions,
36 dropManualMemoization,
37 inferReactivePlaces,
38 inlineImmediatelyInvokedFunctionExpressions,
39 } from '../Inference';
40 import {
41 constantPropagation,
42 deadCodeElimination,
43 pruneMaybeThrows,
44 } from '../Optimization';
45 import {
46 CodegenFunction,
47 alignObjectMethodScopes,
48 assertScopeInstructionsWithinScopes,
49 assertWellFormedBreakTargets,
50 buildReactiveFunction,
51 codegenFunction,
52 extractScopeDeclarationsFromDestructuring,
53 inferReactiveScopeVariables,
54 memoizeFbtAndMacroOperandsInSameScope,
55 mergeReactiveScopesThatInvalidateTogether,
56 promoteUsedTemporaries,
57 propagateEarlyReturns,
58 pruneHoistedContexts,
59 pruneNonEscapingScopes,
60 pruneNonReactiveDependencies,
61 pruneUnusedLValues,
62 pruneUnusedLabels,
63 pruneUnusedScopes,
64 renameVariables,
65 } from '../ReactiveScopes';
66 import {alignMethodCallScopes} from '../ReactiveScopes/AlignMethodCallScopes';
67 import {alignReactiveScopesToBlockScopesHIR} from '../ReactiveScopes/AlignReactiveScopesToBlockScopesHIR';
68 import {flattenReactiveLoopsHIR} from '../ReactiveScopes/FlattenReactiveLoopsHIR';
69 import {flattenScopesWithHooksOrUseHIR} from '../ReactiveScopes/FlattenScopesWithHooksOrUseHIR';
70 import {pruneAlwaysInvalidatingScopes} from '../ReactiveScopes/PruneAlwaysInvalidatingScopes';
71 import {stabilizeBlockIds} from '../ReactiveScopes/StabilizeBlockIds';
72 import {
73 eliminateRedundantPhi,
74 enterSSA,
75 rewriteInstructionKindsBasedOnReassignment,
76 } from '../SSA';
77 import {inferTypes} from '../TypeInference';
78 import {
79 validateContextVariableLValues,
80 validateHooksUsage,
81 validateNoCapitalizedCalls,
82 validateNoRefAccessInRender,
83 validateNoSetStateInRender,
84 validatePreservedManualMemoization,
85 validateUseMemo,
86 } from '../Validation';
87 import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLocalsNotReassignedAfterRender';
88 import {outlineFunctions} from '../Optimization/OutlineFunctions';
89 import {validateNoSetStateInEffects} from '../Validation/ValidateNoSetStateInEffects';
90 import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
91 import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
92 import {outlineJSX} from '../Optimization/OutlineJsx';
93 import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
94 import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
95 import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions';
96 import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
97 import {inferMutationAliasingRanges} from '../Inference/InferMutationAliasingRanges';
98 import {validateNoDerivedComputationsInEffects} from '../Validation/ValidateNoDerivedComputationsInEffects';
99 import {validateNoDerivedComputationsInEffects_exp} from '../Validation/ValidateNoDerivedComputationsInEffects_exp';
100 import {nameAnonymousFunctions} from '../Transform/NameAnonymousFunctions';
101 import {optimizeForSSR} from '../Optimization/OptimizeForSSR';
102 import {validateExhaustiveDependencies} from '../Validation/ValidateExhaustiveDependencies';
103 import {validateSourceLocations} from '../Validation/ValidateSourceLocations';
104
105 export type CompilerPipelineValue =
106 | {kind: 'ast'; name: string; value: CodegenFunction}
107 | {kind: 'hir'; name: string; value: HIRFunction}
108 | {kind: 'reactive'; name: string; value: ReactiveFunction}
109 | {kind: 'debug'; name: string; value: string};
110
111 function run(
112 func: NodePath<
113 t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
114 >,
115 config: EnvironmentConfig,
116 fnType: ReactFunctionType,
117 mode: CompilerOutputMode,
118 programContext: ProgramContext,
119 logger: Logger | null,
120 filename: string | null,
121 code: string | null,
122 ): Result<CodegenFunction, CompilerError> {
123 const contextIdentifiers = findContextIdentifiers(func);
124 const env = new Environment(
125 func.scope,
126 fnType,
127 mode,
128 config,
129 contextIdentifiers,
130 func,
131 logger,
132 filename,
133 code,
134 programContext,
135 );
136 env.logger?.debugLogIRs?.({
137 kind: 'debug',
138 name: 'EnvironmentConfig',
139 value: prettyFormat(env.config),
140 });
141 return runWithEnvironment(func, env);
142 }
143
144 /*
145 * Note: this is split from run() to make `config` out of scope, so that all
146 * access to feature flags has to be through the Environment for consistency.
147 */
148 function runWithEnvironment(
149 func: NodePath<
150 t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
151 >,
152 env: Environment,
153 ): Result<CodegenFunction, CompilerError> {
154 const log = (value: CompilerPipelineValue): void => {
155 env.logger?.debugLogIRs?.(value);
156 };
157 const hir = lower(func, env);
158 log({kind: 'hir', name: 'HIR', value: hir});
159
160 pruneMaybeThrows(hir);
161 log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
162
163 validateContextVariableLValues(hir);
164 log({kind: 'debug', name: 'ValidateContextVariableLValues', value: 'ok'});
165 validateUseMemo(hir);
166 log({kind: 'debug', name: 'ValidateUseMemo', value: 'ok'});
167
168 if (env.enableDropManualMemoization) {
169 dropManualMemoization(hir);
170 log({kind: 'hir', name: 'DropManualMemoization', value: hir});
171 }
172
173 inlineImmediatelyInvokedFunctionExpressions(hir);
174 log({
175 kind: 'hir',
176 name: 'InlineImmediatelyInvokedFunctionExpressions',
177 value: hir,
178 });
179
180 mergeConsecutiveBlocks(hir);
181 log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
182
183 assertConsistentIdentifiers(hir);
184 log({kind: 'debug', name: 'AssertConsistentIdentifiers', value: 'ok'});
185 assertTerminalSuccessorsExist(hir);
186 log({kind: 'debug', name: 'AssertTerminalSuccessorsExist', value: 'ok'});
187
188 enterSSA(hir);
189 log({kind: 'hir', name: 'SSA', value: hir});
190
191 eliminateRedundantPhi(hir);
192 log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
193
194 assertConsistentIdentifiers(hir);
195 log({kind: 'debug', name: 'AssertConsistentIdentifiers', value: 'ok'});
196
197 constantPropagation(hir);
198 log({kind: 'hir', name: 'ConstantPropagation', value: hir});
199
200 inferTypes(hir);
201 log({kind: 'hir', name: 'InferTypes', value: hir});
202
203 if (env.enableValidations) {
204 if (env.config.validateHooksUsage) {
205 validateHooksUsage(hir);
206 log({kind: 'debug', name: 'ValidateHooksUsage', value: 'ok'});
207 }
208 if (env.config.validateNoCapitalizedCalls) {
209 validateNoCapitalizedCalls(hir);
210 log({kind: 'debug', name: 'ValidateNoCapitalizedCalls', value: 'ok'});
211 }
212 }
213
214 optimizePropsMethodCalls(hir);
215 log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir});
216
217 analyseFunctions(hir);
218 log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
219
220 inferMutationAliasingEffects(hir);
221 log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
222
223 if (env.outputMode === 'ssr') {
224 optimizeForSSR(hir);
225 log({kind: 'hir', name: 'OptimizeForSSR', value: hir});
226 }
227
228 // Note: Has to come after infer reference effects because "dead" code may still affect inference
229 deadCodeElimination(hir);
230 log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
231 pruneMaybeThrows(hir);
232 log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
233
234 inferMutationAliasingRanges(hir, {
235 isFunctionExpression: false,
236 });
237 log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
238 if (env.enableValidations) {
239 validateLocalsNotReassignedAfterRender(hir);
240 log({
241 kind: 'debug',
242 name: 'ValidateLocalsNotReassignedAfterRender',
243 value: 'ok',
244 });
245
246 if (env.config.assertValidMutableRanges) {
247 assertValidMutableRanges(hir);
248 log({kind: 'debug', name: 'AssertValidMutableRanges', value: 'ok'});
249 }
250
251 if (env.config.validateRefAccessDuringRender) {
252 validateNoRefAccessInRender(hir);
253 log({kind: 'debug', name: 'ValidateNoRefAccessInRender', value: 'ok'});
254 }
255
256 if (env.config.validateNoSetStateInRender) {
257 validateNoSetStateInRender(hir);
258 log({kind: 'debug', name: 'ValidateNoSetStateInRender', value: 'ok'});
259 }
260
261 if (
262 env.config.validateNoDerivedComputationsInEffects_exp &&
263 env.outputMode === 'lint'
264 ) {
265 env.logErrors(validateNoDerivedComputationsInEffects_exp(hir));
266 log({
267 kind: 'debug',
268 name: 'ValidateNoDerivedComputationsInEffects',
269 value: 'ok',
270 });
271 } else if (env.config.validateNoDerivedComputationsInEffects) {
272 validateNoDerivedComputationsInEffects(hir);
273 log({
274 kind: 'debug',
275 name: 'ValidateNoDerivedComputationsInEffects',
276 value: 'ok',
277 });
278 }
279
280 if (env.config.validateNoSetStateInEffects && env.outputMode === 'lint') {
281 env.logErrors(validateNoSetStateInEffects(hir, env));
282 log({kind: 'debug', name: 'ValidateNoSetStateInEffects', value: 'ok'});
283 }
284
285 if (env.config.validateNoJSXInTryStatements && env.outputMode === 'lint') {
286 env.logErrors(validateNoJSXInTryStatement(hir));
287 log({kind: 'debug', name: 'ValidateNoJSXInTryStatement', value: 'ok'});
288 }
289
290 validateNoFreezingKnownMutableFunctions(hir);
291 log({
292 kind: 'debug',
293 name: 'ValidateNoFreezingKnownMutableFunctions',
294 value: 'ok',
295 });
296 }
297
298 inferReactivePlaces(hir);
299 log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
300
301 if (env.enableValidations) {
302 if (
303 env.config.validateExhaustiveMemoizationDependencies ||
304 env.config.validateExhaustiveEffectDependencies
305 ) {
306 // NOTE: this relies on reactivity inference running first
307 validateExhaustiveDependencies(hir);
308 log({kind: 'debug', name: 'ValidateExhaustiveDependencies', value: 'ok'});
309 }
310 }
311
312 rewriteInstructionKindsBasedOnReassignment(hir);
313 log({
314 kind: 'hir',
315 name: 'RewriteInstructionKindsBasedOnReassignment',
316 value: hir,
317 });
318
319 if (
320 env.enableValidations &&
321 env.config.validateStaticComponents &&
322 env.outputMode === 'lint'
323 ) {
324 env.logErrors(validateStaticComponents(hir));
325 log({kind: 'debug', name: 'ValidateStaticComponents', value: 'ok'});
326 }
327
328 if (env.enableMemoization) {
329 /**
330 * Only create reactive scopes (which directly map to generated memo blocks)
331 * if inferred memoization is enabled. This makes all later passes which
332 * transform reactive-scope labeled instructions no-ops.
333 */
334 inferReactiveScopeVariables(hir);
335 log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
336 }
337
338 const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
339 log({
340 kind: 'hir',
341 name: 'MemoizeFbtAndMacroOperandsInSameScope',
342 value: hir,
343 });
344
345 if (env.config.enableJsxOutlining) {
346 outlineJSX(hir);
347 }
348
349 if (env.config.enableNameAnonymousFunctions) {
350 nameAnonymousFunctions(hir);
351 log({
352 kind: 'hir',
353 name: 'NameAnonymousFunctions',
354 value: hir,
355 });
356 }
357
358 if (env.config.enableFunctionOutlining) {
359 outlineFunctions(hir, fbtOperands);
360 log({kind: 'hir', name: 'OutlineFunctions', value: hir});
361 }
362
363 alignMethodCallScopes(hir);
364 log({
365 kind: 'hir',
366 name: 'AlignMethodCallScopes',
367 value: hir,
368 });
369
370 alignObjectMethodScopes(hir);
371 log({
372 kind: 'hir',
373 name: 'AlignObjectMethodScopes',
374 value: hir,
375 });
376
377 pruneUnusedLabelsHIR(hir);
378 log({
379 kind: 'hir',
380 name: 'PruneUnusedLabelsHIR',
381 value: hir,
382 });
383
384 alignReactiveScopesToBlockScopesHIR(hir);
385 log({
386 kind: 'hir',
387 name: 'AlignReactiveScopesToBlockScopesHIR',
388 value: hir,
389 });
390
391 mergeOverlappingReactiveScopesHIR(hir);
392 log({
393 kind: 'hir',
394 name: 'MergeOverlappingReactiveScopesHIR',
395 value: hir,
396 });
397 assertValidBlockNesting(hir);
398 log({kind: 'debug', name: 'AssertValidBlockNesting', value: 'ok'});
399
400 buildReactiveScopeTerminalsHIR(hir);
401 log({
402 kind: 'hir',
403 name: 'BuildReactiveScopeTerminalsHIR',
404 value: hir,
405 });
406
407 assertValidBlockNesting(hir);
408 log({kind: 'debug', name: 'AssertValidBlockNesting', value: 'ok'});
409
410 flattenReactiveLoopsHIR(hir);
411 log({
412 kind: 'hir',
413 name: 'FlattenReactiveLoopsHIR',
414 value: hir,
415 });
416
417 flattenScopesWithHooksOrUseHIR(hir);
418 log({
419 kind: 'hir',
420 name: 'FlattenScopesWithHooksOrUseHIR',
421 value: hir,
422 });
423 assertTerminalSuccessorsExist(hir);
424 log({kind: 'debug', name: 'AssertTerminalSuccessorsExist', value: 'ok'});
425 assertTerminalPredsExist(hir);
426 log({kind: 'debug', name: 'AssertTerminalPredsExist', value: 'ok'});
427
428 propagateScopeDependenciesHIR(hir);
429 log({
430 kind: 'hir',
431 name: 'PropagateScopeDependenciesHIR',
432 value: hir,
433 });
434
435 const reactiveFunction = buildReactiveFunction(hir);
436 log({
437 kind: 'reactive',
438 name: 'BuildReactiveFunction',
439 value: reactiveFunction,
440 });
441
442 assertWellFormedBreakTargets(reactiveFunction);
443 log({kind: 'debug', name: 'AssertWellFormedBreakTargets', value: 'ok'});
444
445 pruneUnusedLabels(reactiveFunction);
446 log({
447 kind: 'reactive',
448 name: 'PruneUnusedLabels',
449 value: reactiveFunction,
450 });
451 assertScopeInstructionsWithinScopes(reactiveFunction);
452 log({
453 kind: 'debug',
454 name: 'AssertScopeInstructionsWithinScopes',
455 value: 'ok',
456 });
457
458 pruneNonEscapingScopes(reactiveFunction);
459 log({
460 kind: 'reactive',
461 name: 'PruneNonEscapingScopes',
462 value: reactiveFunction,
463 });
464
465 pruneNonReactiveDependencies(reactiveFunction);
466 log({
467 kind: 'reactive',
468 name: 'PruneNonReactiveDependencies',
469 value: reactiveFunction,
470 });
471
472 pruneUnusedScopes(reactiveFunction);
473 log({
474 kind: 'reactive',
475 name: 'PruneUnusedScopes',
476 value: reactiveFunction,
477 });
478
479 mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
480 log({
481 kind: 'reactive',
482 name: 'MergeReactiveScopesThatInvalidateTogether',
483 value: reactiveFunction,
484 });
485
486 pruneAlwaysInvalidatingScopes(reactiveFunction);
487 log({
488 kind: 'reactive',
489 name: 'PruneAlwaysInvalidatingScopes',
490 value: reactiveFunction,
491 });
492
493 propagateEarlyReturns(reactiveFunction);
494 log({
495 kind: 'reactive',
496 name: 'PropagateEarlyReturns',
497 value: reactiveFunction,
498 });
499
500 pruneUnusedLValues(reactiveFunction);
501 log({
502 kind: 'reactive',
503 name: 'PruneUnusedLValues',
504 value: reactiveFunction,
505 });
506
507 promoteUsedTemporaries(reactiveFunction);
508 log({
509 kind: 'reactive',
510 name: 'PromoteUsedTemporaries',
511 value: reactiveFunction,
512 });
513
514 extractScopeDeclarationsFromDestructuring(reactiveFunction);
515 log({
516 kind: 'reactive',
517 name: 'ExtractScopeDeclarationsFromDestructuring',
518 value: reactiveFunction,
519 });
520
521 stabilizeBlockIds(reactiveFunction);
522 log({
523 kind: 'reactive',
524 name: 'StabilizeBlockIds',
525 value: reactiveFunction,
526 });
527
528 const uniqueIdentifiers = renameVariables(reactiveFunction);
529 log({
530 kind: 'reactive',
531 name: 'RenameVariables',
532 value: reactiveFunction,
533 });
534
535 pruneHoistedContexts(reactiveFunction);
536 log({
537 kind: 'reactive',
538 name: 'PruneHoistedContexts',
539 value: reactiveFunction,
540 });
541
542 if (
543 env.config.enablePreserveExistingMemoizationGuarantees ||
544 env.config.validatePreserveExistingMemoizationGuarantees
545 ) {
546 validatePreservedManualMemoization(reactiveFunction);
547 log({
548 kind: 'debug',
549 name: 'ValidatePreservedManualMemoization',
550 value: 'ok',
551 });
552 }
553
554 const ast = codegenFunction(reactiveFunction, {
555 uniqueIdentifiers,
556 fbtOperands,
557 });
558 log({kind: 'ast', name: 'Codegen', value: ast});
559 for (const outlined of ast.outlined) {
560 log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
561 }
562
563 if (env.config.validateSourceLocations) {
564 validateSourceLocations(func, ast, env);
565 }
566
567 /**
568 * This flag should be only set for unit / fixture tests to check
569 * that Forget correctly handles unexpected errors (e.g. exceptions
570 * thrown by babel functions or other unexpected exceptions).
571 */
572 if (env.config.throwUnknownException__testonly) {
573 throw new Error('unexpected error');
574 }
575
576 if (env.hasErrors()) {
577 return Err(env.aggregateErrors());
578 }
579 return Ok(ast);
580 }
581
582 export function compileFn(
583 func: NodePath<
584 t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
585 >,
586 config: EnvironmentConfig,
587 fnType: ReactFunctionType,
588 mode: CompilerOutputMode,
589 programContext: ProgramContext,
590 logger: Logger | null,
591 filename: string | null,
592 code: string | null,
593 ): Result<CodegenFunction, CompilerError> {
594 return run(
595 func,
596 config,
597 fnType,
598 mode,
599 programContext,
600 logger,
601 filename,
602 code,
603 );
604 }