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:
Dominic Gannaway 2018-06-27 13:03:56 -07:00 committed by Facebook Github Bot
parent fa4b52f573
commit 7c63a7cd42
13 changed files with 120 additions and 28 deletions

View File

@ -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) {

View File

@ -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();

View File

@ -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);

View File

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

View File

@ -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>>,

View File

@ -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);

View File

@ -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) => {

View File

@ -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);

View File

@ -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;

View File

@ -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;

View 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(); }

View 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()); }

View 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(); }