UNPKG

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