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:
Chris Blappert 2017-10-11 16:10:23 -07:00 committed by Facebook Github Bot
parent c9b03cc454
commit 3d2b586bc4
13 changed files with 529 additions and 235 deletions

View 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),
])
),
]);
}
}

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

@ -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
);

View File

@ -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>;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -13,8 +13,9 @@ function produceObject() {
}
function additional2() {
"use strict";
let x1 = produceObject();
bar = function() { return x1; }
global.bar = function() { return x1; }
}
inspect = function() {

View 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);
}

View 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_);
}

View 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_);*/
}

View File

@ -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);
}
})();
})();