prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
203 lines (159 loc) • 8.31 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ResidualFunctionInitializers = void 0;
var _index = require("../values/index.js");
var t = _interopRequireWildcard(require("@babel/types"));
var _NameGenerator = require("../utils/NameGenerator.js");
var _traverseFast = _interopRequireDefault(require("../utils/traverse-fast.js"));
var _invariant = _interopRequireDefault(require("../invariant.js"));
var _babelhelpers = require("../utils/babelhelpers.js");
var _factorify = require("./factorify.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.
*/
/* strict-local */
// This class manages information about values
// which are only referenced by residual functions,
// and it provides the ability to generate initialization code for those values that
// can be placed into the residual functions.
class ResidualFunctionInitializers {
constructor(locationService) {
this.functionInitializerInfos = new Map();
this.initializers = new Map();
this.sharedInitializers = new Map();
this.locationService = locationService;
} // ownId: uid of the FunctionValue, initializer ids are strings of sorted lists of FunctionValues referencing the value
registerValueOnlyReferencedByResidualFunctions(functionValues, val) {
(0, _invariant.default)(functionValues.length >= 1);
let infos = [];
for (let functionValue of functionValues) {
let info = this.functionInitializerInfos.get(functionValue);
if (info === undefined) this.functionInitializerInfos.set(functionValue, info = {
ownId: this.functionInitializerInfos.size.toString(),
initializerIds: new Set()
});
infos.push(info);
}
let id = infos.map(info => info.ownId).sort().join();
for (let info of infos) info.initializerIds.add(id);
let initializer = this.initializers.get(id);
if (initializer === undefined) this.initializers.set(id, initializer = {
id,
order: infos.length,
values: [],
body: {
type: "DelayInitializations",
parentBody: undefined,
entries: [],
done: false
}
});
initializer.values.push(val);
return initializer.body;
}
scrubFunctionInitializers() {
// Deleting trivial entries in order to avoid creating empty initialization functions that serve no purpose.
for (let initializer of this.initializers.values()) if (initializer.body.entries.length === 0) this.initializers.delete(initializer.id);
for (let [functionValue, info] of this.functionInitializerInfos) {
for (let id of info.initializerIds) {
let initializer = this.initializers.get(id);
if (initializer === undefined) {
info.initializerIds.delete(id);
}
}
if (info.initializerIds.size === 0) this.functionInitializerInfos.delete(functionValue);
}
}
_conditionalInitialization(containingAdditionalFunction, initializedValues, initializationStatements) {
if (initializationStatements.length === 1 && t.isIfStatement(initializationStatements[0])) {
return initializationStatements[0];
} // We have some initialization code, and it should only get executed once,
// so we are going to guard it.
// First, let's see if one of the initialized values is guaranteed to not
// be undefined after initialization. In that case, we can use that state-change
// to figure out if initialization needs to run.
let location;
for (let value of initializedValues) {
// function declarations get hoisted, so let's not use their initialization state as a marker
if (!value.mightBeUndefined() && !(value instanceof _index.FunctionValue)) {
location = this.locationService.getLocation(value);
if (location !== undefined) break;
}
}
if (location === undefined) {
// Second, if we didn't find a non-undefined value, let's make one up.
// It will transition from `undefined` to `null`.
location = this.locationService.createLocation(containingAdditionalFunction);
initializationStatements.unshift(t.expressionStatement(t.assignmentExpression("=", location, _babelhelpers.nullExpression)));
}
return t.ifStatement(t.binaryExpression("===", location, _babelhelpers.voidExpression), t.blockStatement(initializationStatements));
}
hasInitializerStatement(functionValue) {
return !!this.functionInitializerInfos.get(functionValue);
}
factorifyInitializers(nameGenerator) {
for (const initializer of this.initializers.values()) {
(0, _factorify.factorifyObjects)(initializer.body.entries, nameGenerator);
}
}
getInitializerStatement(functionValue) {
let initializerInfo = this.functionInitializerInfos.get(functionValue);
if (initializerInfo === undefined) return undefined;
let containingAdditionalFunction = this.locationService.getContainingAdditionalFunction(functionValue);
(0, _invariant.default)(initializerInfo.initializerIds.size > 0);
let ownInitializer = this.initializers.get(initializerInfo.ownId);
let initializedValues;
let initializationStatements = [];
let initializers = [];
for (let initializerId of initializerInfo.initializerIds) {
let initializer = this.initializers.get(initializerId);
(0, _invariant.default)(initializer !== undefined);
(0, _invariant.default)(initializer.body.entries.length > 0);
initializers.push(initializer);
} // Sorting initializers by the number of scopes they are required by.
// Note that the scope sets form a lattice, and this sorting effectively
// ensures that value initializers that depend on other value initializers
// get called in the right order.
initializers.sort((i, j) => j.order - i.order);
for (let initializer of initializers) {
if (initializerInfo.initializerIds.size === 1 || initializer === ownInitializer) {
initializedValues = initializer.values;
}
if (initializer === ownInitializer) {
initializationStatements = initializationStatements.concat(initializer.body.entries);
} else {
let ast = this.sharedInitializers.get(initializer.id);
if (ast === undefined) {
ast = this._conditionalInitialization(containingAdditionalFunction, initializer.values, initializer.body.entries); // We inline compact initializers, as calling a function would introduce too much
// overhead. To determine if an initializer is compact, we count the number of
// nodes in the AST, and check if it exceeds a certain threshold.
// TODO #885: Study in more detail which threshold is the best compromise in terms of
// code size and performance.
let count = 0;
(0, _traverseFast.default)(t.file(t.program([ast])), node => {
count++;
return false;
});
if (count > 24) {
let id = this.locationService.createFunction(containingAdditionalFunction, [ast]);
ast = t.expressionStatement(t.callExpression(id, []));
}
this.sharedInitializers.set(initializer.id, ast);
}
initializationStatements.push(ast);
}
}
return this._conditionalInitialization(containingAdditionalFunction, initializedValues || [], initializationStatements);
}
}
exports.ResidualFunctionInitializers = ResidualFunctionInitializers;
//# sourceMappingURL=ResidualFunctionInitializers.js.map