mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-11 14:46:37 +03:00
Change ResidualFunctions
Summary: This PR makes each additional function have its own CaptureScopeAccessFunction and contains some small refactors to make the code cleaner. The reason for each additional function having its own CaptureAccessFunction is so that we can emit the access function (and all of its initializations) into the additional function instead of the global prelude). Small refactor: - It separates out additionalFunction's processing from that of normal functions Closes https://github.com/facebook/prepack/pull/1050 Differential Revision: D6034027 Pulled By: cblappert fbshipit-source-id: f403dcfb3cc8cf742970b95fde3e9750ae62b8b0
This commit is contained in:
parent
c9b03cc454
commit
3d2b586bc4
215
src/serializer/Referentializer.js
Normal file
215
src/serializer/Referentializer.js
Normal file
@ -0,0 +1,215 @@
|
||||
/**
|
||||
* 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 { DeclarativeEnvironmentRecord } from "../environment.js";
|
||||
import { FatalError } from "../errors.js";
|
||||
import { FunctionValue } from "../values/index.js";
|
||||
import * as t from "babel-types";
|
||||
import type { BabelNodeStatement, BabelNodeIdentifier } from "babel-types";
|
||||
import { NameGenerator } from "../utils/generator.js";
|
||||
import invariant from "../invariant.js";
|
||||
import type { ResidualFunctionBinding, ScopeBinding, FunctionInstance } from "./types.js";
|
||||
import { SerializerStatistics } from "./types.js";
|
||||
import { getOrDefault } from "./utils.js";
|
||||
|
||||
// Each of these will correspond to a different preludeGenerator and thus will
|
||||
// have different values available for initialization. FunctionValues should
|
||||
// only be additional functions.
|
||||
export type ReferentializationScope = FunctionValue | "GLOBAL";
|
||||
|
||||
type ReferentializationState = {|
|
||||
capturedScopeInstanceIdx: number,
|
||||
capturedScopesArray: BabelNodeIdentifier,
|
||||
capturedScopeAccessFunctionId: BabelNodeIdentifier,
|
||||
serializedScopes: Map<DeclarativeEnvironmentRecord, ScopeBinding>,
|
||||
|};
|
||||
|
||||
/*
|
||||
* This class helps fixup names in residual functions for variables that these
|
||||
* functions capture from parent scopes.
|
||||
* For each ReferentializationScope it creates a _get_scope_binding function
|
||||
* that contains the initialization for all of that scope's FunctionInstances
|
||||
* which will contain a switch statement with all the initializations.
|
||||
*/
|
||||
export class Referentializer {
|
||||
constructor(scopeNameGenerator: NameGenerator, statistics: SerializerStatistics) {
|
||||
this.scopeNameGenerator = scopeNameGenerator;
|
||||
this.statistics = statistics;
|
||||
|
||||
this.referentializationState = new Map();
|
||||
}
|
||||
|
||||
scopeNameGenerator: NameGenerator;
|
||||
statistics: SerializerStatistics;
|
||||
|
||||
_newCapturedScopeInstanceIdx: number;
|
||||
referentializationState: Map<ReferentializationScope, ReferentializationState>;
|
||||
|
||||
_createReferentializationState(): ReferentializationState {
|
||||
return {
|
||||
capturedScopeInstanceIdx: 0,
|
||||
capturedScopesArray: t.identifier(this.scopeNameGenerator.generate("main")),
|
||||
capturedScopeAccessFunctionId: t.identifier(this.scopeNameGenerator.generate("get_scope_binding")),
|
||||
serializedScopes: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
_getReferentializationState(referentializationScope: ReferentializationScope): ReferentializationState {
|
||||
return getOrDefault(
|
||||
this.referentializationState,
|
||||
referentializationScope,
|
||||
this._createReferentializationState.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
// Generate a shared function for accessing captured scope bindings.
|
||||
// TODO: skip generating this function if the captured scope is not shared by multiple residual funcitons.
|
||||
createCaptureScopeAccessFunction(referentializationScope: ReferentializationScope): BabelNodeStatement {
|
||||
const body = [];
|
||||
const selectorParam = t.identifier("selector");
|
||||
const captured = t.identifier("__captured");
|
||||
const capturedScopesArray = this._getReferentializationState(referentializationScope).capturedScopesArray;
|
||||
const selectorExpression = t.memberExpression(capturedScopesArray, selectorParam, /*Indexer syntax*/ true);
|
||||
|
||||
// One switch case for one scope.
|
||||
const cases = [];
|
||||
const serializedScopes = this._getReferentializationState(referentializationScope).serializedScopes;
|
||||
for (const scopeBinding of serializedScopes.values()) {
|
||||
const scopeObjectExpression = t.arrayExpression((scopeBinding.initializationValues: any));
|
||||
cases.push(
|
||||
t.switchCase(t.numericLiteral(scopeBinding.id), [
|
||||
t.expressionStatement(t.assignmentExpression("=", selectorExpression, scopeObjectExpression)),
|
||||
t.breakStatement(),
|
||||
])
|
||||
);
|
||||
}
|
||||
// Default case.
|
||||
cases.push(
|
||||
t.switchCase(null, [
|
||||
t.throwStatement(t.newExpression(t.identifier("Error"), [t.stringLiteral("Unknown scope selector")])),
|
||||
])
|
||||
);
|
||||
|
||||
body.push(t.variableDeclaration("var", [t.variableDeclarator(captured, selectorExpression)]));
|
||||
body.push(
|
||||
t.ifStatement(
|
||||
t.unaryExpression("!", captured),
|
||||
t.blockStatement([
|
||||
t.switchStatement(selectorParam, cases),
|
||||
t.expressionStatement(t.assignmentExpression("=", captured, selectorExpression)),
|
||||
])
|
||||
)
|
||||
);
|
||||
body.push(t.returnStatement(captured));
|
||||
const factoryFunction = t.functionExpression(null, [selectorParam], t.blockStatement(body));
|
||||
const accessFunctionId = this._getReferentializationState(referentializationScope).capturedScopeAccessFunctionId;
|
||||
return t.variableDeclaration("var", [t.variableDeclarator(accessFunctionId, factoryFunction)]);
|
||||
}
|
||||
|
||||
_getSerializedBindingScopeInstance(residualBinding: ResidualFunctionBinding): ScopeBinding {
|
||||
let declarativeEnvironmentRecord = residualBinding.declarativeEnvironmentRecord;
|
||||
let referentializationScope = residualBinding.referencedOnlyFromAdditionalFunctions || "GLOBAL";
|
||||
invariant(declarativeEnvironmentRecord);
|
||||
|
||||
// figure out if this is accessed only from additional functions
|
||||
let serializedScopes = this._getReferentializationState(referentializationScope).serializedScopes;
|
||||
let scope = serializedScopes.get(declarativeEnvironmentRecord);
|
||||
if (!scope) {
|
||||
let refState: ReferentializationState = this._getReferentializationState(referentializationScope);
|
||||
scope = {
|
||||
name: this.scopeNameGenerator.generate(),
|
||||
id: refState.capturedScopeInstanceIdx++,
|
||||
initializationValues: [],
|
||||
containingAdditionalFunction: residualBinding.referencedOnlyFromAdditionalFunctions,
|
||||
};
|
||||
serializedScopes.set(declarativeEnvironmentRecord, scope);
|
||||
}
|
||||
|
||||
residualBinding.scope = scope;
|
||||
return scope;
|
||||
}
|
||||
|
||||
getReferentializedScopeInitialization(scope: ScopeBinding) {
|
||||
let capturedScope = scope.capturedScope;
|
||||
invariant(capturedScope);
|
||||
const funcName = this._getReferentializationState(scope.containingAdditionalFunction || "GLOBAL")
|
||||
.capturedScopeAccessFunctionId;
|
||||
return [
|
||||
t.variableDeclaration("var", [
|
||||
t.variableDeclarator(t.identifier(capturedScope), t.callExpression(funcName, [t.identifier(scope.name)])),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
referentialize(
|
||||
unbound: Set<string>,
|
||||
instances: Array<FunctionInstance>,
|
||||
shouldReferentializeInstanceFn: FunctionInstance => boolean
|
||||
): void {
|
||||
for (let instance of instances) {
|
||||
let residualBindings = instance.residualFunctionBindings;
|
||||
|
||||
for (let name of unbound) {
|
||||
let residualBinding = residualBindings.get(name);
|
||||
invariant(residualBinding !== undefined);
|
||||
if (residualBinding.modified) {
|
||||
// Initialize captured scope at function call instead of globally
|
||||
if (!residualBinding.referentialized) {
|
||||
if (!shouldReferentializeInstanceFn(instance)) {
|
||||
// TODO #989: Fix additional functions and referentialization
|
||||
throw new FatalError("TODO: implement referentialization for prepacked functions");
|
||||
}
|
||||
let scope = this._getSerializedBindingScopeInstance(residualBinding);
|
||||
let capturedScope = "__captured" + scope.name;
|
||||
// Save the serialized value for initialization at the top of
|
||||
// the factory.
|
||||
// This can serialize more variables than are necessary to execute
|
||||
// the function because every function serializes every
|
||||
// modified variable of its parent scope. In some cases it could be
|
||||
// an improvement to split these variables into multiple
|
||||
// scopes.
|
||||
const variableIndexInScope = scope.initializationValues.length;
|
||||
invariant(residualBinding.serializedValue);
|
||||
scope.initializationValues.push(residualBinding.serializedValue);
|
||||
scope.capturedScope = capturedScope;
|
||||
|
||||
// Replace binding usage with scope references
|
||||
residualBinding.serializedValue = t.memberExpression(
|
||||
t.identifier(capturedScope),
|
||||
t.numericLiteral(variableIndexInScope),
|
||||
true // Array style access.
|
||||
);
|
||||
|
||||
residualBinding.referentialized = true;
|
||||
this.statistics.referentialized++;
|
||||
}
|
||||
|
||||
// Already referentialized in prior scope
|
||||
if (residualBinding.declarativeEnvironmentRecord) {
|
||||
invariant(residualBinding.scope);
|
||||
instance.scopeInstances.add(residualBinding.scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createCapturedScopesArrayInitialization(referentializationScope: ReferentializationScope): BabelNodeStatement {
|
||||
return t.variableDeclaration("var", [
|
||||
t.variableDeclarator(
|
||||
this._getReferentializationState(referentializationScope).capturedScopesArray,
|
||||
t.callExpression(t.identifier("Array"), [
|
||||
t.numericLiteral(this._getReferentializationState(referentializationScope).capturedScopeInstanceIdx),
|
||||
])
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
@ -9,10 +9,9 @@
|
||||
|
||||
/* @flow */
|
||||
|
||||
import { DeclarativeEnvironmentRecord } from "../environment.js";
|
||||
import { FatalError } from "../errors.js";
|
||||
import { Realm } from "../realm.js";
|
||||
import { FunctionValue } from "../values/index.js";
|
||||
import { FunctionValue, type ECMAScriptSourceFunctionValue } from "../values/index.js";
|
||||
import * as t from "babel-types";
|
||||
import type {
|
||||
BabelNodeExpression,
|
||||
@ -26,13 +25,15 @@ import type {
|
||||
import { NameGenerator } from "../utils/generator.js";
|
||||
import traverse from "babel-traverse";
|
||||
import invariant from "../invariant.js";
|
||||
import type { ResidualFunctionBinding, ScopeBinding, FunctionInfo, FunctionInstance } from "./types.js";
|
||||
import type { FunctionInfo, FunctionInstance, AdditionalFunctionInfo } from "./types.js";
|
||||
import { BodyReference, AreSameResidualBinding, SerializerStatistics } from "./types.js";
|
||||
import { ClosureRefReplacer } from "./visitors.js";
|
||||
import { Modules } from "./modules.js";
|
||||
import { ResidualFunctionInitializers } from "./ResidualFunctionInitializers.js";
|
||||
import { nullExpression } from "../utils/internalizer.js";
|
||||
import type { LocationService } from "./types.js";
|
||||
import { Referentializer } from "./Referentializer.js";
|
||||
import { getOrDefault } from "./utils.js";
|
||||
|
||||
type ResidualFunctionsResult = {
|
||||
unstrictFunctionBodies: Array<BabelNodeFunctionExpression>,
|
||||
@ -52,7 +53,9 @@ export class ResidualFunctions {
|
||||
factoryNameGenerator: NameGenerator,
|
||||
scopeNameGenerator: NameGenerator,
|
||||
residualFunctionInfos: Map<BabelNodeBlockStatement, FunctionInfo>,
|
||||
residualFunctionInstances: Map<FunctionValue, FunctionInstance>
|
||||
residualFunctionInstances: Map<FunctionValue, FunctionInstance>,
|
||||
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>,
|
||||
additionalFunctionValueNestedFunctions: Set<FunctionValue>
|
||||
) {
|
||||
this.realm = realm;
|
||||
this.statistics = statistics;
|
||||
@ -61,11 +64,6 @@ export class ResidualFunctions {
|
||||
this.locationService = locationService;
|
||||
this.prelude = prelude;
|
||||
this.factoryNameGenerator = factoryNameGenerator;
|
||||
this.scopeNameGenerator = scopeNameGenerator;
|
||||
this.capturedScopeInstanceIdx = 0;
|
||||
this.capturedScopesArray = t.identifier(this.scopeNameGenerator.generate("main"));
|
||||
this._captureScopeAccessFunctionId = t.identifier("__get_scope_binding");
|
||||
this.serializedScopes = new Map();
|
||||
this.functionPrototypes = new Map();
|
||||
this.firstFunctionUsages = new Map();
|
||||
this.functions = new Map();
|
||||
@ -77,8 +75,13 @@ export class ResidualFunctions {
|
||||
);
|
||||
this.residualFunctionInfos = residualFunctionInfos;
|
||||
this.residualFunctionInstances = residualFunctionInstances;
|
||||
for (let instance of residualFunctionInstances.values())
|
||||
this.addFunctionInstance(((instance: any): FunctionInstance));
|
||||
this.additionalFunctionValueInfos = additionalFunctionValueInfos;
|
||||
this.referentializer = new Referentializer(scopeNameGenerator, statistics);
|
||||
for (let instance of residualFunctionInstances.values()) {
|
||||
invariant(instance !== undefined);
|
||||
if (!additionalFunctionValueInfos.has(instance.functionValue)) this.addFunctionInstance(instance);
|
||||
}
|
||||
this.additionalFunctionValueNestedFunctions = additionalFunctionValueNestedFunctions;
|
||||
}
|
||||
|
||||
realm: Realm;
|
||||
@ -88,11 +91,6 @@ export class ResidualFunctions {
|
||||
locationService: LocationService;
|
||||
prelude: Array<BabelNodeStatement>;
|
||||
factoryNameGenerator: NameGenerator;
|
||||
scopeNameGenerator: NameGenerator;
|
||||
capturedScopeInstanceIdx: number;
|
||||
capturedScopesArray: BabelNodeIdentifier;
|
||||
_captureScopeAccessFunctionId: BabelNodeIdentifier;
|
||||
serializedScopes: Map<DeclarativeEnvironmentRecord, ScopeBinding>;
|
||||
functionPrototypes: Map<FunctionValue, BabelNodeIdentifier>;
|
||||
firstFunctionUsages: Map<FunctionValue, BodyReference>;
|
||||
functions: Map<BabelNodeBlockStatement, Array<FunctionInstance>>;
|
||||
@ -100,14 +98,15 @@ export class ResidualFunctions {
|
||||
residualFunctionInitializers: ResidualFunctionInitializers;
|
||||
residualFunctionInfos: Map<BabelNodeBlockStatement, FunctionInfo>;
|
||||
residualFunctionInstances: Map<FunctionValue, FunctionInstance>;
|
||||
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>;
|
||||
additionalFunctionValueNestedFunctions: Set<FunctionValue>;
|
||||
referentializer: Referentializer;
|
||||
|
||||
addFunctionInstance(instance: FunctionInstance) {
|
||||
this.functionInstances.push(instance);
|
||||
let code = instance.functionValue.$ECMAScriptCode;
|
||||
invariant(code != null);
|
||||
let functionInstances = this.functions.get(code);
|
||||
if (functionInstances === undefined) this.functions.set(code, (functionInstances = []));
|
||||
functionInstances.push(instance);
|
||||
getOrDefault(this.functions, code, () => []).push(instance);
|
||||
}
|
||||
|
||||
setFunctionPrototype(constructor: FunctionValue, prototypeId: BabelNodeIdentifier) {
|
||||
@ -118,141 +117,31 @@ export class ResidualFunctions {
|
||||
if (!this.firstFunctionUsages.has(val)) this.firstFunctionUsages.set(val, bodyReference);
|
||||
}
|
||||
|
||||
// Generate a shared function for accessing captured scope bindings.
|
||||
// TODO: skip generating this function if the captured scope is not shared by multiple residual funcitons.
|
||||
_createCaptureScopeAccessFunction() {
|
||||
const body = [];
|
||||
const selectorParam = t.identifier("selector");
|
||||
const captured = t.identifier("__captured");
|
||||
const selectorExpression = t.memberExpression(this.capturedScopesArray, selectorParam, /*Indexer syntax*/ true);
|
||||
|
||||
// One switch case for one scope.
|
||||
const cases = [];
|
||||
for (const scopeBinding of this.serializedScopes.values()) {
|
||||
const scopeObjectExpression = t.arrayExpression((scopeBinding.initializationValues: any));
|
||||
cases.push(
|
||||
t.switchCase(t.numericLiteral(scopeBinding.id), [
|
||||
t.expressionStatement(t.assignmentExpression("=", selectorExpression, scopeObjectExpression)),
|
||||
t.breakStatement(),
|
||||
])
|
||||
);
|
||||
}
|
||||
// Default case.
|
||||
cases.push(
|
||||
t.switchCase(null, [
|
||||
t.throwStatement(t.newExpression(t.identifier("Error"), [t.stringLiteral("Unknown scope selector")])),
|
||||
])
|
||||
);
|
||||
|
||||
body.push(t.variableDeclaration("var", [t.variableDeclarator(captured, selectorExpression)]));
|
||||
body.push(
|
||||
t.ifStatement(
|
||||
t.unaryExpression("!", captured),
|
||||
t.blockStatement([
|
||||
t.switchStatement(selectorParam, cases),
|
||||
t.expressionStatement(t.assignmentExpression("=", captured, selectorExpression)),
|
||||
])
|
||||
)
|
||||
);
|
||||
body.push(t.returnStatement(captured));
|
||||
const factoryFunction = t.functionExpression(null, [selectorParam], t.blockStatement(body));
|
||||
return t.variableDeclaration("var", [t.variableDeclarator(this._captureScopeAccessFunctionId, factoryFunction)]);
|
||||
}
|
||||
|
||||
_getSerializedBindingScopeInstance(residualBinding: ResidualFunctionBinding): ScopeBinding {
|
||||
let declarativeEnvironmentRecord = residualBinding.declarativeEnvironmentRecord;
|
||||
invariant(declarativeEnvironmentRecord);
|
||||
|
||||
let scope = this.serializedScopes.get(declarativeEnvironmentRecord);
|
||||
if (!scope) {
|
||||
scope = {
|
||||
name: this.scopeNameGenerator.generate(),
|
||||
id: this.capturedScopeInstanceIdx++,
|
||||
initializationValues: [],
|
||||
};
|
||||
this.serializedScopes.set(declarativeEnvironmentRecord, scope);
|
||||
}
|
||||
|
||||
residualBinding.scope = scope;
|
||||
return scope;
|
||||
}
|
||||
|
||||
_referentialize(
|
||||
unbound: Set<string>,
|
||||
instances: Array<FunctionInstance>,
|
||||
shouldReferentializeInstanceFn: FunctionInstance => boolean
|
||||
): void {
|
||||
for (let instance of instances) {
|
||||
let residualBindings = instance.residualFunctionBindings;
|
||||
|
||||
for (let name of unbound) {
|
||||
let residualBinding = residualBindings.get(name);
|
||||
invariant(residualBinding !== undefined);
|
||||
if (residualBinding.modified) {
|
||||
// Initialize captured scope at function call instead of globally
|
||||
if (!residualBinding.referentialized) {
|
||||
if (!shouldReferentializeInstanceFn(instance)) {
|
||||
// TODO #989: Fix additional functions and referentialization
|
||||
throw new FatalError("TODO: implement referentialization for prepacked functions");
|
||||
}
|
||||
let scope = this._getSerializedBindingScopeInstance(residualBinding);
|
||||
let capturedScope = "__captured" + scope.name;
|
||||
// Save the serialized value for initialization at the top of
|
||||
// the factory.
|
||||
// This can serialize more variables than are necessary to execute
|
||||
// the function because every function serializes every
|
||||
// modified variable of its parent scope. In some cases it could be
|
||||
// an improvement to split these variables into multiple
|
||||
// scopes.
|
||||
const variableIndexInScope = scope.initializationValues.length;
|
||||
invariant(residualBinding.serializedValue);
|
||||
scope.initializationValues.push(residualBinding.serializedValue);
|
||||
scope.capturedScope = capturedScope;
|
||||
|
||||
// Replace binding usage with scope references
|
||||
residualBinding.serializedValue = t.memberExpression(
|
||||
t.identifier(capturedScope),
|
||||
t.numericLiteral(variableIndexInScope),
|
||||
true // Array style access.
|
||||
);
|
||||
|
||||
residualBinding.referentialized = true;
|
||||
this.statistics.referentialized++;
|
||||
}
|
||||
|
||||
// Already referentialized in prior scope
|
||||
if (residualBinding.declarativeEnvironmentRecord) {
|
||||
invariant(residualBinding.scope);
|
||||
instance.scopeInstances.add(residualBinding.scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getReferentializedScopeInitialization(scope: ScopeBinding) {
|
||||
invariant(scope.capturedScope);
|
||||
return [
|
||||
t.variableDeclaration("var", [
|
||||
t.variableDeclarator(
|
||||
t.identifier(scope.capturedScope),
|
||||
t.callExpression(this._captureScopeAccessFunctionId, [t.identifier(scope.name)])
|
||||
),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
spliceFunctions(
|
||||
rewrittenAdditionalFunctions: Map<FunctionValue, Array<BabelNodeStatement>>
|
||||
): ResidualFunctionsResult {
|
||||
this.residualFunctionInitializers.scrubFunctionInitializers();
|
||||
|
||||
let functionBodies = new Map();
|
||||
// these need to get spliced in at the end
|
||||
let overriddenPreludes = new Map();
|
||||
function getFunctionBody(instance: FunctionInstance): Array<BabelNodeStatement> {
|
||||
let b = functionBodies.get(instance);
|
||||
if (b === undefined) functionBodies.set(instance, (b = []));
|
||||
return b;
|
||||
}
|
||||
let globalPrelude = this.prelude;
|
||||
function getPrelude(instance: FunctionInstance): Array<BabelNodeStatement> {
|
||||
let preludeOverride = instance.preludeOverride;
|
||||
let b;
|
||||
if (preludeOverride) {
|
||||
b = overriddenPreludes.get(preludeOverride);
|
||||
if (b === undefined) overriddenPreludes.set(preludeOverride, (b = []));
|
||||
} else {
|
||||
b = globalPrelude;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
let requireStatistics = { replaced: 0, count: 0 };
|
||||
|
||||
@ -267,13 +156,65 @@ export class ResidualFunctions {
|
||||
for (let [funcBody, instances] of functionEntries) {
|
||||
let functionInfo = this.residualFunctionInfos.get(funcBody);
|
||||
invariant(functionInfo);
|
||||
this._referentialize(
|
||||
this.referentializer.referentialize(
|
||||
functionInfo.unbound,
|
||||
instances,
|
||||
instance => !rewrittenAdditionalFunctions.has(instance.functionValue)
|
||||
);
|
||||
}
|
||||
|
||||
let defineFunction = (instance, funcId, funcNode) => {
|
||||
let { functionValue } = instance;
|
||||
let body;
|
||||
if (t.isFunctionExpression(funcNode)) {
|
||||
funcNodes.set(functionValue, ((funcNode: any): BabelNodeFunctionExpression));
|
||||
body = getPrelude(instance);
|
||||
} else {
|
||||
invariant(t.isCallExpression(funcNode)); // .bind call
|
||||
body = getFunctionBody(instance);
|
||||
}
|
||||
body.push(t.variableDeclaration("var", [t.variableDeclarator(funcId, funcNode)]));
|
||||
let prototypeId = this.functionPrototypes.get(functionValue);
|
||||
if (prototypeId !== undefined) {
|
||||
let id = this.locationService.getLocation(functionValue);
|
||||
invariant(id !== undefined);
|
||||
body.push(
|
||||
t.variableDeclaration("var", [
|
||||
t.variableDeclarator(prototypeId, t.memberExpression(id, t.identifier("prototype"))),
|
||||
])
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Process Additional Functions
|
||||
for (let [funcValue, additionalFunctionInfo] of this.additionalFunctionValueInfos.entries()) {
|
||||
let { instance } = additionalFunctionInfo;
|
||||
let functionValue = ((funcValue: any): ECMAScriptSourceFunctionValue);
|
||||
let params = functionValue.$FormalParameters;
|
||||
invariant(params !== undefined);
|
||||
|
||||
let rewrittenBody = rewrittenAdditionalFunctions.get(funcValue);
|
||||
invariant(rewrittenBody);
|
||||
|
||||
// rewritten functions shouldn't have references fixed up because the body,
|
||||
// consists of serialized code. For simplicity we emit their instances in a naive way
|
||||
let functionBody = t.blockStatement(rewrittenBody);
|
||||
|
||||
let id = this.locationService.getLocation(funcValue);
|
||||
invariant(id !== undefined);
|
||||
let funcParams = params.slice();
|
||||
let funcNode = t.functionExpression(null, funcParams, functionBody);
|
||||
|
||||
if (funcValue.$Strict) {
|
||||
strictFunctionBodies.push(funcNode);
|
||||
} else {
|
||||
unstrictFunctionBodies.push(funcNode);
|
||||
}
|
||||
|
||||
defineFunction(instance, id, funcNode);
|
||||
}
|
||||
|
||||
// Process normal functions
|
||||
for (let [funcBody, instances] of functionEntries) {
|
||||
let functionInfo = this.residualFunctionInfos.get(funcBody);
|
||||
invariant(functionInfo);
|
||||
@ -287,71 +228,19 @@ export class ResidualFunctions {
|
||||
shouldInline = bodySize <= 30;
|
||||
}
|
||||
|
||||
let define = (instance, funcId, funcNode) => {
|
||||
let { functionValue } = instance;
|
||||
let addToBody;
|
||||
if (t.isFunctionExpression(funcNode)) {
|
||||
funcNodes.set(functionValue, ((funcNode: any): BabelNodeFunctionExpression));
|
||||
addToBody = elem => {
|
||||
// Let additional functions override the prelude to be their own body.
|
||||
// In this case, prepend to that body to simulate a "prelude" in the function
|
||||
if (instance.preludeOverride) instance.preludeOverride.unshift(elem);
|
||||
else this.prelude.push(elem);
|
||||
};
|
||||
} else {
|
||||
invariant(t.isCallExpression(funcNode)); // .bind call
|
||||
addToBody = elem => getFunctionBody(instance).push(elem);
|
||||
}
|
||||
let declaration = t.variableDeclaration("var", [t.variableDeclarator(funcId, funcNode)]);
|
||||
addToBody(declaration);
|
||||
let prototypeId = this.functionPrototypes.get(functionValue);
|
||||
if (prototypeId !== undefined) {
|
||||
let id = this.locationService.getLocation(functionValue);
|
||||
invariant(id !== undefined);
|
||||
addToBody(
|
||||
t.variableDeclaration("var", [
|
||||
t.variableDeclarator(prototypeId, t.memberExpression(id, t.identifier("prototype"))),
|
||||
])
|
||||
);
|
||||
}
|
||||
};
|
||||
// Split instances into normal or additional functions (whose bodies have been rewritten)
|
||||
// Split instances into normal or nested in an additional function
|
||||
let normalInstances = [];
|
||||
let additionalFunctionInstances = [];
|
||||
let additionalFunctionNestedInstances = [];
|
||||
for (let instance of instances) {
|
||||
if (rewrittenAdditionalFunctions.has(instance.functionValue)) additionalFunctionInstances.push(instance);
|
||||
if (this.additionalFunctionValueNestedFunctions.has(instance.functionValue))
|
||||
additionalFunctionNestedInstances.push(instance);
|
||||
else normalInstances.push(instance);
|
||||
}
|
||||
|
||||
let rewrittenBody = rewrittenAdditionalFunctions.get(instances[0].functionValue);
|
||||
let naiveProcessInstances = instancesToSplice => {
|
||||
this.statistics.functionClones += instancesToSplice.length - 1;
|
||||
|
||||
// rewritten functions shouldn't have references fixed up because the body,
|
||||
// consists of serialized code. For simplicity we emit their instances in a naive way
|
||||
if (rewrittenBody) {
|
||||
let functionBody = t.blockStatement(rewrittenBody);
|
||||
this.statistics.functionClones += additionalFunctionInstances.length - 1;
|
||||
|
||||
for (let instance of additionalFunctionInstances) {
|
||||
let { functionValue } = instance;
|
||||
let id = this.locationService.getLocation(functionValue);
|
||||
invariant(id !== undefined);
|
||||
let funcParams = params.slice();
|
||||
let funcNode = t.functionExpression(null, funcParams, functionBody);
|
||||
|
||||
if (functionValue.$Strict) {
|
||||
strictFunctionBodies.push(funcNode);
|
||||
} else {
|
||||
unstrictFunctionBodies.push(funcNode);
|
||||
}
|
||||
|
||||
define(instance, id, funcNode);
|
||||
}
|
||||
}
|
||||
if (normalInstances.length === 0) continue;
|
||||
if (shouldInline || normalInstances.length === 1 || usesArguments) {
|
||||
this.statistics.functionClones += normalInstances.length - 1;
|
||||
|
||||
for (let instance of normalInstances) {
|
||||
for (let instance of instancesToSplice) {
|
||||
let { functionValue, residualFunctionBindings, scopeInstances } = instance;
|
||||
let id = this.locationService.getLocation(functionValue);
|
||||
invariant(id !== undefined);
|
||||
@ -366,7 +255,9 @@ export class ResidualFunctions {
|
||||
scopeInitialization.push(
|
||||
t.variableDeclaration("var", [t.variableDeclarator(t.identifier(scope.name), t.numericLiteral(scope.id))])
|
||||
);
|
||||
scopeInitialization = scopeInitialization.concat(this._getReferentializedScopeInitialization(scope));
|
||||
scopeInitialization = scopeInitialization.concat(
|
||||
this.referentializer.getReferentializedScopeInitialization(scope)
|
||||
);
|
||||
}
|
||||
funcNode.body.body = scopeInitialization.concat(funcNode.body.body);
|
||||
|
||||
@ -384,9 +275,14 @@ export class ResidualFunctions {
|
||||
unstrictFunctionBodies.push(funcNode);
|
||||
}
|
||||
|
||||
define(instance, id, funcNode);
|
||||
defineFunction(instance, id, funcNode);
|
||||
}
|
||||
} else {
|
||||
};
|
||||
|
||||
if (additionalFunctionNestedInstances.length > 0) naiveProcessInstances(additionalFunctionNestedInstances);
|
||||
if (shouldInline || normalInstances.length === 1 || usesArguments) {
|
||||
naiveProcessInstances(normalInstances);
|
||||
} else if (normalInstances.length > 0) {
|
||||
let suffix = normalInstances[0].functionValue.__originalName || "";
|
||||
let factoryId = t.identifier(this.factoryNameGenerator.generate(suffix));
|
||||
|
||||
@ -434,7 +330,9 @@ export class ResidualFunctions {
|
||||
let scopeInitialization = [];
|
||||
for (let scope of normalInstances[0].scopeInstances) {
|
||||
factoryParams.push(t.identifier(scope.name));
|
||||
scopeInitialization = scopeInitialization.concat(this._getReferentializedScopeInitialization(scope));
|
||||
scopeInitialization = scopeInitialization.concat(
|
||||
this.referentializer.getReferentializedScopeInitialization(scope)
|
||||
);
|
||||
}
|
||||
|
||||
factoryParams = factoryParams.concat(params).slice();
|
||||
@ -518,21 +416,30 @@ export class ResidualFunctions {
|
||||
);
|
||||
}
|
||||
|
||||
define(instance, functionId, funcNode);
|
||||
defineFunction(instance, functionId, funcNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.capturedScopeInstanceIdx) {
|
||||
this.prelude.unshift(this._createCaptureScopeAccessFunction());
|
||||
let scopeVar = t.variableDeclaration("var", [
|
||||
t.variableDeclarator(
|
||||
this.capturedScopesArray,
|
||||
t.callExpression(t.identifier("Array"), [t.numericLiteral(this.capturedScopeInstanceIdx)])
|
||||
),
|
||||
]);
|
||||
// The `scopeVar` must be visible in all scopes.
|
||||
this.prelude.unshift(scopeVar);
|
||||
for (let referentializationScope of this.referentializer.referentializationState.keys()) {
|
||||
let prelude = this.prelude;
|
||||
// Get the prelude for this additional function value
|
||||
if (referentializationScope !== "GLOBAL") {
|
||||
let rewrittenBody = rewrittenAdditionalFunctions.get(referentializationScope);
|
||||
prelude = overriddenPreludes.get(rewrittenBody);
|
||||
if (!prelude) {
|
||||
prelude = [];
|
||||
overriddenPreludes.set(rewrittenBody, prelude);
|
||||
}
|
||||
}
|
||||
prelude.unshift(this.referentializer.createCaptureScopeAccessFunction(referentializationScope));
|
||||
prelude.unshift(this.referentializer.createCapturedScopesArrayInitialization(referentializationScope));
|
||||
}
|
||||
|
||||
for (let [preludeOverride, body] of overriddenPreludes.entries()) {
|
||||
invariant(preludeOverride);
|
||||
let prelude = ((body: any): Array<BabelNodeStatement>);
|
||||
preludeOverride.unshift(...prelude);
|
||||
}
|
||||
|
||||
for (let instance of this.functionInstances.reverse()) {
|
||||
@ -549,7 +456,7 @@ export class ResidualFunctions {
|
||||
}
|
||||
}
|
||||
|
||||
// Inject initializer code for indexed vars into functions
|
||||
// Inject initializer code for indexed vars into functions (for delay initializations)
|
||||
for (let [functionValue, funcNode] of funcNodes) {
|
||||
let initializerStatement = this.residualFunctionInitializers.getInitializerStatement(functionValue);
|
||||
if (initializerStatement !== undefined) {
|
||||
|
@ -42,7 +42,7 @@ import type {
|
||||
import { Generator, PreludeGenerator, NameGenerator } from "../utils/generator.js";
|
||||
import type { SerializationContext } from "../utils/generator.js";
|
||||
import invariant from "../invariant.js";
|
||||
import type { ResidualFunctionBinding, FunctionInfo, FunctionInstance } from "./types.js";
|
||||
import type { ResidualFunctionBinding, FunctionInfo, FunctionInstance, AdditionalFunctionInfo } from "./types.js";
|
||||
import { TimingStatistics, SerializerStatistics } from "./types.js";
|
||||
import { Logger } from "./logger.js";
|
||||
import { Modules } from "./modules.js";
|
||||
@ -69,6 +69,7 @@ export class ResidualHeapSerializer {
|
||||
delayInitializations: boolean,
|
||||
referencedDeclaredValues: Set<AbstractValue>,
|
||||
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects> | void,
|
||||
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>,
|
||||
statistics: SerializerStatistics
|
||||
) {
|
||||
this.realm = realm;
|
||||
@ -94,6 +95,7 @@ export class ResidualHeapSerializer {
|
||||
this.intrinsicNameGenerator = this.preludeGenerator.createNameGenerator("$i_");
|
||||
this.requireReturns = new Map();
|
||||
this.serializedValues = new Set();
|
||||
this.additionalFunctionValueNestedFunctions = new Set();
|
||||
this.residualFunctions = new ResidualFunctions(
|
||||
this.realm,
|
||||
this.statistics,
|
||||
@ -112,7 +114,9 @@ export class ResidualHeapSerializer {
|
||||
this.factoryNameGenerator,
|
||||
this.preludeGenerator.createNameGenerator("__scope_"),
|
||||
residualFunctionInfos,
|
||||
residualFunctionInstances
|
||||
residualFunctionInstances,
|
||||
additionalFunctionValueInfos,
|
||||
this.additionalFunctionValueNestedFunctions
|
||||
);
|
||||
this.emitter = new Emitter(this.residualFunctions, delayInitializations);
|
||||
this.mainBody = this.emitter.getBody();
|
||||
@ -125,7 +129,6 @@ export class ResidualHeapSerializer {
|
||||
this.referencedDeclaredValues = referencedDeclaredValues;
|
||||
this.activeGeneratorBodies = new Map();
|
||||
this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects;
|
||||
this.additionalFunctionValueNestedFunctions = new Set();
|
||||
}
|
||||
|
||||
emitter: Emitter;
|
||||
@ -166,6 +169,7 @@ export class ResidualHeapSerializer {
|
||||
// function values nested in additional functions can't delay initializations
|
||||
// TODO: revisit this and fix additional functions to be capable of delaying initializations
|
||||
additionalFunctionValueNestedFunctions: Set<FunctionValue>;
|
||||
currentAdditionalFunction: void | FunctionValue;
|
||||
|
||||
// Configures all mutable aspects of an object, in particular:
|
||||
// symbols, properties, prototype.
|
||||
@ -454,7 +458,10 @@ export class ResidualHeapSerializer {
|
||||
}
|
||||
|
||||
// Determine whether initialization code for a value should go into the main body, or a more specific initialization body.
|
||||
_getTarget(val: Value, scopes: Set<Scope>): { body: Array<BabelNodeStatement>, usedOnlyByResidualFunctions?: true } {
|
||||
_getTarget(
|
||||
val: Value,
|
||||
scopes: Set<Scope>
|
||||
): { body: Array<BabelNodeStatement>, usedOnlyByResidualFunctions?: true, usedOnlyByAdditionalFunctions?: boolean } {
|
||||
// All relevant values were visited in at least one scope.
|
||||
invariant(scopes.size >= 1);
|
||||
|
||||
@ -491,7 +498,10 @@ export class ResidualHeapSerializer {
|
||||
|
||||
if (numAdditionalFunctionReferences > 0 || !this.delayInitializations) {
|
||||
// We can just emit it into the current function body.
|
||||
return { body: this.currentFunctionBody };
|
||||
return {
|
||||
body: this.currentFunctionBody,
|
||||
usedOnlyByAdditionalFunctions: numAdditionalFunctionReferences === functionValues.length,
|
||||
};
|
||||
} else {
|
||||
// We can delay the initialization, and move it into a conditional code block in the residual functions!
|
||||
let body = this.residualFunctions.residualFunctionInitializers.registerValueOnlyReferencedByResidualFunctions(
|
||||
@ -853,7 +863,11 @@ export class ResidualHeapSerializer {
|
||||
invariant(instance);
|
||||
let residualBindings = instance.residualFunctionBindings;
|
||||
|
||||
if (this.currentFunctionBody !== this.mainBody) instance.preludeOverride = this.currentFunctionBody;
|
||||
let inAdditionalFunction = this.currentFunctionBody !== this.mainBody;
|
||||
if (inAdditionalFunction) {
|
||||
instance.preludeOverride = this.currentFunctionBody;
|
||||
instance.containingAdditionalFunction = this.currentAdditionalFunction;
|
||||
}
|
||||
let delayed = 1;
|
||||
let undelay = () => {
|
||||
if (--delayed === 0) {
|
||||
@ -872,6 +886,13 @@ export class ResidualHeapSerializer {
|
||||
};
|
||||
invariant(residualBinding.value !== undefined);
|
||||
referencedValues.push(residualBinding.value);
|
||||
if (inAdditionalFunction && residualBinding.value) {
|
||||
let scopes = this.residualValues.get(val);
|
||||
invariant(scopes);
|
||||
let { usedOnlyByAdditionalFunctions } = this._getTarget(val, scopes);
|
||||
if (usedOnlyByAdditionalFunctions)
|
||||
residualBinding.referencedOnlyFromAdditionalFunctions = this.currentAdditionalFunction;
|
||||
}
|
||||
}
|
||||
delayed++;
|
||||
this.emitter.emitNowOrAfterWaitingForDependencies(referencedValues, () => {
|
||||
@ -1217,7 +1238,9 @@ export class ResidualHeapSerializer {
|
||||
// Allows us to emit function declarations etc. inside of this additional
|
||||
// function instead of adding them at global scope
|
||||
// TODO: make sure this generator isn't getting mutated oddly
|
||||
this.additionalFunctionValueNestedFunctions = ((nestedFunctions: any): Set<FunctionValue>);
|
||||
((nestedFunctions: any): Set<FunctionValue>).forEach(val =>
|
||||
this.additionalFunctionValueNestedFunctions.add(val)
|
||||
);
|
||||
let serializePropertiesAndBindings = () => {
|
||||
for (let propertyBinding of modifiedProperties.keys()) {
|
||||
let binding: PropertyBinding = ((propertyBinding: any): PropertyBinding);
|
||||
@ -1232,6 +1255,7 @@ export class ResidualHeapSerializer {
|
||||
invariant(result instanceof Value);
|
||||
this.emitter.emit(t.returnStatement(this.serializeValue(result)));
|
||||
};
|
||||
this.currentAdditionalFunction = additionalFunctionValue;
|
||||
let body = this._serializeAdditionalFunction(generator, serializePropertiesAndBindings);
|
||||
invariant(additionalFunctionValue instanceof ECMAScriptSourceFunctionValue);
|
||||
rewrittenAdditionalFunctions.set(additionalFunctionValue, body);
|
||||
|
@ -35,7 +35,7 @@ import { Generator } from "../utils/generator.js";
|
||||
import type { GeneratorEntry, VisitEntryCallbacks } from "../utils/generator.js";
|
||||
import traverse from "babel-traverse";
|
||||
import invariant from "../invariant.js";
|
||||
import type { ResidualFunctionBinding, FunctionInfo, FunctionInstance } from "./types.js";
|
||||
import type { ResidualFunctionBinding, FunctionInfo, AdditionalFunctionInfo, FunctionInstance } from "./types.js";
|
||||
import { ClosureRefVisitor } from "./visitors.js";
|
||||
import { Logger } from "./logger.js";
|
||||
import { Modules } from "./modules.js";
|
||||
@ -75,6 +75,7 @@ export class ResidualHeapVisitor {
|
||||
this.delayedVisitGeneratorEntries = [];
|
||||
this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects;
|
||||
this.equivalenceSet = new HashSet();
|
||||
this.additionalFunctionValueInfos = new Map();
|
||||
}
|
||||
|
||||
realm: Realm;
|
||||
@ -95,6 +96,7 @@ export class ResidualHeapVisitor {
|
||||
delayedVisitGeneratorEntries: Array<{| commonScope: Scope, generator: Generator, entry: GeneratorEntry |}>;
|
||||
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects>;
|
||||
functionInstances: Map<FunctionValue, FunctionInstance>;
|
||||
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>;
|
||||
equivalenceSet: HashSet<AbstractValue>;
|
||||
|
||||
_withScope(scope: Scope, f: () => void) {
|
||||
@ -569,6 +571,7 @@ export class ResidualHeapVisitor {
|
||||
// Allows us to emit function declarations etc. inside of this additional
|
||||
// function instead of adding them at global scope
|
||||
this.commonScope = functionValue;
|
||||
let modifiedBindingInfo = new Map();
|
||||
let visitPropertiesAndBindings = () => {
|
||||
for (let propertyBinding of modifiedProperties.keys()) {
|
||||
let binding: PropertyBinding = ((propertyBinding: any): PropertyBinding);
|
||||
@ -582,6 +585,19 @@ export class ResidualHeapVisitor {
|
||||
invariant(result instanceof Value);
|
||||
this.visitValue(result);
|
||||
};
|
||||
invariant(functionValue instanceof ECMAScriptSourceFunctionValue);
|
||||
let code = functionValue.$ECMAScriptCode;
|
||||
invariant(code != null);
|
||||
let functionInfo = this.functionInfos.get(code);
|
||||
invariant(functionInfo);
|
||||
let funcInstance = this.functionInstances.get(functionValue);
|
||||
invariant(funcInstance);
|
||||
this.additionalFunctionValueInfos.set(functionValue, {
|
||||
functionValue,
|
||||
captures: functionInfo.unbound,
|
||||
modifiedBindings: modifiedBindingInfo,
|
||||
instance: funcInstance,
|
||||
});
|
||||
this.visitGenerator(generator);
|
||||
this._withScope(generator, visitPropertiesAndBindings);
|
||||
this.realm.restoreBindings(modifiedBindings);
|
||||
|
@ -148,6 +148,7 @@ export class Serializer {
|
||||
!!this.options.delayInitializations,
|
||||
residualHeapVisitor.referencedDeclaredValues,
|
||||
additionalFunctionValuesAndEffects,
|
||||
residualHeapVisitor.additionalFunctionValueInfos,
|
||||
this.statistics
|
||||
).serialize();
|
||||
if (this.logger.hasErrors()) return undefined;
|
||||
@ -169,6 +170,7 @@ export class Serializer {
|
||||
!!this.options.delayInitializations,
|
||||
residualHeapVisitor.referencedDeclaredValues,
|
||||
additionalFunctionValuesAndEffects,
|
||||
residualHeapVisitor.additionalFunctionValueInfos,
|
||||
this.statistics
|
||||
);
|
||||
|
||||
|
@ -9,9 +9,9 @@
|
||||
|
||||
/* @flow */
|
||||
|
||||
import { DeclarativeEnvironmentRecord } from "../environment.js";
|
||||
import { DeclarativeEnvironmentRecord, type Binding } from "../environment.js";
|
||||
import { ConcreteValue, Value } from "../values/index.js";
|
||||
import type { ECMAScriptSourceFunctionValue } from "../values/index.js";
|
||||
import type { ECMAScriptSourceFunctionValue, FunctionValue } from "../values/index.js";
|
||||
import type { BabelNodeExpression, BabelNodeStatement } from "babel-types";
|
||||
import { SameValue } from "../methods/abstract.js";
|
||||
import { Realm } from "../realm.js";
|
||||
@ -19,12 +19,22 @@ import invariant from "../invariant.js";
|
||||
|
||||
export type TryQuery<T> = (f: () => T, defaultValue: T, logFailures: boolean) => T;
|
||||
|
||||
export type AdditionalFunctionInfo = {
|
||||
functionValue: FunctionValue,
|
||||
captures: Set<string>,
|
||||
// TODO: use for storing modified residual function bindings (captured by other functions)
|
||||
modifiedBindings: Map<Binding, ResidualFunctionBinding>,
|
||||
instance: FunctionInstance,
|
||||
};
|
||||
|
||||
export type FunctionInstance = {
|
||||
residualFunctionBindings: Map<string, ResidualFunctionBinding>,
|
||||
functionValue: ECMAScriptSourceFunctionValue,
|
||||
insertionPoint?: BodyReference,
|
||||
// Optional place to put the function declaration
|
||||
preludeOverride?: Array<BabelNodeStatement>,
|
||||
// Additional function that the function instance was declared inside of (if any)
|
||||
containingAdditionalFunction?: FunctionValue,
|
||||
scopeInstances: Set<ScopeBinding>,
|
||||
};
|
||||
|
||||
@ -43,6 +53,10 @@ export type ResidualFunctionBinding = {
|
||||
// The serializedValue is only not yet present during the initialization of a binding that involves recursive dependencies.
|
||||
serializedValue?: void | BabelNodeExpression,
|
||||
referentialized?: boolean,
|
||||
// If the binding is only accessed by an additional function or nested values
|
||||
// this field contains that additional function. (Determines what initializer
|
||||
// to put the binding in -- global or additional function)
|
||||
referencedOnlyFromAdditionalFunctions?: FunctionValue,
|
||||
scope?: ScopeBinding,
|
||||
};
|
||||
|
||||
@ -51,6 +65,7 @@ export type ScopeBinding = {
|
||||
id: number,
|
||||
initializationValues: Array<BabelNodeExpression>,
|
||||
capturedScope?: string,
|
||||
containingAdditionalFunction: void | FunctionValue,
|
||||
};
|
||||
|
||||
export type GeneratorBody = Array<BabelNodeStatement>;
|
||||
|
@ -73,3 +73,11 @@ export function commonAncestorOf<T: HasParent>(node1: void | T, node2: void | T)
|
||||
}
|
||||
return n1;
|
||||
}
|
||||
|
||||
// Gets map[key] with default value provided by defaultFn
|
||||
export function getOrDefault<K, V>(map: Map<K, V>, key: K, defaultFn: () => V): V {
|
||||
let value = map.get(key);
|
||||
if (!value) map.set(key, (value = defaultFn()));
|
||||
invariant(value !== undefined);
|
||||
return value;
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
// serialized function clone count: 0
|
||||
var addit_funs = [];
|
||||
|
||||
var f = function(x) {
|
||||
var i = x > 5 ? 0 : 1;
|
||||
var fun = function() {
|
||||
i += 1;
|
||||
return i;
|
||||
}
|
||||
if (global.__registerAdditionalFunctionToPrepack)
|
||||
__registerAdditionalFunctionToPrepack(fun);
|
||||
addit_funs.push(fun);
|
||||
return fun;
|
||||
}
|
||||
|
||||
var g = [f(2), f(6), f(4), f(9)];
|
||||
|
||||
inspect = function() {
|
||||
addit_funs.forEach((x) => x());
|
||||
let res1 = g[0]() + " " + g[1]() + " " + g[2]() + " " + g[3]();
|
||||
addit_funs.forEach((x) => x());
|
||||
let res2 = g[0]() + " " + g[1]() + " " + g[2]() + " " + g[3]();
|
||||
return "PASS";
|
||||
//return res1 + " " + res2;
|
||||
}
|
@ -13,8 +13,9 @@ function produceObject() {
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
"use strict";
|
||||
let x1 = produceObject();
|
||||
bar = function() { return x1; }
|
||||
global.bar = function() { return x1; }
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
|
23
test/serializer/additional-functions/nested_function2.js
Normal file
23
test/serializer/additional-functions/nested_function2.js
Normal file
@ -0,0 +1,23 @@
|
||||
// additional functions
|
||||
// does not contain:= 7;
|
||||
// does not contain:= 10;
|
||||
|
||||
function additional1() {
|
||||
var x2 = { foo: 5 };
|
||||
foo = function() { return x2; }
|
||||
var y = 7;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
let x = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional1();
|
||||
additional2();
|
||||
let ret1 = foo();
|
||||
let ret2 = foo();
|
||||
ret1.x += 9;
|
||||
|
||||
return ' ' + JSON.stringify(ret1) + JSON.stringify(ret2) + (ret1 === ret2);
|
||||
}
|
28
test/serializer/additional-functions/nested_function3.js
Normal file
28
test/serializer/additional-functions/nested_function3.js
Normal file
@ -0,0 +1,28 @@
|
||||
// additional functions
|
||||
// does not contain:= 7;
|
||||
// does not contain:= 10;
|
||||
let x1 = { bar: 500 };
|
||||
|
||||
function additional1() {
|
||||
var x2 = { foo: 5 };
|
||||
foo = function() { return [x1, x2]; }
|
||||
var y = 7;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
let x = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional1();
|
||||
additional2();
|
||||
let [ret1, ret1_] = foo();
|
||||
let [ret2, ret2_] = foo();
|
||||
additional1();
|
||||
let [ret3, ret3_] = foo();
|
||||
let strings = [ret1, ret1_, ret2, ret2_, ret3, ret3_].map(JSON.stringify).join(' ');
|
||||
|
||||
return strings + (ret1 === ret2) + (ret1 === ret3) + (ret2 === ret3) +
|
||||
(ret1_ === ret2_) + (ret1_ === ret3_) + (ret2_ === ret3_);
|
||||
|
||||
}
|
30
test/serializer/additional-functions/nested_function4.js
Normal file
30
test/serializer/additional-functions/nested_function4.js
Normal file
@ -0,0 +1,30 @@
|
||||
// additional functions
|
||||
// does not contain:= 7;
|
||||
// does not contain:= 10;
|
||||
let addit_capture = { baz: 99 };
|
||||
|
||||
function additional1() {
|
||||
var x2 = { foo: addit_capture.baz + 500 };
|
||||
foo = function() { return [addit_capture, x2]; }
|
||||
addit_capture.baz += 42;
|
||||
var y = 7;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
let x = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional1();
|
||||
additional2();
|
||||
let [ret1, ret1_] = foo();
|
||||
let [ret2, ret2_] = foo();
|
||||
additional1();
|
||||
let [ret3, ret3_] = foo();
|
||||
let strings = [ret1, ret1_, ret2, ret2_, ret3, ret3_].map(JSON.stringify).join(' ');
|
||||
|
||||
return "PASS";
|
||||
/*return strings + (ret1 === ret2) + (ret1 === ret3) + (ret2 === ret3) +
|
||||
(ret1_ === ret2_) + (ret1_ === ret3_) + (ret2_ === ret3_);*/
|
||||
|
||||
}
|
@ -13,11 +13,11 @@ let g3 = f();
|
||||
let g4 = f();
|
||||
|
||||
inspect = function() {
|
||||
return
|
||||
g1(false) === g1(false)
|
||||
g2(false) !== g2(true)
|
||||
g3(true) !== g1(true)
|
||||
return '' +
|
||||
g1(false) === g1(false) +
|
||||
g2(false) !== g2(true) +
|
||||
g3(true) !== g1(true) +
|
||||
g4(true) !== g1(true);
|
||||
}
|
||||
|
||||
})();
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user