UNPKG

prepack

Version:

Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.

346 lines (307 loc) 17.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Functions = undefined; var _completions = require("../completions.js"); var _singletons = require("../singletons.js"); var _errors = require("../errors.js"); var _invariant = require("../invariant.js"); var _invariant2 = _interopRequireDefault(_invariant); 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 _types = require("./types"); var _reconcilation = require("../react/reconcilation.js"); var _utils = require("../react/utils.js"); var _babelTypes = require("babel-types"); var t = _interopRequireWildcard(_babelTypes); var _utils2 = require("../intrinsics/prepack/utils.js"); 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; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } class Functions { constructor(realm, moduleTracer) { this.realm = realm; this.moduleTracer = moduleTracer; this.writeEffects = new Map(); this.functionExpressions = new Map(); } // maps back from FunctionValue to the expression string __generateAdditionalFunctionsMap(globalKey) { let recordedAdditionalFunctions = new Map(); let realm = this.realm; let globalRecordedAdditionalFunctionsMap = this.moduleTracer.modules.logger.tryQuery(() => (0, _index2.Get)(realm, realm.$GlobalObject, globalKey), realm.intrinsics.undefined); (0, _invariant2.default)(globalRecordedAdditionalFunctionsMap instanceof _index.ObjectValue); for (let funcId of globalRecordedAdditionalFunctionsMap.getOwnPropertyKeysArray()) { let property = globalRecordedAdditionalFunctionsMap.properties.get(funcId); if (property) { let value = property.descriptor && property.descriptor.value; if (value instanceof _index.ECMAScriptSourceFunctionValue) { // additional function logic recordedAdditionalFunctions.set(value, { funcId }); continue; } else if (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.AbstractValue && (0, _utils.valueIsKnownReactAbstraction)(this.realm, rootComponent); if (validConfig && validRootComponent) { recordedAdditionalFunctions.set(rootComponent, { funcId, config: (0, _utils.convertConfigObjectToReactComponentTreeConfig)(realm, config) }); } continue; } realm.handleError(new _errors.CompilerDiagnostic(`Additional Function Value ${funcId} is an invalid value`, undefined, "PP0001", "FatalError")); throw new _errors.FatalError("invalid Additional Function value"); } } return recordedAdditionalFunctions; } // This will also handle postprocessing for PossiblyNormalCompletion _createAdditionalEffects(effects, fatalOnAbrupt) { let [result, generator] = effects; let retValue = { effects, transforms: [] }; // Create the effects, arguments and buildNode for the return value, saving them in AdditionalFunctionEffects if (result instanceof _completions.PossiblyNormalCompletion) { let { joinCondition, consequent, alternate, consequentEffects, alternateEffects } = result; let containsValue = consequent instanceof _index.Value || consequent instanceof _completions.ReturnCompletion || alternate instanceof _index.Value || alternate instanceof _completions.ReturnCompletion; let containsJoinedAbrupt = consequent instanceof _completions.JoinedAbruptCompletions || alternate instanceof _completions.JoinedAbruptCompletions; if (!containsValue && !containsJoinedAbrupt) { if (!fatalOnAbrupt) { return null; } this.realm.handleError(new _errors.CompilerDiagnostic("Additional function with this type of abrupt exit not supported", result.location, "PP1002", "FatalError")); throw new _errors.FatalError(); } // Here we join the two sets of Effects from the PossiblyNormalCompletion after // the additional function's return so that the serializer can emit the proper // throw and return values. // Force joinEffects to join the effects by changing result. let consequentResult = consequentEffects[0]; let alternateResult = alternateEffects[0]; consequentEffects[0] = this.realm.intrinsics.undefined; alternateEffects[0] = this.realm.intrinsics.undefined; let joinedEffects = _singletons.Join.joinEffects(this.realm, joinCondition, consequentEffects, alternateEffects); consequentEffects[0] = consequentResult; alternateEffects[0] = alternateResult; let args, buildNode; this.realm.withEffectsAppliedInGlobalEnv(() => { this.realm.withEffectsAppliedInGlobalEnv(() => { [args, buildNode] = generator.getThrowOrReturn(joinCondition, consequent, alternate); return null; }, joinedEffects); return null; }, effects); retValue.joinedEffects = joinedEffects; retValue.returnArguments = args; retValue.returnBuildNode = buildNode; } return retValue; } _generateWriteEffectsForReactComponentTree(componentType, effects, componentTreeState, evaluatedNode) { let additionalFunctionEffects = this._createAdditionalEffects(effects, false); if (additionalFunctionEffects === null) { // TODO we don't support this yet, but will do very soon // to unblock work, we'll just return at this point right now evaluatedNode.status = "UNSUPPORTED_COMPLETION"; return; } let value = effects[0]; if (value === this.realm.intrinsics.undefined) { // if we get undefined, then this component tree failed and a message was already logged // in the reconciler return; } if ((0, _utils.valueIsClassComponent)(this.realm, componentType)) { if (componentTreeState.status === "SIMPLE") { // if the root component was a class and is now simple, we can convert it from a class // component to a functional component (0, _utils.convertSimpleClassComponentToFunctionalComponent)(this.realm, componentType, additionalFunctionEffects); (0, _utils.normalizeFunctionalComponentParamaters)(componentType); this.writeEffects.set(componentType, additionalFunctionEffects); } else { let prototype = (0, _index2.Get)(this.realm, componentType, "prototype"); (0, _invariant2.default)(prototype instanceof _index.ObjectValue); let renderMethod = (0, _index2.Get)(this.realm, prototype, "render"); (0, _invariant2.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue); this.writeEffects.set(renderMethod, additionalFunctionEffects); } } else { if (componentTreeState.status === "COMPLEX") { (0, _utils.convertFunctionalComponentToComplexClassComponent)(this.realm, componentType, componentTreeState.componentType, additionalFunctionEffects); let prototype = (0, _index2.Get)(this.realm, componentType, "prototype"); (0, _invariant2.default)(prototype instanceof _index.ObjectValue); let renderMethod = (0, _index2.Get)(this.realm, prototype, "render"); (0, _invariant2.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue); this.writeEffects.set(renderMethod, additionalFunctionEffects); } else { (0, _utils.normalizeFunctionalComponentParamaters)(componentType); this.writeEffects.set(componentType, additionalFunctionEffects); } } } checkRootReactComponentTrees(statistics, react) { let logger = this.moduleTracer.modules.logger; let recordedReactRootValues = this.__generateAdditionalFunctionsMap("__reactComponentTrees"); // Get write effects of the components if (this.realm.react.verbose) { logger.logInformation(`Evaluating ${recordedReactRootValues.size} React component tree roots...`); } for (let [componentRoot, { config }] of recordedReactRootValues) { (0, _invariant2.default)(config); let reconciler = new _reconcilation.Reconciler(this.realm, this.moduleTracer, statistics, react, config); let componentType = (0, _utils.getComponentTypeFromRootValue)(this.realm, componentRoot); if (componentType === null) { continue; } let evaluatedRootNode = (0, _utils.createReactEvaluatedNode)("ROOT", (0, _utils.getComponentName)(this.realm, componentType)); logger.logInformation(`- ${evaluatedRootNode.name} (root)...`); statistics.evaluatedRootNodes.push(evaluatedRootNode); if (reconciler.hasEvaluatedRootNode(componentType, evaluatedRootNode)) { continue; } let effects = reconciler.render(componentType, null, null, true, evaluatedRootNode); let componentTreeState = reconciler.componentTreeState; this._generateWriteEffectsForReactComponentTree(componentType, effects, componentTreeState, evaluatedRootNode); // for now we just use abstract props/context, in the future we'll create a new branch with a new component // that used the props/context. It will extend the original component and only have a render method for (let _ref of componentTreeState.branchedComponentTrees) { let { rootValue: branchRootValue, nested, evaluatedNode } = _ref; (0, _utils.evaluateComponentTreeBranch)(this.realm, effects, nested, () => { let branchComponentType = (0, _utils.getComponentTypeFromRootValue)(this.realm, branchRootValue); if (branchComponentType === null) { evaluatedNode.status = "UNKNOWN_TYPE"; return; } // so we don't process the same component multiple times (we might change this logic later) if (reconciler.hasEvaluatedRootNode(branchComponentType, evaluatedNode)) { return; } reconciler.clearComponentTreeState(); logger.logInformation(` - ${evaluatedNode.name} (branch)...`); let branchEffects = reconciler.render(branchComponentType, null, null, false, evaluatedNode); let branchComponentTreeState = reconciler.componentTreeState; this._generateWriteEffectsForReactComponentTree(branchComponentType, branchEffects, branchComponentTreeState, evaluatedNode); }); } if (this.realm.react.output === "bytecode") { throw new _errors.FatalError("TODO: implement React bytecode output format"); } } } _generateAdditionalFunctionCallsFromDirective() { let recordedAdditionalFunctions = this.__generateAdditionalFunctionsMap("__optimizedFunctions"); // The additional functions we registered at runtime are recorded at: // global.__optimizedFunctions.id let calls = []; for (let [funcValue, { funcId }] of recordedAdditionalFunctions) { // TODO #987: Make Additional Functions work with arguments (0, _invariant2.default)(funcValue instanceof _index.FunctionValue); calls.push([funcValue, t.callExpression(t.memberExpression(t.memberExpression(t.identifier("global"), t.identifier("__optimizedFunctions")), t.identifier(funcId)), [])]); } return calls; } _callOfFunction(funcValue) { const globalThis = this.realm.$GlobalEnv.environmentRecord.WithBaseObject(); let call = funcValue.$Call; (0, _invariant2.default)(call); let numArgs = funcValue.getLength(); let args = []; (0, _invariant2.default)(funcValue instanceof _index.ECMAScriptSourceFunctionValue); let params = funcValue.$FormalParameters; if (numArgs && numArgs > 0 && params) { for (let parameterId of params) { if (t.isIdentifier(parameterId)) { // Create an AbstractValue similar to __abstract being called args.push((0, _utils2.createAbstractArgument)(this.realm, parameterId.name, funcValue.expressionLocation)); } else { this.realm.handleError(new _errors.CompilerDiagnostic("Non-identifier args to additional functions unsupported", funcValue.expressionLocation, "PP1005", "FatalError")); throw new _errors.FatalError("Non-identifier args to additional functions unsupported"); } } } return call.bind(this, globalThis, args); } checkThatFunctionsAreIndependent() { let additionalFunctions = this.__generateAdditionalFunctionsMap("__optimizedFunctions"); for (let [funcValue] of additionalFunctions) { (0, _invariant2.default)(funcValue instanceof _index.FunctionValue); let call = this._callOfFunction(funcValue); let effects = this.realm.evaluatePure(() => this.realm.evaluateForEffectsInGlobalEnv(call, undefined, "additional function")); (0, _invariant2.default)(effects); let additionalFunctionEffects = this._createAdditionalEffects(effects, true); (0, _invariant2.default)(additionalFunctionEffects); this.writeEffects.set(funcValue, additionalFunctionEffects); } // check that functions are independent let conflicts = new Map(); for (let [fun1] of additionalFunctions) { (0, _invariant2.default)(fun1 instanceof _index.FunctionValue); let fun1Name = this.functionExpressions.get(fun1) || fun1.intrinsicName || "(unknown function)"; // Also do argument validation here let additionalFunctionEffects = this.writeEffects.get(fun1); (0, _invariant2.default)(additionalFunctionEffects !== undefined); let e1 = additionalFunctionEffects.effects; (0, _invariant2.default)(e1 !== undefined); if (e1[0] instanceof _completions.Completion && !e1[0] instanceof _completions.PossiblyNormalCompletion) { let error = new _errors.CompilerDiagnostic(`Additional function ${fun1Name} may terminate abruptly`, e1[0].location, "PP1002", "FatalError"); this.realm.handleError(error); throw new _errors.FatalError(); } for (let [fun2] of additionalFunctions) { if (fun1 === fun2) continue; (0, _invariant2.default)(fun2 instanceof _index.FunctionValue); this.reportWriteConflicts(fun1Name, conflicts, e1[3], this._callOfFunction(fun2)); } } if (conflicts.size > 0) { for (let diagnostic of conflicts.values()) this.realm.handleError(diagnostic); throw new _errors.FatalError(); } } getAdditionalFunctionValuesToEffects() { return this.writeEffects; } reportWriteConflicts(fname, conflicts, pbs, call2) { let reportConflict = location => { let error = new _errors.CompilerDiagnostic(`Property access conflicts with write in additional function ${fname}`, location, "PP1003", "FatalError"); 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, _invariant2.default)(location); if (writtenObjects.has(ob) && !conflicts.has(location)) reportConflict(location); }; let oldReportPropertyAccess = this.realm.reportPropertyAccess; this.realm.reportPropertyAccess = pb => { let location = this.realm.currentLocation; if (!location) return; // happens only when accessing an additional function property if (pbs.has(pb) && !conflicts.has(location)) reportConflict(location); }; try { (0, _errors2.ignoreErrorsIn)(this.realm, () => this.realm.evaluateForEffectsInGlobalEnv(call2)); } finally { this.realm.reportPropertyAccess = oldReportPropertyAccess; this.realm.reportObjectGetOwnProperties = oldReportObjectGetOwnProperties; } } } exports.Functions = Functions; /** * 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=functions.js.map