From db5ed0c7d386a3e0461203fc2ac23c6879ec6730 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 9 Jul 2018 16:46:51 -0700 Subject: [PATCH] Optimize Object.assign calls by merging the temporal entries together where possible (#2206) Summary: Release notes: adds an optimization to Object.assign that attempts to merge calls together where possible I frequently see `Objet.assign` calls litter our internal bundle. Many of them can actually be merged together to reduce bloat and runtime overhead (on that matter, `Object.assign` isn't a cheap call). We do this by adding a new `TemporalObjectAssignEntry` entry, that allows us to add some fine-tuning of Object.assign temporal entries we create. The new `TemporalObjectAssignEntry` entry has an optimization that aims to merge entries by checking if linked nodes, specifically the `Object.assign` calls `to` field, to see if it can literally merge the arguments of many `Object.assign`s together. If a `to` is visited and can't be omitted, it doesn't try to apply this optimization. What we end up with, is a single `Object.assign` call and the others all get dead-code eliminated away because of the `mutatesOnly` logic from earlier. Pull Request resolved: https://github.com/facebook/prepack/pull/2206 Reviewed By: NTillmann Differential Revision: D8775391 Pulled By: trueadm fbshipit-source-id: 41e5d6bb1d51fcfff66b2fe758bd51492ec472d9 --- src/intrinsics/ecma262/JSON.js | 5 +- src/intrinsics/ecma262/Object.js | 15 +- src/react/ReactEquivalenceSet.js | 10 +- src/react/components.js | 27 ++- src/react/utils.js | 38 ++- src/realm.js | 39 ++- src/serializer/Emitter.js | 4 +- src/serializer/ResidualHeapSerializer.js | 6 +- src/serializer/types.js | 2 +- src/utils/generator.js | 227 ++++++++++++++++-- src/values/AbstractValue.js | 9 +- .../optimized-functions/DeadObjectAssign.js | 1 + .../optimized-functions/DeadObjectAssign10.js | 16 ++ .../optimized-functions/DeadObjectAssign11.js | 18 ++ .../optimized-functions/DeadObjectAssign12.js | 22 ++ .../optimized-functions/DeadObjectAssign13.js | 15 ++ .../optimized-functions/DeadObjectAssign14.js | 22 ++ .../optimized-functions/DeadObjectAssign15.js | 25 ++ .../optimized-functions/DeadObjectAssign16.js | 16 ++ .../optimized-functions/DeadObjectAssign17.js | 17 ++ .../optimized-functions/DeadObjectAssign18.js | 17 ++ .../optimized-functions/DeadObjectAssign19.js | 19 ++ .../optimized-functions/DeadObjectAssign2.js | 1 + .../optimized-functions/DeadObjectAssign20.js | 18 ++ .../optimized-functions/DeadObjectAssign21.js | 41 ++++ .../optimized-functions/DeadObjectAssign22.js | 40 +++ .../optimized-functions/DeadObjectAssign3.js | 1 + .../optimized-functions/DeadObjectAssign4.js | 17 ++ .../optimized-functions/DeadObjectAssign5.js | 15 ++ .../optimized-functions/DeadObjectAssign6.js | 16 ++ .../optimized-functions/DeadObjectAssign7.js | 16 ++ .../optimized-functions/DeadObjectAssign8.js | 15 ++ .../optimized-functions/DeadObjectAssign9.js | 15 ++ 33 files changed, 688 insertions(+), 77 deletions(-) create mode 100644 test/serializer/optimized-functions/DeadObjectAssign10.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign11.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign12.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign13.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign14.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign15.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign16.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign17.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign18.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign19.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign20.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign21.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign22.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign4.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign5.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign6.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign7.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign8.js create mode 100644 test/serializer/optimized-functions/DeadObjectAssign9.js diff --git a/src/intrinsics/ecma262/JSON.js b/src/intrinsics/ecma262/JSON.js index ea7504bb8..16e4606e3 100644 --- a/src/intrinsics/ecma262/JSON.js +++ b/src/intrinsics/ecma262/JSON.js @@ -520,9 +520,8 @@ export default function(realm: Realm): ObjectValue { let unfiltered; if (text instanceof AbstractValue && text.kind === "JSON.stringify(...)") { // Enable cloning via JSON.parse(JSON.stringify(...)). - let gen = realm.preludeGenerator; - invariant(gen); // text is abstract, so we are doing abstract interpretation - let temporalBuildNodeEntryArgs = gen.derivedIds.get(text.intrinsicName); + // text is abstract, so we are doing abstract interpretation + let temporalBuildNodeEntryArgs = realm.derivedIds.get(text.intrinsicName); invariant(temporalBuildNodeEntryArgs !== undefined); let args = temporalBuildNodeEntryArgs.args; invariant(args[0] instanceof Value); // since text.kind === "JSON.stringify(...)" diff --git a/src/intrinsics/ecma262/Object.js b/src/intrinsics/ecma262/Object.js index 27abc7c06..bd0bb4003 100644 --- a/src/intrinsics/ecma262/Object.js +++ b/src/intrinsics/ecma262/Object.js @@ -100,7 +100,7 @@ function copyKeys(realm: Realm, keys, from, to): void { function applyObjectAssignSource( realm: Realm, - nextSource: ObjectValue | AbstractObjectValue, + nextSource: Value, to: ObjectValue | AbstractObjectValue, delayedSources: Array, to_must_be_partial: boolean @@ -152,7 +152,7 @@ function applyObjectAssignSource( function tryAndApplySourceOrRecover( realm: Realm, - nextSource: ObjectValue | AbstractObjectValue, + nextSource: Value, to: ObjectValue | AbstractObjectValue, delayedSources: Array, to_must_be_partial: boolean @@ -225,7 +225,7 @@ export default function(realm: Realm): NativeFunctionValue { // ECMA262 19.1.2.1 if (!realm.isCompatibleWith(realm.MOBILE_JSC_VERSION) && !realm.isCompatibleWith("mobile")) { - let ObjectAssign = func.defineNativeMethod("assign", 2, (context, [target, ...sources]) => { + func.defineNativeMethod("assign", 2, (context, [target, ...sources]) => { // 1. Let to be ? ToObject(target). let to = To.ToObject(realm, target); let to_must_be_partial = false; @@ -274,17 +274,20 @@ export default function(realm: Realm): NativeFunctionValue { to.makeSimple(); // Tell serializer that it may add properties to to only after temporalTo has been emitted - let temporalArgs = [ObjectAssign, to, ...delayedSources]; + let temporalArgs = [to, ...delayedSources]; + let preludeGenerator = realm.preludeGenerator; + invariant(preludeGenerator !== undefined); let temporalTo = AbstractValue.createTemporalFromBuildFunction( realm, ObjectValue, temporalArgs, - ([methodNode, targetNode, ...sourceNodes]: Array) => { - return t.callExpression(methodNode, [targetNode, ...sourceNodes]); + ([targetNode, ...sourceNodes]: Array) => { + return t.callExpression(preludeGenerator.memoizeReference("Object.assign"), [targetNode, ...sourceNodes]); }, { skipInvariant: true, mutatesOnly: [to], + temporalType: "OBJECT_ASSIGN", } ); invariant(temporalTo instanceof AbstractObjectValue); diff --git a/src/react/ReactEquivalenceSet.js b/src/react/ReactEquivalenceSet.js index 84bbcddc7..a817ee8c0 100644 --- a/src/react/ReactEquivalenceSet.js +++ b/src/react/ReactEquivalenceSet.js @@ -143,12 +143,12 @@ export class ReactEquivalenceSet { if (!this.residualReactElementVisitor.wasTemporalAliasDeclaredInCurrentScope(temporalAlias)) { return temporalAlias; } - let temporalBuildNodeEntryArgs = this.realm.getTemporalBuildNodeEntryArgsFromDerivedValue(temporalAlias); + let temporalBuildNodeEntry = this.realm.getTemporalBuildNodeEntryFromDerivedValue(temporalAlias); - if (temporalBuildNodeEntryArgs === undefined) { + if (temporalBuildNodeEntry === undefined) { return temporalAlias; } - let temporalArgs = temporalBuildNodeEntryArgs.args; + let temporalArgs = temporalBuildNodeEntry.args; if (temporalArgs.length === 0) { return temporalAlias; } @@ -169,9 +169,9 @@ export class ReactEquivalenceSet { } } else if (arg instanceof AbstractObjectValue && !arg.values.isTop() && arg.kind !== "conditional") { // Might be a temporal, so let's check - let childTemporalBuildNodeEntryArgs = this.realm.getTemporalBuildNodeEntryArgsFromDerivedValue(arg); + let childTemporalBuildNodeEntry = this.realm.getTemporalBuildNodeEntryFromDerivedValue(arg); - if (childTemporalBuildNodeEntryArgs !== undefined) { + if (childTemporalBuildNodeEntry !== undefined) { equivalenceArg = this._getTemporalValue(arg, visitedValues); invariant(equivalenceArg instanceof AbstractObjectValue); diff --git a/src/react/components.js b/src/react/components.js index de44f7b39..60991f7b8 100644 --- a/src/react/components.js +++ b/src/react/components.js @@ -19,7 +19,6 @@ import { NativeFunctionValue, ECMAScriptFunctionValue, Value, - FunctionValue, } from "../values/index.js"; import * as t from "babel-types"; import type { BabelNodeIdentifier } from "babel-types"; @@ -370,18 +369,20 @@ export function applyGetDerivedStateFromProps( let c = AbstractValue.createFromLogicalOp(realm, "&&", a, b); invariant(c instanceof AbstractValue); let newState = new ObjectValue(realm, realm.intrinsics.ObjectPrototype); + let preludeGenerator = realm.preludeGenerator; + invariant(preludeGenerator !== undefined); // we cannot use the standard Object.assign as partial state // is not simple. however, given getDerivedStateFromProps is // meant to be pure, we can assume that there are no getters on // the partial abstract state AbstractValue.createTemporalFromBuildFunction( realm, - FunctionValue, - [objectAssign, newState, prevState, state], - ([methodNode, ..._args]) => { - return t.callExpression(methodNode, ((_args: any): Array)); + ObjectValue, + [newState, prevState, state], + ([..._args]) => { + return t.callExpression(preludeGenerator.memoizeReference("Object.assign"), ((_args: any): Array)); }, - { skipInvariant: true } + { skipInvariant: true, mutatesOnly: [newState], temporalType: "OBJECT_ASSIGN" } ); newState.makeSimple(); newState.makePartial(); @@ -395,14 +396,20 @@ export function applyGetDerivedStateFromProps( objectAssignCall(realm.intrinsics.undefined, [newState, prevState, state]); } catch (e) { if (realm.isInPureScope() && e instanceof FatalError) { + let preludeGenerator = realm.preludeGenerator; + invariant(preludeGenerator !== undefined); AbstractValue.createTemporalFromBuildFunction( realm, - FunctionValue, + ObjectValue, [objectAssign, newState, prevState, state], - ([methodNode, ..._args]) => { - return t.callExpression(methodNode, ((_args: any): Array)); + ([..._args]) => { + return t.callExpression(preludeGenerator.memoizeReference("Object.assign"), ((_args: any): Array)); }, - { skipInvariant: true, mutatesOnly: [newState] } + { + skipInvariant: true, + mutatesOnly: [newState], + temporalType: "OBJECT_ASSIGN", + } ); newState.makeSimple(); newState.makePartial(); diff --git a/src/react/utils.js b/src/react/utils.js index 4aef18f23..e930e6af4 100644 --- a/src/react/utils.js +++ b/src/react/utils.js @@ -30,7 +30,7 @@ import { UndefinedValue, Value, } from "../values/index.js"; -import { Generator } from "../utils/generator.js"; +import { Generator, TemporalObjectAssignEntry } from "../utils/generator.js"; import type { Descriptor, FunctionBodyAstNode, @@ -944,20 +944,30 @@ function applyClonedTemporalAlias(realm: Realm, props: ObjectValue, clonedProps: // be a better option. invariant(false, "TODO applyClonedTemporalAlias conditional"); } - let temporalBuildNodeEntryArgs = realm.getTemporalBuildNodeEntryArgsFromDerivedValue(temporalAlias); - invariant(temporalBuildNodeEntryArgs !== undefined); - let temporalArgs = temporalBuildNodeEntryArgs.args; + let temporalBuildNodeEntry = realm.getTemporalBuildNodeEntryFromDerivedValue(temporalAlias); + if (!(temporalBuildNodeEntry instanceof TemporalObjectAssignEntry)) { + invariant(false, "TODO nont TemporalObjectAssignEntry"); + } + invariant(temporalBuildNodeEntry !== undefined); + let temporalArgs = temporalBuildNodeEntry.args; // replace the original props with the cloned one let newTemporalArgs = temporalArgs.map(arg => (arg === props ? clonedProps : arg)); + let preludeGenerator = realm.preludeGenerator; + invariant(preludeGenerator !== undefined); + let to = newTemporalArgs[0]; let temporalTo = AbstractValue.createTemporalFromBuildFunction( realm, ObjectValue, newTemporalArgs, - ([methodNode, targetNode, ...sourceNodes]: Array) => { - return t.callExpression(methodNode, [targetNode, ...sourceNodes]); + ([targetNode, ...sourceNodes]: Array) => { + return t.callExpression(preludeGenerator.memoizeReference("Object.assign"), [targetNode, ...sourceNodes]); }, - { skipInvariant: true } + { + skipInvariant: true, + mutatesOnly: [to], + temporalType: "OBJECT_ASSIGN", + } ); invariant(temporalTo instanceof AbstractObjectValue); invariant(clonedProps instanceof ObjectValue); @@ -1059,15 +1069,21 @@ export function applyObjectAssignConfigsForReactElement(realm: Realm, to: Object // prepare our temporal Object.assign fallback to.makePartial(); to.makeSimple(); - let temporalArgs = [objAssign, to, ...delayedSources]; + let temporalArgs = [to, ...delayedSources]; + let preludeGenerator = realm.preludeGenerator; + invariant(preludeGenerator !== undefined); let temporalTo = AbstractValue.createTemporalFromBuildFunction( realm, ObjectValue, temporalArgs, - ([methodNode, ..._args]) => { - return t.callExpression(methodNode, ((_args: any): Array)); + ([..._args]) => { + return t.callExpression(preludeGenerator.memoizeReference("Object.assign"), ((_args: any): Array)); }, - { skipInvariant: true, mutatesOnly: [to] } + { + skipInvariant: true, + mutatesOnly: [to], + temporalType: "OBJECT_ASSIGN", + } ); invariant(temporalTo instanceof AbstractObjectValue); temporalTo.values = new ValuesDomain(to); diff --git a/src/realm.js b/src/realm.js index c88fd7601..fa03df04c 100644 --- a/src/realm.js +++ b/src/realm.js @@ -64,7 +64,7 @@ import { import type { Compatibility, RealmOptions, ReactOutputTypes, InvariantModeTypes } from "./options.js"; import invariant from "./invariant.js"; import seedrandom from "seedrandom"; -import { Generator, PreludeGenerator, type TemporalBuildNodeEntryArgs } from "./utils/generator.js"; +import { Generator, PreludeGenerator, type TemporalBuildNodeEntry } from "./utils/generator.js"; import { emptyExpression, voidExpression } from "./utils/babelhelpers.js"; import { Environment, Functions, Join, Properties, To, Widen, Path } from "./singletons.js"; import type { ReactSymbolTypes } from "./react/utils.js"; @@ -248,6 +248,10 @@ export class Realm { this.partialEvaluators = (Object.create(null): any); this.$GlobalEnv = ((undefined: any): LexicalEnvironment); + this.derivedIds = new Map(); + this.temporalEntryArgToEntries = new Map(); + this.temporalEntryCounter = 0; + this.instantRender = { enabled: opts.instantRender || false, }; @@ -341,6 +345,10 @@ export class Realm { $GlobalEnv: LexicalEnvironment; intrinsics: Intrinsics; + derivedIds: Map; + temporalEntryArgToEntries: Map>; + temporalEntryCounter: number; + instantRender: { enabled: boolean, }; @@ -1807,12 +1815,29 @@ export class Realm { return !this._abstractValuesDefined.has(nameString); } - getTemporalBuildNodeEntryArgsFromDerivedValue(value: Value): void | TemporalBuildNodeEntryArgs { + getTemporalBuildNodeEntryFromDerivedValue(value: Value): void | TemporalBuildNodeEntry { let name = value.intrinsicName; - invariant(name); - let preludeGenerator = this.preludeGenerator; - invariant(preludeGenerator !== undefined); - let temporalBuildNodeEntryArgs = preludeGenerator.derivedIds.get(name); - return temporalBuildNodeEntryArgs; + if (!name) { + return undefined; + } + let temporalBuildNodeEntry = value.$Realm.derivedIds.get(name); + return temporalBuildNodeEntry; + } + + getTemporalGeneratorEntriesReferencingArg(arg: AbstractValue | ObjectValue): void | Set { + return this.temporalEntryArgToEntries.get(arg); + } + + saveTemporalGeneratorEntryArgs(temporalBuildNodeEntry: TemporalBuildNodeEntry): void { + let args = temporalBuildNodeEntry.args; + for (let arg of args) { + let temporalEntries = this.temporalEntryArgToEntries.get(arg); + + if (temporalEntries === undefined) { + temporalEntries = new Set(); + this.temporalEntryArgToEntries.set(arg, temporalEntries); + } + temporalEntries.add(temporalBuildNodeEntry); + } } } diff --git a/src/serializer/Emitter.js b/src/serializer/Emitter.js index b554ecbf5..8de9acb47 100644 --- a/src/serializer/Emitter.js +++ b/src/serializer/Emitter.js @@ -21,7 +21,7 @@ import { } from "../values/index.js"; import type { BabelNodeStatement } from "babel-types"; import type { SerializedBody } from "./types.js"; -import { Generator, type TemporalBuildNodeEntryArgs } from "../utils/generator.js"; +import { Generator, type TemporalBuildNodeEntry } from "../utils/generator.js"; import invariant from "../invariant.js"; import { BodyReference } from "./types.js"; import { ResidualFunctions } from "./ResidualFunctions.js"; @@ -67,7 +67,7 @@ export class Emitter { residualFunctions: ResidualFunctions, referencedDeclaredValues: Map, conditionalFeasibility: Map, - derivedIds: Map + derivedIds: Map ) { this._mainBody = { type: "MainGenerator", parentBody: undefined, entries: [], done: false }; this._waitingForValues = new Map(); diff --git a/src/serializer/ResidualHeapSerializer.js b/src/serializer/ResidualHeapSerializer.js index 368d956a5..9db9f9f45 100644 --- a/src/serializer/ResidualHeapSerializer.js +++ b/src/serializer/ResidualHeapSerializer.js @@ -195,7 +195,7 @@ export class ResidualHeapSerializer { this.residualFunctions, referencedDeclaredValues, conditionalFeasibility, - this.preludeGenerator.derivedIds + this.realm.derivedIds ); this.mainBody = this.emitter.getBody(); this.residualHeapInspector = residualHeapInspector; @@ -1897,7 +1897,7 @@ export class ResidualHeapSerializer { if (serializedValue.type === "Identifier") { let id = ((serializedValue: any): BabelNodeIdentifier); invariant( - !this.preludeGenerator.derivedIds.has(id.name) || + !this.realm.derivedIds.has(id.name) || this.emitter.cannotDeclare() || this.emitter.hasBeenDeclared(val) || (this.emitter.emittingToAdditionalFunction() && this.referencedDeclaredValues.get(val) === undefined), @@ -2284,7 +2284,7 @@ export class ResidualHeapSerializer { this.generator.serialize(this._getContext()); this.getStatistics().generators++; - invariant(this.emitter.declaredCount() <= this.preludeGenerator.derivedIds.size); + invariant(this.emitter.declaredCount() <= this.realm.derivedIds.size); // TODO #20: add timers diff --git a/src/serializer/types.js b/src/serializer/types.js index 329f63085..6c834afa8 100644 --- a/src/serializer/types.js +++ b/src/serializer/types.js @@ -10,7 +10,7 @@ /* @flow strict-local */ import { DeclarativeEnvironmentRecord, type Binding } from "../environment.js"; -import { ConcreteValue, Value, ObjectValue, AbstractValue } from "../values/index.js"; +import { AbstractValue, ConcreteValue, ObjectValue, Value } from "../values/index.js"; import type { ECMAScriptSourceFunctionValue, FunctionValue } from "../values/index.js"; import type { BabelNodeExpression, BabelNodeStatement, BabelNodeMemberExpression } from "babel-types"; import { SameValue } from "../methods/abstract.js"; diff --git a/src/utils/generator.js b/src/utils/generator.js index e6cab60ee..5e6a74f43 100644 --- a/src/utils/generator.js +++ b/src/utils/generator.js @@ -89,6 +89,8 @@ export type VisitEntryCallbacks = {| visitBindingAssignment: (Binding, Value) => Value, |}; +export type TemporalBuildNodeType = "OBJECT_ASSIGN"; + export type DerivedExpressionBuildNodeFunction = ( Array, SerializationContext, @@ -102,6 +104,16 @@ export type GeneratorBuildNodeFunction = ( ) => BabelNodeStatement; export class GeneratorEntry { + constructor(realm: Realm) { + // We increment the index of every TemporalBuildNodeEntry 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 build node + // 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++; + } + visit(callbacks: VisitEntryCallbacks, containingGenerator: Generator): boolean { invariant(false, "GeneratorEntry is an abstract base class"); } @@ -113,6 +125,16 @@ export class GeneratorEntry { getDependencies(): void | Array { invariant(false, "GeneratorEntry is an abstract base class"); } + + notEqualToAndDoesNotHappenBefore(entry: GeneratorEntry): boolean { + return this.index > entry.index; + } + + notEqualToAndDoesNotHappenAfter(entry: GeneratorEntry): boolean { + return this.index < entry.index; + } + + index: number; } export type TemporalBuildNodeEntryArgs = { @@ -123,11 +145,12 @@ export type TemporalBuildNodeEntryArgs = { dependencies?: Array, isPure?: boolean, mutatesOnly?: Array, + temporalType?: TemporalBuildNodeType, }; export class TemporalBuildNodeEntry extends GeneratorEntry { - constructor(args: TemporalBuildNodeEntryArgs) { - super(); + constructor(realm: Realm, args: TemporalBuildNodeEntryArgs) { + super(realm); Object.assign(this, args); if (this.mutatesOnly !== undefined) { invariant(!this.isPure); @@ -144,6 +167,7 @@ export class TemporalBuildNodeEntry extends GeneratorEntry { dependencies: void | Array; isPure: void | boolean; mutatesOnly: void | Array; + temporalType: void | TemporalBuildNodeType; visit(callbacks: VisitEntryCallbacks, containingGenerator: Generator): boolean { let omit = this.isPure && this.declared && callbacks.canOmit(this.declared); @@ -210,6 +234,38 @@ export class TemporalBuildNodeEntry extends GeneratorEntry { } } +export class TemporalObjectAssignEntry extends TemporalBuildNodeEntry { + visit(callbacks: VisitEntryCallbacks, containingGenerator: Generator): boolean { + let declared = this.declared; + if (!(declared instanceof AbstractObjectValue || declared instanceof 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); + } +} + type ModifiedPropertyEntryArgs = {| propertyBinding: PropertyBinding, newDescriptor: void | Descriptor, @@ -217,8 +273,8 @@ type ModifiedPropertyEntryArgs = {| |}; class ModifiedPropertyEntry extends GeneratorEntry { - constructor(args: ModifiedPropertyEntryArgs) { - super(); + constructor(realm: Realm, args: ModifiedPropertyEntryArgs) { + super(realm); Object.assign(this, args); } @@ -255,8 +311,8 @@ type ModifiedBindingEntryArgs = {| |}; class ModifiedBindingEntry extends GeneratorEntry { - constructor(args: ModifiedBindingEntryArgs) { - super(); + constructor(realm: Realm, args: ModifiedBindingEntryArgs) { + super(realm); Object.assign(this, args); } @@ -309,8 +365,8 @@ class ModifiedBindingEntry extends GeneratorEntry { } class ReturnValueEntry extends GeneratorEntry { - constructor(generator: Generator, returnValue: Value) { - super(); + constructor(realm: Realm, generator: Generator, returnValue: Value) { + super(realm); this.returnValue = returnValue.promoteEmptyToUndefined(); this.containingGenerator = generator; } @@ -339,7 +395,7 @@ class ReturnValueEntry extends GeneratorEntry { class IfThenElseEntry extends GeneratorEntry { constructor(generator: Generator, completion: PossiblyNormalCompletion | ForkedAbruptCompletion, realm: Realm) { - super(); + super(realm); this.completion = completion; this.containingGenerator = generator; this.condition = completion.joinCondition; @@ -381,8 +437,8 @@ class IfThenElseEntry extends GeneratorEntry { } class BindingAssignmentEntry extends GeneratorEntry { - constructor(binding: Binding, value: Value) { - super(); + constructor(realm: Realm, binding: Binding, value: Value) { + super(realm); this.binding = binding; this.value = value; } @@ -518,7 +574,7 @@ export class Generator { } } this._entries.push( - new ModifiedPropertyEntry({ + new ModifiedPropertyEntry(this.realm, { propertyBinding, newDescriptor: desc, containingGenerator: this, @@ -529,7 +585,7 @@ export class Generator { emitBindingModification(modifiedBinding: Binding) { invariant(this.effectsToApply !== undefined); this._entries.push( - new ModifiedBindingEntry({ + new ModifiedBindingEntry(this.realm, { modifiedBinding, newValue: modifiedBinding.value, containingGenerator: this, @@ -538,7 +594,7 @@ export class Generator { } emitReturnValue(result: Value) { - this._entries.push(new ReturnValueEntry(this, result)); + this._entries.push(new ReturnValueEntry(this.realm, this, result)); } emitIfThenElse(result: PossiblyNormalCompletion | ForkedAbruptCompletion, realm: Realm) { @@ -587,7 +643,7 @@ export class Generator { } emitBindingAssignment(binding: Binding, value: Value) { - this._entries.push(new BindingAssignmentEntry(binding, value)); + this._entries.push(new BindingAssignmentEntry(this.realm, binding, value)); } emitPropertyAssignment(object: ObjectValue, key: string, value: Value) { @@ -957,6 +1013,7 @@ export class Generator { isPure?: boolean, skipInvariant?: boolean, mutatesOnly?: Array, + temporalType?: TemporalBuildNodeType, |} ): AbstractValue { invariant(buildNode_ instanceof Function || args.length === 0); @@ -968,7 +1025,7 @@ export class Generator { this.realm, types, values, - 1735003607742176 + this.preludeGenerator.derivedIds.size, + 1735003607742176 + this.realm.derivedIds.size, [], id, options @@ -988,6 +1045,7 @@ export class Generator { ]); }, mutatesOnly: optionalArgs ? optionalArgs.mutatesOnly : undefined, + temporalType: optionalArgs ? optionalArgs.temporalType : undefined, }); let type = types.getType(); res.intrinsicName = id.name; @@ -1066,15 +1124,21 @@ export class Generator { return res; } - // PITFALL Warning: adding a new kind of TemporalBuildNodeEntry that is not the result of a join or composition - // will break this purgeEntriesWithGeneratorDepencies. - _addEntry(entry: TemporalBuildNodeEntryArgs): void { - this._entries.push(new TemporalBuildNodeEntry(entry)); + _addEntry(entryArgs: TemporalBuildNodeEntryArgs): TemporalBuildNodeEntry { + let entry; + if (entryArgs.temporalType === "OBJECT_ASSIGN") { + entry = new TemporalObjectAssignEntry(this.realm, entryArgs); + } else { + entry = new TemporalBuildNodeEntry(this.realm, entryArgs); + } + this.realm.saveTemporalGeneratorEntryArgs(entry); + this._entries.push(entry); + return entry; } - _addDerivedEntry(id: string, entry: TemporalBuildNodeEntryArgs): void { - this._addEntry(entry); - this.preludeGenerator.derivedIds.set(id, entry); + _addDerivedEntry(id: string, entryArgs: TemporalBuildNodeEntryArgs): void { + let entry = this._addEntry(entryArgs); + this.realm.derivedIds.set(id, entry); } appendGenerator(other: Generator, leadingComment: string): void { @@ -1174,7 +1238,6 @@ export class NameGenerator { export class PreludeGenerator { constructor(debugNames: ?boolean, uniqueSuffix: ?string) { this.prelude = []; - this.derivedIds = new Map(); this.memoizedRefs = new Map(); this.nameGenerator = new NameGenerator(new Set(), !!debugNames, uniqueSuffix || "", "_$"); this.usesThis = false; @@ -1183,7 +1246,6 @@ export class PreludeGenerator { } prelude: Array; - derivedIds: Map; memoizedRefs: Map; nameGenerator: NameGenerator; usesThis: boolean; @@ -1251,3 +1313,118 @@ export class PreludeGenerator { return ref; } } + +type TemporalBuildNodeEntryOptimizationStatus = "NO_OPTIMIZATION" | "POSSIBLE_OPTIMIZATION"; + +// 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); +// +export function attemptToMergeEquivalentObjectAssigns( + realm: Realm, + callbacks: VisitEntryCallbacks, + temporalBuildNodeEntry: TemporalBuildNodeEntry +): TemporalBuildNodeEntryOptimizationStatus | TemporalObjectAssignEntry { + let args = temporalBuildNodeEntry.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 AbstractObjectValue) { + let otherTemporalBuildNodeEntry = realm.getTemporalBuildNodeEntryFromDerivedValue(possibleOtherObjectAssignTo); + if (!(otherTemporalBuildNodeEntry instanceof TemporalObjectAssignEntry)) { + continue; + } + let otherArgs = otherTemporalBuildNodeEntry.args; + // Object.assign has at least 1 arg + if (otherArgs.length < 2) { + continue; + } + let otherArgsToUse = []; + for (let x = 1; x < otherArgs.length; x++) { + let arg = otherArgs[x]; + // The arg might have been havoced, so ensure we do not continue in this case + if (arg instanceof ObjectValue && arg.mightBeHavocedObject()) { + continue loopThroughArgs; + } + if (arg instanceof ObjectValue || arg instanceof 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(otherTemporalBuildNodeEntry) && + temporalGeneratorEntry.notEqualToAndDoesNotHappenAfter(temporalBuildNodeEntry) + ) { + 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)) { + let newArgs = [to, ...otherArgsToUse]; + + for (let x = 2; x < args.length; x++) { + let arg = args[x]; + // We don't want to add the "to" that we're merging with! + if (arg !== possibleOtherObjectAssignTo) { + newArgs.push(arg); + } + } + // 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({}, temporalBuildNodeEntry, { + 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"; +} diff --git a/src/values/AbstractValue.js b/src/values/AbstractValue.js index e7c353b5c..2e022024c 100644 --- a/src/values/AbstractValue.js +++ b/src/values/AbstractValue.js @@ -20,7 +20,7 @@ import type { import { CompilerDiagnostic, FatalError } from "../errors.js"; import type { Realm } from "../realm.js"; import type { PropertyKeyValue } from "../types.js"; -import { PreludeGenerator } from "../utils/generator.js"; +import { PreludeGenerator, type TemporalBuildNodeType } from "../utils/generator.js"; import buildExpressionTemplate from "../utils/builder.js"; import { @@ -142,11 +142,10 @@ export default class AbstractValue extends Value { addSourceNamesTo(names: Array, visited: Set = new Set()) { if (visited.has(this)) return; visited.add(this); - let gen = this.$Realm.preludeGenerator; + let realm = this.$Realm; function add_intrinsic(name: string) { if (name.startsWith("_$")) { - if (gen === undefined) return; - let temporalBuildNodeEntryArgs = gen.derivedIds.get(name); + let temporalBuildNodeEntryArgs = realm.derivedIds.get(name); invariant(temporalBuildNodeEntryArgs !== undefined); add_args(temporalBuildNodeEntryArgs.args); } else if (names.indexOf(name) < 0) { @@ -784,6 +783,7 @@ export default class AbstractValue extends Value { isPure?: boolean, skipInvariant?: boolean, mutatesOnly?: Array, + temporalType?: TemporalBuildNodeType, |} ): AbstractValue { invariant(resultType !== UndefinedValue); @@ -824,6 +824,7 @@ export default class AbstractValue extends Value { isPure?: boolean, skipInvariant?: boolean, mutatesOnly?: Array, + temporalType?: TemporalBuildNodeType, |} ): AbstractValue | UndefinedValue { let types = new TypesDomain(resultType); diff --git a/test/serializer/optimized-functions/DeadObjectAssign.js b/test/serializer/optimized-functions/DeadObjectAssign.js index 84efa0bec..5db8a5469 100644 --- a/test/serializer/optimized-functions/DeadObjectAssign.js +++ b/test/serializer/optimized-functions/DeadObjectAssign.js @@ -1,4 +1,5 @@ // Copies of assign\(:0 + global.f = function() { var x = global.__abstract ? global.__abstract({}, "({})") : {}; global.__makeSimple && __makeSimple(x); diff --git a/test/serializer/optimized-functions/DeadObjectAssign10.js b/test/serializer/optimized-functions/DeadObjectAssign10.js new file mode 100644 index 000000000..49afa9626 --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign10.js @@ -0,0 +1,16 @@ +// Copies of _\$B:2 +// inline expressions + +// _$B is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(foo, bar) { + var a = Object.assign({}, foo, bar, {a: 1}); + var b = Object.assign({}, a, {a: 2}); + var c = Object.assign({}, b, {a: 2}, {d: 5}); + return c; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({b: 1}, {c: 2})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign11.js b/test/serializer/optimized-functions/DeadObjectAssign11.js new file mode 100644 index 000000000..9ee2a3622 --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign11.js @@ -0,0 +1,18 @@ +// Copies of _\$B:2 +// inline expressions + +// _$B is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(foo, bar) { + var a = Object.assign({}, foo, bar, {a: 1}); + foo = {}; + var b = Object.assign({}, a, {a: 2}); + bar = {}; + var c = Object.assign({}, b, {a: 2}, {d: 5}); + return c; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({b: 1}, {c: 2})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign12.js b/test/serializer/optimized-functions/DeadObjectAssign12.js new file mode 100644 index 000000000..612808b9f --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign12.js @@ -0,0 +1,22 @@ +// Copies of _\$D:4 +// inline expressions + +// _$D is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(x, foo, bar) { + var a = Object.assign({}, foo, bar, {a: 1}); + foo = {}; + var b + if (x) { + b = Object.assign({}, a, {a: 2}); + } else { + b = Object.assign({}, a, {a: 5}); + } + var c = Object.assign({}, b, {a: 2}, {d: 5}); + return c; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f(false, {b: 1}, {c: 2})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign13.js b/test/serializer/optimized-functions/DeadObjectAssign13.js new file mode 100644 index 000000000..fc9f950cd --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign13.js @@ -0,0 +1,15 @@ +// Copies of _\$7:3 +// inline expressions + +// _$7 is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(x, foo) { + var a = Object.assign({}, foo); + + return x ? Object.assign({}, a, {a: 1}) : Object.assign({}, a, {a: 2}) +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f(false, {a: 3})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign14.js b/test/serializer/optimized-functions/DeadObjectAssign14.js new file mode 100644 index 000000000..190fdf81a --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign14.js @@ -0,0 +1,22 @@ +// Copies of _\$4:3 +// inline expressions + +// _$4 is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(x, foo) { + var a = Object.assign({}, foo); + var b = Object.assign({}, a); + // b gets visited + var someVal = b; + if (x) { + // a gets visited + someVal = a; + } + // a should still exist + return someVal; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f(false, {a: 3})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign15.js b/test/serializer/optimized-functions/DeadObjectAssign15.js new file mode 100644 index 000000000..ea832974d --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign15.js @@ -0,0 +1,25 @@ +// Copies of _\$4:3 +// inline expressions + +// _$4 is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(x, foo) { + var a = Object.assign({}, foo); + var b = Object.assign({}, a); + // b gets visited + var someVal = b; + if (x) { + // a gets visited + function foo() { + return a; + } + someVal = foo; + } + // a should still exist + return someVal; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f(false, {a: 3})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign16.js b/test/serializer/optimized-functions/DeadObjectAssign16.js new file mode 100644 index 000000000..e0a6df764 --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign16.js @@ -0,0 +1,16 @@ +// Copies of _\$4:3 +// inline expressions + +// _$4 is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(o) { + var p = Object.assign({}, o); + o.x = 42; + var q = Object.assign({}, p); + return q; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({x: 10})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign17.js b/test/serializer/optimized-functions/DeadObjectAssign17.js new file mode 100644 index 000000000..68f9c0728 --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign17.js @@ -0,0 +1,17 @@ +// Copies of _\$C:2 +// inline expressions + +// _$C is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(o) { + var p = Object.assign({}, o, {a: 1}); + var p2 = Object.assign({}, o, {a: 2}); + var q = Object.assign({}, p, {a: 3}); + var q2 = Object.assign({}, q, {a: 4}); + return q2; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({a: 10})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign18.js b/test/serializer/optimized-functions/DeadObjectAssign18.js new file mode 100644 index 000000000..1588d5b87 --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign18.js @@ -0,0 +1,17 @@ +// Copies of _\$C:3 +// inline expressions + +// _$C is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(o) { + var p = Object.assign({}, o, {a: 1}); + var q = Object.assign({}, p, {a: 3}); + var p2 = Object.assign({}, o, {a: 2}); + var q2 = Object.assign({}, p2, {a: 4}); + return [q, q2]; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({a: 10})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign19.js b/test/serializer/optimized-functions/DeadObjectAssign19.js new file mode 100644 index 000000000..353fffdf6 --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign19.js @@ -0,0 +1,19 @@ +// Copies of _\$H:3 +// inline expressions + +// _$H is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(o) { + var p = Object.assign({}, o, {a: 1}); + var p2 = Object.assign({}, o, {a: 2}); + p2.a = 100; + var q = Object.assign({}, p, {a: 3}); + var q2 = Object.assign({}, q, {a: 4}); + var q2 = Object.assign({}, q2, p2, {a: 1}, p2); + return q2; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({a: 10})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign2.js b/test/serializer/optimized-functions/DeadObjectAssign2.js index 63ed03bd3..96032e06e 100644 --- a/test/serializer/optimized-functions/DeadObjectAssign2.js +++ b/test/serializer/optimized-functions/DeadObjectAssign2.js @@ -1,4 +1,5 @@ // Copies of .assign;:1 + global.f = function() { var x = global.__abstract ? global.__abstract({}, "({a: 1})") : {a: 1}; var val = {}; diff --git a/test/serializer/optimized-functions/DeadObjectAssign20.js b/test/serializer/optimized-functions/DeadObjectAssign20.js new file mode 100644 index 000000000..8da23477f --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign20.js @@ -0,0 +1,18 @@ +// Copies of _\$5:2 +// inline expressions + +// _$5 is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(o) { + var p = Object.assign({}, o); + var l = {}; + var q = Object.assign({}, p, l); + l.x = 42; + var r = Object.assign({}, q); + return r; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({x: 10})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign21.js b/test/serializer/optimized-functions/DeadObjectAssign21.js new file mode 100644 index 000000000..f22caef6f --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign21.js @@ -0,0 +1,41 @@ +// Copies of _\$E:4 +// inline expressions + +// _$E is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(o) { + var p = Object.assign({}, o, {a: 1}); + var q = Object.assign({}, p, {a: 3}); + var p2 = Object.assign({}, o, {a: 2}); + var q2 = Object.assign({}, p2, {a: 4}); + return [q, q2]; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({a: 10})); } + + +function f(o1, o2, g, h) { + let a = Object.assign({}, o1); + let b = Object.assign({}, o2); + g(a, b); + let p = Object.assign({}, o1); + h(o2); // can mutate o1 ! + let q = Object.assign({}, p); + return q; +} + +if (global.__optimize) __optimize(f); + +inspect = function() { + return f({}, {}, + function(o1, o2) { + o2.o1 = o1; + }, + function(o2) { + o2.o1.x = 42; + } + ).x; +} \ No newline at end of file diff --git a/test/serializer/optimized-functions/DeadObjectAssign22.js b/test/serializer/optimized-functions/DeadObjectAssign22.js new file mode 100644 index 000000000..a7d4b6e91 --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign22.js @@ -0,0 +1,40 @@ +// Copies of _\$A:3 +// inline expressions + +// _$A is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(o) { + var p = Object.assign({}, o, {a: 1}); + var q = Object.assign({}, p, {a: 3}); + var p2 = Object.assign({}, o, {a: 2}); + var q2 = Object.assign({}, p2, {a: 4}); + return [q, q2]; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({a: 10})); } + + +function f(o2, g, h) { + let o1 = {}; + g(o1, o2); // leaks o1 + let p = Object.assign({}, o1); + h(o2); // can mutate o1 ! + let q = Object.assign({}, p); + return q; +} + +if (global.__optimize) __optimize(f); + +inspect = function() { + return f({}, + function(o1, o2) { + o2.o1 = o1; + }, + function(o2) { + o2.o1.x = 42; + } + ).x; +} \ No newline at end of file diff --git a/test/serializer/optimized-functions/DeadObjectAssign3.js b/test/serializer/optimized-functions/DeadObjectAssign3.js index cb42db913..267863d68 100644 --- a/test/serializer/optimized-functions/DeadObjectAssign3.js +++ b/test/serializer/optimized-functions/DeadObjectAssign3.js @@ -1,4 +1,5 @@ // Copies of assign\(:0 + global.f = function() { var x = global.__abstract ? global.__abstract({}, "({})") : {}; global.__makeSimple && __makeSimple(x); diff --git a/test/serializer/optimized-functions/DeadObjectAssign4.js b/test/serializer/optimized-functions/DeadObjectAssign4.js new file mode 100644 index 000000000..c6871884b --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign4.js @@ -0,0 +1,17 @@ +// Copies of _\$4:2 +// inline expressions + +// Why? _$4 is the variable for Object.assign, and there should be +// two copies of it. One for it's declaration and one for its reference. +// We use inline expressions on all test iterations to ensure the copies +// count is always constant. + +function f(foo) { + var a = Object.assign({}, foo); + var b = Object.assign({}, a); + return b; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return f({}); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign5.js b/test/serializer/optimized-functions/DeadObjectAssign5.js new file mode 100644 index 000000000..9a5bfa37b --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign5.js @@ -0,0 +1,15 @@ +// Copies of _\$4:3 +// inline expressions + +// _$4 is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(foo) { + var a = Object.assign({}, foo); + var b = Object.assign({}, a); + return [a, b]; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign6.js b/test/serializer/optimized-functions/DeadObjectAssign6.js new file mode 100644 index 000000000..08b62a29e --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign6.js @@ -0,0 +1,16 @@ +// Copies of _\$5:3 +// inline expressions + +// _$5 is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(foo) { + var a = Object.assign({}, foo); + var bar = a.x; + var b = Object.assign({}, a); + return [b, bar]; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({x: 1})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign7.js b/test/serializer/optimized-functions/DeadObjectAssign7.js new file mode 100644 index 000000000..3f872a502 --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign7.js @@ -0,0 +1,16 @@ +// Copies of _\$5:2 +// inline expressions + +// _$5 is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(foo) { + var a = Object.assign({}, foo); + var bar = a.x; + var b = Object.assign({}, a); + return [a, bar]; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({x: 1})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign8.js b/test/serializer/optimized-functions/DeadObjectAssign8.js new file mode 100644 index 000000000..42ae51390 --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign8.js @@ -0,0 +1,15 @@ +// Copies of _\$6:2 +// inline expressions + +// _$6 is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(foo, bar) { + var a = Object.assign({}, foo, bar, {a: 1}); + var b = Object.assign({}, a); + return b; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({b: 1}, {c: 2})); } diff --git a/test/serializer/optimized-functions/DeadObjectAssign9.js b/test/serializer/optimized-functions/DeadObjectAssign9.js new file mode 100644 index 000000000..c926d75f9 --- /dev/null +++ b/test/serializer/optimized-functions/DeadObjectAssign9.js @@ -0,0 +1,15 @@ +// Copies of _\$7:2 +// inline expressions + +// _$7 is the variable for Object.assign. See DeadObjectAssign4.js for +// a larger explanation. + +function f(foo, bar) { + var a = Object.assign({}, foo, bar, {a: 1}); + var b = Object.assign({}, a, {a: 2}); + return b; +} + +if (global.__optimize) __optimize(f); + +global.inspect = function() { return JSON.stringify(f({b: 1}, {c: 2})); }