UNPKG

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