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