prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
230 lines (194 loc) • 11.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Referentializer = undefined;
var _environment = require("../environment.js");
var _errors = require("../errors.js");
var _index = require("../values/index.js");
var _babelTypes = require("babel-types");
var t = _interopRequireWildcard(_babelTypes);
var _generator = require("../utils/generator.js");
var _invariant = require("../invariant.js");
var _invariant2 = _interopRequireDefault(_invariant);
var _types = require("./types.js");
var _utils = require("./utils.js");
var _realm = require("../realm.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
/*
* 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.
*/
// Each of these will correspond to a different preludeGenerator and thus will
// have different values available for initialization. FunctionValues should
// only be additional functions.
class Referentializer {
constructor(realm, options, scopeNameGenerator, referentializedNameGenerator, statistics) {
this._options = options;
this.scopeNameGenerator = scopeNameGenerator;
this.statistics = statistics;
this.referentializationState = new Map();
this._referentializedNameGenerator = referentializedNameGenerator;
this.realm = realm;
}
_createReferentializationState() {
return {
capturedScopeInstanceIdx: 0,
capturedScopesArray: t.identifier(this.scopeNameGenerator.generate("main")),
capturedScopeAccessFunctionId: t.identifier(this.scopeNameGenerator.generate("get_scope_binding")),
serializedScopes: new Map()
};
}
_getReferentializationState(referentializationScope) {
return (0, _utils.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 functions.
createCaptureScopeAccessFunction(referentializationScope) {
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);
cases.push(t.switchCase(t.numericLiteral(scopeBinding.id), [t.expressionStatement(t.assignmentExpression("=", captured, 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)]));
body.push(t.switchStatement(selectorParam, cases));
body.push(t.expressionStatement(t.assignmentExpression("=", selectorExpression, captured)));
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) {
let declarativeEnvironmentRecord = residualBinding.declarativeEnvironmentRecord;
let referentializationScope = residualBinding.referencedOnlyFromAdditionalFunctions || "GLOBAL";
(0, _invariant2.default)(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 = this._getReferentializationState(referentializationScope);
scope = {
name: this.scopeNameGenerator.generate(),
id: refState.capturedScopeInstanceIdx++,
initializationValues: [],
containingAdditionalFunction: residualBinding.referencedOnlyFromAdditionalFunctions
};
serializedScopes.set(declarativeEnvironmentRecord, scope);
}
(0, _invariant2.default)(!residualBinding.scope || residualBinding.scope === scope);
residualBinding.scope = scope;
return scope;
}
getReferentializedScopeInitialization(scope) {
const capturedScope = scope.capturedScope;
(0, _invariant2.default)(capturedScope);
const state = this._getReferentializationState(scope.containingAdditionalFunction || "GLOBAL");
const funcName = state.capturedScopeAccessFunctionId;
const scopeArray = state.capturedScopesArray;
const scopeName = t.identifier(scope.name);
// First get scope array entry and check if it's already initialized.
// Only if not yet, then call the initialization function.
const init = t.logicalExpression("||", t.memberExpression(scopeArray, scopeName, true), t.callExpression(funcName, [scopeName]));
return [t.variableDeclaration("var", [t.variableDeclarator(t.identifier(capturedScope), init)])];
}
referentializeBinding(residualBinding, name, instance) {
if (this._options.simpleClosures) {
// When simpleClosures is enabled, then space for captured mutable bindings is allocated upfront.
let serializedBindingId = t.identifier(this._referentializedNameGenerator.generate(name));
let serializedValue = residualBinding.serializedValue;
(0, _invariant2.default)(serializedValue);
let declar = t.variableDeclaration("var", [t.variableDeclarator(serializedBindingId, serializedValue)]);
instance.initializationStatements.push(declar);
residualBinding.serializedValue = serializedBindingId;
} else {
// When simpleClosures is not enabled, then space for captured mutable bindings is allocated lazily.
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;
(0, _invariant2.default)(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.
);
}
this.statistics.referentialized++;
}
// Cleans all scopes between passes of the serializer
cleanInstance(instance) {
instance.initializationStatements = [];
for (let b of instance.residualFunctionBindings.values()) {
let binding = b;
if (binding.referentialized && binding.declarativeEnvironmentRecord) {
let declarativeEnvironmentRecord = binding.declarativeEnvironmentRecord;
let referentializationScope = binding.referencedOnlyFromAdditionalFunctions || "GLOBAL";
let refState = this.referentializationState.get(referentializationScope);
if (refState) {
let scope = refState.serializedScopes.get(declarativeEnvironmentRecord);
if (scope) {
scope.initializationValues = [];
}
}
}
delete binding.serializedValue;
}
}
referentialize(unbound, instances, shouldReferentializeInstanceFn) {
for (let instance of instances) {
let residualBindings = instance.residualFunctionBindings;
for (let name of unbound) {
let residualBinding = residualBindings.get(name);
(0, _invariant2.default)(residualBinding !== undefined);
if (residualBinding.modified) {
// Initialize captured scope at function call instead of globally
if (!residualBinding.declarativeEnvironmentRecord) residualBinding.referentialized = true;
if (!residualBinding.referentialized) {
if (!shouldReferentializeInstanceFn(instance)) {
// TODO #989: Fix additional functions and referentialization
this.realm.handleError(new _errors.CompilerDiagnostic("Referentialization for prepacked functions unimplemented", instance.functionValue.loc, "PP1005", "FatalError"));
throw new _errors.FatalError("TODO: implement referentialization for prepacked functions");
}
if (!this._options.simpleClosures) this._getSerializedBindingScopeInstance(residualBinding);
residualBinding.referentialized = true;
}
(0, _invariant2.default)(residualBinding.referentialized);
if (residualBinding.declarativeEnvironmentRecord && residualBinding.scope) {
instance.scopeInstances.set(residualBinding.scope.name, residualBinding.scope);
}
}
}
}
}
createCapturedScopesArrayInitialization(referentializationScope) {
return t.variableDeclaration("var", [t.variableDeclarator(this._getReferentializationState(referentializationScope).capturedScopesArray, t.callExpression(t.identifier("Array"), [t.numericLiteral(this._getReferentializationState(referentializationScope).capturedScopeInstanceIdx)]))]);
}
}
exports.Referentializer = Referentializer; /**
* 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.
*/
//# sourceMappingURL=Referentializer.js.map