prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
392 lines (288 loc) • 17 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Functions = void 0;
var _completions = require("../completions.js");
var _errors = require("../errors.js");
var _invariant = _interopRequireDefault(require("../invariant.js"));
var _realm = require("../realm.js");
var _errors2 = require("../utils/errors.js");
var _index = require("../values/index.js");
var _index2 = require("../methods/index.js");
var _modules = require("../utils/modules.js");
var _utils = require("./utils.js");
var _types = require("./types");
var _utils2 = require("../react/utils.js");
var _optimizing = require("../react/optimizing.js");
var _babelhelpers = require("../utils/babelhelpers");
var _singletons = require("../singletons.js");
var _descriptors = require("../descriptors.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
class Functions {
constructor(realm, moduleTracer) {
this.realm = realm;
this.moduleTracer = moduleTracer;
this._writeEffects = new Map();
this._noopFunction = undefined;
this._optimizedFunctionId = 0;
this.reactFunctionMap = new Map();
}
_unwrapAbstract(value) {
let elements = value.values.getElements();
if (elements) {
let possibleValues = [...elements].filter(element => !(element instanceof _index.EmptyValue || element instanceof _index.UndefinedValue));
if (possibleValues.length === 1) {
return possibleValues[0];
}
}
return value;
}
_optimizedFunctionEntryOfValue(value) {
let realm = this.realm; // if we conditionally called __optimize, we may have an AbstractValue that is the union of Empty or Undefined and
// a function/component to optimize
if (value instanceof _index.AbstractValue) {
value = this._unwrapAbstract(value);
}
(0, _invariant.default)(value instanceof _index.ObjectValue); // React component tree logic
let config = (0, _index2.Get)(realm, value, "config");
let rootComponent = (0, _index2.Get)(realm, value, "rootComponent");
let validConfig = config instanceof _index.ObjectValue || config === realm.intrinsics.undefined;
let validRootComponent = rootComponent instanceof _index.ECMAScriptSourceFunctionValue || rootComponent instanceof _index.BoundFunctionValue || rootComponent instanceof _index.AbstractValue && (0, _utils2.valueIsKnownReactAbstraction)(this.realm, rootComponent);
if (validConfig && validRootComponent) {
return {
value: rootComponent,
config: (0, _utils2.convertConfigObjectToReactComponentTreeConfig)(realm, config)
};
}
let location = (0, _babelhelpers.optionalStringOfLocation)(value.expressionLocation);
let result = realm.handleError(new _errors.CompilerDiagnostic(`Optimized Function Value ${location} is an not a function or react element`, realm.currentLocation, "PP0033", "Warning")); // Here we can recover by ignoring the __optimize call and emit correct code
if (result !== "Recover") throw new _errors.FatalError("Optimized Function Values must be functions or react elements");
}
_generateInitialAdditionalFunctions(globalKey) {
let recordedAdditionalFunctions = [];
let realm = this.realm;
let globalRecordedAdditionalFunctionsMap = this.moduleTracer.modules.logger.tryQuery(() => (0, _index2.Get)(realm, realm.$GlobalObject, globalKey), realm.intrinsics.undefined);
(0, _invariant.default)(globalRecordedAdditionalFunctionsMap instanceof _index.ObjectValue);
for (let funcId of _singletons.Properties.GetOwnPropertyKeysArray(realm, globalRecordedAdditionalFunctionsMap, true, false)) {
let property = globalRecordedAdditionalFunctionsMap.properties.get(funcId);
if (property) {
(0, _invariant.default)(property.descriptor instanceof _descriptors.PropertyDescriptor);
let value = property.descriptor.value;
(0, _invariant.default)(value !== undefined);
let entry = this._optimizedFunctionEntryOfValue(value);
if (entry) recordedAdditionalFunctions.push(entry);
}
}
return recordedAdditionalFunctions;
}
_generateOptimizedFunctionsFromRealm() {
let realm = this.realm;
let recordedAdditionalFunctions = [];
for (let [valueToOptimize, argModel] of realm.optimizedFunctions) {
let value = valueToOptimize instanceof _index.AbstractValue ? this._unwrapAbstract(valueToOptimize) : valueToOptimize;
(0, _invariant.default)(value instanceof _index.ECMAScriptSourceFunctionValue); // Check for case where __optimize was called in speculative context where effects were discarded
if (!value.isValid()) {
let error = new _errors.CompilerDiagnostic("Called __optimize on function in failed speculative context", value.expressionLocation, "PP1008", "RecoverableError");
if (realm.handleError(error) !== "Recover") throw new _errors.FatalError();
} else {
recordedAdditionalFunctions.push({
value,
argModel
});
}
}
return recordedAdditionalFunctions;
}
optimizeReactComponentTreeRoots(statistics) {
let logger = this.moduleTracer.modules.logger;
let recordedReactRootValues = this._generateInitialAdditionalFunctions("__reactComponentTrees"); // Get write effects of the components
if (this.realm.react.verbose) {
logger.logInformation(`Evaluating ${recordedReactRootValues.length} React component tree roots...`);
}
let alreadyEvaluated = new Map();
for (let _ref of recordedReactRootValues) {
let {
value: componentRoot,
config
} = _ref;
(0, _invariant.default)(config);
(0, _optimizing.optimizeReactComponentTreeRoot)(this.realm, componentRoot, config, this._writeEffects, logger, statistics, alreadyEvaluated, this.reactFunctionMap);
}
} // Note: this may only be used by nested optimized functions that are known to be evaluated inside of their parent
// optimized function's __optimize call (e.g. array.map/filter). In this case, lexical nesting is equivalent to the
// nesting of __optimize calls.
getDeclaringOptimizedFunction(functionValue) {
for (let [optimizedFunctionValue, additionalEffects] of this._writeEffects) {
// CreatedObjects is all objects created by this optimized function but not
// nested optimized functions.
let createdObjects = additionalEffects.effects.createdObjects;
if (createdObjects.has(functionValue)) return optimizedFunctionValue;
}
}
processCollectedNestedOptimizedFunctions() {
for (let [functionValue, effects] of this.realm.collectedNestedOptimizedFunctionEffects) {
let additionalFunctionEffects = (0, _utils.createAdditionalEffects)(this.realm, effects, true, "AdditionalFunctionEffects", this._writeEffects, this.reactFunctionMap, functionValue, this.getDeclaringOptimizedFunction(functionValue));
(0, _invariant.default)(additionalFunctionEffects !== null);
this._writeEffects.set(functionValue, additionalFunctionEffects);
}
}
_withEmptyOptimizedFunctionList({
value,
argModel
}, func) {
let oldRealmOptimizedFunctions = this.realm.optimizedFunctions;
this.realm.optimizedFunctions = new Map();
let currentOptimizedFunctionId = this._optimizedFunctionId++;
(0, _invariant.default)(value instanceof _index.ECMAScriptSourceFunctionValue);
for (let t1 of this.realm.tracers) t1.beginOptimizingFunction(currentOptimizedFunctionId, value);
this.realm.withNewOptimizedFunction(() => func(value, argModel), value);
for (let t2 of this.realm.tracers) t2.endOptimizingFunction(currentOptimizedFunctionId);
for (let [oldValue, model] of oldRealmOptimizedFunctions) this.realm.optimizedFunctions.set(oldValue, model);
}
checkThatFunctionsAreIndependent() {
let additionalFunctionsToProcess = this._generateOptimizedFunctionsFromRealm(); // When we find declarations of nested optimized functions, we need to apply the parent
// effects.
let additionalFunctionStack = [];
let additionalFunctions = new Set(additionalFunctionsToProcess.map(entry => entry.value));
let recordWriteEffectsForOptimizedFunctionAndNestedFunctions = (functionValue, argModel) => {
additionalFunctionStack.push(functionValue);
let call = _singletons.Utils.createModelledFunctionCall(this.realm, functionValue, argModel);
let realm = this.realm;
let logCompilerDiagnostic = (msg, location) => {
let error = new _errors.CompilerDiagnostic(msg, location, "PP1007", "Warning");
realm.handleError(error);
};
let effects = realm.evaluatePure(() => realm.evaluateForEffectsInGlobalEnv(call, undefined, "additional function"),
/*bubbles*/
true, (sideEffectType, binding, expressionLocation) => (0, _utils.handleReportedSideEffect)(logCompilerDiagnostic, sideEffectType, binding, expressionLocation));
(0, _invariant.default)(effects);
let additionalFunctionEffects = (0, _utils.createAdditionalEffects)(this.realm, effects, true, "AdditionalFunctionEffects", this._writeEffects, this.reactFunctionMap, functionValue, this.getDeclaringOptimizedFunction(functionValue));
(0, _invariant.default)(additionalFunctionEffects);
effects = additionalFunctionEffects.effects;
if (this._writeEffects.has(functionValue)) {
let error = new _errors.CompilerDiagnostic("Trying to optimize a function with two parent optimized functions, which is not currently allowed.", functionValue.expressionLocation, "PP1009", "RecoverableError"); // we can recover by assuming one set of effects to show further diagnostics
if (realm.handleError(error) !== "Recover") throw new _errors.FatalError();
} else {
this._writeEffects.set(functionValue, additionalFunctionEffects);
} // Conceptually this will ensure that the nested additional function is defined
// although for later cases, we'll apply the effects of the parents only.
this.realm.withEffectsAppliedInGlobalEnv(() => {
let newOptFuncs = this._generateOptimizedFunctionsFromRealm();
for (let newEntry of newOptFuncs) {
additionalFunctions.add(newEntry.value);
this._withEmptyOptimizedFunctionList(newEntry, recordWriteEffectsForOptimizedFunctionAndNestedFunctions);
} // Now we have to remember the stack of effects that need to be applied to deal with
// this additional function.
return null;
}, additionalFunctionEffects.effects);
(0, _invariant.default)(additionalFunctionStack.pop() === functionValue);
};
for (let funcObject of additionalFunctionsToProcess) {
this._withEmptyOptimizedFunctionList(funcObject, recordWriteEffectsForOptimizedFunctionAndNestedFunctions);
}
(0, _invariant.default)(additionalFunctionStack.length === 0); // check that functions are independent
let conflicts = new Map();
let isParentOf = (possibleParent, fun) => {
if (fun === undefined) return false;
let effects = this._writeEffects.get(fun);
(0, _invariant.default)(effects !== undefined);
if (effects.parentAdditionalFunction !== undefined) {
if (effects.parentAdditionalFunction === possibleParent) return true;
return isParentOf(possibleParent, effects.parentAdditionalFunction);
}
return false;
};
for (let fun1 of additionalFunctions) {
(0, _invariant.default)(fun1 instanceof _index.FunctionValue);
let fun1Location = fun1.expressionLocation;
let fun1Name = fun1.getDebugName() || (0, _babelhelpers.optionalStringOfLocation)(fun1Location); // Also do argument validation here
let additionalFunctionEffects = this._writeEffects.get(fun1);
(0, _invariant.default)(additionalFunctionEffects !== undefined);
let e1 = additionalFunctionEffects.effects;
(0, _invariant.default)(e1 !== undefined);
if (e1.result instanceof _completions.AbruptCompletion) {
let error = new _errors.CompilerDiagnostic(`Additional function ${fun1Name} will terminate abruptly`, e1.result.location, "PP1002", "RecoverableError"); // We generate correct code in this case, but the user probably doesn't want us to emit an unconditional throw
if (this.realm.handleError(error) !== "Recover") throw new _errors.FatalError();
}
for (let fun2 of additionalFunctions) {
if (fun1 === fun2) continue;
(0, _invariant.default)(fun2 instanceof _index.FunctionValue);
let fun2Location = fun2.expressionLocation;
let fun2Name = fun2.getDebugName() || (0, _babelhelpers.optionalStringOfLocation)(fun2Location);
let reportFn = () => {
this.reportWriteConflicts(fun1Name, fun2Name, conflicts, e1.modifiedProperties, isParentOf(fun1, fun2), _singletons.Utils.createModelledFunctionCall(this.realm, fun2));
return null;
}; // Recursively apply all parent effects
let withPossibleParentEffectsApplied = (toExecute, optimizedFunction) => {
let funEffects = this._writeEffects.get(optimizedFunction);
(0, _invariant.default)(funEffects !== undefined);
let parentAdditionalFunction = funEffects.parentAdditionalFunction;
if (parentAdditionalFunction !== undefined) {
let parentEffects = this._writeEffects.get(parentAdditionalFunction);
(0, _invariant.default)(parentEffects !== undefined);
let newToExecute = () => this.realm.withEffectsAppliedInGlobalEnv(toExecute, parentEffects.effects);
withPossibleParentEffectsApplied(newToExecute, parentAdditionalFunction);
} else {
toExecute();
}
};
withPossibleParentEffectsApplied(reportFn, fun2);
}
}
if (conflicts.size > 0) {
for (let diagnostic of conflicts.values()) if (this.realm.handleError(diagnostic) !== "Recover") throw new _errors.FatalError();
}
}
getAdditionalFunctionValuesToEffects() {
return this._writeEffects;
}
reportWriteConflicts(f1name, f2name, conflicts, pbs, f1IsParentOfF2, call2) {
let reportConflict = (location, object = "", key, originalLocation) => {
let firstLocationString = (0, _babelhelpers.optionalStringOfLocation)(originalLocation);
let secondLocationString = (0, _babelhelpers.optionalStringOfLocation)(location);
let propString = key ? ` "${key}"` : "";
let objectString = object ? ` on object "${object}" ` : "";
if (!objectString && key) objectString = " on <unnamed object> ";
let error = new _errors.CompilerDiagnostic(`Write to property${propString}${objectString}at optimized function ${f1name}${firstLocationString} conflicts with access in function ${f2name}${secondLocationString}`, location, "PP1003", "RecoverableError");
conflicts.set(location, error);
};
let writtenObjects = new Set();
pbs.forEach((val, key, m) => {
writtenObjects.add(key.object);
});
let oldReportObjectGetOwnProperties = this.realm.reportObjectGetOwnProperties;
this.realm.reportObjectGetOwnProperties = ob => {
let location = this.realm.currentLocation;
(0, _invariant.default)(location);
if (writtenObjects.has(ob) && !conflicts.has(location)) reportConflict(location, ob.getDebugName(), undefined, ob.expressionLocation);
};
let oldReportPropertyAccess = this.realm.reportPropertyAccess;
this.realm.reportPropertyAccess = (pb, isWrite) => {
if (_index.ObjectValue.refuseSerializationOnPropertyBinding(pb)) return;
let location = this.realm.currentLocation;
if (!location) return; // happens only when accessing an additional function property
if (pbs.has(pb) && (!f1IsParentOfF2 || isWrite) && !conflicts.has(location)) {
let originalLocation = pb.descriptor instanceof _descriptors.PropertyDescriptor && pb.descriptor.value && !Array.isArray(pb.descriptor.value) ? pb.descriptor.value.expressionLocation : undefined;
let keyString = pb.key instanceof _index.Value ? pb.key.toDisplayString() : pb.key;
reportConflict(location, pb.object ? pb.object.getDebugName() : undefined, keyString, originalLocation);
}
};
try {
(0, _errors2.ignoreErrorsIn)(this.realm, () => this.realm.evaluateForEffectsInGlobalEnv(call2));
} finally {
this.realm.reportPropertyAccess = oldReportPropertyAccess;
this.realm.reportObjectGetOwnProperties = oldReportObjectGetOwnProperties;
}
}
}
exports.Functions = Functions;
//# sourceMappingURL=functions.js.map