mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-19 19:17:25 +03:00
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:
parent
45383c8785
commit
45d19dd152
@ -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(
|
||||
|
63
src/realm.js
63
src/realm.js
@ -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;
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
9
test/serializer/abstract/DoWhile6.js
Normal file
9
test/serializer/abstract/DoWhile6.js
Normal 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; }
|
Loading…
Reference in New Issue
Block a user