Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Commit

Permalink
Filter ModifiedBindings by environment's creating optimized function (#…
Browse files Browse the repository at this point in the history
…2551)

Summary:
Release Notes: None

Changes the logic for determining which ModifiedBindings need serialization to work properly in the face of nested optimized functions. We should only serialize `ModifiedBinding`s if their environment was not created by the optimized function or its children (i.e. the binding should not be local to the optimized function).

This also solves the issue that React components don't have a parent chain which is important for properly handling nested optimized functions. Solves #2550.

Solves #2430, Solves #2426, Solves #2423, Solves #2422 (some were solved by previous PRs, just adding the tests here as well).
Pull Request resolved: #2551

Differential Revision: D10010048

Pulled By: cblappert

fbshipit-source-id: 2a855017514832a70d024f1ae9a91e9f07736ce0
  • Loading branch information
cblappert authored and facebook-github-bot committed Oct 3, 2018
1 parent 7f0c13a commit 473470a
Show file tree
Hide file tree
Showing 13 changed files with 276 additions and 86 deletions.
2 changes: 2 additions & 0 deletions src/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export class EnvironmentRecord {
isReadOnly: boolean;
$NewTarget: void | ObjectValue;
id: number;
creatingOptimizedFunction: FunctionValue | void;

static nextId: number = 0;

Expand All @@ -96,6 +97,7 @@ export class EnvironmentRecord {
this.realm = realm;
this.isReadOnly = false;
this.id = EnvironmentRecord.nextId++;
this.creatingOptimizedFunction = realm.currentOptimizedFunction;
}

HasBinding(N: string): boolean {
Expand Down
139 changes: 90 additions & 49 deletions src/react/optimizing.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
/* @flow strict-local */

import { type Effects, Realm } from "../realm.js";
import { AbstractValue, ECMAScriptSourceFunctionValue, ObjectValue, BoundFunctionValue } from "../values/index.js";
import {
AbstractValue,
ECMAScriptSourceFunctionValue,
ObjectValue,
BoundFunctionValue,
FunctionValue,
} from "../values/index.js";
import { createAdditionalEffects } from "../serializer/utils.js";
import {
convertFunctionalComponentToComplexClassComponent,
Expand All @@ -22,7 +28,12 @@ import {
normalizeFunctionalComponentParamaters,
valueIsClassComponent,
} from "./utils.js";
import { type WriteEffects, type ReactEvaluatedNode, ReactStatistics } from "../serializer/types.js";
import {
type WriteEffects,
type ReactEvaluatedNode,
ReactStatistics,
type AdditionalFunctionTransform,
} from "../serializer/types.js";
import { Reconciler, type ComponentTreeState } from "./reconcilation.js";
import { ReconcilerFatalError } from "./errors.js";
import { Properties } from "../singletons.js";
Expand All @@ -31,84 +42,102 @@ import invariant from "../invariant.js";
import type { ReactComponentTreeConfig } from "../types.js";
import { Logger } from "../utils/logger.js";

function applyWriteEffectsForOptimizedComponent(
function writeEffectsKeyOfComponentValue(
realm: Realm,
componentType: ECMAScriptSourceFunctionValue | BoundFunctionValue,
_effects: Effects,
componentTreeState: ComponentTreeState,
evaluatedNode: ReactEvaluatedNode,
writeEffects: WriteEffects,
environmentRecordIdAfterGlobalCode: number
): void {
let effects = _effects;
let additionalFunctionEffects = createAdditionalEffects(
realm,
effects,
false,
"ReactAdditionalFunctionEffects",
environmentRecordIdAfterGlobalCode
);
if (additionalFunctionEffects === null) {
throw new ReconcilerFatalError(
`Failed to optimize React component tree for "${evaluatedNode.name}" due to an unsupported completion`,
evaluatedNode
);
}
effects = additionalFunctionEffects.effects;
let value = effects.result;

if (value === realm.intrinsics.undefined) {
// if we get undefined, then this component tree failed and a message was already logged
// in the reconciler
return;
}
transforms: Array<AdditionalFunctionTransform>
): FunctionValue {
if (valueIsClassComponent(realm, componentType)) {
if (componentTreeState.status === "SIMPLE") {
// if the root component was a class and is now simple, we can convert it from a class
// component to a functional component
if (componentType instanceof BoundFunctionValue) {
let targetFunction = componentType.$BoundTargetFunction;
invariant(targetFunction instanceof ECMAScriptSourceFunctionValue);
convertSimpleClassComponentToFunctionalComponent(realm, targetFunction, additionalFunctionEffects);
convertSimpleClassComponentToFunctionalComponent(realm, targetFunction, transforms);
normalizeFunctionalComponentParamaters(targetFunction);
writeEffects.set(targetFunction, additionalFunctionEffects);
return targetFunction;
} else {
convertSimpleClassComponentToFunctionalComponent(realm, componentType, additionalFunctionEffects);
convertSimpleClassComponentToFunctionalComponent(realm, componentType, transforms);
normalizeFunctionalComponentParamaters(componentType);
writeEffects.set(componentType, additionalFunctionEffects);
return componentType;
}
} else {
let prototype = Get(realm, componentType, "prototype");
invariant(prototype instanceof ObjectValue);
let renderMethod = Get(realm, prototype, "render");
invariant(renderMethod instanceof ECMAScriptSourceFunctionValue);
writeEffects.set(renderMethod, additionalFunctionEffects);
return renderMethod;
}
} else {
if (componentTreeState.status === "COMPLEX") {
convertFunctionalComponentToComplexClassComponent(
realm,
componentType,
componentTreeState.componentType,
additionalFunctionEffects
transforms
);
let prototype = Get(realm, componentType, "prototype");
invariant(prototype instanceof ObjectValue);
let renderMethod = Get(realm, prototype, "render");
invariant(renderMethod instanceof ECMAScriptSourceFunctionValue);
writeEffects.set(renderMethod, additionalFunctionEffects);
return renderMethod;
} else {
if (componentType instanceof BoundFunctionValue) {
let targetFunction = componentType.$BoundTargetFunction;
invariant(targetFunction instanceof ECMAScriptSourceFunctionValue);
normalizeFunctionalComponentParamaters(targetFunction);
writeEffects.set(targetFunction, additionalFunctionEffects);
return targetFunction;
} else {
normalizeFunctionalComponentParamaters(componentType);
writeEffects.set(componentType, additionalFunctionEffects);
return componentType;
}
}
}
}

function applyWriteEffectsForOptimizedComponent(
realm: Realm,
componentType: ECMAScriptSourceFunctionValue | BoundFunctionValue,
_effects: Effects,
componentTreeState: ComponentTreeState,
evaluatedNode: ReactEvaluatedNode,
writeEffects: WriteEffects,
preEvaluationComponentToWriteEffectFunction: Map<FunctionValue, FunctionValue>,
parentOptimizedFunction: FunctionValue | void
): void {
let effects = _effects;
let transforms = [];
let writeEffectsKey = writeEffectsKeyOfComponentValue(realm, componentType, componentTreeState, transforms);
// NB: Must be done here because its required by cAE
preEvaluationComponentToWriteEffectFunction.set(componentType, writeEffectsKey);
let additionalFunctionEffects = createAdditionalEffects(
realm,
effects,
false,
"ReactAdditionalFunctionEffects",
writeEffects,
preEvaluationComponentToWriteEffectFunction,
writeEffectsKey,
parentOptimizedFunction,
transforms
);
if (additionalFunctionEffects === null) {
throw new ReconcilerFatalError(
`Failed to optimize React component tree for "${evaluatedNode.name}" due to an unsupported completion`,
evaluatedNode
);
}
effects = additionalFunctionEffects.effects;
let value = effects.result;

if (value === realm.intrinsics.undefined) {
// if we get undefined, then this component tree failed and a message was already logged
// in the reconciler
return;
}
writeEffects.set(writeEffectsKey, additionalFunctionEffects);
// apply contextTypes for legacy context
if (componentTreeState.contextTypes.size > 0) {
let contextTypes = new ObjectValue(realm, realm.intrinsics.ObjectPrototype);
Expand All @@ -124,9 +153,9 @@ function optimizeReactComponentTreeBranches(
realm: Realm,
reconciler: Reconciler,
writeEffects: WriteEffects,
environmentRecordIdAfterGlobalCode: number,
logger: Logger,
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue | BoundFunctionValue, ReactEvaluatedNode>
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue | BoundFunctionValue, ReactEvaluatedNode>,
preEvaluationComponentToWriteEffectFunction: Map<FunctionValue, FunctionValue>
): void {
if (realm.react.verbose && reconciler.branchedComponentTrees.length > 0) {
logger.logInformation(` Evaluating React component tree branches...`);
Expand All @@ -147,19 +176,26 @@ function optimizeReactComponentTreeBranches(
if (realm.react.verbose) {
logger.logInformation(` Evaluating ${evaluatedNode.name} (branch)`);
}
let branchEffects = reconciler.resolveReactComponentTree(branchComponentType, null, null, evaluatedNode);
let parentOptimizedFunction = realm.currentOptimizedFunction;
let branchEffects = realm.withNewOptimizedFunction(
() => reconciler.resolveReactComponentTree(branchComponentType, null, null, evaluatedNode),
branchComponentType
);

if (realm.react.verbose) {
logger.logInformation(` ✔ ${evaluatedNode.name} (branch)`);
}
let branchComponentTreeState = reconciler.componentTreeState;

applyWriteEffectsForOptimizedComponent(
realm,
branchComponentType,
branchEffects,
branchComponentTreeState,
evaluatedNode,
writeEffects,
environmentRecordIdAfterGlobalCode
preEvaluationComponentToWriteEffectFunction,
parentOptimizedFunction
);
}
}
Expand All @@ -169,10 +205,10 @@ export function optimizeReactComponentTreeRoot(
componentRoot: ECMAScriptSourceFunctionValue | BoundFunctionValue | AbstractValue,
config: ReactComponentTreeConfig,
writeEffects: WriteEffects,
environmentRecordIdAfterGlobalCode: number,
logger: Logger,
statistics: ReactStatistics,
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue | BoundFunctionValue, ReactEvaluatedNode>
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue | BoundFunctionValue, ReactEvaluatedNode>,
preEvaluationComponentToWriteEffectFunction: Map<FunctionValue, FunctionValue>
): void {
let reconciler = new Reconciler(realm, config, alreadyEvaluated, statistics, logger);
let componentType = getComponentTypeFromRootValue(realm, componentRoot);
Expand All @@ -188,7 +224,11 @@ export function optimizeReactComponentTreeRoot(
if (realm.react.verbose) {
logger.logInformation(` Evaluating ${evaluatedRootNode.name} (root)`);
}
let componentTreeEffects = reconciler.resolveReactComponentTree(componentType, null, null, evaluatedRootNode);
let parentOptimizedFunction = realm.currentOptimizedFunction;
let componentTreeEffects = realm.withNewOptimizedFunction(
() => reconciler.resolveReactComponentTree(componentType, null, null, evaluatedRootNode),
componentType
);
if (realm.react.verbose) {
logger.logInformation(` ✔ ${evaluatedRootNode.name} (root)`);
}
Expand All @@ -200,7 +240,8 @@ export function optimizeReactComponentTreeRoot(
reconciler.componentTreeState,
evaluatedRootNode,
writeEffects,
environmentRecordIdAfterGlobalCode
preEvaluationComponentToWriteEffectFunction,
parentOptimizedFunction
);
let startingComponentTreeBranches = 0;
do {
Expand All @@ -209,9 +250,9 @@ export function optimizeReactComponentTreeRoot(
realm,
reconciler,
writeEffects,
environmentRecordIdAfterGlobalCode,
logger,
alreadyEvaluated
alreadyEvaluated,
preEvaluationComponentToWriteEffectFunction
);
} while (startingComponentTreeBranches !== reconciler.branchedComponentTrees.length);
}
10 changes: 5 additions & 5 deletions src/react/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { TemporalObjectAssignEntry } from "../utils/generator.js";
import type { Descriptor, ReactComponentTreeConfig, ReactHint, PropertyBinding } from "../types.js";
import { Get, IsDataDescriptor } from "../methods/index.js";
import { computeBinary } from "../evaluators/BinaryExpression.js";
import type { AdditionalFunctionEffects, ReactEvaluatedNode } from "../serializer/types.js";
import type { AdditionalFunctionTransform, ReactEvaluatedNode } from "../serializer/types.js";
import invariant from "../invariant.js";
import { Create, Properties, To } from "../singletons.js";
import traverse from "@babel/traverse";
Expand Down Expand Up @@ -347,7 +347,7 @@ function GetDescriptorForProperty(value: ObjectValue, propertyName: string): ?De
export function convertSimpleClassComponentToFunctionalComponent(
realm: Realm,
complexComponentType: ECMAScriptSourceFunctionValue,
additionalFunctionEffects: AdditionalFunctionEffects
transforms: Array<AdditionalFunctionTransform>
): void {
let prototype = complexComponentType.properties.get("prototype");
invariant(prototype);
Expand All @@ -362,7 +362,7 @@ export function convertSimpleClassComponentToFunctionalComponent(
// give the function the functional components params
complexComponentType.$FormalParameters = [t.identifier("props"), t.identifier("context")];
// add a transform to occur after the additional function has serialized the body of the class
additionalFunctionEffects.transforms.push((body: Array<BabelNodeStatement>) => {
transforms.push((body: Array<BabelNodeStatement>) => {
// as this was a class before and is now a functional component, we need to replace
// this.props and this.context to props and context, via the function arugments
let funcNode = t.functionExpression(null, [], t.blockStatement(body));
Expand Down Expand Up @@ -493,7 +493,7 @@ export function convertFunctionalComponentToComplexClassComponent(
realm: Realm,
functionalComponentType: ECMAScriptSourceFunctionValue | BoundFunctionValue,
complexComponentType: void | ECMAScriptSourceFunctionValue | BoundFunctionValue,
additionalFunctionEffects: AdditionalFunctionEffects
transforms: Array<AdditionalFunctionTransform>
): void {
invariant(
complexComponentType instanceof ECMAScriptSourceFunctionValue || complexComponentType instanceof BoundFunctionValue
Expand Down Expand Up @@ -527,7 +527,7 @@ export function convertFunctionalComponentToComplexClassComponent(
functionalComponentType.symbols.set(symbol, binding);
}
// add a transform to occur after the additional function has serialized the body of the class
additionalFunctionEffects.transforms.push((body: Array<BabelNodeStatement>) => {
transforms.push((body: Array<BabelNodeStatement>) => {
// as we've converted a functional component to a complex one, we are going to have issues with
// "props" and "context" references, as they're now going to be "this.props" and "this.context".
// we simply need a to add to vars to beginning of the body to get around this
Expand Down
13 changes: 13 additions & 0 deletions src/realm.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ export class Realm {

optimizedFunctions: Map<FunctionValue | AbstractValue, ArgModel | void>;
arrayNestedOptimizedFunctionsEnabled: boolean;
currentOptimizedFunction: FunctionValue | void;

eagerlyRequireModuleDependencies: void | boolean;

Expand Down Expand Up @@ -822,6 +823,18 @@ export class Realm {
return result;
}

withNewOptimizedFunction<T>(func: () => T, optimizedFunction: FunctionValue): T {
let result: T;
let previousOptimizedFunction = this.currentOptimizedFunction;
this.currentOptimizedFunction = optimizedFunction;
try {
result = func();
} finally {
this.currentOptimizedFunction = previousOptimizedFunction;
}
return result;
}

evaluateNodeForEffectsInGlobalEnv(node: BabelNode, state?: any, generatorName?: string): Effects {
return this.wrapInGlobalEnv(() => this.evaluateNodeForEffects(node, false, this.$GlobalEnv, state, generatorName));
}
Expand Down
Loading

0 comments on commit 473470a

Please sign in to comment.