UNPKG

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
"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