prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
328 lines (242 loc) • 14 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Referentializer = void 0;
var _environment = require("../environment.js");
var t = _interopRequireWildcard(require("@babel/types"));
var _generator = _interopRequireDefault(require("@babel/generator"));
var _NameGenerator = require("../utils/NameGenerator");
var _invariant = _interopRequireDefault(require("../invariant.js"));
var _statistics = require("./statistics.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)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
/**
* 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.
*/
/*
* 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.
*/
class Referentializer {
constructor(realm, options, scopeNameGenerator, scopeBindingNameGenerator, leakedNameGenerator, residualOptimizedFunctions) {
this._options = options;
this.scopeNameGenerator = scopeNameGenerator;
this.scopeBindingNameGenerator = scopeBindingNameGenerator;
this.referentializationState = new Map();
this._leakedNameGenerator = leakedNameGenerator;
this.realm = realm;
this._residualOptimizedFunctions = residualOptimizedFunctions;
}
getStatistics() {
(0, _invariant.default)(this.realm.statistics instanceof _statistics.SerializerStatistics, "serialization requires SerializerStatistics");
return this.realm.statistics;
}
_createReferentializationState() {
return {
capturedScopeInstanceIdx: 0,
capturedScopesArray: t.identifier(this.scopeNameGenerator.generate("main")),
capturedScopeAccessFunctionId: t.identifier(this.scopeBindingNameGenerator.generate("get_scope_binding")),
serializedScopes: new Map()
};
}
_getReferentializationState(referentializationScope) {
return (0, _utils.getOrDefault)(this.referentializationState, referentializationScope, this._createReferentializationState.bind(this));
}
createLeakedIds(referentializationScope) {
const leakedIds = [];
const serializedScopes = this._getReferentializationState(referentializationScope).serializedScopes;
for (const scopeBinding of serializedScopes.values()) leakedIds.push(...scopeBinding.leakedIds);
if (leakedIds.length === 0) return [];
return [t.variableDeclaration("var", leakedIds.map(id => t.variableDeclarator(id)))];
}
createCapturedScopesPrelude(referentializationScope) {
let accessFunctionDeclaration = this._createCaptureScopeAccessFunction(referentializationScope);
if (accessFunctionDeclaration === undefined) return [];
return [accessFunctionDeclaration, this._createCapturedScopesArrayInitialization(referentializationScope)];
} // 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) {
// One switch case for one scope.
const cases = [];
const serializedScopes = this._getReferentializationState(referentializationScope).serializedScopes;
const initializationCases = new Map();
for (const scopeBinding of serializedScopes.values()) {
if (scopeBinding.initializationValues.length === 0) continue;
const expr = t.arrayExpression(scopeBinding.initializationValues);
const key = (0, _generator.default)(expr, {}, "").code;
if (!initializationCases.has(key)) {
initializationCases.set(key, {
scopeIDs: [scopeBinding.id],
value: expr
});
} else {
const ic = initializationCases.get(key);
(0, _invariant.default)(ic);
ic.scopeIDs.push(scopeBinding.id);
}
}
if (initializationCases.size === 0) return undefined;
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);
for (const ic of initializationCases.values()) {
ic.scopeIDs.forEach((id, i) => {
let consequent = [];
if (i === ic.scopeIDs.length - 1) {
consequent = [t.expressionStatement(t.assignmentExpression("=", captured, ic.value)), t.breakStatement()];
}
cases.push(t.switchCase(t.numericLiteral(id), consequent));
});
} // Default case.
if (this.realm.invariantLevel >= 1) {
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)]);
}
_getReferentializationScope(residualBinding) {
if (residualBinding.potentialReferentializationScopes.has("GLOBAL")) return "GLOBAL";
if (residualBinding.potentialReferentializationScopes.size > 1) {
// Here we know potentialReferentializationScopes cannot contain "GLOBAL"; Set<FunctionValue> is
// compatible with Set<FunctionValue | Generator>
let scopes = residualBinding.potentialReferentializationScopes;
let parentOptimizedFunction = this._residualOptimizedFunctions.tryGetOutermostOptimizedFunction(scopes);
return parentOptimizedFunction || "GLOBAL";
}
for (let scope of residualBinding.potentialReferentializationScopes) return scope;
(0, _invariant.default)(false);
}
_getSerializedBindingScopeInstance(residualBinding) {
let declarativeEnvironmentRecord = residualBinding.declarativeEnvironmentRecord;
(0, _invariant.default)(declarativeEnvironmentRecord);
let referentializationScope = this._getReferentializationScope(residualBinding); // figure out if this is accessed only from additional functions
let refState = this._getReferentializationState(referentializationScope);
let scope = refState.serializedScopes.get(declarativeEnvironmentRecord);
if (!scope) {
scope = {
name: this.scopeNameGenerator.generate(),
id: refState.capturedScopeInstanceIdx++,
initializationValues: [],
leakedIds: [],
referentializationScope
};
refState.serializedScopes.set(declarativeEnvironmentRecord, scope);
}
(0, _invariant.default)(scope.referentializationScope === referentializationScope);
(0, _invariant.default)(!residualBinding.scope || residualBinding.scope === scope);
residualBinding.scope = scope;
return scope;
}
getReferentializedScopeInitialization(scope, scopeName) {
const capturedScope = scope.capturedScope;
(0, _invariant.default)(capturedScope);
const state = this._getReferentializationState(scope.referentializationScope);
const funcName = state.capturedScopeAccessFunctionId;
const scopeArray = state.capturedScopesArray; // 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)])];
}
referentializeLeakedBinding(residualBinding) {
(0, _invariant.default)(residualBinding.hasLeaked); // When simpleClosures is enabled, then space for captured mutable bindings is allocated upfront.
let serializedBindingId = t.identifier(this._leakedNameGenerator.generate(residualBinding.name));
let scope = this._getSerializedBindingScopeInstance(residualBinding);
scope.leakedIds.push(serializedBindingId);
residualBinding.serializedValue = residualBinding.serializedUnscopedLocation = serializedBindingId;
this.getStatistics().referentialized++;
}
referentializeModifiedBinding(residualBinding) {
(0, _invariant.default)(residualBinding.modified); // 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;
const indexExpression = t.numericLiteral(variableIndexInScope);
(0, _invariant.default)(residualBinding.serializedValue);
scope.initializationValues.push(residualBinding.serializedValue);
scope.capturedScope = capturedScope; // Replace binding usage with scope references
// The rewritten .serializedValue refers to a local capturedScope variable
// which is only accessible from within residual functions where code
// to create this variable is emitted.
residualBinding.serializedValue = t.memberExpression(t.identifier(capturedScope), indexExpression, true // Array style access.
); // .serializedUnscopedLocation is initialized with a more general expressions
// that can be used outside of residual functions.
// TODO: Creating these expressions just in case looks expensive. Measure, and potentially only create lazily.
const state = this._getReferentializationState(scope.referentializationScope);
const funcName = state.capturedScopeAccessFunctionId;
const scopeArray = state.capturedScopesArray; // First get scope array entry and check if it's already initialized.
// Only if not yet, then call the initialization function.
const scopeName = t.numericLiteral(scope.id);
const capturedScopeExpression = t.logicalExpression("||", t.memberExpression(scopeArray, scopeName, true), t.callExpression(funcName, [scopeName]));
residualBinding.serializedUnscopedLocation = t.memberExpression(capturedScopeExpression, indexExpression, true // Array style access.
);
this.getStatistics().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 = this._getReferentializationScope(binding);
let refState = this.referentializationState.get(referentializationScope);
if (refState) {
let scope = refState.serializedScopes.get(declarativeEnvironmentRecord);
if (scope) {
scope.initializationValues = [];
scope.leakedIds = [];
}
}
}
delete binding.serializedValue;
}
}
referentialize(instance) {
let residualBindings = instance.residualFunctionBindings;
for (let residualBinding of residualBindings.values()) {
if (residualBinding === undefined) continue;
if (residualBinding.modified) {
// Initialize captured scope at function call instead of globally
if (!residualBinding.declarativeEnvironmentRecord) residualBinding.referentialized = true;
if (!residualBinding.referentialized) {
if (!residualBinding.hasLeaked) this._getSerializedBindingScopeInstance(residualBinding);
residualBinding.referentialized = true;
}
(0, _invariant.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.newExpression(t.identifier("Array"), [t.numericLiteral(this._getReferentializationState(referentializationScope).capturedScopeInstanceIdx)]))]);
}
}
exports.Referentializer = Referentializer;
//# sourceMappingURL=Referentializer.js.map