blob: 8dfd5f1938df481b014fe169c701bb7c60f1b402 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library dart2js.cps_ir.octagon;
/// For every variable in the constraint system, two [SignedVariable]s exist,
/// representing the positive and negative uses of the variable.
///
/// For instance, `v1 - v2` is represented as `v1 + (-v2)`, with -v2 being
/// a "negative use" of v2.
class SignedVariable {
/// Negated version of this variable.
SignedVariable _negated;
SignedVariable get negated => _negated;
/// Constraints that mention this signed variable.
final List<Constraint> _constraints = <Constraint>[];
static int _hashCount = 0;
final int hashCode = (_hashCount = _hashCount + 1) & 0x0fffffff;
/// Temporary field used by the constraint solver's graph search.
bool _isBeingVisited = false;
SignedVariable._make() {
_negated = new SignedVariable._makeTwin(this);
}
SignedVariable._makeTwin(this._negated);
}
/// A constraint of form `v1 + v2 <= k`.
class Constraint {
final SignedVariable v1, v2;
final int bound;
Constraint(this.v1, this.v2, this.bound);
}
/// A system of constraints of form `v1 + v2 <= k`.
///
/// Constraints can be added and removed in stack-order. The octagon will
/// always determine whether it is in a solvable state, but will otherwise
/// not optimize its internal representation.
///
/// There is currently no support for querying the upper and lower bounds
/// of a variable, (which can be used to approximate ternary constraints
/// `v1 + v2 + v3 <= k`), but it is something we could consider adding.
class Octagon {
/// Number of constraints that have been added since the constraint system
/// became unsolvable (including the constraint that made it unsolvable).
///
/// This is well-defined because constraints are pushed/popped in stack order.
int _unsolvableCounter = 0;
/// True if the constraint system is unsolvable in its current state.
///
/// It will remain unsolvable until a number of constraints have been popped.
bool get isUnsolvable => _unsolvableCounter > 0;
/// True if the constraint system is solvable in its current state.
bool get isSolvable => _unsolvableCounter == 0;
/// Make a new variable, optionally with known lower and upper bounds
/// (both inclusive).
///
/// The constraints generated for [min] and [max] are also expressible using
/// [Constraint] objects, but the constraints added in [makeVariable] live
/// outside the stack discipline (i.e. the bounds are never popped), which is
/// useful when generating variables on-the-fly.
SignedVariable makeVariable([int min, int max]) {
SignedVariable v1 = new SignedVariable._make();
if (min != null) {
// v1 >= min <==> -v1 - v1 <= -2 * min
v1.negated._constraints.add(
new Constraint(v1.negated, v1.negated, -2 * min));
}
if (max != null) {
// v1 <= max <==> v1 + v1 <= 2 * max
v1._constraints.add(new Constraint(v1, v1, 2 * max));
}
return v1;
}
/// Add the constraint `v1 + v2 <= k`.
///
/// The constraint should be removed again using [popConstraint].
void pushConstraint(Constraint constraint) {
if (_unsolvableCounter > 0) {
++_unsolvableCounter;
}
constraint.v1._constraints.add(constraint);
if (constraint.v1 != constraint.v2) {
constraint.v2._constraints.add(constraint);
}
// Check if this constraint has made the system unsolvable.
if (_unsolvableCounter == 0 && _checkUnsolvable(constraint)) {
_unsolvableCounter = 1;
}
}
/// Remove a constraint that was previously added with [pushConstraint].
///
/// Constraints must be added and removed in stack-order.
void popConstraint(Constraint constraint) {
assert(constraint.v1._constraints.last == constraint);
assert(constraint.v2._constraints.last == constraint);
constraint.v1._constraints.removeLast();
if (constraint.v1 != constraint.v2) {
constraint.v2._constraints.removeLast();
}
if (_unsolvableCounter > 0) {
--_unsolvableCounter;
}
}
// Temporaries using during path finding.
SignedVariable _goal;
Map<SignedVariable, int> _distanceToGoal;
/// Return true if the recently added [constraint] made the system unsolvable.
///
/// This function assumes the system was solvable before adding [constraint].
bool _checkUnsolvable(Constraint constraint) {
// Constraints are transitively composed like so:
// v1 - v2 <= k1
// v2 - v3 <= k2
// implies:
// v1 - v3 <= k1 + k2
//
// We construct a graph such that the tightest bound on `v1 - v3` is the
// weight of the shortest path from `v1` to `v3`.
//
// Ever constraint `v1 - v2 <= k` gives rise to two edges:
// (v1) --k--> (v2)
// (-v2) --k--> (-v2)
//
// The system is unsolvable if and only if a negative-weight cycle exists
// in this graph (this corresponds to a variable being less than itself).
// We assume the system was solvable to begin with, so we only look for
// cycles that use the new edges.
//
// The new [constraint] `v1 + v2 <= k` just added the edges:
// (v1) --k--> (-v2)
// (v2) --k--> (-v1)
//
// Look for a path from (-v2) to (v1) with weight at most -k-1, as this
// will complete a negative-weight cycle.
// It suffices to do this once. We need not check for the converse path
// (-v1) to (v2) because of the symmetry in the graph.
//
// Note that the graph symmetry is not a redundancy. Some cycles include
// both of the new edges at once, so they must be added to the graph
// beforehand.
_goal = constraint.v2;
_distanceToGoal = <SignedVariable, int>{};
int targetWeight = -constraint.bound - 1;
int pathWeight = _search(constraint.v1.negated, targetWeight, 0);
return pathWeight != null && pathWeight <= targetWeight;
}
static const int MAX_DEPTH = 100;
/// Returns the shortest path from [v1] to [_goal] (or any path shorter than
/// [budget]), or `null` if no path exists.
int _search(SignedVariable v1, int budget, int depth) {
if (v1 == _goal && budget >= 0) return 0;
// Disregard paths that use a lot of edges.
// In extreme cases (e.g. hundreds of `push` calls or nested ifs) this can
// get slow and/or overflow the stack. Most paths that matter are very
// short (1-5 edges) with some occasional 10-30 length paths in math code.
if (depth >= MAX_DEPTH) return null;
// We found a cycle, but not the one we're looking for. If the constraint
// system was solvable to being with, then this must be a positive-weight
// cycle, and no shortest path goes through a positive-weight cycle.
if (v1._isBeingVisited) return null;
// Check if we have previously searched from here.
if (_distanceToGoal.containsKey(v1)) {
// We have already searched this node, return the cached answer.
// Note that variables may explicitly map to null, so the double lookup
// is necessary.
return _distanceToGoal[v1];
}
v1._isBeingVisited = true;
int shortestDistance = v1 == _goal ? 0 : null;
for (Constraint c in v1._constraints) {
SignedVariable v2 = c.v1 == v1 ? c.v2 : c.v1;
int distance = _search(v2.negated, budget - c.bound, depth + 1);
if (distance != null) {
distance += c.bound; // Pay the cost of using the edge.
if (distance <= budget) {
// Success! We found a path that is short enough so return fast.
// All recursive calls will now return immediately, so there is no
// need to update distanceToGoal, but we need to clear the
// beingVisited flag for the next query.
v1._isBeingVisited = false;
return distance;
} else if (shortestDistance == null || distance < shortestDistance) {
shortestDistance = distance;
}
}
}
v1._isBeingVisited = false;
_distanceToGoal[v1] = shortestDistance;
return shortestDistance;
}
}