prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
1,107 lines (881 loc) • 38.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createOperationDescriptor = createOperationDescriptor;
exports.attemptToMergeEquivalentObjectAssigns = attemptToMergeEquivalentObjectAssigns;
exports.Generator = exports.TemporalObjectAssignEntry = exports.TemporalOperationEntry = exports.GeneratorEntry = void 0;
var _index = require("../values/index.js");
var _errors = require("../errors.js");
var _index2 = require("../domains/index.js");
var _invariant = _interopRequireDefault(require("../invariant.js"));
var _completions = require("../completions.js");
var _singletons = require("../singletons.js");
var _PreludeGenerator = require("./PreludeGenerator.js");
var _descriptors = require("../descriptors.js");
var _environment = require("../environment");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function createOperationDescriptor(type, data = {}) {
return {
data,
type
};
}
class GeneratorEntry {
constructor(realm) {
// We increment the index of every TemporalOperationEntry created.
// This should match up as a form of timeline value due to the tree-like
// structure we use to create entries during evaluation. For example,
// if all AST nodes in a BlockStatement resulted in a temporal operation
// for each AST node, then each would have a sequential index as to its
// position of how it was evaluated in the BlockSstatement.
this.index = realm.temporalEntryCounter++;
}
print(printer) {
(0, _invariant.default)(false, "GeneratorEntry is an abstract base class");
}
visit(callbacks, containingGenerator) {
(0, _invariant.default)(false, "GeneratorEntry is an abstract base class");
}
serialize(context) {
(0, _invariant.default)(false, "GeneratorEntry is an abstract base class");
}
getDependencies() {
(0, _invariant.default)(false, "GeneratorEntry is an abstract base class");
}
notEqualToAndDoesNotHappenBefore(entry) {
return this.index > entry.index;
}
notEqualToAndDoesNotHappenAfter(entry) {
return this.index < entry.index;
}
}
exports.GeneratorEntry = GeneratorEntry;
class TemporalOperationEntry extends GeneratorEntry {
constructor(realm, args) {
super(realm);
Object.assign(this, args);
if (this.mutatesOnly !== undefined) {
(0, _invariant.default)(!this.isPure);
for (let arg of this.mutatesOnly) {
(0, _invariant.default)(this.args.includes(arg));
}
}
(0, _invariant.default)(this.operationDescriptor !== undefined);
}
print(printer) {
const operationDescriptor = this.operationDescriptor;
printer.printGeneratorEntry(this.declared, operationDescriptor.type, this.args, this.operationDescriptor.data, {
isPure: !!this.isPure,
mutatesOnly: this.mutatesOnly
});
}
toDisplayJson(depth) {
if (depth <= 0) return `TemporalOperation${this.index}`;
let obj = _objectSpread({
type: "TemporalOperation"
}, this);
delete obj.operationDescriptor;
return _singletons.Utils.verboseToDisplayJson(obj, depth);
}
visit(callbacks, containingGenerator) {
let omit = this.isPure && this.declared && callbacks.canOmit(this.declared);
if (!omit && this.declared && this.mutatesOnly !== undefined) {
omit = true;
for (let arg of this.mutatesOnly) {
if (!callbacks.canOmit(arg)) {
omit = false;
}
}
}
if (omit) {
callbacks.recordDelayedEntry(containingGenerator, this);
return false;
} else {
if (this.declared) callbacks.recordDeclaration(this.declared);
for (let i = 0, n = this.args.length; i < n; i++) {
let originalArg = this.args[i];
let visitedArg = callbacks.visitEquivalentValue(originalArg);
this.args[i] = visitedArg;
if (i === 0) {
switch (this.operationDescriptor.type) {
case "CALL_BAILOUT":
if (originalArg === this.operationDescriptor.data.thisArg) this.operationDescriptor.data.thisArg = visitedArg;
break;
case "CONDITIONAL_THROW":
this.operationDescriptor.data.value = visitedArg;
break;
default:
break;
}
} else if (i === 1) {
switch (this.operationDescriptor.type) {
case "EMIT_PROPERTY_ASSIGNMENT":
case "LOGICAL_PROPERTY_ASSIGNMENT":
this.operationDescriptor.data.value = visitedArg;
break;
case "CONDITIONAL_PROPERTY_ASSIGNMENT":
if (originalArg === this.operationDescriptor.data.value) this.operationDescriptor.data.value = visitedArg;
break;
case "DEFINE_PROPERTY":
(0, _invariant.default)(visitedArg instanceof _index.ObjectValue);
this.operationDescriptor.data.object = visitedArg;
break;
default:
break;
}
}
}
let dependencies = this.getDependencies();
if (dependencies !== undefined) for (let dependency of dependencies) callbacks.visitGenerator(dependency, containingGenerator);
return true;
}
}
serialize(context) {
let omit = this.isPure && this.declared && context.canOmit(this.declared);
if (!omit && this.declared && this.mutatesOnly !== undefined) {
omit = true;
for (let arg of this.mutatesOnly) {
if (!context.canOmit(arg)) {
omit = false;
}
}
}
if (!omit) {
let nodes = this.args.map((boundArg, i) => context.serializeValue(boundArg));
let valuesToProcess = new Set();
let declaredId = this.declared !== undefined ? this.declared.intrinsicName : undefined;
let node = context.serializeOperationDescriptor(this.operationDescriptor, nodes, context, valuesToProcess, declaredId);
if (node.type === "BlockStatement") {
let block = node;
let statements = block.body;
if (statements.length === 0) return;
if (statements.length === 1) {
node = statements[0];
}
}
let declared = this.declared;
if (declared !== undefined && context.options.debugScopes) {
context.emit(context.serializeDebugScopeComment(declared));
}
context.emit(node);
context.processValues(valuesToProcess);
if (this.declared !== undefined) context.declare(this.declared);
}
}
getDependencies() {
const operationDescriptor = this.operationDescriptor;
switch (operationDescriptor.type) {
case "DO_WHILE":
let generator = operationDescriptor.data.generator;
(0, _invariant.default)(generator !== undefined);
return [generator];
case "JOIN_GENERATORS":
let generators = operationDescriptor.data.generators;
(0, _invariant.default)(generators !== undefined);
return generators;
default:
return undefined;
}
}
}
exports.TemporalOperationEntry = TemporalOperationEntry;
class TemporalObjectAssignEntry extends TemporalOperationEntry {
visit(callbacks, containingGenerator) {
let declared = this.declared;
if (!(declared instanceof _index.AbstractObjectValue || declared instanceof _index.ObjectValue)) {
return false;
}
let realm = declared.$Realm; // The only optimization we attempt to do to Object.assign for now is merging of multiple entries
// into a new generator entry.
let result = attemptToMergeEquivalentObjectAssigns(realm, callbacks, this);
if (result instanceof TemporalObjectAssignEntry) {
let nextResult = result;
while (nextResult instanceof TemporalObjectAssignEntry) {
nextResult = attemptToMergeEquivalentObjectAssigns(realm, callbacks, result); // If we get back a TemporalObjectAssignEntry, then we have successfully merged a single
// Object.assign, but we may be able to merge more. So repeat the process.
if (nextResult instanceof TemporalObjectAssignEntry) {
result = nextResult;
}
} // We have an optimized temporal entry, so replace the current temporal
// entry and visit that entry instead.
this.args = result.args;
} else if (result === "POSSIBLE_OPTIMIZATION") {
callbacks.recordDelayedEntry(containingGenerator, this);
return false;
}
return super.visit(callbacks, containingGenerator);
}
}
exports.TemporalObjectAssignEntry = TemporalObjectAssignEntry;
class ModifiedPropertyEntry extends GeneratorEntry {
constructor(realm, args) {
super(realm);
Object.assign(this, args);
}
print(printer) {
printer.printGeneratorEntry(undefined, "MODIFIED_PROPERTY", [], {
descriptor: this.newDescriptor,
propertyBinding: this.propertyBinding
}, {
isPure: false,
mutatesOnly: undefined
});
}
toDisplayString() {
let propertyKey = this.propertyBinding.key;
let propertyKeyString = propertyKey instanceof _index.Value ? propertyKey.toDisplayString() : propertyKey;
(0, _invariant.default)(propertyKeyString !== undefined);
return `[ModifiedProperty ${propertyKeyString}]`;
}
serialize(context) {
let desc = this.propertyBinding.descriptor;
(0, _invariant.default)(desc === this.newDescriptor);
context.emitPropertyModification(this.propertyBinding);
}
visit(context, containingGenerator) {
(0, _invariant.default)(containingGenerator === this.containingGenerator, "This entry requires effects to be applied and may not be moved");
let desc = this.propertyBinding.descriptor;
(0, _invariant.default)(desc === this.newDescriptor);
context.visitModifiedProperty(this.propertyBinding);
return true;
}
getDependencies() {
return undefined;
}
}
class ModifiedBindingEntry extends GeneratorEntry {
constructor(realm, args) {
super(realm);
Object.assign(this, args);
}
print(printer) {
printer.printGeneratorEntry(undefined, "MODIFIED_BINDING", [], {
binding: this.modifiedBinding,
value: this.modifiedBinding.value
}, {
isPure: false,
mutatesOnly: undefined
});
}
toDisplayString() {
return `[ModifiedBinding ${this.modifiedBinding.name}]`;
}
serialize(context) {
context.emitBindingModification(this.modifiedBinding);
}
visit(context, containingGenerator) {
(0, _invariant.default)(containingGenerator === this.containingGenerator, "This entry requires effects to be applied and may not be moved");
context.visitModifiedBinding(this.modifiedBinding);
return true;
}
getDependencies() {
return undefined;
}
}
class ReturnValueEntry extends GeneratorEntry {
constructor(realm, generator, returnValue) {
super(realm);
this.returnValue = returnValue.promoteEmptyToUndefined();
this.containingGenerator = generator;
}
print(printer) {
printer.printGeneratorEntry(undefined, "RETURN", [this.returnValue], {}, {
isPure: false,
mutatesOnly: undefined
});
}
toDisplayString() {
return `[Return ${this.returnValue.toDisplayString()}]`;
}
visit(context, containingGenerator) {
(0, _invariant.default)(containingGenerator === this.containingGenerator, "This entry requires effects to be applied and may not be moved");
this.returnValue = context.visitEquivalentValue(this.returnValue);
return true;
}
serialize(context) {
context.emit(context.serializeReturnValue(this.returnValue));
}
getDependencies() {
return undefined;
}
}
class BindingAssignmentEntry extends GeneratorEntry {
constructor(realm, binding, value) {
super(realm);
this.binding = binding;
this.value = value;
}
print(printer) {
printer.printGeneratorEntry(undefined, "BINDING_ASSIGNMENT", [this.value], {
binding: this.binding
}, {
isPure: false,
mutatesOnly: undefined
});
}
toDisplayString() {
return `[BindingAssignment ${this.binding.name} = ${this.value.toDisplayString()}]`;
}
serialize(context) {
context.emit(context.serializeBindingAssignment(this.binding, this.value));
}
visit(context, containingGenerator) {
this.value = context.visitBindingAssignment(this.binding, this.value);
return true;
}
getDependencies() {
return undefined;
}
}
class Generator {
constructor(realm, name, pathConditions, effects) {
(0, _invariant.default)(realm.useAbstractInterpretation);
let realmPreludeGenerator = realm.preludeGenerator;
(0, _invariant.default)(realmPreludeGenerator);
this.preludeGenerator = realmPreludeGenerator;
this.realm = realm;
this._entries = [];
this.id = realm.nextGeneratorId++;
this._name = name;
this.effectsToApply = effects;
this.pathConditions = pathConditions;
}
print(printer) {
for (let entry of this._entries) entry.print(printer);
}
toDisplayString() {
return _singletons.Utils.jsonToDisplayString(this, 2);
}
toDisplayJson(depth) {
if (depth <= 0) return `Generator${this.id}-${this._name}`;
return _singletons.Utils.verboseToDisplayJson(this, depth);
}
static _generatorOfEffects(realm, name, additionalFunctionEffects, optimizedFunction, preEvaluationComponentToWriteEffectFunction, effects) {
let {
result,
generator,
modifiedBindings,
modifiedProperties,
createdObjects
} = effects;
let output = new Generator(realm, name, generator.pathConditions, effects);
output.appendGenerator(generator, generator._name);
for (let propertyBinding of modifiedProperties.keys()) {
let object = propertyBinding.object;
(0, _invariant.default)(object.isValid());
if (createdObjects.has(object)) continue; // Created Object's binding
if (_index.ObjectValue.refuseSerializationOnPropertyBinding(propertyBinding)) continue; // modification to internal state
// modifications to intrinsic objects are tracked in the generator
if (object.isIntrinsic()) continue;
output.emitPropertyModification(propertyBinding);
}
for (let [modifiedBinding, previousValue] of modifiedBindings.entries()) {
let cannonicalize = functionValue => preEvaluationComponentToWriteEffectFunction.get(functionValue) || functionValue;
let optimizedFunctionValue = optimizedFunction;
(0, _invariant.default)(optimizedFunctionValue);
(0, _invariant.default)(cannonicalize(optimizedFunctionValue) === optimizedFunctionValue, "These values should be canonical already"); // Walks up the parent chain for the given optimized function checking if the value or any of its parents are
// equal to the optimized function we're currently building a generator for.
let valueOrParentEqualsFunction = functionValue => {
let canonicalOptimizedFunction = cannonicalize(functionValue);
if (canonicalOptimizedFunction === optimizedFunctionValue) return true;
let additionalEffects = additionalFunctionEffects.get(canonicalOptimizedFunction);
(0, _invariant.default)(additionalEffects !== undefined);
let parent = additionalEffects.parentAdditionalFunction;
if (parent !== undefined) return valueOrParentEqualsFunction(parent);
return false;
};
let environment = modifiedBinding.environment;
if (environment instanceof _environment.FunctionEnvironmentRecord && environment.$FunctionObject === optimizedFunctionValue) continue;
let creatingOptimizedFunction = environment.creatingOptimizedFunction;
if (creatingOptimizedFunction && valueOrParentEqualsFunction(creatingOptimizedFunction)) continue; // TODO #2586: modifiedBinding.value should always exist
if (modifiedBinding.value || previousValue.value) {
output.emitBindingModification(modifiedBinding);
}
}
if (result instanceof _index.UndefinedValue) return output;
if (result instanceof _completions.SimpleNormalCompletion) {
output.emitReturnValue(result.value);
} else if (result instanceof _completions.ThrowCompletion) {
output.emitThrow(result.value);
} else if (result instanceof _completions.JoinedNormalAndAbruptCompletions) {
let selector = c => c instanceof _completions.ThrowCompletion && c.value !== realm.intrinsics.__bottomValue && !(c.value instanceof _index.EmptyValue);
output.emitConditionalThrow(_singletons.Join.joinValuesOfSelectedCompletions(selector, result, true));
output.emitReturnValue(result.value);
} else {
(0, _invariant.default)(false);
}
return output;
} // Make sure to to fixup
// how to apply things around sets of things
static fromEffects(effects, realm, name, additionalFunctionEffects, preEvaluationComponentToWriteEffectFunction, optimizedFunction) {
return realm.withEffectsAppliedInGlobalEnv(this._generatorOfEffects.bind(this, realm, name, additionalFunctionEffects, optimizedFunction, preEvaluationComponentToWriteEffectFunction), effects);
}
emitPropertyModification(propertyBinding) {
(0, _invariant.default)(this.effectsToApply !== undefined);
let desc = propertyBinding.descriptor;
if (desc !== undefined && desc instanceof _descriptors.PropertyDescriptor) {
let value = desc.value;
if (value instanceof _index.AbstractValue) {
if (value.kind === "conditional") {
let [c, x, y] = value.args;
if (c instanceof _index.AbstractValue && c.kind === "template for property name condition") {
let ydesc = new _descriptors.PropertyDescriptor(Object.assign({}, desc, {
value: y
}));
let yprop = Object.assign({}, propertyBinding, {
descriptor: ydesc
});
this.emitPropertyModification(yprop);
let xdesc = new _descriptors.PropertyDescriptor(Object.assign({}, desc, {
value: x
}));
let key = c.args[0];
(0, _invariant.default)(key instanceof _index.AbstractValue);
let xprop = Object.assign({}, propertyBinding, {
key,
descriptor: xdesc
});
this.emitPropertyModification(xprop);
return;
}
} else if (value.kind === "template for prototype member expression") {
return;
}
}
}
this._entries.push(new ModifiedPropertyEntry(this.realm, {
propertyBinding,
newDescriptor: desc,
containingGenerator: this
}));
}
emitBindingModification(modifiedBinding) {
(0, _invariant.default)(this.effectsToApply !== undefined);
this._entries.push(new ModifiedBindingEntry(this.realm, {
modifiedBinding,
containingGenerator: this
}));
}
emitReturnValue(result) {
this._entries.push(new ReturnValueEntry(this.realm, this, result));
}
getName() {
return `${this._name}(#${this.id})`;
}
empty() {
return this._entries.length === 0;
}
emitGlobalDeclaration(key, value) {
this.preludeGenerator.declaredGlobals.add(key);
if (!(value instanceof _index.UndefinedValue)) this.emitGlobalAssignment(key, value);
}
emitGlobalAssignment(key, value) {
this._addEntry({
args: [value, new _index.StringValue(this.realm, key)],
operationDescriptor: createOperationDescriptor("GLOBAL_ASSIGNMENT")
});
}
emitConcreteModel(key, value) {
this._addEntry({
args: [(0, _singletons.concretize)(this.realm, value), new _index.StringValue(this.realm, key)],
operationDescriptor: createOperationDescriptor("CONCRETE_MODEL")
});
}
emitGlobalDelete(key) {
this._addEntry({
args: [new _index.StringValue(this.realm, key)],
operationDescriptor: createOperationDescriptor("GLOBAL_DELETE")
});
}
emitBindingAssignment(binding, value) {
this._entries.push(new BindingAssignmentEntry(this.realm, binding, value));
}
emitPropertyAssignment(object, key, value) {
if (object instanceof _index.ObjectValue && object.refuseSerialization) {
return;
}
if (typeof key === "string") {
key = new _index.StringValue(this.realm, key);
}
this._addEntry({
args: [object, value, key],
operationDescriptor: createOperationDescriptor("EMIT_PROPERTY_ASSIGNMENT", {
value
})
});
}
emitDefineProperty(object, key, desc, isDescChanged = true) {
if (object.refuseSerialization) return;
if (desc.enumerable && desc.configurable && desc.writable && desc.value && !isDescChanged) {
let descValue = desc.value;
(0, _invariant.default)(descValue instanceof _index.Value);
this.emitPropertyAssignment(object, key, descValue);
} else {
desc = new _descriptors.PropertyDescriptor(desc);
let descValue = desc.value || object.$Realm.intrinsics.undefined;
(0, _invariant.default)(descValue instanceof _index.Value);
this._addEntry({
args: [new _index.StringValue(this.realm, key), object, descValue, desc.get || object.$Realm.intrinsics.undefined, desc.set || object.$Realm.intrinsics.undefined],
operationDescriptor: createOperationDescriptor("DEFINE_PROPERTY", {
object,
descriptor: desc
})
});
}
}
emitPropertyDelete(object, key) {
if (object.refuseSerialization) return;
this._addEntry({
args: [object, new _index.StringValue(this.realm, key)],
operationDescriptor: createOperationDescriptor("PROPERTY_DELETE")
});
}
emitCall(callFunctionRef, args) {
this._addEntry({
args,
operationDescriptor: createOperationDescriptor("EMIT_CALL", {
callFunctionRef
})
});
}
emitConsoleLog(method, args) {
this._addEntry({
args: [new _index.StringValue(this.realm, method), ...args.map(v => typeof v === "string" ? new _index.StringValue(this.realm, v) : v)],
operationDescriptor: createOperationDescriptor("CONSOLE_LOG")
});
} // test must be a temporal value, which means that it must have a defined intrinsicName
emitDoWhileStatement(test, body) {
this._addEntry({
args: [],
operationDescriptor: createOperationDescriptor("DO_WHILE", {
generator: body,
value: test
})
});
}
emitConditionalThrow(value) {
if (value instanceof _index.EmptyValue) return;
this._issueThrowCompilerDiagnostic(value);
this._addEntry({
args: [value],
operationDescriptor: createOperationDescriptor("CONDITIONAL_THROW", {
value
})
});
}
_issueThrowCompilerDiagnostic(value) {
let message = "Program may terminate with exception";
if (value instanceof _index.ObjectValue) {
let object = value;
let objectMessage = this.realm.evaluateWithUndo(() => object._SafeGetDataPropertyValue("message"));
if (objectMessage instanceof _index.StringValue) message += `: ${objectMessage.value}`;
const objectStack = this.realm.evaluateWithUndo(() => object._SafeGetDataPropertyValue("stack"));
if (objectStack instanceof _index.StringValue) message += `
${objectStack.value}`;
}
const diagnostic = new _errors.CompilerDiagnostic(message, value.expressionLocation, "PP0023", "Warning");
this.realm.handleError(diagnostic);
}
emitThrow(value) {
this._issueThrowCompilerDiagnostic(value);
this.emitStatement([value], createOperationDescriptor("THROW"));
} // Checks the full set of possible concrete values as well as typeof
// for any AbstractValues
// e.g: (obj.property !== undefined && typeof obj.property !== "object")
// NB: if the type of the AbstractValue is top, skips the invariant
emitFullInvariant(object, key, value) {
if (object.refuseSerialization) return;
if (value instanceof _index.AbstractValue) {
let isTop = false;
let concreteComparisons = [];
let typeComparisons = new Set();
function populateComparisonsLists(absValue) {
if (absValue.kind === "abstractConcreteUnion") {
// recurse
for (let nestedValue of absValue.args) if (nestedValue instanceof _index.ConcreteValue) {
concreteComparisons.push(nestedValue);
} else {
(0, _invariant.default)(nestedValue instanceof _index.AbstractValue);
populateComparisonsLists(nestedValue);
}
} else if (absValue.getType() === _index.Value) {
isTop = true;
} else {
typeComparisons.add(absValue.getType());
}
}
populateComparisonsLists(value); // No point in doing the invariant if we don't know the type
// of one of the nested abstract values
if (isTop) {
return;
} else {
this._emitInvariant([new _index.StringValue(this.realm, key), value, value], createOperationDescriptor("FULL_INVARIANT_ABSTRACT", {
concreteComparisons,
typeComparisons
}), createOperationDescriptor("INVARIANT_APPEND"));
}
} else if (value instanceof _index.FunctionValue) {
// We do a special case for functions,
// as we like to use concrete functions in the model to model abstract behaviors.
// These concrete functions do not have the right identity.
this._emitInvariant([new _index.StringValue(this.realm, key), object, value, object], createOperationDescriptor("FULL_INVARIANT_FUNCTION"), createOperationDescriptor("INVARIANT_APPEND"));
} else {
this._emitInvariant([new _index.StringValue(this.realm, key), object, value, object], createOperationDescriptor("FULL_INVARIANT"), createOperationDescriptor("INVARIANT_APPEND"));
}
}
emitPropertyInvariant(object, key, state) {
if (object.refuseSerialization) return;
this._emitInvariant([new _index.StringValue(this.realm, key), object, object], createOperationDescriptor("PROPERTY_INVARIANT", {
state
}), createOperationDescriptor("INVARIANT_APPEND"));
}
_emitInvariant(args, violationConditionOperationDescriptor, appendLastToInvariantOperationDescriptor) {
(0, _invariant.default)(this.realm.invariantLevel > 0);
let invariantOperationDescriptor = createOperationDescriptor("INVARIANT", {
appendLastToInvariantOperationDescriptor,
violationConditionOperationDescriptor
});
this._addEntry({
args,
operationDescriptor: invariantOperationDescriptor
});
}
emitCallAndCaptureResult(types, values, callFunctionRef, args, kind) {
return this.deriveAbstract(types, values, args, createOperationDescriptor("EMIT_CALL_AND_CAPTURE_RESULT", {
callFunctionRef
}), {
kind
});
}
emitStatement(args, operationDescriptor) {
(0, _invariant.default)(typeof operationDescriptor !== "function");
this._addEntry({
args,
operationDescriptor
});
}
emitVoidExpression(types, values, args, operationDescriptor) {
this._addEntry({
args,
operationDescriptor
});
return this.realm.intrinsics.undefined;
}
emitForInStatement(o, lh, sourceObject, targetObject, boundName) {
this._addEntry({
// duplicate args to ensure refcount > 1
args: [o, targetObject, sourceObject, targetObject, sourceObject],
operationDescriptor: createOperationDescriptor("FOR_IN", {
boundName,
lh
})
});
}
deriveConcreteObject(buildValue, args, operationDescriptor, optionalArgs) {
let id = this.preludeGenerator.nameGenerator.generate("derived");
let value = buildValue(id);
value.intrinsicNameGenerated = true;
value.isScopedTemplate = true; // because this object doesn't exist ahead of time, and the visitor would otherwise declare it in the common scope
(0, _invariant.default)(value.intrinsicName === id);
this._addDerivedEntry({
isPure: optionalArgs ? optionalArgs.isPure : undefined,
declared: value,
args,
operationDescriptor
});
return value;
}
deriveAbstract(types, values, args, operationDescriptor, optionalArgs) {
let id = this.preludeGenerator.nameGenerator.generate("derived");
let options = {};
if (optionalArgs && optionalArgs.kind !== undefined) options.kind = optionalArgs.kind;
if (optionalArgs && optionalArgs.shape !== undefined) options.shape = optionalArgs.shape;
let Constructor = _index.Value.isTypeCompatibleWith(types.getType(), _index.ObjectValue) ? _index.AbstractObjectValue : _index.AbstractValue;
let res = new Constructor(this.realm, types, values, 1735003607742176 + this.realm.derivedIds.size, [], createOperationDescriptor("IDENTIFIER", {
id
}), options);
res.intrinsicName = id;
this._addDerivedEntry({
isPure: optionalArgs ? optionalArgs.isPure : undefined,
declared: res,
args,
operationDescriptor,
mutatesOnly: optionalArgs ? optionalArgs.mutatesOnly : undefined
});
let type = types.getType();
if (optionalArgs && optionalArgs.skipInvariant) return res;
let typeofString;
if (type instanceof _index.FunctionValue) typeofString = "function";else if (type === _index.UndefinedValue) (0, _invariant.default)(false);else if (type === _index.NullValue) (0, _invariant.default)(false);else if (type === _index.StringValue) typeofString = "string";else if (type === _index.BooleanValue) typeofString = "boolean";else if (type === _index.NumberValue) typeofString = "number";else if (type === _index.IntegralValue) typeofString = "number";else if (type === _index.SymbolValue) typeofString = "symbol";else if (type === _index.ObjectValue) typeofString = "object";
if (typeofString !== undefined && this.realm.invariantLevel >= 1) {
// Verify that the types are as expected, a failure of this invariant
// should mean the model is wrong.
this._emitInvariant([new _index.StringValue(this.realm, typeofString), res, res], createOperationDescriptor("DERIVED_ABSTRACT_INVARIANT"), createOperationDescriptor("SINGLE_ARG"));
}
return res;
}
visit(callbacks) {
let visitFn = () => {
for (let entry of this._entries) entry.visit(callbacks, this);
return null;
};
if (this.effectsToApply) {
this.realm.withEffectsAppliedInGlobalEnv(visitFn, this.effectsToApply);
} else {
visitFn();
}
}
serialize(context) {
let serializeFn = () => {
context.initGenerator(this);
for (let entry of this._entries) entry.serialize(context);
context.finalizeGenerator(this);
return null;
};
if (this.effectsToApply) {
this.realm.withEffectsAppliedInGlobalEnv(serializeFn, this.effectsToApply);
} else {
serializeFn();
}
}
getDependencies() {
let res = [];
for (let entry of this._entries) {
let dependencies = entry.getDependencies();
if (dependencies !== undefined) res.push(...dependencies);
}
return res;
}
_addEntry(entryArgs) {
let entry;
let operationDescriptor = entryArgs.operationDescriptor;
if (operationDescriptor && operationDescriptor.type === "OBJECT_ASSIGN") {
entry = new TemporalObjectAssignEntry(this.realm, entryArgs);
} else {
entry = new TemporalOperationEntry(this.realm, entryArgs);
}
this.realm.saveTemporalGeneratorEntryArgs(entry);
this._entries.push(entry);
return entry;
}
_addDerivedEntry(entryArgs) {
let declared = entryArgs.declared;
(0, _invariant.default)(declared !== undefined);
let id = declared.intrinsicName;
(0, _invariant.default)(id !== undefined);
let entry = this._addEntry(entryArgs);
this.realm.derivedIds.set(id, entry);
}
appendGenerator(other, leadingComment) {
(0, _invariant.default)(other !== this);
(0, _invariant.default)(other.realm === this.realm);
(0, _invariant.default)(other.preludeGenerator === this.preludeGenerator);
(0, _invariant.default)(other.effectsToApply === undefined);
if (other.empty()) return;
this._entries.push(...other._entries);
}
joinGenerators(joinCondition, generator1, generator2) {
(0, _invariant.default)(generator1 !== this && generator2 !== this && generator1 !== generator2);
if (generator1.empty() && generator2.empty()) return;
let generators = [generator1, generator2];
this._addEntry({
args: [joinCondition],
operationDescriptor: createOperationDescriptor("JOIN_GENERATORS", {
generators
})
});
}
}
exports.Generator = Generator;
// This function attempts to optimize Object.assign calls, by merging mulitple
// calls into one another where possible. For example:
//
// var a = Object.assign({}, someAbstact);
// var b = Object.assign({}, a);
//
// Becomes:
// var b = Object.assign({}, someAbstract, a);
//
function attemptToMergeEquivalentObjectAssigns(realm, callbacks, temporalOperationEntry) {
let args = temporalOperationEntry.args; // If we are Object.assigning 2 or more args
if (args.length < 2) {
return "NO_OPTIMIZATION";
}
let to = args[0]; // Then scan through the args after the "to" of this Object.assign, to see if any
// other sources are the "to" of a previous Object.assign call
loopThroughArgs: for (let i = 1; i < args.length; i++) {
let possibleOtherObjectAssignTo = args[i]; // Ensure that the "to" value can be omitted
// Note: this check is still somewhat fragile and depends on the visiting order
// but it's not a functional problem right now and can be better addressed at a
// later point.
if (!callbacks.canOmit(possibleOtherObjectAssignTo)) {
continue;
} // Check if the "to" was definitely an Object.assign, it should
// be a snapshot AbstractObjectValue
if (possibleOtherObjectAssignTo instanceof _index.AbstractObjectValue) {
let otherTemporalOperationEntry = realm.getTemporalOperationEntryFromDerivedValue(possibleOtherObjectAssignTo);
if (!(otherTemporalOperationEntry instanceof TemporalObjectAssignEntry)) {
continue;
}
let otherArgs = otherTemporalOperationEntry.args; // Object.assign has at least 1 arg
if (otherArgs.length < 1) {
continue;
}
let otherArgsToUse = [];
for (let x = 1; x < otherArgs.length; x++) {
let arg = otherArgs[x]; // The arg might have been leaked, so ensure we do not continue in this case
if (arg instanceof _index.ObjectValue && arg.mightBeLeakedObject()) {
continue loopThroughArgs;
}
if (arg instanceof _index.ObjectValue || arg instanceof _index.AbstractValue) {
let temporalGeneratorEntries = realm.getTemporalGeneratorEntriesReferencingArg(arg); // We need to now check if there are any other temporal entries that exist
// between the Object.assign TemporalObjectAssignEntry that we're trying to
// merge and the current TemporalObjectAssignEntry we're going to merge into.
if (temporalGeneratorEntries !== undefined) {
for (let temporalGeneratorEntry of temporalGeneratorEntries) {
// If the entry is that of another Object.assign, then
// we know that this entry isn't going to cause issues
// with merging the TemporalObjectAssignEntry.
if (temporalGeneratorEntry instanceof TemporalObjectAssignEntry) {
continue;
} // TODO: what if the temporalGeneratorEntry can be omitted and not needed?
// If the index of this entry exists between start and end indexes,
// then we cannot optimize and merge the TemporalObjectAssignEntry
// because another generator entry may have a dependency on the Object.assign
// TemporalObjectAssignEntry we're trying to merge.
if (temporalGeneratorEntry.notEqualToAndDoesNotHappenBefore(otherTemporalOperationEntry) && temporalGeneratorEntry.notEqualToAndDoesNotHappenAfter(temporalOperationEntry)) {
continue loopThroughArgs;
}
}
}
}
otherArgsToUse.push(arg);
} // If we cannot omit the "to" value that means it's being used, so we shall not try to
// optimize this Object.assign.
if (!callbacks.canOmit(to)) {
// our merged Object.assign, shoud look like:
// Object.assign(to, ...prefixArgs, ...otherArgsToUse, ...suffixArgs)
let prefixArgs = args.slice(1, i - 1); // We start at 1, as 0 is the index of "to" a
let suffixArgs = args.slice(i + 1);
let newArgs = [to, ...prefixArgs, ...otherArgsToUse, ...suffixArgs]; // We now create a new TemporalObjectAssignEntry, without mutating the existing
// entry at this point. This new entry is essentially a TemporalObjectAssignEntry
// that contains two Object.assign call TemporalObjectAssignEntry entries that have
// been merged into a single entry. The previous Object.assign TemporalObjectAssignEntry
// should dead-code eliminate away once we replace the original TemporalObjectAssignEntry
// we started with with the new merged on as they will no longer be referenced.
let newTemporalObjectAssignEntryArgs = Object.assign({}, temporalOperationEntry, {
args: newArgs
});
return new TemporalObjectAssignEntry(realm, newTemporalObjectAssignEntryArgs);
} // We might be able to optimize, but we are not sure because "to" can still omit.
// So we return possible optimization status and wait until "to" does get visited.
// It may never get visited, but that's okay as we'll skip the optimization all
// together.
return "POSSIBLE_OPTIMIZATION";
}
}
return "NO_OPTIMIZATION";
}
//# sourceMappingURL=generator.js.map