mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-11 14:46:37 +03:00
Add mutatesOnly
feature the generator entries (#2132)
Summary: Release notes: adds `mutatesOnly` to generator entries to better allow for DCE This PR adds a new option that can be passed as an optional arg to `generator.deriveAbstract` called `mutatesOnly `. See https://github.com/facebook/prepack/pull/2132#pullrequestreview-130509488 for details. With it, we can better dead-code eliminate generator entries when the value entries that are flagged as mutating are dead code. The test showing this is below: Input: ```js global.f = function() { var x = global.__abstract ? global.__abstract({}, "({})") : {}; global.__makeSimple && __makeSimple(x); Object.assign({}, x); return 1; }; if (global.__optimize) __optimize(f); ``` Output (with master): ```js var _$2 = this; var _$3 = _$2.Object; var _$4 = _$3.assign; var _0 = function () { var _$0 = _$4({}, {}); return 1; }; ``` Output (with the changes in this PR): ```js var _0 = function () { return 1; }; var _2 = function () { return global.f(); }; ``` Closes https://github.com/facebook/prepack/pull/2132 Differential Revision: D8667764 Pulled By: trueadm fbshipit-source-id: 5121da7d73330befeb5a39ea50d7b773256d70bc
This commit is contained in:
parent
fa4b52f573
commit
7c63a7cd42
@ -281,7 +281,10 @@ export default function(realm: Realm): NativeFunctionValue {
|
||||
([methodNode, targetNode, ...sourceNodes]: Array<BabelNodeExpression>) => {
|
||||
return t.callExpression(methodNode, [targetNode, ...sourceNodes]);
|
||||
},
|
||||
{ skipInvariant: true }
|
||||
{
|
||||
skipInvariant: true,
|
||||
mutatesOnly: [to],
|
||||
}
|
||||
);
|
||||
invariant(temporalTo instanceof AbstractObjectValue);
|
||||
if (to instanceof AbstractObjectValue) {
|
||||
|
@ -400,7 +400,7 @@ export function applyGetDerivedStateFromProps(
|
||||
([methodNode, ..._args]) => {
|
||||
return t.callExpression(methodNode, ((_args: any): Array<any>));
|
||||
},
|
||||
{ skipInvariant: true }
|
||||
{ skipInvariant: true, mutatesOnly: [newState] }
|
||||
);
|
||||
newState.makeSimple();
|
||||
newState.makePartial();
|
||||
|
@ -1073,7 +1073,7 @@ export function applyObjectAssignConfigsForReactElement(realm: Realm, to: Object
|
||||
([methodNode, ..._args]) => {
|
||||
return t.callExpression(methodNode, ((_args: any): Array<any>));
|
||||
},
|
||||
{ skipInvariant: true }
|
||||
{ skipInvariant: true, mutatesOnly: [to] }
|
||||
);
|
||||
invariant(temporalTo instanceof AbstractObjectValue);
|
||||
temporalTo.values = new ValuesDomain(to);
|
||||
|
@ -66,7 +66,7 @@ type EmitterDependenciesVisitorCallbacks<T> = {
|
||||
export class Emitter {
|
||||
constructor(
|
||||
residualFunctions: ResidualFunctions,
|
||||
referencedDeclaredValues: Map<AbstractValue | ConcreteValue, void | FunctionValue>,
|
||||
referencedDeclaredValues: Map<Value, void | FunctionValue>,
|
||||
conditionalFeasibility: Map<AbstractValue, { t: boolean, f: boolean }>,
|
||||
derivedIds: Map<string, Array<Value>>
|
||||
) {
|
||||
@ -369,10 +369,6 @@ export class Emitter {
|
||||
switch (kind) {
|
||||
case "Object":
|
||||
let proto = val.$Prototype;
|
||||
if (val.temporalAlias !== undefined && !val.isIntrinsic()) {
|
||||
result = recurse(val.temporalAlias);
|
||||
if (result !== undefined) return result;
|
||||
}
|
||||
if (
|
||||
proto instanceof ObjectValue &&
|
||||
// if this is falsy, prototype chain might be cyclic
|
||||
|
@ -10,7 +10,7 @@
|
||||
/* @flow strict-local */
|
||||
|
||||
import { Realm } from "../realm.js";
|
||||
import { AbstractValue, ConcreteValue, FunctionValue, Value, ObjectValue } from "../values/index.js";
|
||||
import { AbstractValue, FunctionValue, Value, ObjectValue } from "../values/index.js";
|
||||
import * as t from "babel-types";
|
||||
import type {
|
||||
BabelNodeExpression,
|
||||
@ -66,7 +66,7 @@ export class LazyObjectsSerializer extends ResidualHeapSerializer {
|
||||
residualClassMethodInstances: Map<FunctionValue, ClassMethodInstance>,
|
||||
residualFunctionInfos: Map<BabelNodeBlockStatement, FunctionInfo>,
|
||||
options: SerializerOptions,
|
||||
referencedDeclaredValues: Map<AbstractValue | ConcreteValue, void | FunctionValue>,
|
||||
referencedDeclaredValues: Map<Value, void | FunctionValue>,
|
||||
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects> | void,
|
||||
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>,
|
||||
declarativeEnvironmentRecordsBindings: Map<DeclarativeEnvironmentRecord, Map<string, ResidualFunctionBinding>>,
|
||||
|
@ -120,7 +120,7 @@ export class ResidualHeapSerializer {
|
||||
residualClassMethodInstances: Map<FunctionValue, ClassMethodInstance>,
|
||||
residualFunctionInfos: Map<BabelNodeBlockStatement, FunctionInfo>,
|
||||
options: SerializerOptions,
|
||||
referencedDeclaredValues: Map<AbstractValue | ConcreteValue, void | FunctionValue>,
|
||||
referencedDeclaredValues: Map<Value, void | FunctionValue>,
|
||||
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects> | void,
|
||||
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>,
|
||||
declarativeEnvironmentRecordsBindings: Map<DeclarativeEnvironmentRecord, Map<string, ResidualFunctionBinding>>,
|
||||
@ -250,7 +250,7 @@ export class ResidualHeapSerializer {
|
||||
_serializedValueWithIdentifiers: Set<Value>;
|
||||
residualFunctions: ResidualFunctions;
|
||||
_options: SerializerOptions;
|
||||
referencedDeclaredValues: Map<AbstractValue | ConcreteValue, void | FunctionValue>;
|
||||
referencedDeclaredValues: Map<Value, void | FunctionValue>;
|
||||
activeGeneratorBodies: Map<Generator, SerializedBody>;
|
||||
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects> | void;
|
||||
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>;
|
||||
@ -2116,8 +2116,16 @@ export class ResidualHeapSerializer {
|
||||
},
|
||||
getPropertyAssignmentStatement: this._getPropertyAssignmentStatement.bind(this),
|
||||
emitDefinePropertyBody: this.emitDefinePropertyBody.bind(this, false, undefined),
|
||||
canOmit: (value: AbstractValue | ConcreteValue) => {
|
||||
return !this.referencedDeclaredValues.has(value);
|
||||
canOmit: (value: Value) => {
|
||||
let canOmit = !this.referencedDeclaredValues.has(value) && !this.residualValues.has(value);
|
||||
if (!canOmit) {
|
||||
return false;
|
||||
}
|
||||
if (value instanceof ObjectValue && value.temporalAlias !== undefined) {
|
||||
let temporalAlias = value.temporalAlias;
|
||||
return !this.referencedDeclaredValues.has(temporalAlias) && !this.residualValues.has(temporalAlias);
|
||||
}
|
||||
return canOmit;
|
||||
},
|
||||
declare: (value: AbstractValue | ConcreteValue) => {
|
||||
this.emitter.declare(value);
|
||||
|
@ -20,7 +20,6 @@ import {
|
||||
AbstractObjectValue,
|
||||
AbstractValue,
|
||||
BoundFunctionValue,
|
||||
ConcreteValue,
|
||||
ECMAScriptFunctionValue,
|
||||
ECMAScriptSourceFunctionValue,
|
||||
EmptyValue,
|
||||
@ -136,7 +135,7 @@ export class ResidualHeapVisitor {
|
||||
// For every abstract value of kind "conditional", this map keeps track of whether the consequent and/or alternate is feasible in any scope
|
||||
conditionalFeasibility: Map<AbstractValue, { t: boolean, f: boolean }>;
|
||||
inspector: HeapInspector;
|
||||
referencedDeclaredValues: Map<AbstractValue | ConcreteValue, void | FunctionValue>;
|
||||
referencedDeclaredValues: Map<Value, void | FunctionValue>;
|
||||
delayedActions: Array<{| scope: Scope, action: () => void | boolean |}>;
|
||||
additionalFunctionValuesAndEffects: Map<FunctionValue, AdditionalFunctionEffects>;
|
||||
functionInstances: Map<FunctionValue, FunctionInstance>;
|
||||
@ -1083,10 +1082,18 @@ export class ResidualHeapVisitor {
|
||||
invariant(this.generatorDAG.isParent(parent, generator));
|
||||
this.visitGenerator(generator, additionalFunctionInfo);
|
||||
},
|
||||
canSkip: (value: AbstractValue | ConcreteValue): boolean => {
|
||||
return !this.referencedDeclaredValues.has(value) && !this.values.has(value);
|
||||
canOmit: (value: Value): boolean => {
|
||||
let canOmit = !this.referencedDeclaredValues.has(value) && !this.values.has(value);
|
||||
if (!canOmit) {
|
||||
return false;
|
||||
}
|
||||
if (value instanceof ObjectValue && value.temporalAlias !== undefined) {
|
||||
let temporalAlias = value.temporalAlias;
|
||||
return !this.referencedDeclaredValues.has(temporalAlias) && !this.values.has(temporalAlias);
|
||||
}
|
||||
return canOmit;
|
||||
},
|
||||
recordDeclaration: (value: AbstractValue | ConcreteValue) => {
|
||||
recordDeclaration: (value: Value) => {
|
||||
this.referencedDeclaredValues.set(value, this._getAdditionalFunctionOfScope());
|
||||
},
|
||||
recordDelayedEntry: (generator, entry: GeneratorEntry) => {
|
||||
|
@ -10,6 +10,7 @@
|
||||
/* @flow */
|
||||
|
||||
import type {
|
||||
ConcretizeType,
|
||||
CreateType,
|
||||
EnvironmentType,
|
||||
FunctionType,
|
||||
@ -18,9 +19,8 @@ import type {
|
||||
PathType,
|
||||
PropertiesType,
|
||||
ToType,
|
||||
WidenType,
|
||||
ConcretizeType,
|
||||
UtilsType,
|
||||
WidenType,
|
||||
} from "./types.js";
|
||||
|
||||
export let Create: CreateType = (null: any);
|
||||
|
@ -72,7 +72,7 @@ export type SerializationContext = {|
|
||||
emitDefinePropertyBody: (ObjectValue, string | SymbolValue, Descriptor) => BabelNodeStatement,
|
||||
emit: BabelNodeStatement => void,
|
||||
processValues: (Set<AbstractValue | ConcreteValue>) => void,
|
||||
canOmit: (AbstractValue | ConcreteValue) => boolean,
|
||||
canOmit: Value => boolean,
|
||||
declare: (AbstractValue | ConcreteValue) => void,
|
||||
emitPropertyModification: PropertyBinding => void,
|
||||
options: SerializerOptions,
|
||||
@ -81,7 +81,7 @@ export type SerializationContext = {|
|
||||
export type VisitEntryCallbacks = {|
|
||||
visitEquivalentValue: Value => Value,
|
||||
visitGenerator: (Generator, Generator) => void,
|
||||
canSkip: (AbstractValue | ConcreteValue) => boolean,
|
||||
canOmit: Value => boolean,
|
||||
recordDeclaration: (AbstractValue | ConcreteValue) => void,
|
||||
recordDelayedEntry: (Generator, GeneratorEntry) => void,
|
||||
visitModifiedObjectProperty: PropertyBinding => void,
|
||||
@ -122,12 +122,19 @@ type TemporalBuildNodeEntryArgs = {
|
||||
buildNode?: GeneratorBuildNodeFunction,
|
||||
dependencies?: Array<Generator>,
|
||||
isPure?: boolean,
|
||||
mutatesOnly?: Array<Value>,
|
||||
};
|
||||
|
||||
class TemporalBuildNodeEntry extends GeneratorEntry {
|
||||
constructor(args: TemporalBuildNodeEntryArgs) {
|
||||
super();
|
||||
Object.assign(this, args);
|
||||
if (this.mutatesOnly !== undefined) {
|
||||
invariant(!this.isPure);
|
||||
for (let arg of this.mutatesOnly) {
|
||||
invariant(this.args.includes(arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declared: void | AbstractValue | ConcreteValue;
|
||||
@ -136,9 +143,20 @@ class TemporalBuildNodeEntry extends GeneratorEntry {
|
||||
buildNode: void | GeneratorBuildNodeFunction;
|
||||
dependencies: void | Array<Generator>;
|
||||
isPure: void | boolean;
|
||||
mutatesOnly: void | Array<Value>;
|
||||
|
||||
visit(callbacks: VisitEntryCallbacks, containingGenerator: Generator): boolean {
|
||||
if (this.isPure && this.declared && callbacks.canSkip(this.declared)) {
|
||||
let omit = this.isPure && this.declared && callbacks.canOmit(this.declared);
|
||||
|
||||
if (!omit && this.declared && this.mutatesOnly !== undefined) {
|
||||
omit = true;
|
||||
for (let arg of this.mutatesOnly) {
|
||||
if (!callbacks.canOmit(arg)) {
|
||||
omit = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (omit) {
|
||||
callbacks.recordDelayedEntry(containingGenerator, this);
|
||||
return false;
|
||||
} else {
|
||||
@ -151,7 +169,17 @@ class TemporalBuildNodeEntry extends GeneratorEntry {
|
||||
}
|
||||
|
||||
serialize(context: SerializationContext) {
|
||||
if (!this.isPure || !this.declared || !context.canOmit(this.declared)) {
|
||||
let omit = this.isPure && this.declared && context.canOmit(this.declared);
|
||||
|
||||
if (!omit && this.declared && this.mutatesOnly !== undefined) {
|
||||
omit = true;
|
||||
for (let arg of this.mutatesOnly) {
|
||||
if (!context.canOmit(arg)) {
|
||||
omit = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!omit) {
|
||||
let nodes = this.args.map((boundArg, i) => context.serializeValue(boundArg));
|
||||
if (this.buildNode) {
|
||||
let valuesToProcess = new Set();
|
||||
@ -927,7 +955,12 @@ export class Generator {
|
||||
values: ValuesDomain,
|
||||
args: Array<Value>,
|
||||
buildNode_: DerivedExpressionBuildNodeFunction | BabelNodeExpression,
|
||||
optionalArgs?: {| kind?: AbstractValueKind, isPure?: boolean, skipInvariant?: boolean |}
|
||||
optionalArgs?: {|
|
||||
kind?: AbstractValueKind,
|
||||
isPure?: boolean,
|
||||
skipInvariant?: boolean,
|
||||
mutatesOnly?: Array<Value>,
|
||||
|}
|
||||
): AbstractValue {
|
||||
invariant(buildNode_ instanceof Function || args.length === 0);
|
||||
let id = t.identifier(this.preludeGenerator.nameGenerator.generate("derived"));
|
||||
@ -958,6 +991,7 @@ export class Generator {
|
||||
),
|
||||
]);
|
||||
},
|
||||
mutatesOnly: optionalArgs ? optionalArgs.mutatesOnly : undefined,
|
||||
});
|
||||
let type = types.getType();
|
||||
res.intrinsicName = id.name;
|
||||
|
@ -776,7 +776,12 @@ export default class AbstractValue extends Value {
|
||||
template: PreludeGenerator => ({}) => BabelNodeExpression,
|
||||
resultType: typeof Value,
|
||||
operands: Array<Value>,
|
||||
optionalArgs?: {| kind?: AbstractValueKind, isPure?: boolean, skipInvariant?: boolean |}
|
||||
optionalArgs?: {|
|
||||
kind?: AbstractValueKind,
|
||||
isPure?: boolean,
|
||||
skipInvariant?: boolean,
|
||||
mutatesOnly?: Array<Value>,
|
||||
|}
|
||||
): AbstractValue {
|
||||
invariant(resultType !== UndefinedValue);
|
||||
let temp = AbstractValue.createFromTemplate(realm, template, resultType, operands, "");
|
||||
@ -811,7 +816,12 @@ export default class AbstractValue extends Value {
|
||||
resultType: typeof Value,
|
||||
args: Array<Value>,
|
||||
buildFunction: AbstractValueBuildNodeFunction,
|
||||
optionalArgs?: {| kind?: AbstractValueKind, isPure?: boolean, skipInvariant?: boolean |}
|
||||
optionalArgs?: {|
|
||||
kind?: AbstractValueKind,
|
||||
isPure?: boolean,
|
||||
skipInvariant?: boolean,
|
||||
mutatesOnly?: Array<Value>,
|
||||
|}
|
||||
): AbstractValue | UndefinedValue {
|
||||
let types = new TypesDomain(resultType);
|
||||
let values = ValuesDomain.topVal;
|
||||
|
11
test/serializer/optimized-functions/DeadObjectAssign.js
Normal file
11
test/serializer/optimized-functions/DeadObjectAssign.js
Normal file
@ -0,0 +1,11 @@
|
||||
// Copies of assign\(:0
|
||||
global.f = function() {
|
||||
var x = global.__abstract ? global.__abstract({}, "({})") : {};
|
||||
global.__makeSimple && __makeSimple(x);
|
||||
Object.assign({}, x);
|
||||
return 1;
|
||||
};
|
||||
|
||||
if (global.__optimize) __optimize(f);
|
||||
|
||||
global.inspect = function() { return global.f(); }
|
11
test/serializer/optimized-functions/DeadObjectAssign2.js
Normal file
11
test/serializer/optimized-functions/DeadObjectAssign2.js
Normal file
@ -0,0 +1,11 @@
|
||||
// Copies of .assign;:1
|
||||
global.f = function() {
|
||||
var x = global.__abstract ? global.__abstract({}, "({a: 1})") : {a: 1};
|
||||
var val = {};
|
||||
Object.assign(val, x);
|
||||
return [1, val];
|
||||
};
|
||||
|
||||
if (global.__optimize) __optimize(f);
|
||||
|
||||
global.inspect = function() { return JSON.stringify(global.f()); }
|
12
test/serializer/optimized-functions/DeadObjectAssign3.js
Normal file
12
test/serializer/optimized-functions/DeadObjectAssign3.js
Normal file
@ -0,0 +1,12 @@
|
||||
// Copies of assign\(:0
|
||||
global.f = function() {
|
||||
var x = global.__abstract ? global.__abstract({}, "({})") : {};
|
||||
global.__makeSimple && __makeSimple(x);
|
||||
var val = {};
|
||||
Object.assign(val, x);
|
||||
return 1;
|
||||
};
|
||||
|
||||
if (global.__optimize) __optimize(f);
|
||||
|
||||
global.inspect = function() { return global.f(); }
|
Loading…
Reference in New Issue
Block a user