mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-11 14:46:37 +03:00
Clean up of React nested optimized logic (#2348)
Summary: Release notes: none Follow up to https://github.com/facebook/prepack/pull/2346. This PR cleans up all the React specific nested optimized function logic, removing most of the old legacy code in favor of the simpler/cleaner approach. This also introduces a `bubbleSideEffectReports` parameter to `evaluatePure` for specific case where we don't want side-effects to be reported to parent `evaluatePure` calls; necessary for the React reconciler to not throw a `UnsupportedSideEffect` which will cause the reconciliation process to hard-fail (instead nested optimize functions bail-out to be inline abstract function calls like before, and we do not try and optimize the nested function). Lastly, we now throw an `SideEffect` object when evaluating a nested optimized function and detecting a side-effect. There's no point in continuing evaluation of a given side-effectful function, so this speeds things up a bit by terminating execution. Pull Request resolved: https://github.com/facebook/prepack/pull/2348 Differential Revision: D9138290 Pulled By: trueadm fbshipit-source-id: 6d468ea0430be0c0190ff9817b2872df368c325d
This commit is contained in:
parent
1b4f70658f
commit
7cb3129cd0
@ -70,3 +70,10 @@ export class InvariantError extends Error {
|
||||
}
|
||||
|
||||
export type ErrorHandler = (error: CompilerDiagnostic, suppressDiagnostics: boolean) => ErrorHandlerResult;
|
||||
|
||||
// When a side-effect occurs when evaluating a pure nested optimized function, we stop execution of that function
|
||||
// and catch the error to properly handle the according logic (either bail-out or report the error).
|
||||
// Ideally this should extend FatalError, but that will mean re-working every call-site that catches FatalError
|
||||
// and make it treat NestedOptimizedFunctionSideEffect errors differently, which isn't ideal so maybe a better
|
||||
// FatalError catching/handling process is needed throughout the codebase at some point.
|
||||
export class NestedOptimizedFunctionSideEffect extends Error {}
|
||||
|
@ -227,7 +227,11 @@ export default function(realm: Realm): void {
|
||||
invariant(functionValue instanceof ECMAScriptSourceFunctionValue);
|
||||
invariant(typeof functionValue.$Call === "function");
|
||||
let functionCall: Function = functionValue.$Call;
|
||||
return realm.evaluatePure(() => functionCall(realm.intrinsics.undefined, []), /*reportSideEffectFunc*/ null);
|
||||
return realm.evaluatePure(
|
||||
() => functionCall(realm.intrinsics.undefined, []),
|
||||
/*bubbles*/ true,
|
||||
/*reportSideEffectFunc*/ null
|
||||
);
|
||||
}
|
||||
),
|
||||
writable: true,
|
||||
|
@ -306,6 +306,7 @@ export function evaluateClassConstructor(
|
||||
/*state*/ null,
|
||||
`react component constructor: ${constructorFunc.getName()}`
|
||||
),
|
||||
/*bubbles*/ true,
|
||||
/*reportSideEffectFunc*/ null
|
||||
);
|
||||
|
||||
|
@ -344,20 +344,32 @@ class ReactDOMServerRenderer {
|
||||
|
||||
if (nestedOptimizedFunctionEffects !== undefined) {
|
||||
for (let [func, effects] of nestedOptimizedFunctionEffects) {
|
||||
let resolvedEffects = this.realm.evaluateForEffects(
|
||||
() => {
|
||||
let result = effects.result;
|
||||
this.realm.applyEffects(effects);
|
||||
let funcCall = () => {
|
||||
let result = effects.result;
|
||||
this.realm.applyEffects(effects);
|
||||
if (result instanceof SimpleNormalCompletion) {
|
||||
result = result.value;
|
||||
}
|
||||
invariant(result instanceof Value);
|
||||
return this.render(result, namespace, depth);
|
||||
};
|
||||
let pureFuncCall = () =>
|
||||
this.realm.evaluatePure(funcCall, /*bubbles*/ true, () => {
|
||||
invariant(false, "SSR _renderArrayValue side-effect should have been caught in main React reconciler");
|
||||
});
|
||||
|
||||
if (result instanceof SimpleNormalCompletion) {
|
||||
result = result.value;
|
||||
}
|
||||
invariant(result instanceof Value);
|
||||
return this.render(result, namespace, depth);
|
||||
},
|
||||
/*state*/ null,
|
||||
`react nested optimized closure`
|
||||
);
|
||||
let resolvedEffects;
|
||||
let saved_pathConditions = this.realm.pathConditions;
|
||||
this.realm.pathConditions = [];
|
||||
try {
|
||||
resolvedEffects = this.realm.evaluateForEffects(
|
||||
pureFuncCall,
|
||||
/*state*/ null,
|
||||
`react SSR resolve nested optimized closure`
|
||||
);
|
||||
} finally {
|
||||
this.realm.pathConditions = saved_pathConditions;
|
||||
}
|
||||
nestedOptimizedFunctionEffects.set(func, resolvedEffects);
|
||||
this.realm.collectedNestedOptimizedFunctionEffects.set(func, resolvedEffects);
|
||||
}
|
||||
@ -468,42 +480,17 @@ class ReactDOMServerRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
function handleNestedOptimizedFunctions(realm: Realm, reconciler: Reconciler, staticMarkup: boolean): void {
|
||||
for (let { func, evaluatedNode, componentType, context } of reconciler.nestedOptimizedClosures) {
|
||||
if (reconciler.hasEvaluatedNestedClosure(func)) {
|
||||
continue;
|
||||
}
|
||||
if (func instanceof ECMAScriptSourceFunctionValue && reconciler.hasEvaluatedRootNode(func, evaluatedNode)) {
|
||||
continue;
|
||||
}
|
||||
let closureEffects = reconciler.resolveNestedOptimizedClosure(func, [], componentType, context, evaluatedNode);
|
||||
|
||||
let closureEffectsRenderedToString = realm.evaluateForEffectsWithPriorEffects(
|
||||
[closureEffects],
|
||||
() => {
|
||||
let serverRenderer = new ReactDOMServerRenderer(realm, staticMarkup);
|
||||
invariant(closureEffects.result instanceof SimpleNormalCompletion);
|
||||
return serverRenderer.render(closureEffects.result.value);
|
||||
},
|
||||
"handleNestedOptimizedFunctions"
|
||||
);
|
||||
|
||||
realm.react.optimizedNestedClosuresToWrite.push({
|
||||
effects: closureEffectsRenderedToString,
|
||||
func,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function renderToString(
|
||||
realm: Realm,
|
||||
reactElement: ObjectValue,
|
||||
staticMarkup: boolean
|
||||
): StringValue | AbstractValue {
|
||||
let reactStatistics = new ReactStatistics();
|
||||
let alreadyEvaluated = new Map();
|
||||
let reconciler = new Reconciler(
|
||||
realm,
|
||||
{ firstRenderOnly: true, isRoot: true, modelString: undefined },
|
||||
alreadyEvaluated,
|
||||
reactStatistics
|
||||
);
|
||||
let typeValue = getProperty(realm, reactElement, "type");
|
||||
@ -527,6 +514,5 @@ export function renderToString(
|
||||
invariant(effects.result instanceof SimpleNormalCompletion);
|
||||
let serverRenderer = new ReactDOMServerRenderer(realm, staticMarkup);
|
||||
let renderValue = serverRenderer.render(effects.result.value);
|
||||
handleNestedOptimizedFunctions(realm, reconciler, staticMarkup);
|
||||
return renderValue;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
/* @flow strict-local */
|
||||
|
||||
import { type Effects, Realm } from "../realm.js";
|
||||
import { AbstractValue, ECMAScriptSourceFunctionValue, BoundFunctionValue, ObjectValue } from "../values/index.js";
|
||||
import { AbstractValue, ECMAScriptSourceFunctionValue, ObjectValue } from "../values/index.js";
|
||||
import { createAdditionalEffects } from "../serializer/utils.js";
|
||||
import {
|
||||
convertFunctionalComponentToComplexClassComponent,
|
||||
@ -110,7 +110,8 @@ function optimizeReactComponentTreeBranches(
|
||||
reconciler: Reconciler,
|
||||
writeEffects: WriteEffects,
|
||||
environmentRecordIdAfterGlobalCode: number,
|
||||
logger: Logger
|
||||
logger: Logger,
|
||||
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue, ReactEvaluatedNode>
|
||||
): void {
|
||||
if (realm.react.verbose && reconciler.branchedComponentTrees.length > 0) {
|
||||
logger.logInformation(` Evaluating React component tree branches...`);
|
||||
@ -123,10 +124,10 @@ function optimizeReactComponentTreeBranches(
|
||||
evaluatedNode.status = "UNKNOWN_TYPE";
|
||||
continue;
|
||||
}
|
||||
// so we don't process the same component multiple times (we might change this logic later)
|
||||
if (reconciler.hasEvaluatedRootNode(branchComponentType, evaluatedNode)) {
|
||||
continue;
|
||||
if (alreadyEvaluated.has(branchComponentType)) {
|
||||
return;
|
||||
}
|
||||
alreadyEvaluated.set(branchComponentType, evaluatedNode);
|
||||
reconciler.clearComponentTreeState();
|
||||
if (realm.react.verbose) {
|
||||
logger.logInformation(` Evaluating ${evaluatedNode.name} (branch)`);
|
||||
@ -148,53 +149,6 @@ function optimizeReactComponentTreeBranches(
|
||||
}
|
||||
}
|
||||
|
||||
function optimizeReactNestedClosures(
|
||||
realm: Realm,
|
||||
reconciler: Reconciler,
|
||||
writeEffects: WriteEffects,
|
||||
environmentRecordIdAfterGlobalCode: number,
|
||||
logger: Logger
|
||||
): void {
|
||||
if (realm.react.verbose && reconciler.nestedOptimizedClosures.length > 0) {
|
||||
logger.logInformation(` Evaluating nested closures...`);
|
||||
}
|
||||
for (let { func, evaluatedNode, nestedEffects, componentType, context } of reconciler.nestedOptimizedClosures) {
|
||||
if (reconciler.hasEvaluatedNestedClosure(func)) {
|
||||
continue;
|
||||
}
|
||||
if (func instanceof ECMAScriptSourceFunctionValue && reconciler.hasEvaluatedRootNode(func, evaluatedNode)) {
|
||||
continue;
|
||||
}
|
||||
if (realm.react.verbose) {
|
||||
logger.logInformation(` Evaluating function "${getComponentName(realm, func)}"`);
|
||||
}
|
||||
let closureEffects = reconciler.resolveNestedOptimizedClosure(
|
||||
func,
|
||||
nestedEffects,
|
||||
componentType,
|
||||
context,
|
||||
evaluatedNode
|
||||
);
|
||||
if (realm.react.verbose) {
|
||||
logger.logInformation(` ✔ function "${getComponentName(realm, func)}"`);
|
||||
}
|
||||
let additionalFunctionEffects = createAdditionalEffects(
|
||||
realm,
|
||||
closureEffects,
|
||||
true,
|
||||
"ReactNestedAdditionalFunctionEffects",
|
||||
environmentRecordIdAfterGlobalCode
|
||||
);
|
||||
invariant(additionalFunctionEffects);
|
||||
if (func instanceof BoundFunctionValue) {
|
||||
invariant(func.$BoundTargetFunction instanceof ECMAScriptSourceFunctionValue);
|
||||
writeEffects.set(func.$BoundTargetFunction, additionalFunctionEffects);
|
||||
} else {
|
||||
writeEffects.set(func, additionalFunctionEffects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function optimizeReactComponentTreeRoot(
|
||||
realm: Realm,
|
||||
componentRoot: ECMAScriptSourceFunctionValue | AbstractValue,
|
||||
@ -202,18 +156,20 @@ export function optimizeReactComponentTreeRoot(
|
||||
writeEffects: WriteEffects,
|
||||
environmentRecordIdAfterGlobalCode: number,
|
||||
logger: Logger,
|
||||
statistics: ReactStatistics
|
||||
statistics: ReactStatistics,
|
||||
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue, ReactEvaluatedNode>
|
||||
): void {
|
||||
let reconciler = new Reconciler(realm, config, statistics, logger);
|
||||
let reconciler = new Reconciler(realm, config, alreadyEvaluated, statistics, logger);
|
||||
let componentType = getComponentTypeFromRootValue(realm, componentRoot);
|
||||
if (componentType === null) {
|
||||
return;
|
||||
}
|
||||
let evaluatedRootNode = createReactEvaluatedNode("ROOT", getComponentName(realm, componentType));
|
||||
statistics.evaluatedRootNodes.push(evaluatedRootNode);
|
||||
if (reconciler.hasEvaluatedRootNode(componentType, evaluatedRootNode)) {
|
||||
if (alreadyEvaluated.has(componentType)) {
|
||||
return;
|
||||
}
|
||||
let evaluatedRootNode = createReactEvaluatedNode("ROOT", getComponentName(realm, componentType));
|
||||
statistics.evaluatedRootNodes.push(evaluatedRootNode);
|
||||
alreadyEvaluated.set(componentType, evaluatedRootNode);
|
||||
if (realm.react.verbose) {
|
||||
logger.logInformation(` Evaluating ${evaluatedRootNode.name} (root)`);
|
||||
}
|
||||
@ -231,36 +187,16 @@ export function optimizeReactComponentTreeRoot(
|
||||
writeEffects,
|
||||
environmentRecordIdAfterGlobalCode
|
||||
);
|
||||
|
||||
let startingComponentTreeBranches = 0;
|
||||
do {
|
||||
startingComponentTreeBranches = reconciler.branchedComponentTrees.length;
|
||||
optimizeReactComponentTreeBranches(realm, reconciler, writeEffects, environmentRecordIdAfterGlobalCode, logger);
|
||||
if (realm.react.optimizeNestedFunctions) {
|
||||
optimizeReactNestedClosures(realm, reconciler, writeEffects, environmentRecordIdAfterGlobalCode, logger);
|
||||
}
|
||||
optimizeReactComponentTreeBranches(
|
||||
realm,
|
||||
reconciler,
|
||||
writeEffects,
|
||||
environmentRecordIdAfterGlobalCode,
|
||||
logger,
|
||||
alreadyEvaluated
|
||||
);
|
||||
} while (startingComponentTreeBranches !== reconciler.branchedComponentTrees.length);
|
||||
}
|
||||
|
||||
export function applyOptimizedReactComponents(
|
||||
realm: Realm,
|
||||
writeEffects: WriteEffects,
|
||||
environmentRecordIdAfterGlobalCode: number
|
||||
): void {
|
||||
for (let { effects, func } of realm.react.optimizedNestedClosuresToWrite) {
|
||||
let additionalFunctionEffects = createAdditionalEffects(
|
||||
realm,
|
||||
effects,
|
||||
true,
|
||||
"ReactNestedAdditionalFunctionEffects",
|
||||
environmentRecordIdAfterGlobalCode
|
||||
);
|
||||
invariant(additionalFunctionEffects);
|
||||
if (func instanceof BoundFunctionValue) {
|
||||
invariant(func.$BoundTargetFunction instanceof ECMAScriptSourceFunctionValue);
|
||||
writeEffects.set(func.$BoundTargetFunction, additionalFunctionEffects);
|
||||
} else {
|
||||
writeEffects.set(func, additionalFunctionEffects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ import {
|
||||
BooleanValue,
|
||||
BoundFunctionValue,
|
||||
ECMAScriptSourceFunctionValue,
|
||||
FunctionValue,
|
||||
NullValue,
|
||||
NumberValue,
|
||||
ObjectValue,
|
||||
@ -32,7 +31,6 @@ import {
|
||||
cloneProps,
|
||||
createReactEvaluatedNode,
|
||||
doNotOptimizeComponent,
|
||||
evaluateWithNestedParentEffects,
|
||||
flattenChildren,
|
||||
hardModifyReactObjectPropertyBinding,
|
||||
getComponentName,
|
||||
@ -50,8 +48,8 @@ import {
|
||||
} from "./utils.js";
|
||||
import { Get } from "../methods/index.js";
|
||||
import invariant from "../invariant.js";
|
||||
import { Properties } from "../singletons.js";
|
||||
import { FatalError, CompilerDiagnostic } from "../errors.js";
|
||||
import { Havoc, Properties, Utils } from "../singletons.js";
|
||||
import { FatalError, NestedOptimizedFunctionSideEffect } from "../errors.js";
|
||||
import {
|
||||
type BranchStatusEnum,
|
||||
getValueWithBranchingLogicApplied,
|
||||
@ -80,7 +78,6 @@ import { Logger } from "../utils/logger.js";
|
||||
import type { ClassComponentMetadata, ReactComponentTreeConfig, ReactHint } from "../types.js";
|
||||
import { handleReportedSideEffect } from "../serializer/utils.js";
|
||||
import { createOperationDescriptor } from "../utils/generator.js";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
type ComponentResolutionStrategy =
|
||||
| "NORMAL"
|
||||
@ -137,10 +134,15 @@ function setContextCurrentValue(contextObject: ObjectValue | AbstractObjectValue
|
||||
}
|
||||
}
|
||||
|
||||
function throwUnsupportedSideEffectError(msg: string) {
|
||||
throw new UnsupportedSideEffect(msg);
|
||||
}
|
||||
|
||||
export class Reconciler {
|
||||
constructor(
|
||||
realm: Realm,
|
||||
componentTreeConfig: ReactComponentTreeConfig,
|
||||
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue, ReactEvaluatedNode>,
|
||||
statistics: ReactStatistics,
|
||||
logger?: Logger
|
||||
) {
|
||||
@ -149,9 +151,7 @@ export class Reconciler {
|
||||
this.logger = logger;
|
||||
this.componentTreeConfig = componentTreeConfig;
|
||||
this.componentTreeState = this._createComponentTreeState();
|
||||
this.alreadyEvaluatedRootNodes = new Map();
|
||||
this.alreadyEvaluatedNestedClosures = new Set();
|
||||
this.nestedOptimizedClosures = [];
|
||||
this.alreadyEvaluated = alreadyEvaluated;
|
||||
this.branchedComponentTrees = [];
|
||||
}
|
||||
|
||||
@ -159,11 +159,9 @@ export class Reconciler {
|
||||
statistics: ReactStatistics;
|
||||
logger: void | Logger;
|
||||
componentTreeState: ComponentTreeState;
|
||||
alreadyEvaluatedRootNodes: Map<ECMAScriptSourceFunctionValue, ReactEvaluatedNode>;
|
||||
alreadyEvaluatedNestedClosures: Set<FunctionValue>;
|
||||
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue, ReactEvaluatedNode>;
|
||||
componentTreeConfig: ReactComponentTreeConfig;
|
||||
currentEffectsStack: Array<Effects>;
|
||||
nestedOptimizedClosures: Array<OptimizedClosure>;
|
||||
branchedComponentTrees: Array<BranchReactComponentTree>;
|
||||
|
||||
resolveReactComponentTree(
|
||||
@ -176,7 +174,6 @@ export class Reconciler {
|
||||
try {
|
||||
let initialProps = props || getInitialProps(this.realm, componentType, this.componentTreeConfig);
|
||||
let initialContext = context || getInitialContext(this.realm, componentType);
|
||||
this.alreadyEvaluatedRootNodes.set(componentType, evaluatedRootNode);
|
||||
let { result } = this._resolveComponent(componentType, initialProps, initialContext, "ROOT", evaluatedRootNode);
|
||||
this.statistics.optimizedTrees++;
|
||||
return result;
|
||||
@ -189,10 +186,7 @@ export class Reconciler {
|
||||
|
||||
try {
|
||||
this.realm.react.activeReconciler = this;
|
||||
let throwUnsupportedSideEffectError = (msg: string) => {
|
||||
throw new UnsupportedSideEffect(msg);
|
||||
};
|
||||
let effects = this.realm.wrapInGlobalEnv(() =>
|
||||
return this.realm.wrapInGlobalEnv(() =>
|
||||
this.realm.evaluatePure(
|
||||
() =>
|
||||
this.realm.evaluateForEffects(
|
||||
@ -200,111 +194,11 @@ export class Reconciler {
|
||||
/*state*/ null,
|
||||
`react component: ${getComponentName(this.realm, componentType)}`
|
||||
),
|
||||
/*bubbles*/ true,
|
||||
(sideEffectType, binding, expressionLocation) =>
|
||||
handleReportedSideEffect(throwUnsupportedSideEffectError, sideEffectType, binding, expressionLocation)
|
||||
)
|
||||
);
|
||||
this._handleNestedOptimizedClosuresFromEffects(effects, evaluatedRootNode);
|
||||
return effects;
|
||||
} finally {
|
||||
this.realm.react.activeReconciler = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
_handleNestedOptimizedClosuresFromEffects(effects: Effects, evaluatedNode: ReactEvaluatedNode) {
|
||||
for (let { nestedEffects } of this.nestedOptimizedClosures) {
|
||||
if (nestedEffects.length === 0) {
|
||||
nestedEffects.push(...nestedEffects, effects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolveNestedOptimizedClosure(
|
||||
func: ECMAScriptSourceFunctionValue | BoundFunctionValue,
|
||||
nestedEffects: Array<Effects>,
|
||||
componentType: Value | null,
|
||||
context: ObjectValue | AbstractObjectValue | null,
|
||||
evaluatedNode: ReactEvaluatedNode
|
||||
): Effects {
|
||||
const resolveOptimizedClosure = () => {
|
||||
let baseObject = this.realm.$GlobalEnv.environmentRecord.WithBaseObject();
|
||||
// we want to optimize the function that is bound
|
||||
if (func instanceof BoundFunctionValue) {
|
||||
// we want to set the "this" to be the bound object
|
||||
// for firstRender this will optimize the function
|
||||
// for updates, "this" will be intrinsic, so either way
|
||||
// they should both work
|
||||
baseObject = func.$BoundThis;
|
||||
invariant(func.$BoundTargetFunction instanceof ECMAScriptSourceFunctionValue);
|
||||
func = func.$BoundTargetFunction;
|
||||
}
|
||||
let numArgs = func.getLength();
|
||||
let args = [];
|
||||
let targetFunc = func;
|
||||
|
||||
this.alreadyEvaluatedNestedClosures.add(func);
|
||||
invariant(targetFunc instanceof ECMAScriptSourceFunctionValue);
|
||||
let params = targetFunc.$FormalParameters;
|
||||
if (numArgs && numArgs > 0 && params) {
|
||||
for (let parameterId of params) {
|
||||
if (t.isIdentifier(parameterId)) {
|
||||
// Create an AbstractValue similar to __abstract being called
|
||||
args.push(
|
||||
AbstractValue.createAbstractArgument(
|
||||
this.realm,
|
||||
((parameterId: any): BabelNodeIdentifier).name,
|
||||
targetFunc.expressionLocation
|
||||
)
|
||||
);
|
||||
} else {
|
||||
this.realm.handleError(
|
||||
new CompilerDiagnostic(
|
||||
"Non-identifier args to additional functions unsupported",
|
||||
targetFunc.expressionLocation,
|
||||
"PP1005",
|
||||
"FatalError"
|
||||
)
|
||||
);
|
||||
throw new FatalError("Non-identifier args to additional functions unsupported");
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
invariant(
|
||||
baseObject instanceof ObjectValue ||
|
||||
baseObject instanceof AbstractObjectValue ||
|
||||
baseObject instanceof UndefinedValue
|
||||
);
|
||||
let value = getValueFromFunctionCall(this.realm, func, baseObject, args);
|
||||
invariant(componentType instanceof Value);
|
||||
invariant(context instanceof ObjectValue || context instanceof AbstractObjectValue);
|
||||
let result = this._resolveDeeply(componentType, value, context, "NEW_BRANCH", evaluatedNode);
|
||||
this.statistics.optimizedNestedClosures++;
|
||||
return result;
|
||||
} catch (error) {
|
||||
this._handleComponentTreeRootFailure(error, evaluatedNode);
|
||||
// flow belives we can get here, when it should never be possible
|
||||
invariant(false, "resolveNestedOptimizedClosure error not handled correctly");
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
this.realm.react.activeReconciler = this;
|
||||
let throwUnsupportedSideEffectError = (msg: string) => {
|
||||
throw new UnsupportedSideEffect(msg);
|
||||
};
|
||||
let effects = this.realm.wrapInGlobalEnv(() =>
|
||||
this.realm.evaluatePure(
|
||||
() =>
|
||||
evaluateWithNestedParentEffects(this.realm, nestedEffects, () =>
|
||||
this.realm.evaluateForEffects(resolveOptimizedClosure, /*state*/ null, `react nested optimized closure`)
|
||||
),
|
||||
(sideEffectType, binding, expressionLocation) =>
|
||||
handleReportedSideEffect(throwUnsupportedSideEffectError, sideEffectType, binding, expressionLocation)
|
||||
)
|
||||
);
|
||||
this._handleNestedOptimizedClosuresFromEffects(effects, evaluatedNode);
|
||||
return effects;
|
||||
} finally {
|
||||
this.realm.react.activeReconciler = undefined;
|
||||
}
|
||||
@ -314,23 +208,6 @@ export class Reconciler {
|
||||
this.componentTreeState = this._createComponentTreeState();
|
||||
}
|
||||
|
||||
_queueOptimizedClosure(
|
||||
func: ECMAScriptSourceFunctionValue | BoundFunctionValue,
|
||||
evaluatedNode: ReactEvaluatedNode,
|
||||
componentType: Value | null,
|
||||
context: ObjectValue | AbstractObjectValue | null
|
||||
): void {
|
||||
if (this.realm.react.optimizeNestedFunctions) {
|
||||
this.nestedOptimizedClosures.push({
|
||||
evaluatedNode,
|
||||
func,
|
||||
nestedEffects: [],
|
||||
componentType,
|
||||
context,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_queueNewComponentTree(
|
||||
rootValue: Value,
|
||||
evaluatedNode: ReactEvaluatedNode,
|
||||
@ -343,7 +220,7 @@ export class Reconciler {
|
||||
invariant(rootValue instanceof ECMAScriptSourceFunctionValue || rootValue instanceof AbstractValue);
|
||||
this.componentTreeState.deadEnds++;
|
||||
let componentType = getComponentTypeFromRootValue(this.realm, rootValue);
|
||||
if (componentType !== null && !this.hasEvaluatedRootNode(componentType, evaluatedNode)) {
|
||||
if (componentType !== null && !this.alreadyEvaluated.has(componentType)) {
|
||||
this.branchedComponentTrees.push({
|
||||
context,
|
||||
evaluatedNode,
|
||||
@ -364,7 +241,7 @@ export class Reconciler {
|
||||
if (branchStatus !== "ROOT") {
|
||||
// if the tree is simple and we're not in a branch, we can make this tree complex
|
||||
// and make this complex component the root
|
||||
let evaluatedComplexNode = this.alreadyEvaluatedRootNodes.get(componentType);
|
||||
let evaluatedComplexNode = this.alreadyEvaluated.get(componentType);
|
||||
if (
|
||||
branchStatus === "NO_BRANCH" &&
|
||||
this.componentTreeState.status === "SIMPLE" &&
|
||||
@ -543,7 +420,14 @@ export class Reconciler {
|
||||
if (propsValue instanceof ObjectValue && propsValue.properties.has("children")) {
|
||||
let renderProp = getProperty(this.realm, propsValue, "children");
|
||||
|
||||
this._findReactComponentTrees(propsValue, evaluatedChildNode, "NORMAL_FUNCTIONS");
|
||||
this._findReactComponentTrees(
|
||||
propsValue,
|
||||
evaluatedChildNode,
|
||||
"NORMAL_FUNCTIONS",
|
||||
componentType,
|
||||
context,
|
||||
branchStatus
|
||||
);
|
||||
if (renderProp instanceof ECMAScriptSourceFunctionValue) {
|
||||
if (typeValue instanceof ObjectValue || typeValue instanceof AbstractObjectValue) {
|
||||
// make sure this context is in our tree
|
||||
@ -562,10 +446,23 @@ export class Reconciler {
|
||||
}
|
||||
}
|
||||
}
|
||||
this._queueOptimizedClosure(renderProp, evaluatedChildNode, componentType, context);
|
||||
this._evaluateNestedOptimizedFunctionAndStoreEffects(
|
||||
componentType,
|
||||
context,
|
||||
branchStatus,
|
||||
evaluatedChildNode,
|
||||
renderProp
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
this._findReactComponentTrees(renderProp, evaluatedChildNode, "NESTED_CLOSURES");
|
||||
this._findReactComponentTrees(
|
||||
renderProp,
|
||||
evaluatedChildNode,
|
||||
"NESTED_CLOSURES",
|
||||
componentType,
|
||||
context,
|
||||
branchStatus
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -605,6 +502,7 @@ export class Reconciler {
|
||||
componentType: Value,
|
||||
reactElement: ObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
evaluatedNode: ReactEvaluatedNode
|
||||
): Value | void {
|
||||
let typeValue = getProperty(this.realm, reactElement, "type");
|
||||
@ -619,12 +517,32 @@ export class Reconciler {
|
||||
let renderProp = getProperty(this.realm, propsValue, "render");
|
||||
|
||||
if (renderProp instanceof ECMAScriptSourceFunctionValue) {
|
||||
this._queueOptimizedClosure(renderProp, evaluatedChildNode, componentType, context);
|
||||
this._evaluateNestedOptimizedFunctionAndStoreEffects(
|
||||
componentType,
|
||||
context,
|
||||
branchStatus,
|
||||
evaluatedChildNode,
|
||||
renderProp
|
||||
);
|
||||
} else if (renderProp instanceof AbstractValue) {
|
||||
this._findReactComponentTrees(renderProp, evaluatedChildNode, "NESTED_CLOSURES", componentType, context);
|
||||
this._findReactComponentTrees(
|
||||
renderProp,
|
||||
evaluatedChildNode,
|
||||
"NESTED_CLOSURES",
|
||||
componentType,
|
||||
context,
|
||||
branchStatus
|
||||
);
|
||||
}
|
||||
}
|
||||
this._findReactComponentTrees(propsValue, evaluatedChildNode, "NORMAL_FUNCTIONS");
|
||||
this._findReactComponentTrees(
|
||||
propsValue,
|
||||
evaluatedChildNode,
|
||||
"NORMAL_FUNCTIONS",
|
||||
componentType,
|
||||
context,
|
||||
branchStatus
|
||||
);
|
||||
return;
|
||||
}
|
||||
// this is the worst case, we were unable to find the render prop function
|
||||
@ -1022,13 +940,26 @@ export class Reconciler {
|
||||
return value;
|
||||
}
|
||||
|
||||
_resolveUnknownComponentType(reactElement: ObjectValue, evaluatedNode: ReactEvaluatedNode) {
|
||||
_resolveUnknownComponentType(
|
||||
componentType: Value,
|
||||
reactElement: ObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
evaluatedNode: ReactEvaluatedNode
|
||||
): ObjectValue {
|
||||
let typeValue = getProperty(this.realm, reactElement, "type");
|
||||
let propsValue = getProperty(this.realm, reactElement, "props");
|
||||
|
||||
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
|
||||
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus);
|
||||
if (typeValue instanceof AbstractValue) {
|
||||
this._findReactComponentTrees(typeValue, evaluatedNode, "FUNCTIONAL_COMPONENTS");
|
||||
this._findReactComponentTrees(
|
||||
typeValue,
|
||||
evaluatedNode,
|
||||
"FUNCTIONAL_COMPONENTS",
|
||||
componentType,
|
||||
context,
|
||||
branchStatus
|
||||
);
|
||||
return reactElement;
|
||||
} else {
|
||||
let evaluatedChildNode = createReactEvaluatedNode("BAIL-OUT", getComponentName(this.realm, typeValue));
|
||||
@ -1041,7 +972,13 @@ export class Reconciler {
|
||||
}
|
||||
}
|
||||
|
||||
_resolveReactElementBadRef(reactElement: ObjectValue, evaluatedNode: ReactEvaluatedNode) {
|
||||
_resolveReactElementBadRef(
|
||||
componentType: Value,
|
||||
reactElement: ObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
evaluatedNode: ReactEvaluatedNode
|
||||
): ObjectValue {
|
||||
let typeValue = getProperty(this.realm, reactElement, "type");
|
||||
let propsValue = getProperty(this.realm, reactElement, "props");
|
||||
|
||||
@ -1051,16 +988,18 @@ export class Reconciler {
|
||||
evaluatedChildNode.message = bailOutMessage;
|
||||
|
||||
this._queueNewComponentTree(typeValue, evaluatedChildNode);
|
||||
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
|
||||
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus);
|
||||
this._assignBailOutMessage(reactElement, bailOutMessage);
|
||||
return reactElement;
|
||||
}
|
||||
|
||||
_resolveReactElementUndefinedRender(
|
||||
componentType: Value,
|
||||
reactElement: ObjectValue,
|
||||
evaluatedNode: ReactEvaluatedNode,
|
||||
branchStatus: BranchStatusEnum
|
||||
) {
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
evaluatedNode: ReactEvaluatedNode
|
||||
): ObjectValue {
|
||||
let typeValue = getProperty(this.realm, reactElement, "type");
|
||||
let propsValue = getProperty(this.realm, reactElement, "props");
|
||||
|
||||
@ -1070,7 +1009,7 @@ export class Reconciler {
|
||||
evaluatedChildNode.message = bailOutMessage;
|
||||
|
||||
this._assignBailOutMessage(reactElement, bailOutMessage);
|
||||
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
|
||||
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus);
|
||||
return reactElement;
|
||||
}
|
||||
|
||||
@ -1080,7 +1019,7 @@ export class Reconciler {
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
evaluatedNode: ReactEvaluatedNode
|
||||
) {
|
||||
): ObjectValue {
|
||||
let propsValue = getProperty(this.realm, reactElement, "props");
|
||||
// terminal host component. Start evaluating its children.
|
||||
if (propsValue instanceof ObjectValue && propsValue.properties.has("children")) {
|
||||
@ -1119,7 +1058,7 @@ export class Reconciler {
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
evaluatedNode: ReactEvaluatedNode
|
||||
) {
|
||||
): ObjectValue {
|
||||
this.statistics.componentsEvaluated++;
|
||||
if (this.componentTreeConfig.firstRenderOnly) {
|
||||
let evaluatedChildNode = createReactEvaluatedNode("INLINED", "React.Fragment");
|
||||
@ -1198,7 +1137,7 @@ export class Reconciler {
|
||||
// point, so if we're here, then the FatalError has been recovered explicitly
|
||||
!(refValue instanceof AbstractValue)
|
||||
) {
|
||||
this._resolveReactElementBadRef(reactElement, evaluatedNode);
|
||||
this._resolveReactElementBadRef(componentType, reactElement, context, branchStatus, evaluatedNode);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1209,7 +1148,7 @@ export class Reconciler {
|
||||
if (
|
||||
!(typeValue instanceof ECMAScriptSourceFunctionValue || valueIsKnownReactAbstraction(this.realm, typeValue))
|
||||
) {
|
||||
return this._resolveUnknownComponentType(reactElement, evaluatedNode);
|
||||
return this._resolveUnknownComponentType(componentType, reactElement, context, branchStatus, evaluatedNode);
|
||||
}
|
||||
let evaluatedChildNode = createReactEvaluatedNode("INLINED", getComponentName(this.realm, typeValue));
|
||||
let render = this._resolveComponent(
|
||||
@ -1233,7 +1172,13 @@ export class Reconciler {
|
||||
}
|
||||
case "RELAY_QUERY_RENDERER": {
|
||||
invariant(typeValue instanceof AbstractObjectValue);
|
||||
result = this._resolveRelayQueryRendererComponent(componentType, reactElement, context, evaluatedNode);
|
||||
result = this._resolveRelayQueryRendererComponent(
|
||||
componentType,
|
||||
reactElement,
|
||||
context,
|
||||
branchStatus,
|
||||
evaluatedNode
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "CONTEXT_PROVIDER": {
|
||||
@ -1269,7 +1214,13 @@ export class Reconciler {
|
||||
}
|
||||
|
||||
if (result instanceof UndefinedValue) {
|
||||
return this._resolveReactElementUndefinedRender(reactElement, evaluatedNode, branchStatus);
|
||||
return this._resolveReactElementUndefinedRender(
|
||||
componentType,
|
||||
reactElement,
|
||||
context,
|
||||
branchStatus,
|
||||
evaluatedNode
|
||||
);
|
||||
}
|
||||
|
||||
// If we have a new result and we might have a key value then wrap our inlined result in a
|
||||
@ -1280,7 +1231,14 @@ export class Reconciler {
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return this._resolveComponentResolutionFailure(error, reactElement, evaluatedNode, branchStatus);
|
||||
return this._resolveComponentResolutionFailure(
|
||||
componentType,
|
||||
error,
|
||||
reactElement,
|
||||
context,
|
||||
evaluatedNode,
|
||||
branchStatus
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1325,8 +1283,10 @@ export class Reconciler {
|
||||
}
|
||||
|
||||
_resolveComponentResolutionFailure(
|
||||
componentType: Value,
|
||||
error: Error | Completion,
|
||||
reactElement: ObjectValue,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
evaluatedNode: ReactEvaluatedNode,
|
||||
branchStatus: BranchStatusEnum
|
||||
): Value {
|
||||
@ -1357,7 +1317,14 @@ export class Reconciler {
|
||||
let propsValue = getProperty(this.realm, reactElement, "props");
|
||||
// assign a bail out message
|
||||
if (error instanceof NewComponentTreeBranch) {
|
||||
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
|
||||
this._findReactComponentTrees(
|
||||
propsValue,
|
||||
evaluatedNode,
|
||||
"NORMAL_FUNCTIONS",
|
||||
componentType,
|
||||
context,
|
||||
branchStatus
|
||||
);
|
||||
evaluatedNode.children.push(error.evaluatedNode);
|
||||
// NO-OP (we don't queue a newComponentTree as this was already done)
|
||||
} else {
|
||||
@ -1367,7 +1334,14 @@ export class Reconciler {
|
||||
}
|
||||
evaluatedNode.children.push(evaluatedChildNode);
|
||||
this._queueNewComponentTree(typeValue, evaluatedChildNode);
|
||||
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
|
||||
this._findReactComponentTrees(
|
||||
propsValue,
|
||||
evaluatedNode,
|
||||
"NORMAL_FUNCTIONS",
|
||||
componentType,
|
||||
context,
|
||||
branchStatus
|
||||
);
|
||||
if (error instanceof ExpectedBailOut) {
|
||||
evaluatedChildNode.message = error.message;
|
||||
this._assignBailOutMessage(reactElement, error.message);
|
||||
@ -1444,20 +1418,32 @@ export class Reconciler {
|
||||
|
||||
if (nestedOptimizedFunctionEffects !== undefined) {
|
||||
for (let [func, effects] of nestedOptimizedFunctionEffects) {
|
||||
let resolvedEffects = this.realm.evaluateForEffects(
|
||||
() => {
|
||||
let result = effects.result;
|
||||
this.realm.applyEffects(effects);
|
||||
let funcCall = () => {
|
||||
let result = effects.result;
|
||||
this.realm.applyEffects(effects);
|
||||
if (result instanceof SimpleNormalCompletion) {
|
||||
result = result.value;
|
||||
}
|
||||
invariant(result instanceof Value);
|
||||
return this._resolveDeeply(componentType, result, context, branchStatus, evaluatedNode, needsKey);
|
||||
};
|
||||
let pureFuncCall = () =>
|
||||
this.realm.evaluatePure(funcCall, /*bubbles*/ true, (sideEffectType, binding, expressionLocation) =>
|
||||
handleReportedSideEffect(throwUnsupportedSideEffectError, sideEffectType, binding, expressionLocation)
|
||||
);
|
||||
|
||||
if (result instanceof SimpleNormalCompletion) {
|
||||
result = result.value;
|
||||
}
|
||||
invariant(result instanceof Value);
|
||||
return this._resolveDeeply(componentType, result, context, branchStatus, evaluatedNode, needsKey);
|
||||
},
|
||||
/*state*/ null,
|
||||
`react nested optimized closure`
|
||||
);
|
||||
let resolvedEffects;
|
||||
let saved_pathConditions = this.realm.pathConditions;
|
||||
this.realm.pathConditions = [];
|
||||
try {
|
||||
resolvedEffects = this.realm.evaluateForEffects(
|
||||
pureFuncCall,
|
||||
/*state*/ null,
|
||||
`react resolve nested optimized closure`
|
||||
);
|
||||
} finally {
|
||||
this.realm.pathConditions = saved_pathConditions;
|
||||
}
|
||||
this.statistics.optimizedNestedClosures++;
|
||||
nestedOptimizedFunctionEffects.set(func, resolvedEffects);
|
||||
this.realm.collectedNestedOptimizedFunctionEffects.set(func, resolvedEffects);
|
||||
@ -1473,33 +1459,18 @@ export class Reconciler {
|
||||
return children;
|
||||
}
|
||||
|
||||
hasEvaluatedRootNode(componentType: ECMAScriptSourceFunctionValue, evaluateNode: ReactEvaluatedNode): boolean {
|
||||
if (this.alreadyEvaluatedRootNodes.has(componentType)) {
|
||||
let alreadyEvaluatedNode = this.alreadyEvaluatedRootNodes.get(componentType);
|
||||
invariant(alreadyEvaluatedNode);
|
||||
evaluateNode.children = alreadyEvaluatedNode.children;
|
||||
evaluateNode.status = alreadyEvaluatedNode.status;
|
||||
evaluateNode.name = alreadyEvaluatedNode.name;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
hasEvaluatedNestedClosure(func: ECMAScriptSourceFunctionValue | BoundFunctionValue): boolean {
|
||||
return this.alreadyEvaluatedNestedClosures.has(func);
|
||||
}
|
||||
|
||||
_findReactComponentTrees(
|
||||
value: Value,
|
||||
evaluatedNode: ReactEvaluatedNode,
|
||||
treatFunctionsAs: "NORMAL_FUNCTIONS" | "NESTED_CLOSURES" | "FUNCTIONAL_COMPONENTS",
|
||||
componentType?: Value,
|
||||
context?: ObjectValue | AbstractObjectValue
|
||||
context?: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum
|
||||
): void {
|
||||
if (value instanceof AbstractValue) {
|
||||
if (value.args.length > 0) {
|
||||
for (let arg of value.args) {
|
||||
this._findReactComponentTrees(arg, evaluatedNode, treatFunctionsAs, componentType, context);
|
||||
this._findReactComponentTrees(arg, evaluatedNode, treatFunctionsAs, componentType, context, branchStatus);
|
||||
}
|
||||
} else {
|
||||
this.componentTreeState.deadEnds++;
|
||||
@ -1515,7 +1486,14 @@ export class Reconciler {
|
||||
this._queueNewComponentTree(value, evaluatedChildNode);
|
||||
} else if (treatFunctionsAs === "NESTED_CLOSURES") {
|
||||
invariant(componentType && context);
|
||||
this._queueOptimizedClosure(value, evaluatedNode, componentType, context);
|
||||
let evaluatedChildNode = createReactEvaluatedNode("RENDER_PROPS", getComponentName(this.realm, value));
|
||||
this._evaluateNestedOptimizedFunctionAndStoreEffects(
|
||||
componentType,
|
||||
context,
|
||||
branchStatus,
|
||||
evaluatedChildNode,
|
||||
value
|
||||
);
|
||||
}
|
||||
} else if (value instanceof ObjectValue) {
|
||||
if (isReactElement(value)) {
|
||||
@ -1528,8 +1506,8 @@ export class Reconciler {
|
||||
evaluatedNode.children.push(evaluatedChildNode);
|
||||
this._queueNewComponentTree(typeValue, evaluatedChildNode);
|
||||
}
|
||||
this._findReactComponentTrees(ref, evaluatedNode, treatFunctionsAs, componentType, context);
|
||||
this._findReactComponentTrees(props, evaluatedNode, treatFunctionsAs, componentType, context);
|
||||
this._findReactComponentTrees(ref, evaluatedNode, treatFunctionsAs, componentType, context, branchStatus);
|
||||
this._findReactComponentTrees(props, evaluatedNode, treatFunctionsAs, componentType, context, branchStatus);
|
||||
} else {
|
||||
for (let [propName, binding] of value.properties) {
|
||||
if (binding && binding.descriptor && binding.descriptor.enumerable) {
|
||||
@ -1538,11 +1516,65 @@ export class Reconciler {
|
||||
evaluatedNode,
|
||||
treatFunctionsAs,
|
||||
componentType,
|
||||
context
|
||||
context,
|
||||
branchStatus
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_evaluateNestedOptimizedFunctionAndStoreEffects(
|
||||
componentType: Value,
|
||||
context: ObjectValue | AbstractObjectValue,
|
||||
branchStatus: BranchStatusEnum,
|
||||
evaluatedNode: ReactEvaluatedNode,
|
||||
func: ECMAScriptSourceFunctionValue | BoundFunctionValue,
|
||||
thisValue?: Value = this.realm.intrinsics.undefined
|
||||
): void {
|
||||
if (!this.realm.react.optimizeNestedFunctions) {
|
||||
return;
|
||||
}
|
||||
let funcToModel = func;
|
||||
if (func instanceof BoundFunctionValue) {
|
||||
funcToModel = func.$BoundTargetFunction;
|
||||
thisValue = func.$BoundThis;
|
||||
}
|
||||
invariant(funcToModel instanceof ECMAScriptSourceFunctionValue);
|
||||
let funcCall = Utils.createModelledFunctionCall(this.realm, funcToModel, undefined, thisValue);
|
||||
// We take the modelled function and wrap it in a pure evaluation so we can check for
|
||||
// side-effects that occur when evaluating the function. If there are side-effects, then
|
||||
// we don't try and optimize the nested function.
|
||||
let pureFuncCall = () =>
|
||||
this.realm.evaluatePure(funcCall, /*bubbles*/ false, () => {
|
||||
throw new NestedOptimizedFunctionSideEffect();
|
||||
});
|
||||
let effects;
|
||||
let saved_pathConditions = this.realm.pathConditions;
|
||||
this.realm.pathConditions = [];
|
||||
try {
|
||||
effects = this.realm.evaluateForEffects(
|
||||
() => {
|
||||
let result = pureFuncCall();
|
||||
return this._resolveDeeply(componentType, result, context, branchStatus, evaluatedNode, false);
|
||||
},
|
||||
null,
|
||||
"React nestedOptimizedFunction"
|
||||
);
|
||||
} catch (e) {
|
||||
// If the nested optimized function had side-effects, we need to fallback to
|
||||
// the default behaviour and havoc the nested functions so any bindings
|
||||
// within the function properly leak and materialize.
|
||||
if (e instanceof NestedOptimizedFunctionSideEffect) {
|
||||
Havoc.value(this.realm, func);
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
this.realm.pathConditions = saved_pathConditions;
|
||||
}
|
||||
this.statistics.optimizedNestedClosures++;
|
||||
this.realm.collectedNestedOptimizedFunctionEffects.set(funcToModel, effects);
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,8 @@
|
||||
|
||||
/* @flow */
|
||||
|
||||
import { Realm, Effects } from "../realm.js";
|
||||
import { AbruptCompletion, Completion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js";
|
||||
import { Realm } from "../realm.js";
|
||||
import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js";
|
||||
import type { BabelNode, BabelNodeJSXIdentifier } from "@babel/types";
|
||||
import { parseExpression } from "@babel/parser";
|
||||
import {
|
||||
@ -29,7 +29,7 @@ import {
|
||||
UndefinedValue,
|
||||
Value,
|
||||
} from "../values/index.js";
|
||||
import { Generator, TemporalObjectAssignEntry } from "../utils/generator.js";
|
||||
import { TemporalObjectAssignEntry } from "../utils/generator.js";
|
||||
import type { Descriptor, ReactComponentTreeConfig, ReactHint, PropertyBinding } from "../types.js";
|
||||
import { Get, cloneDescriptor } from "../methods/index.js";
|
||||
import { computeBinary } from "../evaluators/BinaryExpression.js";
|
||||
@ -603,48 +603,6 @@ export function flattenChildren(realm: Realm, array: ArrayValue): ArrayValue {
|
||||
return flattenedChildren;
|
||||
}
|
||||
|
||||
export function evaluateWithNestedParentEffects(
|
||||
realm: Realm,
|
||||
nestedEffects: Array<Effects>,
|
||||
f: () => Effects
|
||||
): Effects {
|
||||
let nextEffects = nestedEffects.slice();
|
||||
let modifiedBindings;
|
||||
let modifiedProperties;
|
||||
let createdObjects;
|
||||
let value;
|
||||
|
||||
if (nextEffects.length !== 0) {
|
||||
let effects = nextEffects.shift();
|
||||
value = effects.result;
|
||||
if (value instanceof Completion) value = value.shallowCloneWithoutEffects();
|
||||
createdObjects = effects.createdObjects;
|
||||
modifiedBindings = effects.modifiedBindings;
|
||||
modifiedProperties = effects.modifiedProperties;
|
||||
realm.applyEffects(
|
||||
new Effects(
|
||||
value,
|
||||
new Generator(realm, "evaluateWithNestedEffects", effects.generator.pathConditions),
|
||||
modifiedBindings,
|
||||
modifiedProperties,
|
||||
createdObjects
|
||||
)
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (nextEffects.length === 0) {
|
||||
return f();
|
||||
} else {
|
||||
return evaluateWithNestedParentEffects(realm, nextEffects, f);
|
||||
}
|
||||
} finally {
|
||||
if (modifiedBindings && modifiedProperties) {
|
||||
realm.undoBindings(modifiedBindings);
|
||||
realm.restoreProperties(modifiedProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function is mainly use to get internal properties
|
||||
// on objects that we know are safe to access internally
|
||||
// such as ReactElements. Getting properties here does
|
||||
|
18
src/realm.js
18
src/realm.js
@ -33,7 +33,6 @@ import {
|
||||
AbstractObjectValue,
|
||||
AbstractValue,
|
||||
ArrayValue,
|
||||
BoundFunctionValue,
|
||||
ConcreteValue,
|
||||
ECMAScriptSourceFunctionValue,
|
||||
FunctionValue,
|
||||
@ -305,7 +304,6 @@ export class Realm {
|
||||
hoistableFunctions: new WeakMap(),
|
||||
hoistableReactElements: new WeakMap(),
|
||||
noopFunction: undefined,
|
||||
optimizedNestedClosuresToWrite: [],
|
||||
optimizeNestedFunctions: opts.reactOptimizeNestedFunctions || false,
|
||||
output: opts.reactOutput || "create-element",
|
||||
propsWithNoPartialKeyOrRef: new WeakSet(),
|
||||
@ -408,10 +406,6 @@ export class Realm {
|
||||
hoistableFunctions: WeakMap<FunctionValue, boolean>,
|
||||
hoistableReactElements: WeakMap<ObjectValue, boolean>,
|
||||
noopFunction: void | ECMAScriptSourceFunctionValue,
|
||||
optimizedNestedClosuresToWrite: Array<{
|
||||
effects: Effects,
|
||||
func: ECMAScriptSourceFunctionValue | BoundFunctionValue,
|
||||
}>,
|
||||
optimizeNestedFunctions: boolean,
|
||||
output?: ReactOutputTypes,
|
||||
propsWithNoPartialKeyOrRef: WeakSet<ObjectValue | AbstractObjectValue>,
|
||||
@ -734,17 +728,23 @@ export class Realm {
|
||||
// call.
|
||||
evaluatePure<T>(
|
||||
f: () => T,
|
||||
bubbleSideEffectReports: boolean,
|
||||
reportSideEffectFunc:
|
||||
| null
|
||||
| ((sideEffectType: SideEffectType, binding: void | Binding | PropertyBinding, value: void | Value) => void)
|
||||
): T {
|
||||
let saved_createdObjectsTrackedForLeaks = this.createdObjectsTrackedForLeaks;
|
||||
let saved_reportSideEffectCallbacks;
|
||||
// Track all objects (including function closures) created during
|
||||
// this call. This will be used to make the assumption that every
|
||||
// *other* object is unchanged (pure). These objects are marked
|
||||
// as leaked if they're passed to abstract functions.
|
||||
this.createdObjectsTrackedForLeaks = new Set();
|
||||
if (reportSideEffectFunc !== null) {
|
||||
if (!bubbleSideEffectReports) {
|
||||
saved_reportSideEffectCallbacks = this.reportSideEffectCallbacks;
|
||||
this.reportSideEffectCallbacks = new Set();
|
||||
}
|
||||
this.reportSideEffectCallbacks.add(reportSideEffectFunc);
|
||||
}
|
||||
try {
|
||||
@ -752,6 +752,9 @@ export class Realm {
|
||||
} finally {
|
||||
this.createdObjectsTrackedForLeaks = saved_createdObjectsTrackedForLeaks;
|
||||
if (reportSideEffectFunc !== null) {
|
||||
if (!bubbleSideEffectReports && saved_reportSideEffectCallbacks !== undefined) {
|
||||
this.reportSideEffectCallbacks = saved_reportSideEffectCallbacks;
|
||||
}
|
||||
this.reportSideEffectCallbacks.delete(reportSideEffectFunc);
|
||||
}
|
||||
}
|
||||
@ -1603,7 +1606,8 @@ export class Realm {
|
||||
createdObjectsTrackedForLeaks !== undefined &&
|
||||
!createdObjectsTrackedForLeaks.has(object) &&
|
||||
// __markPropertyAsChecked__ is set by realm.markPropertyAsChecked
|
||||
(typeof binding.key !== "string" || !binding.key.includes("__propertyHasBeenChecked__"))
|
||||
(typeof binding.key !== "string" || !binding.key.includes("__propertyHasBeenChecked__")) &&
|
||||
binding.key !== "_temporalAlias"
|
||||
) {
|
||||
if (binding.object === this.$GlobalObject) {
|
||||
for (let callback of this.reportSideEffectCallbacks) {
|
||||
|
@ -32,7 +32,7 @@ import { createAdditionalEffects } from "./utils.js";
|
||||
import { ReactStatistics } from "./types";
|
||||
import type { AdditionalFunctionEffects, WriteEffects } from "./types";
|
||||
import { convertConfigObjectToReactComponentTreeConfig, valueIsKnownReactAbstraction } from "../react/utils.js";
|
||||
import { applyOptimizedReactComponents, optimizeReactComponentTreeRoot } from "../react/optimizing.js";
|
||||
import { optimizeReactComponentTreeRoot } from "../react/optimizing.js";
|
||||
import { handleReportedSideEffect } from "./utils.js";
|
||||
import type { ArgModel } from "../types.js";
|
||||
import { stringOfLocation } from "../utils/babelhelpers";
|
||||
@ -161,6 +161,7 @@ export class Functions {
|
||||
if (this.realm.react.verbose) {
|
||||
logger.logInformation(`Evaluating ${recordedReactRootValues.length} React component tree roots...`);
|
||||
}
|
||||
let alreadyEvaluated = new Map();
|
||||
for (let { value: componentRoot, config } of recordedReactRootValues) {
|
||||
invariant(config);
|
||||
optimizeReactComponentTreeRoot(
|
||||
@ -170,10 +171,10 @@ export class Functions {
|
||||
this.writeEffects,
|
||||
environmentRecordIdAfterGlobalCode,
|
||||
logger,
|
||||
statistics
|
||||
statistics,
|
||||
alreadyEvaluated
|
||||
);
|
||||
}
|
||||
applyOptimizedReactComponents(this.realm, this.writeEffects, environmentRecordIdAfterGlobalCode);
|
||||
}
|
||||
|
||||
getDeclaringOptimizedFunction(functionValue: ECMAScriptSourceFunctionValue) {
|
||||
@ -235,6 +236,7 @@ export class Functions {
|
||||
};
|
||||
let effects: Effects = realm.evaluatePure(
|
||||
() => realm.evaluateForEffectsInGlobalEnv(call, undefined, "additional function"),
|
||||
/*bubbles*/ true,
|
||||
(sideEffectType, binding, expressionLocation) =>
|
||||
handleReportedSideEffect(logCompilerDiagnostic, sideEffectType, binding, expressionLocation)
|
||||
);
|
||||
|
@ -24,6 +24,7 @@ import { IsAccessorDescriptor, IsPropertyKey, IsArrayIndex } from "../methods/is
|
||||
import { Havoc, Properties, To, Utils } from "../singletons.js";
|
||||
import { type OperationDescriptor } from "../utils/generator.js";
|
||||
import invariant from "../invariant.js";
|
||||
import { NestedOptimizedFunctionSideEffect } from "../errors.js";
|
||||
|
||||
type PossibleNestedOptimizedFunctions = [
|
||||
{ func: BoundFunctionValue | ECMAScriptSourceFunctionValue, thisValue: Value },
|
||||
@ -42,28 +43,36 @@ function evaluatePossibleNestedOptimizedFunctionsAndStoreEffects(
|
||||
}
|
||||
invariant(funcToModel instanceof ECMAScriptSourceFunctionValue);
|
||||
let funcCall = Utils.createModelledFunctionCall(realm, funcToModel, undefined, thisValue);
|
||||
let hadSideEffects = false;
|
||||
// We take the modelled function and wrap it in a pure evaluation so we can check for
|
||||
// side-effects that occur when evaluating the function. If there are side-effects, then
|
||||
// we don't try and optimize the nested function.
|
||||
let pureFuncCall = () =>
|
||||
realm.evaluatePure(funcCall, () => {
|
||||
hadSideEffects = true;
|
||||
realm.evaluatePure(funcCall, /*bubbles*/ false, () => {
|
||||
throw new NestedOptimizedFunctionSideEffect();
|
||||
});
|
||||
let effects = realm.evaluateForEffects(pureFuncCall, null, "temporalArray nestedOptimizedFunction");
|
||||
if (hadSideEffects) {
|
||||
let effects;
|
||||
let saved_pathConditions = realm.pathConditions;
|
||||
realm.pathConditions = [];
|
||||
try {
|
||||
effects = realm.evaluateForEffects(pureFuncCall, null, "temporalArray nestedOptimizedFunction");
|
||||
} catch (e) {
|
||||
// If the nested optimized function had side-effects, we need to fallback to
|
||||
// the default behaviour and havoc the nested functions so any bindings
|
||||
// within the function properly leak and materialize.
|
||||
Havoc.value(realm, func);
|
||||
} else {
|
||||
// Check if effects were pure then add them
|
||||
if (abstractArrayValue.nestedOptimizedFunctionEffects === undefined) {
|
||||
abstractArrayValue.nestedOptimizedFunctionEffects = new Map();
|
||||
if (e instanceof NestedOptimizedFunctionSideEffect) {
|
||||
Havoc.value(realm, func);
|
||||
return;
|
||||
}
|
||||
abstractArrayValue.nestedOptimizedFunctionEffects.set(funcToModel, effects);
|
||||
realm.collectedNestedOptimizedFunctionEffects.set(funcToModel, effects);
|
||||
throw e;
|
||||
} finally {
|
||||
realm.pathConditions = saved_pathConditions;
|
||||
}
|
||||
// Check if effects were pure then add them
|
||||
if (abstractArrayValue.nestedOptimizedFunctionEffects === undefined) {
|
||||
abstractArrayValue.nestedOptimizedFunctionEffects = new Map();
|
||||
}
|
||||
abstractArrayValue.nestedOptimizedFunctionEffects.set(funcToModel, effects);
|
||||
realm.collectedNestedOptimizedFunctionEffects.set(funcToModel, effects);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9626,7 +9626,7 @@ ReactStatistics {
|
||||
|
||||
exports[`Simple 5: (JSX => JSX) 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 4,
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
@ -9641,22 +9641,16 @@ ReactStatistics {
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Child",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 3,
|
||||
"optimizedTrees": 2,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Simple 5: (JSX => createElement) 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 4,
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
@ -9671,22 +9665,16 @@ ReactStatistics {
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Child",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 3,
|
||||
"optimizedTrees": 2,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Simple 5: (createElement => JSX) 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 4,
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
@ -9701,22 +9689,16 @@ ReactStatistics {
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Child",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 3,
|
||||
"optimizedTrees": 2,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Simple 5: (createElement => createElement) 1`] = `
|
||||
ReactStatistics {
|
||||
"componentsEvaluated": 4,
|
||||
"componentsEvaluated": 3,
|
||||
"evaluatedRootNodes": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
@ -9731,16 +9713,10 @@ ReactStatistics {
|
||||
"name": "App",
|
||||
"status": "ROOT",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"message": "",
|
||||
"name": "Child",
|
||||
"status": "ROOT",
|
||||
},
|
||||
],
|
||||
"inlinedComponents": 0,
|
||||
"optimizedNestedClosures": 0,
|
||||
"optimizedTrees": 3,
|
||||
"optimizedTrees": 2,
|
||||
}
|
||||
`;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user