| Index: pkg/compiler/lib/src/cps_ir/octagon.dart
|
| diff --git a/pkg/compiler/lib/src/cps_ir/octagon.dart b/pkg/compiler/lib/src/cps_ir/octagon.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..8dfd5f1938df481b014fe169c701bb7c60f1b402
|
| --- /dev/null
|
| +++ b/pkg/compiler/lib/src/cps_ir/octagon.dart
|
| @@ -0,0 +1,215 @@
|
| +// 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;
|
| + }
|
| +}
|
|
|