Unify simplifyAbstractValue and AbstractValue.refineWithPathCondition.

Summary:
Release notes: none

This is a no new functionality refactor that gets rid of the duplication of logic between these two functions.

Addresses #1019.

I don't think I'm quite done with the issue, since the factory functions also contain simplification logic.
Closes https://github.com/facebook/prepack/pull/1074

Differential Revision: D6035089

Pulled By: hermanventer

fbshipit-source-id: 11f3d1a51927ae67beddfa46197088228b93cb9e
This commit is contained in:
Herman Venter 2017-10-11 16:55:26 -07:00 committed by Facebook Github Bot
parent 3d2b586bc4
commit acf67a3362
7 changed files with 69 additions and 100 deletions

View File

@ -17,6 +17,7 @@ import * as evaluators from "./evaluators/index.js";
import * as partialEvaluators from "./partial-evaluators/index.js";
import { NewGlobalEnvironment } from "./methods/index.js";
import { ObjectValue } from "./values/index.js";
import simplifyAbstractValue from "./utils/simplifier.js";
export default function(opts: RealmOptions = {}): Realm {
let r = new Realm(opts);
@ -28,6 +29,7 @@ export default function(opts: RealmOptions = {}): Realm {
initializeGlobal(r);
for (let name in evaluators) r.evaluators[name] = evaluators[name];
for (let name in partialEvaluators) r.partialEvaluators[name] = partialEvaluators[name];
r.simplifyAbstractValue = simplifyAbstractValue.bind(null, r);
r.$GlobalEnv = NewGlobalEnvironment(r, r.$GlobalObject, r.$GlobalObject);
return r;
}

View File

@ -100,7 +100,7 @@ export function GetReferencedNamePartial(realm: Realm, V: Reference): AbstractVa
// ECMA262 6.2.3.1
export function GetValue(realm: Realm, V: Reference | Value): Value {
let val = dereference(realm, V);
if (val instanceof AbstractValue) return val.refineWithPathCondition();
if (val instanceof AbstractValue) return realm.simplifyAbstractValue(val);
return val;
}

View File

@ -594,12 +594,7 @@ export function joinValuesAsConditional(
v1: void | Value,
v2: void | Value
): Value {
let result = AbstractValue.createFromConditionalOp(realm, condition, v1, v2);
if (result instanceof AbstractValue) {
if (v1) result.mightBeEmpty = v1.mightHaveBeenDeleted();
if (v2 && !result.mightBeEmpty) result.mightBeEmpty = v2.mightHaveBeenDeleted();
}
return result;
return AbstractValue.createFromConditionalOp(realm, condition, v1, v2);
}
export function joinPropertyBindings(

View File

@ -232,6 +232,7 @@ export class Realm {
metadata?: any
) => [Completion | Reference | Value, BabelNode, Array<BabelNodeStatement>],
};
simplifyAbstractValue: AbstractValue => Value;
tracers: Array<Tracer>;

View File

@ -118,6 +118,6 @@ function pushInversePathCondition(condition: Value) {
function pushRefinedConditions(unrefinedConditions: Array<AbstractValue>) {
for (let unrefinedCond of unrefinedConditions) {
pushPathCondition(unrefinedCond.refineWithPathCondition());
pushPathCondition(unrefinedCond.$Realm.simplifyAbstractValue(unrefinedCond));
}
}

View File

@ -9,6 +9,7 @@
/* @flow */
import type { BabelNodeSourceLocation } from "babel-types";
import { FatalError } from "../errors.js";
import { ValuesDomain } from "../domains/index.js";
import invariant from "../invariant.js";
@ -33,29 +34,74 @@ export default function simplifyAbstractValue(realm: Realm, value: AbstractValue
}
}
function pathImplies(condition: AbstractValue): boolean {
let path = condition.$Realm.pathConditions;
for (let i = path.length - 1; i >= 0; i--) {
let pathCondition = path[i];
if (pathCondition.implies(condition)) return true;
}
return false;
}
function simplify(realm, value: Value): Value {
if (value instanceof ConcreteValue) return value;
invariant(value instanceof AbstractValue);
switch (value.kind) {
let op = value.kind;
switch (op) {
case "!":
return negate(realm, value.args[0]);
return negate(realm, value.args[0], value.expressionLocation);
case "||":
case "&&":
case "&&": {
let x = simplify(realm, value.args[0]);
let y = simplify(realm, value.args[1]);
if (x instanceof AbstractValue && x.equals(y)) return x;
// todo: remove this check when there is an alternative way to indicate that an intrinsic object is nullable
if (!(x instanceof AbstractObjectValue && x.isIntrinsic())) {
// true && y <=> y
// true || y <=> true
if (!x.mightNotBeTrue()) return op === "&&" ? y : x;
// (x == false) && y <=> x
// false || y <=> y
if (!x.mightNotBeFalse()) return op === "||" ? y : x;
}
if (x.getType() === BooleanValue && y.getType() === BooleanValue) {
// (x: boolean) && true <=> x
// x || true <=> true
if (!y.mightNotBeTrue()) return op === "&&" ? x : realm.intrinsics.true;
// (x: boolean) && false <=> false
// (x: boolean) || false <=> x
if (!y.mightNotBeFalse()) return op === "||" ? x : realm.intrinsics.false;
}
return AbstractValue.createFromLogicalOp(realm, (value.kind: any), x, y, value.expressionLocation);
}
case "==":
case "!=":
case "===":
case "!==":
return simplifyEquality(realm, value);
case "conditional": {
let c = simplify(realm, value.args[0]);
let x = simplify(realm, value.args[1]);
let y = simplify(realm, value.args[2]);
if (!c.mightNotBeTrue()) return x;
if (!c.mightNotBeFalse()) return y;
invariant(c instanceof AbstractValue);
if (pathImplies(c)) return x;
let notc = AbstractValue.createFromUnaryOp(realm, "!", c);
if (pathImplies(notc)) return y;
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "===", value, x))) return x;
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "!==", value, x))) return y;
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "!==", value, y))) return x;
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "===", value, y))) return y;
return AbstractValue.createFromConditionalOp(realm, c, x, y, value.expressionLocation);
}
default:
return value;
}
}
function simplifyEquality(realm: Realm, equality: AbstractValue): Value {
let loc = equality.expressionLocation;
let op = equality.kind;
let [x, y] = equality.args;
if (x instanceof ConcreteValue) [x, y] = [y, x];
@ -66,38 +112,38 @@ function simplifyEquality(realm: Realm, equality: AbstractValue): Value {
if (op === "===" || op === "!==") {
// if xx === undefined && xy !== undefined then cond <=> x === undefined
if (!y.mightNotBeUndefined() && !xx.mightNotBeUndefined() && !xy.mightBeUndefined())
return op === "===" ? makeBoolean(realm, cond) : negate(realm, cond);
return op === "===" ? makeBoolean(realm, cond, loc) : negate(realm, cond, loc);
// if xx !== undefined && xy === undefined then !cond <=> x === undefined
if (!y.mightNotBeUndefined() && !xx.mightBeUndefined() && !xy.mightNotBeUndefined())
return op === "===" ? negate(realm, cond) : makeBoolean(realm, cond);
return op === "===" ? negate(realm, cond, loc) : makeBoolean(realm, cond, loc);
// if xx === null && xy !== null then cond <=> x === null
if (!y.mightNotBeNull() && !xx.mightNotBeNull() && !xy.mightBeNull())
return op === "===" ? makeBoolean(realm, cond) : negate(realm, cond);
return op === "===" ? makeBoolean(realm, cond, loc) : negate(realm, cond, loc);
// if xx !== null && xy === null then !cond <=> x === null
if (!y.mightNotBeNull() && !xx.mightBeNull() && !xy.mightNotBeNull())
return op === "===" ? negate(realm, cond) : makeBoolean(realm, cond);
return op === "===" ? negate(realm, cond, loc) : makeBoolean(realm, cond, loc);
} else {
invariant(op === "==" || op === "!=");
// if xx cannot be undefined/null and xy is undefined/null then !cond <=> x == undefined/null
if (!xx.mightBeUndefined() && !xx.mightBeNull() && (!xy.mightNotBeUndefined() || !xy.mightNotBeNull()))
return op === "==" ? negate(realm, cond) : makeBoolean(realm, cond);
return op === "==" ? negate(realm, cond, loc) : makeBoolean(realm, cond, loc);
// if xx is undefined/null and xy cannot be undefined/null then cond <=> x == undefined/null
if ((!xx.mightNotBeUndefined() || !xx.mightNotBeNull()) && !xy.mightBeUndefined() && !xy.mightBeNull())
return op === "==" ? makeBoolean(realm, cond) : negate(realm, cond);
return op === "==" ? makeBoolean(realm, cond, loc) : negate(realm, cond, loc);
}
}
return equality;
}
function makeBoolean(realm: Realm, value: Value): Value {
function makeBoolean(realm: Realm, value: Value, loc: ?BabelNodeSourceLocation = undefined): Value {
if (value.getType() === BooleanValue) return value;
if (value instanceof ConcreteValue) return new BooleanValue(realm, ToBoolean(realm, value));
invariant(value instanceof AbstractValue);
let v = AbstractValue.createFromUnaryOp(realm, "!", value, true, value.expressionLocation);
return AbstractValue.createFromUnaryOp(realm, "!", v, true, value.expressionLocation);
return AbstractValue.createFromUnaryOp(realm, "!", v, true, loc || value.expressionLocation);
}
function negate(realm: Realm, value: Value): Value {
function negate(realm: Realm, value: Value, loc: ?BabelNodeSourceLocation = undefined): Value {
if (value instanceof ConcreteValue) return ValuesDomain.computeUnary(realm, "!", value);
invariant(value instanceof AbstractValue);
if (value.kind === "!") return makeBoolean(realm, value.args[0]);
@ -140,7 +186,7 @@ function negate(realm: Realm, value: Value): Value {
if (invertedComparison !== undefined) {
let left = simplify(realm, value.args[0]);
let right = simplify(realm, value.args[1]);
return AbstractValue.createFromBinaryOp(realm, invertedComparison, left, right, value.expressionLocation);
return AbstractValue.createFromBinaryOp(realm, invertedComparison, left, right, loc || value.expressionLocation);
}
let invertedLogicalOp;
switch (value.kind) {
@ -156,8 +202,8 @@ function negate(realm: Realm, value: Value): Value {
if (invertedLogicalOp !== undefined) {
let left = negate(realm, value.args[0]);
let right = negate(realm, value.args[1]);
return AbstractValue.createFromLogicalOp(realm, invertedLogicalOp, left, right, value.expressionLocation);
return AbstractValue.createFromLogicalOp(realm, invertedLogicalOp, left, right, loc || value.expressionLocation);
}
}
return AbstractValue.createFromUnaryOp(realm, "!", value, true, value.expressionLocation);
return AbstractValue.createFromUnaryOp(realm, "!", value, true, loc || value.expressionLocation);
}

View File

@ -294,83 +294,6 @@ export default class AbstractValue extends Value {
return result;
}
// Simplify an already constructed abstract value in the light of the path conditions that apply in the
// context where is this value is now being used.
// TODO #1019: this logic largely duplicates the functionality in simplifyAbstractValue.
// To fix this, two thing have to happen:
// 1. Avoid injecting simplifier.js into the big Flow dependency cycle that includes AbstractValue. This can probably
// be done by storing simplifyAbstractValue in the realm.
// 2. Make simplifyAbstractValue path sensitive by checking for each condition if the current path conditions
// imply them and then simplifying appropriately.
refineWithPathCondition(): Value {
function pathImplies(condition: AbstractValue): boolean {
let path = condition.$Realm.pathConditions;
for (let i = path.length - 1; i >= 0; i--) {
let pathCondition = path[i];
if (pathCondition.implies(condition)) return true;
}
return false;
}
let realm = this.$Realm;
if (realm.pathConditions.length === 0) return this;
let op = this.kind;
let result = (() => {
if (op === "&&" || op === "||") {
let [left, right] = this.args;
invariant(left instanceof AbstractValue); // otherwise the factory would have simplified it.
let refinedLeft = left.refineWithPathCondition();
let refinedRight = right instanceof AbstractValue ? right.refineWithPathCondition() : right;
// todo: remove this check when there is an alternative way to indicate that an intrinsic object is nullable
if (!(refinedLeft instanceof AbstractObjectValue && refinedLeft.isIntrinsic())) {
// true && y <=> y
// true || y <=> true
if (!refinedLeft.mightNotBeTrue()) return op === "&&" ? refinedRight : refinedLeft;
// (x == false) && y <=> x
// false || y <=> y
if (!refinedLeft.mightNotBeFalse()) return op === "||" ? refinedRight : refinedLeft;
}
if (refinedLeft.getType() === BooleanValue && refinedRight.getType() === BooleanValue) {
// (x: boolean) && true <=> x
// x || true <=> true
if (!refinedRight.mightNotBeTrue()) return op === "&&" ? refinedLeft : realm.intrinsics.true;
// (x: boolean) && false <=> false
// (x: boolean) || false <=> x
if (!refinedRight.mightNotBeFalse()) return op === "||" ? refinedLeft : realm.intrinsics.false;
}
// return this if no refinements happened
if (left === refinedLeft && right === refinedRight) return this;
// recreate operation using refined operands
return AbstractValue.createFromLogicalOp(realm, op, refinedLeft, refinedRight, this.expressionLocation);
}
if (op === "!") {
let arg = this.args[0];
invariant(arg instanceof AbstractValue); // otherwise this abstract value should have been folded to a constant
let refinedArg = arg.refineWithPathCondition();
if (arg === refinedArg) return this;
if (!refinedArg.mightNotBeTrue()) return realm.intrinsics.false;
if (!refinedArg.mightNotBeFalse()) return realm.intrinsics.true;
invariant(refinedArg instanceof AbstractValue); // concrete values always make up their mind above
return AbstractValue.createFromUnaryOp(realm, op, refinedArg);
}
if (op !== "conditional") return this;
let [condition, trueVal, falseVal] = this.args;
invariant(trueVal !== undefined);
invariant(falseVal !== undefined);
invariant(condition instanceof AbstractValue);
let inverseCondition = AbstractValue.createFromUnaryOp(this.$Realm, "!", condition);
if (pathImplies(condition)) return trueVal;
if (pathImplies(inverseCondition)) return falseVal;
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "===", this, trueVal))) return trueVal;
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "!==", this, trueVal))) return falseVal;
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "!==", this, falseVal))) return trueVal;
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "===", this, falseVal))) return falseVal;
return this;
})();
if (result !== this && result instanceof AbstractValue) return result.refineWithPathCondition();
return result;
}
throwIfNotConcrete(): ConcreteValue {
AbstractValue.reportIntrospectionError(this);
throw new FatalError();
@ -539,6 +462,8 @@ export default class AbstractValue extends Value {
kind: "conditional",
});
result.expressionLocation = loc;
if (left) result.mightBeEmpty = left.mightHaveBeenDeleted();
if (right && !result.mightBeEmpty) result.mightBeEmpty = right.mightHaveBeenDeleted();
return result;
}