mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-10-26 23:32:02 +03:00
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
This commit is contained in:
parent
dd1f18da0c
commit
db5ed0c7d3
@ -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(...)"
|
||||
|
@ -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<Value>,
|
||||
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<Value>,
|
||||
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<BabelNodeExpression>) => {
|
||||
return t.callExpression(methodNode, [targetNode, ...sourceNodes]);
|
||||
([targetNode, ...sourceNodes]: Array<BabelNodeExpression>) => {
|
||||
return t.callExpression(preludeGenerator.memoizeReference("Object.assign"), [targetNode, ...sourceNodes]);
|
||||
},
|
||||
{
|
||||
skipInvariant: true,
|
||||
mutatesOnly: [to],
|
||||
temporalType: "OBJECT_ASSIGN",
|
||||
}
|
||||
);
|
||||
invariant(temporalTo instanceof AbstractObjectValue);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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<any>));
|
||||
ObjectValue,
|
||||
[newState, prevState, state],
|
||||
([..._args]) => {
|
||||
return t.callExpression(preludeGenerator.memoizeReference("Object.assign"), ((_args: any): Array<any>));
|
||||
},
|
||||
{ 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<any>));
|
||||
([..._args]) => {
|
||||
return t.callExpression(preludeGenerator.memoizeReference("Object.assign"), ((_args: any): Array<any>));
|
||||
},
|
||||
{ skipInvariant: true, mutatesOnly: [newState] }
|
||||
{
|
||||
skipInvariant: true,
|
||||
mutatesOnly: [newState],
|
||||
temporalType: "OBJECT_ASSIGN",
|
||||
}
|
||||
);
|
||||
newState.makeSimple();
|
||||
newState.makePartial();
|
||||
|
@ -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<BabelNodeExpression>) => {
|
||||
return t.callExpression(methodNode, [targetNode, ...sourceNodes]);
|
||||
([targetNode, ...sourceNodes]: Array<BabelNodeExpression>) => {
|
||||
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<any>));
|
||||
([..._args]) => {
|
||||
return t.callExpression(preludeGenerator.memoizeReference("Object.assign"), ((_args: any): Array<any>));
|
||||
},
|
||||
{ skipInvariant: true, mutatesOnly: [to] }
|
||||
{
|
||||
skipInvariant: true,
|
||||
mutatesOnly: [to],
|
||||
temporalType: "OBJECT_ASSIGN",
|
||||
}
|
||||
);
|
||||
invariant(temporalTo instanceof AbstractObjectValue);
|
||||
temporalTo.values = new ValuesDomain(to);
|
||||
|
39
src/realm.js
39
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<string, TemporalBuildNodeEntry>;
|
||||
temporalEntryArgToEntries: Map<Value, Set<TemporalBuildNodeEntry>>;
|
||||
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<TemporalBuildNodeEntry> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Value, void | FunctionValue>,
|
||||
conditionalFeasibility: Map<AbstractValue, { t: boolean, f: boolean }>,
|
||||
derivedIds: Map<string, TemporalBuildNodeEntryArgs>
|
||||
derivedIds: Map<string, TemporalBuildNodeEntry>
|
||||
) {
|
||||
this._mainBody = { type: "MainGenerator", parentBody: undefined, entries: [], done: false };
|
||||
this._waitingForValues = new Map();
|
||||
|
@ -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
|
||||
|
||||
|
@ -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";
|
||||
|
@ -89,6 +89,8 @@ export type VisitEntryCallbacks = {|
|
||||
visitBindingAssignment: (Binding, Value) => Value,
|
||||
|};
|
||||
|
||||
export type TemporalBuildNodeType = "OBJECT_ASSIGN";
|
||||
|
||||
export type DerivedExpressionBuildNodeFunction = (
|
||||
Array<BabelNodeExpression>,
|
||||
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<Generator> {
|
||||
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<Generator>,
|
||||
isPure?: boolean,
|
||||
mutatesOnly?: Array<Value>,
|
||||
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<Generator>;
|
||||
isPure: void | boolean;
|
||||
mutatesOnly: void | Array<Value>;
|
||||
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<Value>,
|
||||
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<BabelNodeStatement>;
|
||||
derivedIds: Map<string, TemporalBuildNodeEntryArgs>;
|
||||
memoizedRefs: Map<string, BabelNodeIdentifier>;
|
||||
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";
|
||||
}
|
||||
|
@ -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<string>, visited: Set<AbstractValue> = 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<Value>,
|
||||
temporalType?: TemporalBuildNodeType,
|
||||
|}
|
||||
): AbstractValue {
|
||||
invariant(resultType !== UndefinedValue);
|
||||
@ -824,6 +824,7 @@ export default class AbstractValue extends Value {
|
||||
isPure?: boolean,
|
||||
skipInvariant?: boolean,
|
||||
mutatesOnly?: Array<Value>,
|
||||
temporalType?: TemporalBuildNodeType,
|
||||
|}
|
||||
): AbstractValue | UndefinedValue {
|
||||
let types = new TypesDomain(resultType);
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Copies of assign\(:0
|
||||
|
||||
global.f = function() {
|
||||
var x = global.__abstract ? global.__abstract({}, "({})") : {};
|
||||
global.__makeSimple && __makeSimple(x);
|
||||
|
16
test/serializer/optimized-functions/DeadObjectAssign10.js
Normal file
16
test/serializer/optimized-functions/DeadObjectAssign10.js
Normal file
@ -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})); }
|
18
test/serializer/optimized-functions/DeadObjectAssign11.js
Normal file
18
test/serializer/optimized-functions/DeadObjectAssign11.js
Normal file
@ -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})); }
|
22
test/serializer/optimized-functions/DeadObjectAssign12.js
Normal file
22
test/serializer/optimized-functions/DeadObjectAssign12.js
Normal file
@ -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})); }
|
15
test/serializer/optimized-functions/DeadObjectAssign13.js
Normal file
15
test/serializer/optimized-functions/DeadObjectAssign13.js
Normal file
@ -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})); }
|
22
test/serializer/optimized-functions/DeadObjectAssign14.js
Normal file
22
test/serializer/optimized-functions/DeadObjectAssign14.js
Normal file
@ -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})); }
|
25
test/serializer/optimized-functions/DeadObjectAssign15.js
Normal file
25
test/serializer/optimized-functions/DeadObjectAssign15.js
Normal file
@ -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})); }
|
16
test/serializer/optimized-functions/DeadObjectAssign16.js
Normal file
16
test/serializer/optimized-functions/DeadObjectAssign16.js
Normal file
@ -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})); }
|
17
test/serializer/optimized-functions/DeadObjectAssign17.js
Normal file
17
test/serializer/optimized-functions/DeadObjectAssign17.js
Normal file
@ -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})); }
|
17
test/serializer/optimized-functions/DeadObjectAssign18.js
Normal file
17
test/serializer/optimized-functions/DeadObjectAssign18.js
Normal file
@ -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})); }
|
19
test/serializer/optimized-functions/DeadObjectAssign19.js
Normal file
19
test/serializer/optimized-functions/DeadObjectAssign19.js
Normal file
@ -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})); }
|
@ -1,4 +1,5 @@
|
||||
// Copies of .assign;:1
|
||||
|
||||
global.f = function() {
|
||||
var x = global.__abstract ? global.__abstract({}, "({a: 1})") : {a: 1};
|
||||
var val = {};
|
||||
|
18
test/serializer/optimized-functions/DeadObjectAssign20.js
Normal file
18
test/serializer/optimized-functions/DeadObjectAssign20.js
Normal file
@ -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})); }
|
41
test/serializer/optimized-functions/DeadObjectAssign21.js
Normal file
41
test/serializer/optimized-functions/DeadObjectAssign21.js
Normal file
@ -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;
|
||||
}
|
40
test/serializer/optimized-functions/DeadObjectAssign22.js
Normal file
40
test/serializer/optimized-functions/DeadObjectAssign22.js
Normal file
@ -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;
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
// Copies of assign\(:0
|
||||
|
||||
global.f = function() {
|
||||
var x = global.__abstract ? global.__abstract({}, "({})") : {};
|
||||
global.__makeSimple && __makeSimple(x);
|
||||
|
17
test/serializer/optimized-functions/DeadObjectAssign4.js
Normal file
17
test/serializer/optimized-functions/DeadObjectAssign4.js
Normal file
@ -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({}); }
|
15
test/serializer/optimized-functions/DeadObjectAssign5.js
Normal file
15
test/serializer/optimized-functions/DeadObjectAssign5.js
Normal file
@ -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({})); }
|
16
test/serializer/optimized-functions/DeadObjectAssign6.js
Normal file
16
test/serializer/optimized-functions/DeadObjectAssign6.js
Normal file
@ -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})); }
|
16
test/serializer/optimized-functions/DeadObjectAssign7.js
Normal file
16
test/serializer/optimized-functions/DeadObjectAssign7.js
Normal file
@ -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})); }
|
15
test/serializer/optimized-functions/DeadObjectAssign8.js
Normal file
15
test/serializer/optimized-functions/DeadObjectAssign8.js
Normal file
@ -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})); }
|
15
test/serializer/optimized-functions/DeadObjectAssign9.js
Normal file
15
test/serializer/optimized-functions/DeadObjectAssign9.js
Normal file
@ -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})); }
|
Loading…
Reference in New Issue
Block a user