Fix referentialization of optimized functions (#2544)

Summary:
Release Notes: None

Before, during referentialization, we would always default to the global scope if the value was accessed in more than one optimized function scope. Now we look for the outermost optimized function.

This logic already existed in ResidualHeapSerializer, so I refactored it out into `serializer/utils.js`.

Fixes #2428
Pull Request resolved: https://github.com/facebook/prepack/pull/2544

Differential Revision: D9926077

Pulled By: cblappert

fbshipit-source-id: c4ee6c07c7409534e9be14df25b582078a7ec77c
This commit is contained in:
Chris Blappert 2018-09-19 17:40:12 -07:00 committed by Facebook Github Bot
parent 48802fc106
commit 571dc330bb
12 changed files with 234 additions and 146 deletions

View File

@ -98,7 +98,7 @@ function checkResidualFunctions(modules: Modules, startFunc: number, totalToAnal
else return "Recover";
};
modules.resolveInitializedModules();
let residualHeapVisitor = new ResidualHeapVisitor(realm, modules.logger, modules, new Map(), "NO_REFERENTIALIZE");
let residualHeapVisitor = new ResidualHeapVisitor(realm, modules.logger, modules, new Map());
residualHeapVisitor.visitRoots();
if (modules.logger.hasErrors()) return;
let totalFunctions = 0;

View File

@ -22,6 +22,7 @@ import { HeapInspector } from "../utils/HeapInspector.js";
import { ResidualHeapValueIdentifiers } from "./ResidualHeapValueIdentifiers.js";
import { ResidualHeapSerializer } from "./ResidualHeapSerializer.js";
import { getOrDefault } from "./utils.js";
import type { ResidualOptimizedFunctions } from "./ResidualOptimizedFunctions";
import type { Referentializer } from "./Referentializer.js";
import { GeneratorDAG } from "./GeneratorDAG.js";
@ -46,9 +47,10 @@ export class LazyObjectsSerializer extends ResidualHeapSerializer {
residualHeapInspector: HeapInspector,
residualHeapInfo: ResidualHeapInfo,
options: SerializerOptions,
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects> | void,
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects>,
referentializer: Referentializer,
generatorDAG: GeneratorDAG
generatorDAG: GeneratorDAG,
residualOptimizedFunctions: ResidualOptimizedFunctions
) {
super(
realm,
@ -60,7 +62,8 @@ export class LazyObjectsSerializer extends ResidualHeapSerializer {
options,
additionalFunctionValuesAndEffects,
referentializer,
generatorDAG
generatorDAG,
residualOptimizedFunctions
);
this._lazyObjectIdSeed = 1;

View File

@ -17,10 +17,11 @@ import type { BabelNodeStatement, BabelNodeExpression, BabelNodeIdentifier } fro
import { NameGenerator } from "../utils/NameGenerator";
import invariant from "../invariant.js";
import type { ResidualFunctionBinding, ScopeBinding, FunctionInstance } from "./types.js";
import { type ReferentializationScope } from "./types.js";
import type { ReferentializationScope, Scope } from "./types.js";
import { SerializerStatistics } from "./statistics.js";
import { getOrDefault } from "./utils.js";
import { Realm } from "../realm.js";
import type { ResidualOptimizedFunctions } from "./ResidualOptimizedFunctions";
type ReferentializationState = {|
capturedScopeInstanceIdx: number,
@ -42,7 +43,8 @@ export class Referentializer {
options: SerializerOptions,
scopeNameGenerator: NameGenerator,
scopeBindingNameGenerator: NameGenerator,
leakedNameGenerator: NameGenerator
leakedNameGenerator: NameGenerator,
residualOptimizedFunctions: ResidualOptimizedFunctions
) {
this._options = options;
this.scopeNameGenerator = scopeNameGenerator;
@ -51,6 +53,8 @@ export class Referentializer {
this.referentializationState = new Map();
this._leakedNameGenerator = leakedNameGenerator;
this.realm = realm;
this._residualOptimizedFunctions = residualOptimizedFunctions;
}
_options: SerializerOptions;
@ -61,6 +65,7 @@ export class Referentializer {
_newCapturedScopeInstanceIdx: number;
referentializationState: Map<ReferentializationScope, ReferentializationState>;
_leakedNameGenerator: NameGenerator;
_residualOptimizedFunctions: ResidualOptimizedFunctions;
getStatistics(): SerializerStatistics {
invariant(this.realm.statistics instanceof SerializerStatistics, "serialization requires SerializerStatistics");
@ -161,8 +166,11 @@ export class Referentializer {
_getReferentializationScope(residualBinding: ResidualFunctionBinding): ReferentializationScope {
if (residualBinding.potentialReferentializationScopes.has("GLOBAL")) return "GLOBAL";
if (residualBinding.potentialReferentializationScopes.size > 1) {
// TODO #2428: Revisit for nested optimized functions.
return "GLOBAL";
// Here we know potentialReferentializationScopes cannot contain "GLOBAL"; Set<FunctionValue> is
// compatible with Set<FunctionValue | Generator>
let scopes = ((residualBinding.potentialReferentializationScopes: any): Set<Scope>);
let parentOptimizedFunction = this._residualOptimizedFunctions.tryGetOutermostOptimizedFunction(scopes);
return parentOptimizedFunction || "GLOBAL";
}
for (let scope of residualBinding.potentialReferentializationScopes) return scope;
invariant(false);

View File

@ -11,7 +11,6 @@
import type { Logger } from "../utils/logger.js";
import type { Modules } from "../utils/modules.js";
import type { Referentializer } from "./Referentializer.js";
import type { Realm } from "../realm.js";
import type { ObjectRefCount, AdditionalFunctionEffects } from "./types.js";
import type { ResidualHeapValueIdentifiers } from "./ResidualHeapValueIdentifiers";
@ -44,10 +43,9 @@ export class ResidualHeapGraphGenerator extends ResidualHeapVisitor {
modules: Modules,
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects>,
valueIdentifiers: ResidualHeapValueIdentifiers,
valueToEdgeRecord: Map<Value, ObjectRefCount>,
referentializer: Referentializer
valueToEdgeRecord: Map<Value, ObjectRefCount>
) {
super(realm, logger, modules, additionalFunctionValuesAndEffects, referentializer);
super(realm, logger, modules, additionalFunctionValuesAndEffects);
this._valueToEdgeRecord = valueToEdgeRecord;
this._valueIdentifiers = valueIdentifiers;
this._visitedValues = new Set();

View File

@ -11,7 +11,6 @@
import type { Logger } from "../utils/logger.js";
import type { Modules } from "../utils/modules.js";
import type { Referentializer } from "./Referentializer.js";
import type { Realm } from "../realm.js";
import type { ObjectRefCount, AdditionalFunctionEffects } from "./types.js";
@ -28,10 +27,9 @@ export class ResidualHeapRefCounter extends ResidualHeapVisitor {
realm: Realm,
logger: Logger,
modules: Modules,
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects>,
referentializer: Referentializer
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects>
) {
super(realm, logger, modules, additionalFunctionValuesAndEffects, referentializer);
super(realm, logger, modules, additionalFunctionValuesAndEffects);
this._valueToEdgeRecord = new Map();
this._path = [];
}

View File

@ -79,7 +79,7 @@ import { canHoistFunction } from "../react/hoisting.js";
import { To } from "../singletons.js";
import { ResidualReactElementSerializer } from "./ResidualReactElementSerializer.js";
import type { Binding } from "../environment.js";
import { GlobalEnvironmentRecord, DeclarativeEnvironmentRecord, FunctionEnvironmentRecord } from "../environment.js";
import { GlobalEnvironmentRecord, DeclarativeEnvironmentRecord } from "../environment.js";
import type { Referentializer } from "./Referentializer.js";
import { GeneratorDAG } from "./GeneratorDAG.js";
import { type Replacement, getReplacement } from "./ResidualFunctionInstantiator.js";
@ -87,6 +87,7 @@ import { describeValue } from "../utils.js";
import { getAsPropertyNameExpression } from "../utils/babelhelpers.js";
import { ResidualOperationSerializer } from "./ResidualOperationSerializer.js";
import { PropertyDescriptor, AbstractJoinedDescriptor } from "../descriptors.js";
import type { ResidualOptimizedFunctions } from "./ResidualOptimizedFunctions";
function commentStatement(text: string) {
let s = t.emptyStatement();
@ -120,15 +121,17 @@ export class ResidualHeapSerializer {
residualHeapInspector: HeapInspector,
residualHeapInfo: ResidualHeapInfo,
options: SerializerOptions,
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects> | void,
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects>,
referentializer: Referentializer,
generatorDAG: GeneratorDAG
generatorDAG: GeneratorDAG,
residualOptimizedFunctions: ResidualOptimizedFunctions
) {
this.realm = realm;
this.logger = logger;
this.modules = modules;
this.residualHeapValueIdentifiers = residualHeapValueIdentifiers;
this.referentializer = referentializer;
this._residualOptimizedFunctions = residualOptimizedFunctions;
let realmGenerator = this.realm.generator;
invariant(realmGenerator);
@ -152,7 +155,11 @@ export class ResidualHeapSerializer {
this.serializedValues = new Set();
this._serializedValueWithIdentifiers = new Set();
this.additionalFunctionValueNestedFunctions = new Set();
this.residualReactElementSerializer = new ResidualReactElementSerializer(this.realm, this);
this.residualReactElementSerializer = new ResidualReactElementSerializer(
this.realm,
this,
residualOptimizedFunctions
);
this.residualFunctions = new ResidualFunctions(
this.realm,
options,
@ -253,7 +260,7 @@ export class ResidualHeapSerializer {
_options: SerializerOptions;
referencedDeclaredValues: Map<Value, void | FunctionValue>;
activeGeneratorBodies: Map<Generator, SerializedBody>;
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects> | void;
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects>;
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>;
rewrittenAdditionalFunctions: Map<FunctionValue, Array<BabelNodeStatement>>;
declarativeEnvironmentRecordsBindings: Map<DeclarativeEnvironmentRecord, Map<string, ResidualFunctionBinding>>;
@ -261,6 +268,7 @@ export class ResidualHeapSerializer {
residualReactElementSerializer: ResidualReactElementSerializer;
referentializer: Referentializer;
additionalFunctionGenerators: Map<FunctionValue, Generator>;
_residualOptimizedFunctions: ResidualOptimizedFunctions;
// function values nested in additional functions can't delay initializations
// TODO: revisit this and fix additional functions to be capable of delaying initializations
@ -809,87 +817,6 @@ export class ResidualHeapSerializer {
return Array.from(result);
}
isDefinedInsideFunction(childFunction: FunctionValue, maybeParentFunctions: Set<FunctionValue>): boolean {
for (let maybeParentFunction of maybeParentFunctions) {
if (childFunction === maybeParentFunction) {
continue;
}
let additionalFVEffects = this.additionalFunctionValuesAndEffects;
if (additionalFVEffects) {
// for optimized functions, we should use created objects
let maybeParentFunctionInfo = additionalFVEffects.get(maybeParentFunction);
if (maybeParentFunctionInfo && maybeParentFunctionInfo.effects.createdObjects.has(childFunction)) return true;
} else {
// for other functions, check environment records
let env = childFunction.$Environment;
while (env.parent !== null) {
let envRecord = env.environmentRecord;
if (envRecord instanceof FunctionEnvironmentRecord && envRecord.$FunctionObject === maybeParentFunction)
return true;
env = env.parent;
}
}
}
return false;
}
// Check if an optimized function defines the given set of functions.
definesFunctions(possibleParentFunction: FunctionValue, functions: Set<FunctionValue>): boolean {
let additionalFVEffects = this.additionalFunctionValuesAndEffects;
invariant(additionalFVEffects);
let maybeParentFunctionInfo = additionalFVEffects.get(possibleParentFunction);
invariant(maybeParentFunctionInfo);
let createdObjects = maybeParentFunctionInfo.effects.createdObjects;
for (let func of functions) if (func !== possibleParentFunction && !createdObjects.has(func)) return false;
return true;
}
// Try and get the root optimized function when passed in an optimized function
// that may or may not be nested in the tree of said root, or is the root optimized function
tryGetOptimizedFunctionRoot(val: Value): void | FunctionValue {
let scopes = this.residualValues.get(val);
let functionValues = new Set();
invariant(scopes !== undefined);
for (let scope of scopes) {
let s = scope;
while (s instanceof Generator) {
s = this.generatorDAG.getParent(s);
}
if (s === "GLOBAL") return undefined;
invariant(s instanceof FunctionValue);
functionValues.add(s);
}
let outermostAdditionalFunctions = new Set();
// Get the set of optimized functions that may be the root
for (let functionValue of functionValues) {
if (this.additionalFunctionGenerators.has(functionValue)) {
if (!this.isDefinedInsideFunction(functionValue, functionValues))
outermostAdditionalFunctions.add(functionValue);
} else {
let f = this.tryGetOptimizedFunctionRoot(functionValue);
if (f === undefined) return undefined;
if (!this.isDefinedInsideFunction(f, functionValues)) outermostAdditionalFunctions.add(f);
}
}
if (outermostAdditionalFunctions.size === 1) return [...outermostAdditionalFunctions][0];
let additionalFVEffects = this.additionalFunctionValuesAndEffects;
invariant(additionalFVEffects);
// See if any of the outermost (or any of their parents) are the outermost optimized function
let possibleRoots = [...outermostAdditionalFunctions];
while (possibleRoots.length > 0) {
let possibleRoot = possibleRoots.shift();
if (this.definesFunctions(possibleRoot, outermostAdditionalFunctions)) return possibleRoot;
let additionalFunctionEffects = additionalFVEffects.get(possibleRoot);
invariant(additionalFunctionEffects);
let parent = additionalFunctionEffects.parentAdditionalFunction;
if (parent) possibleRoots.push(parent);
}
return undefined;
}
_getActiveBodyOfGenerator(generator: Generator): void | SerializedBody {
return generator === this.generator ? this.mainBody : this.activeGeneratorBodies.get(generator);
}
@ -927,7 +854,7 @@ export class ResidualHeapSerializer {
}
}
let optimizedFunctionRoot = this.tryGetOptimizedFunctionRoot(val);
let optimizedFunctionRoot = this._residualOptimizedFunctions.tryGetOptimizedFunctionRoot(val);
if (generators.length === 0) {
// This value is only referenced from residual functions.
if (
@ -1594,7 +1521,7 @@ export class ResidualHeapSerializer {
invariant(instance !== undefined);
let residualBindings = instance.residualFunctionBindings;
let inOptimizedFunction = this.tryGetOptimizedFunctionRoot(val);
let inOptimizedFunction = this._residualOptimizedFunctions.tryGetOptimizedFunctionRoot(val);
if (inOptimizedFunction !== undefined) instance.containingAdditionalFunction = inOptimizedFunction;
let bindingsEmittedSemaphore = new CountingSemaphore(() => {
invariant(instance);
@ -1635,8 +1562,7 @@ export class ResidualHeapSerializer {
}
bindingsEmittedSemaphore.releaseOne();
this._emitObjectProperties(val);
let additionalFVEffects = this.additionalFunctionValuesAndEffects;
let additionalEffects = additionalFVEffects && additionalFVEffects.get(val);
let additionalEffects = this.additionalFunctionValuesAndEffects.get(val);
if (additionalEffects) this._serializeAdditionalFunction(val, additionalEffects);
}
@ -2269,7 +2195,9 @@ export class ResidualHeapSerializer {
): Array<BabelNodeStatement> {
let newBody = { type, parentBody: undefined, entries: [], done: false, optimizedFunction };
let optimizedFunctionRoot =
optimizedFunction === undefined ? undefined : this.tryGetOptimizedFunctionRoot(optimizedFunction);
optimizedFunction === undefined
? undefined
: this._residualOptimizedFunctions.tryGetOptimizedFunctionRoot(optimizedFunction);
let isChild = !!optimizedFunctionRoot || type === "Generator";
let oldBody = this.emitter.beginEmitting(generator, newBody, /*isChild*/ isChild);
invariant(!this.activeGeneratorBodies.has(generator));
@ -2496,12 +2424,10 @@ export class ResidualHeapSerializer {
}
prepareAdditionalFunctionValues(): void {
let additionalFVEffects = this.additionalFunctionValuesAndEffects;
if (additionalFVEffects)
for (let [additionalFunctionValue, { generator }] of additionalFVEffects.entries()) {
invariant(!this.additionalFunctionGenerators.has(additionalFunctionValue));
this.additionalFunctionGenerators.set(additionalFunctionValue, generator);
}
for (let [additionalFunctionValue, { generator }] of this.additionalFunctionValuesAndEffects.entries()) {
invariant(!this.additionalFunctionGenerators.has(additionalFunctionValue));
this.additionalFunctionGenerators.set(additionalFunctionValue, generator);
}
}
// Hook point for any serialization needs to be done after generator serialization is complete.

View File

@ -54,7 +54,6 @@ import { ClosureRefVisitor } from "./visitors.js";
import { Logger } from "../utils/logger.js";
import { Modules } from "../utils/modules.js";
import { HeapInspector } from "../utils/HeapInspector.js";
import { Referentializer } from "./Referentializer.js";
import {
canIgnoreClassLengthProperty,
ClassPropertiesToIgnore,
@ -85,15 +84,12 @@ export class ResidualHeapVisitor {
realm: Realm,
logger: Logger,
modules: Modules,
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects>,
// Referentializer is null if we're just checking what values exist
referentializer: Referentializer | "NO_REFERENTIALIZE"
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects>
) {
invariant(realm.useAbstractInterpretation);
this.realm = realm;
this.logger = logger;
this.modules = modules;
this.referentializer = referentializer === "NO_REFERENTIALIZE" ? undefined : referentializer;
this.declarativeEnvironmentRecordsBindings = new Map();
this.globalBindings = new Map();
@ -123,7 +119,6 @@ export class ResidualHeapVisitor {
realm: Realm;
logger: Logger;
modules: Modules;
referentializer: Referentializer | void;
globalGenerator: Generator;
// Caches that ensure one ResidualFunctionBinding exists per (record, name) pair
@ -1304,10 +1299,6 @@ export class ResidualHeapVisitor {
for (let moduleValue of this.modules.initializedModules.values()) this.visitValue(moduleValue);
this._visitUntilFixpoint();
let referentializer = this.referentializer;
if (referentializer !== undefined)
for (let instance of this.functionInstances.values()) referentializer.referentialize(instance);
}
_visitUntilFixpoint(): void {

View File

@ -0,0 +1,117 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/* @flow */
import { FunctionValue } from "../values/index.js";
import type { AdditionalFunctionEffects } from "./types";
import invariant from "../invariant.js";
import { GeneratorDAG } from "./GeneratorDAG";
import type { Scope } from "./types.js";
import { FunctionEnvironmentRecord } from "../environment";
import type { Value } from "../values/index";
import { Generator } from "../utils/generator";
export class ResidualOptimizedFunctions {
constructor(
generatorDAG: GeneratorDAG,
optimizedFunctionsAndEffects: Map<FunctionValue, AdditionalFunctionEffects>,
residualValues: Map<Value, Set<Scope>>
) {
this._generatorDAG = generatorDAG;
this._optimizedFunctionsAndEffects = optimizedFunctionsAndEffects;
this._residualValues = residualValues;
}
_generatorDAG: GeneratorDAG;
_optimizedFunctionsAndEffects: Map<FunctionValue, AdditionalFunctionEffects>;
_residualValues: Map<Value, Set<Scope>>;
_isDefinedInsideFunction(childFunction: FunctionValue, maybeParentFunctions: Set<FunctionValue>): boolean {
for (let maybeParentFunction of maybeParentFunctions) {
if (childFunction === maybeParentFunction) {
continue;
}
// for optimized functions, we should use created objects
let maybeParentFunctionInfo = this._optimizedFunctionsAndEffects.get(maybeParentFunction);
if (maybeParentFunctionInfo && maybeParentFunctionInfo.effects.createdObjects.has(childFunction)) return true;
else {
// for other functions, check environment records
let env = childFunction.$Environment;
while (env.parent !== null) {
let envRecord = env.environmentRecord;
if (envRecord instanceof FunctionEnvironmentRecord && envRecord.$FunctionObject === maybeParentFunction)
return true;
env = env.parent;
}
}
}
return false;
}
// Check if an optimized function defines the given set of functions.
_definesFunctions(possibleParentFunction: FunctionValue, functions: Set<FunctionValue>): boolean {
let maybeParentFunctionInfo = this._optimizedFunctionsAndEffects.get(possibleParentFunction);
invariant(maybeParentFunctionInfo);
let createdObjects = maybeParentFunctionInfo.effects.createdObjects;
for (let func of functions) if (func !== possibleParentFunction && !createdObjects.has(func)) return false;
return true;
}
// Try and get the root optimized function when passed in an optimized function
// that may or may not be nested in the tree of said root, or is the root optimized function
tryGetOptimizedFunctionRoot(val: Value): void | FunctionValue {
let scopes = this._residualValues.get(val);
invariant(scopes !== undefined);
return this.tryGetOutermostOptimizedFunction(scopes);
}
// Try and get the optimized function that contains all the scopes passed in (may be one of the
// scopes passed in)
tryGetOutermostOptimizedFunction(scopes: Set<Scope>): void | FunctionValue {
let functionValues = new Set();
invariant(scopes !== undefined);
for (let scope of scopes) {
let s = scope;
while (s instanceof Generator) {
s = this._generatorDAG.getParent(s);
}
if (s === "GLOBAL") return undefined;
invariant(s instanceof FunctionValue);
functionValues.add(s);
}
let outermostAdditionalFunctions = new Set();
// Get the set of optimized functions that may be the root
for (let functionValue of functionValues) {
if (this._optimizedFunctionsAndEffects.has(functionValue)) {
if (!this._isDefinedInsideFunction(functionValue, functionValues))
outermostAdditionalFunctions.add(functionValue);
} else {
let f = this.tryGetOptimizedFunctionRoot(functionValue);
if (f === undefined) return undefined;
if (!this._isDefinedInsideFunction(f, functionValues)) outermostAdditionalFunctions.add(f);
}
}
if (outermostAdditionalFunctions.size === 1) return [...outermostAdditionalFunctions][0];
// See if any of the outermost (or any of their parents) are the outermost optimized function
let possibleRoots = [...outermostAdditionalFunctions];
while (possibleRoots.length > 0) {
let possibleRoot = possibleRoots.shift();
if (this._definesFunctions(possibleRoot, outermostAdditionalFunctions)) return possibleRoot;
let additionalFunctionEffects = this._optimizedFunctionsAndEffects.get(possibleRoot);
invariant(additionalFunctionEffects);
let parent = additionalFunctionEffects.parentAdditionalFunction;
if (parent) possibleRoots.push(parent);
}
return undefined;
}
}

View File

@ -23,6 +23,7 @@ import { traverseReactElement } from "../react/elements.js";
import { canExcludeReactElementObjectProperty, getReactSymbol, getProperty } from "../react/utils.js";
import type { ReactOutputTypes } from "../options.js";
import type { LazilyHoistedNodes } from "./types.js";
import type { ResidualOptimizedFunctions } from "./ResidualOptimizedFunctions";
type ReactElementAttributeType = "SPREAD" | "PROPERTY" | "PENDING";
type ReactElementChildType = "NORMAL" | "PENDING";
@ -47,12 +48,17 @@ type ReactElement = {
};
export class ResidualReactElementSerializer {
constructor(realm: Realm, residualHeapSerializer: ResidualHeapSerializer) {
constructor(
realm: Realm,
residualHeapSerializer: ResidualHeapSerializer,
residualOptimizedFunctions: ResidualOptimizedFunctions
) {
this.realm = realm;
this.residualHeapSerializer = residualHeapSerializer;
this.logger = residualHeapSerializer.logger;
this.reactOutput = realm.react.output || "create-element";
this._lazilyHoistedNodes = new Map();
this._residualOptimizedFunctions = residualOptimizedFunctions;
}
realm: Realm;
@ -60,6 +66,7 @@ export class ResidualReactElementSerializer {
reactOutput: ReactOutputTypes;
residualHeapSerializer: ResidualHeapSerializer;
_lazilyHoistedNodes: Map<FunctionValue, LazilyHoistedNodes>;
_residualOptimizedFunctions: ResidualOptimizedFunctions;
_createReactElement(value: ObjectValue): ReactElement {
return { attributes: [], children: [], declared: false, type: undefined, value };
@ -82,7 +89,7 @@ export class ResidualReactElementSerializer {
): void {
// if the currentHoistedReactElements is not defined, we create it an emit the function call
// this should only occur once per additional function
const optimizedFunction = this.residualHeapSerializer.tryGetOptimizedFunctionRoot(reactElement);
const optimizedFunction = this._residualOptimizedFunctions.tryGetOptimizedFunctionRoot(reactElement);
invariant(optimizedFunction);
let lazilyHoistedNodes = this._lazilyHoistedNodes.get(optimizedFunction);
if (lazilyHoistedNodes === undefined) {
@ -130,7 +137,7 @@ export class ResidualReactElementSerializer {
let propsValue = getProperty(this.realm, value, "props");
let shouldHoist =
this.residualHeapSerializer.tryGetOptimizedFunctionRoot(value) !== undefined &&
this._residualOptimizedFunctions.tryGetOptimizedFunctionRoot(value) !== undefined &&
canHoistReactElement(this.realm, value);
let id = this.residualHeapSerializer.getSerializeObjectIdentifier(value);
@ -156,7 +163,7 @@ export class ResidualReactElementSerializer {
originalCreateElementIdentifier = this.residualHeapSerializer.serializeValue(createElement);
if (shouldHoist) {
const optimizedFunction = this.residualHeapSerializer.tryGetOptimizedFunctionRoot(value);
const optimizedFunction = this._residualOptimizedFunctions.tryGetOptimizedFunctionRoot(value);
invariant(optimizedFunction);
const lazilyHoistedNodes = this._lazilyHoistedNodes.get(optimizedFunction);
// if we haven't created a lazilyHoistedNodes before, then this is the first time

View File

@ -39,6 +39,7 @@ import { Get } from "../methods/index.js";
import { ObjectValue, Value, FunctionValue } from "../values/index.js";
import { Properties } from "../singletons.js";
import { PropertyDescriptor } from "../descriptors.js";
import { ResidualOptimizedFunctions } from "./ResidualOptimizedFunctions";
export class Serializer {
constructor(realm: Realm, serializerOptions: SerializerOptions = {}) {
@ -189,13 +190,6 @@ export class Serializer {
// Deep traversal of the heap to identify the necessary scope of residual functions
let preludeGenerator = this.realm.preludeGenerator;
invariant(preludeGenerator !== undefined);
let referentializer = new Referentializer(
this.realm,
this.options,
preludeGenerator.createNameGenerator("__scope_"),
preludeGenerator.createNameGenerator("__get_scope_binding_"),
preludeGenerator.createNameGenerator("__leaked_")
);
if (this.realm.react.verbose) {
this.logger.logInformation(`Visiting evaluated nodes...`);
}
@ -204,14 +198,30 @@ export class Serializer {
this.realm,
this.logger,
this.modules,
additionalFunctionValuesAndEffects,
referentializer
additionalFunctionValuesAndEffects
);
statistics.deepTraversal.measure(() => residualHeapVisitor.visitRoots());
return [residualHeapVisitor.toInfo(), residualHeapVisitor.generatorDAG, residualHeapVisitor.inspector];
})();
if (this.logger.hasErrors()) return undefined;
let residualOptimizedFunctions = new ResidualOptimizedFunctions(
generatorDAG,
additionalFunctionValuesAndEffects,
residualHeapInfo.values
);
let referentializer = new Referentializer(
this.realm,
this.options,
preludeGenerator.createNameGenerator("__scope_"),
preludeGenerator.createNameGenerator("__get_scope_binding_"),
preludeGenerator.createNameGenerator("__leaked_"),
residualOptimizedFunctions
);
statistics.referentialization.measure(() => {
for (let instance of residualHeapInfo.functionInstances.values()) referentializer.referentialize(instance);
});
if (this.realm.react.verbose) {
this.logger.logInformation(`Serializing evaluated nodes...`);
}
@ -227,8 +237,7 @@ export class Serializer {
this.realm,
this.logger,
this.modules,
additionalFunctionValuesAndEffects,
referentializer
additionalFunctionValuesAndEffects
);
heapRefCounter.visitRoots();
@ -238,8 +247,7 @@ export class Serializer {
this.modules,
additionalFunctionValuesAndEffects,
residualHeapValueIdentifiers,
heapRefCounter.getResult(),
referentializer
heapRefCounter.getResult()
);
heapGraphGenerator.visitRoots();
invariant(this.options.heapGraphFormat);
@ -262,7 +270,8 @@ export class Serializer {
this.options,
additionalFunctionValuesAndEffects,
referentializer,
generatorDAG
generatorDAG,
residualOptimizedFunctions
).serialize();
});
if (this.logger.hasErrors()) return undefined;
@ -284,7 +293,8 @@ export class Serializer {
this.options,
additionalFunctionValuesAndEffects,
referentializer,
generatorDAG
generatorDAG,
residualOptimizedFunctions
).serialize()
);
})();

View File

@ -31,6 +31,7 @@ export class SerializerStatistics extends RealmStatistics {
this.checkThatFunctionsAreIndependent = new PerformanceTracker(getTime, getMemory);
this.processCollectedNestedOptimizedFunctions = new PerformanceTracker(getTime, getMemory);
this.deepTraversal = new PerformanceTracker(getTime, getMemory);
this.referentialization = new PerformanceTracker(getTime, getMemory);
this.referenceCounts = new PerformanceTracker(getTime, getMemory);
this.serializePass = new PerformanceTracker(getTime, getMemory);
this.babelGenerate = new PerformanceTracker(getTime, getMemory);
@ -99,6 +100,7 @@ export class SerializerStatistics extends RealmStatistics {
processCollectedNestedOptimizedFunctions: PerformanceTracker;
deepTraversal: PerformanceTracker;
referenceCounts: PerformanceTracker;
referentialization: PerformanceTracker;
serializePass: PerformanceTracker;
babelGenerate: PerformanceTracker;
dumpIR: PerformanceTracker;
@ -142,10 +144,10 @@ export class SerializerStatistics extends RealmStatistics {
);
console.log(
`${format(this.deepTraversal)} visiting residual heap, ${format(
this.referenceCounts
)} reference counting, ${format(this.serializePass)} generating AST, ${format(
this.babelGenerate
)} generating source code`
this.referentialization
)} referentializing functions, ${format(this.referenceCounts)} reference counting, ${format(
this.serializePass
)} generating AST, ${format(this.babelGenerate)} generating source code`
);
}
}

View File

@ -0,0 +1,28 @@
(function() {
function mkLocation() {
var x;
function mkSetter() {
function setter(y) {
x = y;
}
return setter;
}
function mkGetter() {
function getter() {
return x;
}
return getter;
}
if (global.__optimize) __optimize(mkSetter);
if (global.__optimize) __optimize(mkGetter);
return { mkSetter, mkGetter };
}
if (global.__optimize) __optimize(mkLocation);
global.inspect = function() {
let l1 = mkLocation();
let l2 = mkLocation();
l1.mkSetter()(42);
l2.mkSetter()(23);
return l1.mkGetter()();
};
})();