Assign simple properties inside loops

Summary:
Release note: Basic support for do while loops with abstract bounds. Issue #1229

Take properties into account when doing fix point computation. Use property paths rather than phi nodes. Simplify code for local variables as well.
Closes https://github.com/facebook/prepack/pull/1255

Differential Revision: D6576293

Pulled By: hermanventer

fbshipit-source-id: 9e19704cbf5c8cbcc937bd99ec32af9ffe577ebf
This commit is contained in:
Herman Venter 2017-12-14 19:32:27 -08:00 committed by Facebook Github Bot
parent 45383c8785
commit 45d19dd152
8 changed files with 116 additions and 46 deletions

View File

@ -122,7 +122,7 @@ export class WidenImplementation {
let widen = (b: Binding, v1: void | Value, v2: void | Value) => {
invariant(v2 !== undefined); // Local variables are not going to get deleted as a result of widening
let result = this.widenValues(realm, v1 || b.value, v2);
if (result instanceof AbstractValue && result.kind === "widening") {
if (result instanceof AbstractValue && result.kind === "widened") {
let phiNode = b.phiNode;
if (phiNode === undefined) {
// Create a temporal location for binding
@ -183,7 +183,7 @@ export class WidenImplementation {
c2: CreatedObjects
): PropertyBindings {
let widen = (b: PropertyBinding, d1: void | Descriptor, d2: void | Descriptor) => {
invariant(d1 !== undefined || d2 !== undefined, "widenMaps ensures that this cannot happen");
if (d1 === undefined && d2 === undefined) return undefined;
// If the PropertyBinding object has been freshly allocated do not widen (that happens in AbstractObjectValue)
if (d1 === undefined) {
if (b.object instanceof ObjectValue && c2.has(b.object)) return d2; // no widen
@ -210,7 +210,31 @@ export class WidenImplementation {
}
invariant(d2 !== undefined);
}
return this.widenDescriptors(realm, d1, d2);
let result = this.widenDescriptors(realm, d1, d2);
if (result && result.value instanceof AbstractValue && result.value.kind === "widened") {
let rval = result.value;
let pathNode = b.pathNode;
if (pathNode === undefined) {
//Since properties already have mutable storage locations associated with them, we do not
//need phi nodes. What we need is an abstract value with a build node that results in a memberExpression
//that resolves to the storage location of the property.
// For now, we only handle loop invariant properties
//i.e. properties where the member expresssion does not involve any values written to inside the loop.
//todo: handle the case where key is an abstract value.
let key = b.key;
if (typeof key === "string") {
pathNode = AbstractValue.createFromWidenedProperty(realm, rval, [b.object], ([o]) =>
t.memberExpression(o, t.identifier(key))
);
} else {
throw new FatalError("todo: handle the case where key is an abstract value");
}
b.pathNode = pathNode;
}
result.value = pathNode;
}
return result;
};
return this.widenMaps(m1, m2, widen);
}
@ -218,7 +242,11 @@ export class WidenImplementation {
widenDescriptors(realm: Realm, d1: void | Descriptor, d2: Descriptor): void | Descriptor {
if (d1 === undefined) {
// d2 is a property written to only in the (n+1)th iteration
return d2; // no widening needed. Note that another fixed point iteration will occur.
if (!IsDataDescriptor(realm, d2)) return d2; // accessor properties need not be widened.
let dc = cloneDescriptor(d2);
invariant(dc !== undefined);
dc.value = this.widenValues(realm, d2.value, d2.value);
return dc;
} else {
if (equalDescriptors(d1, d2)) {
if (!IsDataDescriptor(realm, d1)) return d1; // identical accessor properties need not be widened.
@ -274,17 +302,16 @@ export class WidenImplementation {
}
containsPropertyBindings(m1: PropertyBindings, m2: PropertyBindings): boolean {
let equalsPropertyBinding = (d1: void | Descriptor, d2: void | Descriptor) => {
if (d1 === undefined || d2 === undefined) return false;
let [v1, v2] = [d1.value, d2.value];
let containsPropertyBinding = (d1: void | Descriptor, d2: void | Descriptor) => {
let [v1, v2] = [d1 && d1.value, d2 && d2.value];
if (v1 === undefined) return v2 === undefined;
if (v1 instanceof Value && v2 instanceof Value && !this._containsValues(v1, v2)) return false;
if (v1 instanceof Value && v2 instanceof Value) return this._containsValues(v1, v2);
if (Array.isArray(v1) && Array.isArray(v2)) {
return this._containsArray(((v1: any): Array<Value>), ((v2: any): Array<Value>));
}
return false;
return v2 === undefined;
};
return this.containsMap(m1, m2, equalsPropertyBinding);
return this.containsMap(m1, m2, containsPropertyBinding);
}
_containsArray(

View File

@ -34,13 +34,7 @@ import invariant from "./invariant.js";
import seedrandom from "seedrandom";
import { Generator, PreludeGenerator } from "./utils/generator.js";
import { Environment, Functions, Join, Properties, To, Widen, Path } from "./singletons.js";
import type {
BabelNode,
BabelNodeIdentifier,
BabelNodeSourceLocation,
BabelNodeLVal,
BabelNodeStatement,
} from "babel-types";
import type { BabelNode, BabelNodeSourceLocation, BabelNodeLVal, BabelNodeStatement } from "babel-types";
import * as t from "babel-types";
export type Bindings = Map<Binding, void | Value>;
@ -512,11 +506,10 @@ export class Realm {
if (Widen.containsEffects(effects1, effects2)) {
// effects1 includes every value present in effects2, so doing another iteration using effects2 will not
// result in any more values being added to abstract domains and hence a fixpoint has been reached.
let [, , bindings1, pbindings1] = effects1;
if (pbindings1.size > 0) return undefined;
// Generate code using effects2 because its expressions have not been widened away.
let [, gen, bindings2, pbindings2] = effects2;
if (pbindings2.size > 0) return undefined;
this._emitLocalAssignments(gen, bindings1, bindings2);
this._emitLocalAssignments(gen, bindings2);
this._emitPropertAssignments(gen, pbindings2);
return [effects1, effects2];
}
effects1 = Widen.widenEffects(this, effects1, effects2);
@ -527,18 +520,9 @@ export class Realm {
}
// populate the loop body generator with assignments that will update the phiNodes
_emitLocalAssignments(gen: Generator, bindings1: Bindings, bindings2: Bindings) {
// bindings1 maps local bindings to widened values whose build nodes return the identity of the correspoding phiNodes
// bindings2 maps local bindings to the unwidened values whose build nodes result in expressions that reference phiNodes
let idFor: Map<any, BabelNodeIdentifier> = new Map();
bindings1.forEach((val, key, map) => {
if (val instanceof AbstractValue && val.kind === "widening") {
let id = val.buildNode([]);
idFor.set(key, (id: any));
}
});
_emitLocalAssignments(gen: Generator, bindings: Bindings) {
let tvalFor: Map<any, AbstractValue> = new Map();
bindings2.forEach((val, key, map) => {
bindings.forEach((val, key, map) => {
if (val instanceof AbstractValue) {
invariant(val._buildNode !== undefined);
let tval = gen.derive(val.types, val.values, [val], ([n]) => n, {
@ -547,20 +531,45 @@ export class Realm {
tvalFor.set(key, tval);
}
});
bindings2.forEach((val, key, map) => {
bindings.forEach((val, key, map) => {
if (val instanceof AbstractValue) {
let id = idFor.get(key);
invariant(id !== undefined);
let phiNode = key.phiNode;
let tval = tvalFor.get(key);
invariant(tval !== undefined);
gen.emitStatement([tval], ([v]) => {
invariant(id !== undefined);
return t.expressionStatement(t.assignmentExpression("=", id, v));
invariant(phiNode !== undefined);
let id = phiNode.buildNode([]);
return t.expressionStatement(t.assignmentExpression("=", (id: any), v));
});
}
});
}
// populate the loop body generator with assignments that will update properties modified inside the loop
_emitPropertAssignments(gen: Generator, pbindings: PropertyBindings) {
let tvalFor: Map<any, AbstractValue> = new Map();
pbindings.forEach((val, key, map) => {
let value = val && val.value;
if (value instanceof AbstractValue) {
invariant(value._buildNode !== undefined);
let tval = gen.derive(value.types, value.values, [value], ([n]) => n, {
skipInvariant: true,
});
tvalFor.set(key, tval);
}
});
pbindings.forEach((val, key, map) => {
let path = key.pathNode;
let tval = tvalFor.get(key);
invariant(tval !== undefined);
gen.emitStatement([key.object, tval], ([o, v]) => {
invariant(path !== undefined);
let lh = path.buildNode([o]);
return t.expressionStatement(t.assignmentExpression("=", (lh: any), v));
});
});
}
composeEffects(priorEffects: Effects, subsequentEffects: Effects): Effects {
let [, pg, pb, pp, po] = priorEffects;
let [sc, sg, sb, sp, so] = subsequentEffects;

View File

@ -238,6 +238,7 @@ export class ResidualHeapSerializer {
// inject properties
for (let [key, propertyBinding] of properties) {
invariant(propertyBinding);
if (propertyBinding.pathNode !== undefined) continue; // Property is assigned to inside loop
let desc = propertyBinding.descriptor;
if (desc === undefined) continue; //deleted
if (this.residualHeapInspector.canIgnoreProperty(obj, key)) continue;
@ -652,11 +653,16 @@ export class ResidualHeapSerializer {
serializeValue(val: Value, referenceOnly?: boolean, bindingType?: BabelVariableKind): BabelNodeExpression {
invariant(!val.refuseSerialization);
if (val instanceof AbstractValue && val.kind === "widening") {
this.serializedValues.add(val);
let name = val.intrinsicName;
invariant(name !== undefined);
return t.identifier(name);
if (val instanceof AbstractValue) {
if (val.kind === "widened") {
this.serializedValues.add(val);
let name = val.intrinsicName;
invariant(name !== undefined);
return t.identifier(name);
} else if (val.kind === "widened property") {
this.serializedValues.add(val);
return this._serializeAbstractValueHelper(val);
}
}
if (this._serializedValueWithIdentifiers.has(val)) {

View File

@ -145,6 +145,7 @@ export class ResidualHeapVisitor {
) {
continue;
}
if (propertyBindingKey.pathNode !== undefined) continue; // property is written to inside a loop
invariant(propertyBindingValue);
this.visitObjectProperty(propertyBindingValue);
}

View File

@ -135,6 +135,8 @@ export type PropertyBinding = {
descriptor?: Descriptor,
object: ObjectValue | AbstractObjectValue,
key: any,
// contains a build node that produces a member expression that resolves to this property binding (location)
pathNode?: AbstractValue,
};
export type LexicalEnvironmentTypes = "global" | "module" | "script" | "function" | "block" | "catch" | "loop" | "with";

View File

@ -157,7 +157,7 @@ export default class AbstractObjectValue extends AbstractValue {
desc.value = Join.joinValuesAsConditional(this.$Realm, cond, d1Value, d2Value);
}
return desc;
} else if (this.kind === "widening") {
} else if (this.kind === "widened") {
// This abstract object was created by repeated assignments of freshly allocated objects to the same binding inside a loop
let [ob1, ob2] = this.args; // ob1: summary of iterations 1...n, ob2: summary of iteration n+1
invariant(ob1 instanceof ObjectValue);

View File

@ -582,7 +582,7 @@ export default class AbstractValue extends Value {
// Creates a union of an abstract value with one or more concrete values.
// The build node for the abstract values becomes the build node for the union.
// Use this only to allow instrinsic abstract objects to be null and/or undefined.
static createAbstractConcreteUnion(realm: Realm, ...elements: Array<Value>) {
static createAbstractConcreteUnion(realm: Realm, ...elements: Array<Value>): AbstractValue {
let concreteValues: Array<ConcreteValue> = (elements.filter(e => e instanceof ConcreteValue): any);
invariant(concreteValues.length > 0 && concreteValues.length === elements.length - 1);
let concreteSet = new Set(concreteValues);
@ -604,14 +604,30 @@ export default class AbstractValue extends Value {
return result;
}
static createFromWidenedProperty(
realm: Realm,
resultTemplate: AbstractValue,
args: Array<Value>,
buildFunction: AbstractValueBuildNodeFunction
): AbstractValue {
let types = resultTemplate.types;
let values = resultTemplate.values;
let [hash] = hashCall("widened property", ...args);
let Constructor = Value.isTypeCompatibleWith(types.getType(), ObjectValue) ? AbstractObjectValue : AbstractValue;
let result = new Constructor(realm, types, values, hash, args, buildFunction);
result.kind = "widened property";
result.expressionLocation = resultTemplate.expressionLocation;
return result;
}
static createFromWidening(realm: Realm, value1: Value, value2: Value): AbstractValue {
// todo: #1174 look at kind and figure out much narrower widenings
let types = TypesDomain.joinValues(value1, value2);
let values = ValuesDomain.topVal;
let [hash] = hashCall("widening");
let [hash] = hashCall("widened");
let Constructor = Value.isTypeCompatibleWith(types.getType(), ObjectValue) ? AbstractObjectValue : AbstractValue;
let result = new Constructor(realm, types, values, hash, []);
result.kind = "widening";
result.kind = "widened";
result.expressionLocation = value1.expressionLocation;
return result;
}

View File

@ -0,0 +1,9 @@
let n = global.__abstract ? __abstract("number", "10") : 10;
let i = 0;
let ob = { j: 0 };
do {
i++;
ob.j = i;
} while (ob.j < n);
inspect = function() { return i + " " + ob.j; }