// Copyright (c) 2012, 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.

part of ssa;


class ValueRangeInfo {
  final ConstantSystem constantSystem;

  IntValue intZero;
  IntValue intOne;

  ValueRangeInfo(this.constantSystem) {
    intZero = newIntValue(0);
    intOne = newIntValue(1);
  }

  Value newIntValue(int value) {
    return new IntValue(value, this);
  }

  Value newInstructionValue(HInstruction instruction) {
    return new InstructionValue(instruction, this);
  }

  Value newPositiveValue(HInstruction instruction) {
    return new PositiveValue(instruction, this);
  }

  Value newAddValue(Value left, Value right) {
    return new AddValue(left, right, this);
  }

  Value newSubtractValue(Value left, Value right) {
    return new SubtractValue(left, right, this);
  }

  Value newNegateValue(Value value) {
    return new NegateValue(value, this);
  }

  Range newUnboundRange() {
    return new Range.unbound(this);
  }

  Range newNormalizedRange(Value low, Value up) {
    return new Range.normalize(low, up, this);
  }

  Range newMarkerRange() {
    return new Range(new MarkerValue(false, this),
                     new MarkerValue(true, this),
                     this);
  }
}

/**
 * A [Value] represents both symbolic values like the value of a
 * parameter, or the length of an array, and concrete values, like
 * constants.
 */
abstract class Value {
  final ValueRangeInfo info;
  const Value(this.info);

  Value operator +(Value other) => const UnknownValue();
  Value operator -(Value other) => const UnknownValue();
  Value operator -()  => const UnknownValue();
  Value operator &(Value other) => const UnknownValue();

  Value min(Value other) {
    if (this == other) return this;
    if (other == const MinIntValue()) return other;
    if (other == const MaxIntValue()) return this;
    Value value = this - other;
    if (value.isPositive) return other;
    if (value.isNegative) return this;
    return const UnknownValue();
  }

  Value max(Value other) {
    if (this == other) return this;
    if (other == const MinIntValue()) return this;
    if (other == const MaxIntValue()) return other;
    Value value = this - other;
    if (value.isPositive) return this;
    if (value.isNegative) return other;
    return const UnknownValue();
  }

  bool get isNegative => false;
  bool get isPositive => false;
  bool get isZero => false;
}

/**
 * The [MarkerValue] class is used to recognize ranges of loop
 * updates.
 */
class MarkerValue extends Value {
  /// If [positive] is true (respectively false), the marker goes
  /// to [MaxIntValue] (respectively [MinIntValue]) when being added
  /// to a positive (respectively negative) value.
  final bool positive;

  const MarkerValue(this.positive, info) : super(info);

  Value operator +(Value other) {
    if (other.isPositive && positive) return const MaxIntValue();
    if (other.isNegative && !positive) return const MinIntValue();
    if (other is IntValue) return this;
    return const UnknownValue();
  }

  Value operator -(Value other) {
    if (other.isPositive && !positive) return const MinIntValue();
    if (other.isNegative && positive) return const MaxIntValue();
    if (other is IntValue) return this;
    return const UnknownValue();
  }
}

/**
 * An [IntValue] contains a constant integer value.
 */
class IntValue extends Value {
  final int value;

  const IntValue(this.value, info) : super(info);

  Value operator +(other) {
    if (other.isZero) return this;
    if (other is !IntValue) return other + this;
    ConstantSystem constantSystem = info.constantSystem;
    var constant = constantSystem.add.fold(
        constantSystem.createInt(value), constantSystem.createInt(other.value));
    if (!constant.isInt) return const UnknownValue();
    return info.newIntValue(constant.value);
  }

  Value operator -(other) {
    if (other.isZero) return this;
    if (other is !IntValue) return -other + this;
    ConstantSystem constantSystem = info.constantSystem;
    var constant = constantSystem.subtract.fold(
        constantSystem.createInt(value), constantSystem.createInt(other.value));
    if (!constant.isInt) return const UnknownValue();
    return info.newIntValue(constant.value);
  }

  Value operator -() {
    if (isZero) return this;
    ConstantSystem constantSystem = info.constantSystem;
    var constant = constantSystem.negate.fold(
        constantSystem.createInt(value));
    if (!constant.isInt) return const UnknownValue();
    return info.newIntValue(constant.value);
  }

  Value operator &(other) {
    if (other is !IntValue) return const UnknownValue();
    ConstantSystem constantSystem = info.constantSystem;
    var constant = constantSystem.bitAnd.fold(
        constantSystem.createInt(value), constantSystem.createInt(other.value));
    return info.newIntValue(constant.value);
  }

  Value min(other) {
    if (other is !IntValue) return other.min(this);
    return this.value < other.value ? this : other;
  }

  Value max(other) {
    if (other is !IntValue) return other.max(this);
    return this.value < other.value ? other : this;
  }

  bool operator ==(other) {
    if (other is !IntValue) return false;
    return this.value == other.value;
  }

  int get hashCode => throw new UnsupportedError('IntValue.hashCode');

  String toString() => 'IntValue $value';
  bool get isNegative => value < 0;
  bool get isPositive => value >= 0;
  bool get isZero => value == 0;
}

/**
 * The [MaxIntValue] represents the maximum value an integer can have,
 * which is currently +infinity.
 */
class MaxIntValue extends Value {
  const MaxIntValue() : super(null);
  Value operator +(Value other) => this;
  Value operator -(Value other) => this;
  Value operator -() => const MinIntValue();
  Value min(Value other) => other;
  Value max(Value other) => this;
  String toString() => 'Max';
  bool get isNegative => false;
  bool get isPositive => true;
}

/**
 * The [MinIntValue] represents the minimum value an integer can have,
 * which is currently -infinity.
 */
class MinIntValue extends Value {
  const MinIntValue() : super(null);
  Value operator +(Value other) => this;
  Value operator -(Value other) => this;
  Value operator -() => const MaxIntValue();
  Value min(Value other) => this;
  Value max(Value other) => other;
  String toString() => 'Min';
  bool get isNegative => true;
  bool get isPositive => false;
}

/**
 * The [UnknownValue] is the sentinel in our analysis to mark an
 * operation that could not be done because of too much complexity.
 */
class UnknownValue extends Value {
  const UnknownValue() : super(null);
  Value operator +(Value other) => const UnknownValue();
  Value operator -(Value other) => const UnknownValue();
  Value operator -() => const UnknownValue();
  Value min(Value other) => const UnknownValue();
  Value max(Value other) => const UnknownValue();
  bool get isNegative => false;
  bool get isPositive => false;
  String toString() => 'Unknown';
}

/**
 * A symbolic value representing an [HInstruction].
 */
class InstructionValue extends Value {
  final HInstruction instruction;
  InstructionValue(this.instruction, info) : super(info);

  bool operator ==(other) {
    if (other is !InstructionValue) return false;
    return this.instruction == other.instruction;
  }

  int get hashCode => throw new UnsupportedError('InstructionValue.hashCode');

  Value operator +(Value other) {
    if (other.isZero) return this;
    if (other is IntValue) {
      if (other.isNegative) {
        return info.newSubtractValue(this, -other);
      }
      return info.newAddValue(this, other);
    }
    if (other is InstructionValue) {
      return info.newAddValue(this, other);
    }
    return other + this;
  }

  Value operator -(Value other) {
    if (other.isZero) return this;
    if (this == other) return info.intZero;
    if (other is IntValue) {
      if (other.isNegative) {
        return info.newAddValue(this, -other);
      }
      return info.newSubtractValue(this, other);
    }
    if (other is InstructionValue) {
      return info.newSubtractValue(this, other);
    }
    return -other + this;
  }

  Value operator -() {
    return info.newNegateValue(this);
  }

  bool get isNegative => false;
  bool get isPositive => false;

  String toString() => 'Instruction: $instruction';
}

/**
 * Special value for instructions whose type is a positive integer.
 */
class PositiveValue extends InstructionValue {
  PositiveValue(HInstruction instruction, info) : super(instruction, info);
  bool get isPositive => true;
}

/**
 * Represents a binary operation on two [Value], where the operation
 * did not yield a canonical value.
 */
class BinaryOperationValue extends Value {
  final Value left;
  final Value right;
  BinaryOperationValue(this.left, this.right, info) : super(info);
}

class AddValue extends BinaryOperationValue {
  AddValue(left, right, info) : super(left, right, info);

  bool operator ==(other) {
    if (other is !AddValue) return false;
    return (left == other.left && right == other.right)
      || (left == other.right && right == other.left);
  }

  int get hashCode => throw new UnsupportedError('AddValue.hashCode');

  Value operator -() => -left - right;

  Value operator +(Value other) {
    if (other.isZero) return this;
    Value value = left + other;
    if (value != const UnknownValue() && value is! BinaryOperationValue) {
      return value + right;
    }
    // If the result is not simple enough, we try the same approach
    // with [right].
    value = right + other;
    if (value != const UnknownValue() && value is! BinaryOperationValue) {
      return left + value;
    }
    return const UnknownValue();
  }

  Value operator -(Value other) {
    if (other.isZero) return this;
    Value value = left - other;
    if (value != const UnknownValue() && value is! BinaryOperationValue) {
      return value + right;
    }
    // If the result is not simple enough, we try the same approach
    // with [right].
    value = right - other;
    if (value != const UnknownValue() && value is! BinaryOperationValue) {
      return left + value;
    }
    return const UnknownValue();
  }

  bool get isNegative => left.isNegative && right.isNegative;
  bool get isPositive => left.isPositive && right.isPositive;
  String toString() => '$left + $right';
}

class SubtractValue extends BinaryOperationValue {
  SubtractValue(left, right, info) : super(left, right, info);

  bool operator ==(other) {
    if (other is !SubtractValue) return false;
    return left == other.left && right == other.right;
  }

  int get hashCode => throw new UnsupportedError('SubtractValue.hashCode');

  Value operator -() => right - left;

  Value operator +(Value other) {
    if (other.isZero) return this;
    Value value = left + other;
    if (value != const UnknownValue() && value is! BinaryOperationValue) {
      return value - right;
    }
    // If the result is not simple enough, we try the same approach
    // with [right].
    value = other - right;
    if (value != const UnknownValue() && value is! BinaryOperationValue) {
      return left + value;
    }
    return const UnknownValue();
  }

  Value operator -(Value other) {
    if (other.isZero) return this;
    Value value = left - other;
    if (value != const UnknownValue() && value is! BinaryOperationValue) {
      return value - right;
    }
    // If the result is not simple enough, we try the same approach
    // with [right].
    value = right + other;
    if (value != const UnknownValue() && value is! BinaryOperationValue) {
      return left - value;
    }
    return const UnknownValue();
  }

  bool get isNegative => left.isNegative && right.isPositive;
  bool get isPositive => left.isPositive && right.isNegative;
  String toString() => '$left - $right';
}

class NegateValue extends Value {
  final Value value;
  NegateValue(this.value, info) : super(info);

  bool operator ==(other) {
    if (other is !NegateValue) return false;
    return value == other.value;
  }

  int get hashCode => throw new UnsupportedError('Negate.hashCode');

  Value operator +(other) {
    if (other.isZero) return this;
    if (other == value) return info.intZero;
    if (other is NegateValue) return this - other.value;
    if (other is IntValue) {
      if (other.isNegative) {
        return info.newSubtractValue(this, -other);
      }
      return info.newSubtractValue(other, value);
    }
    if (other is InstructionValue) {
      return info.newSubtractValue(other, value);
    }
    return other - value;
  }

  Value operator &(Value other) => const UnknownValue();

  Value operator -(other) {
    if (other.isZero) return this;
    if (other is IntValue) {
      if (other.isNegative) {
        return info.newSubtractValue(-other, value);
      }
      return info.newSubtractValue(this, other);
    }
    if (other is InstructionValue) {
      return info.newSubtractValue(this, other);
    }
    if (other is NegateValue) return this + other.value;
    return -other - value;
  }

  Value operator -() => value;

  bool get isNegative => value.isPositive;
  bool get isPositive => value.isNegative;
  String toString() => '-$value';
}

/**
 * A [Range] represents the possible integer values an instruction
 * can have, from its [lower] bound to its [upper] bound, both
 * included.
 */
class Range {
  final Value lower;
  final Value upper;
  final ValueRangeInfo info;
  Range(this.lower, this.upper, this.info) {
    assert(lower != const UnknownValue());
    assert(upper != const UnknownValue());
  }

  Range.unbound(info) : this(const MinIntValue(), const MaxIntValue(), info);

  /**
   * Checks if the given values are unknown, and creates a
   * range that does not have any unknown values.
   */
  Range.normalize(Value low, Value up, info) : this(
      low == const UnknownValue() ? const MinIntValue() : low,
      up == const UnknownValue() ? const MaxIntValue() : up,
      info);

  Range union(Range other) {
    return info.newNormalizedRange(
        lower.min(other.lower), upper.max(other.upper));
  }

  Range intersection(Range other) {
    Value low = lower.max(other.lower);
    Value up = upper.min(other.upper);
    // If we could not compute max or min, pick a value in the two
    // ranges, with priority to [IntValue]s because they are simpler.
    if (low == const UnknownValue()) {
      if (lower is IntValue) low = lower;
      else if (other.lower is IntValue) low = other.lower;
      else low = lower;
    }
    if (up == const UnknownValue()) {
      if (upper is IntValue) up = upper;
      else if (other.upper is IntValue) up = other.upper;
      else up = upper;
    }
    return info.newNormalizedRange(low, up);
  }

  Range operator +(Range other) {
    return info.newNormalizedRange(lower + other.lower, upper + other.upper);
  }

  Range operator -(Range other) {
    return info.newNormalizedRange(lower - other.upper, upper - other.lower);
  }

  Range operator -() {
    return info.newNormalizedRange(-upper, -lower);
  }

  Range operator &(Range other) {
    if (isSingleValue
        && other.isSingleValue
        && lower is IntValue
        && other.lower is IntValue) {
      return info.newNormalizedRange(lower & other.lower, upper & other.upper);
    }
    if (isPositive && other.isPositive) {
      Value up = upper.min(other.upper);
      if (up == const UnknownValue()) {
        // If we could not find a trivial bound, just try to use the
        // one that is an int.
        up = upper is IntValue ? upper : other.upper;
        // Make sure we get the same upper bound, whether it's a & b
        // or b & a.
        if (up is! IntValue && upper != other.upper) up = const MaxIntValue();
      }
      return info.newNormalizedRange(info.intZero, up);
    } else if (isPositive) {
      return info.newNormalizedRange(info.intZero, upper);
    } else if (other.isPositive) {
      return info.newNormalizedRange(info.intZero, other.upper);
    } else {
      return info.newUnboundRange();
    }
  }

  bool operator ==(other) {
    if (other is! Range) return false;
    return other.lower == lower && other.upper == upper;
  }

  int get hashCode => throw new UnsupportedError('Range.hashCode');

  bool operator <(Range other) {
    return upper != other.lower && upper.min(other.lower) == upper;
  }

  bool operator >(Range other) {
    return lower != other.upper && lower.max(other.upper) == lower;
  }

  bool operator <=(Range other) {
    return upper.min(other.lower) == upper;
  }

  bool operator >=(Range other) {
    return lower.max(other.upper) == lower;
  }

  bool get isNegative => upper.isNegative;
  bool get isPositive => lower.isPositive;
  bool get isSingleValue => lower == upper;

  String toString() => '[$lower, $upper]';
}

/**
 * Visits the graph in dominator order, and computes value ranges for
 * integer instructions. While visiting the graph, this phase also
 * removes unnecessary bounds checks, and comparisons that are proven
 * to be true or false.
 */
class SsaValueRangeAnalyzer extends HBaseVisitor implements OptimizationPhase {
  String get name => 'SSA value range builder';

  /**
   * List of [HRangeConversion] instructions created by the phase. We
   * save them here in order to remove them once the phase is done.
   */
  final List<HRangeConversion> conversions = <HRangeConversion>[];

  /**
   * Value ranges for integer instructions. This map gets populated by
   * the dominator tree visit.
   */
  final Map<HInstruction, Range> ranges = new Map<HInstruction, Range>();

  final Compiler compiler;
  final ConstantSystem constantSystem;
  final ValueRangeInfo info;

  CodegenWorkItem work;
  HGraph graph;

  SsaValueRangeAnalyzer(this.compiler, constantSystem, this.work)
      : info = new ValueRangeInfo(constantSystem),
        this.constantSystem = constantSystem;

  void visitGraph(HGraph graph) {
    this.graph = graph;
    visitDominatorTree(graph);
    // We remove the range conversions after visiting the graph so
    // that the graph does not get polluted with these instructions
    // only necessary for this phase.
    removeRangeConversion();
    JavaScriptBackend backend = compiler.backend;
    // TODO(herhut): Find a cleaner way to pass around ranges.
    backend.optimizer.ranges = ranges;
  }

  void removeRangeConversion() {
    conversions.forEach((HRangeConversion instruction) {
      instruction.block.rewrite(instruction, instruction.inputs[0]);;
      instruction.block.remove(instruction);
    });
  }

  void visitBasicBlock(HBasicBlock block) {

    void visit(HInstruction instruction) {
      Range range = instruction.accept(this);
      if (instruction.isInteger(compiler)) {
        assert(range != null);
        ranges[instruction] = range;
      }
    }

    block.forEachPhi(visit);
    block.forEachInstruction(visit);
  }

  Range visitInstruction(HInstruction instruction) {
    if (instruction.isPositiveInteger(compiler)) {
      return info.newNormalizedRange(
          info.intZero, info.newPositiveValue(instruction));
    } else if (instruction.isInteger(compiler)) {
      InstructionValue value = info.newInstructionValue(instruction);
      return info.newNormalizedRange(value, value);
    } else {
      return info.newUnboundRange();
    }
  }

  Range visitPhi(HPhi phi) {
    if (!phi.isInteger(compiler)) return info.newUnboundRange();
    // Some phases may replace instructions that change the inputs of
    // this phi. Only the [SsaTypesPropagation] phase will update the
    // phi type. Play it safe by assuming the [SsaTypesPropagation]
    // phase is not necessarily run before the [ValueRangeAnalyzer].
    if (phi.inputs.any((i) => !i.isInteger(compiler))) {
      return info.newUnboundRange();
    }
    if (phi.block.isLoopHeader()) {
      Range range = new LoopUpdateRecognizer(compiler, ranges, info).run(phi);
      if (range == null) return info.newUnboundRange();
      return range;
    }

    Range range = ranges[phi.inputs[0]];
    for (int i = 1; i < phi.inputs.length; i++) {
      range = range.union(ranges[phi.inputs[i]]);
    }
    return range;
  }

  Range visitConstant(HConstant constant) {
    if (!constant.isInteger(compiler)) return info.newUnboundRange();
    NumConstant constantNum = constant.constant;
    if (constantNum.isMinusZero) constantNum = new IntConstant(0);
    Value value = info.newIntValue(constantNum.value);
    return info.newNormalizedRange(value, value);
  }

  Range visitFieldGet(HFieldGet fieldGet) {
    if (!fieldGet.isInteger(compiler)) return info.newUnboundRange();
    if (!fieldGet.receiver.isIndexablePrimitive(compiler)) {
      return visitInstruction(fieldGet);
    }
    JavaScriptBackend backend = compiler.backend;
    assert(fieldGet.element == backend.jsIndexableLength);
    PositiveValue value = info.newPositiveValue(fieldGet);
    // We know this range is above zero. To simplify the analysis, we
    // put the zero value as the lower bound of this range. This
    // allows to easily remove the second bound check in the following
    // expression: a[1] + a[0].
    return info.newNormalizedRange(info.intZero, value);
  }

  Range visitBoundsCheck(HBoundsCheck check) {
    // Save the next instruction, in case the check gets removed.
    HInstruction next = check.next;
    Range indexRange = ranges[check.index];
    Range lengthRange = ranges[check.length];
    if (indexRange == null) {
      indexRange = info.newUnboundRange();
      assert(!check.index.isInteger(compiler));
    }
    assert(check.length.isInteger(compiler));

    // Check if the index is strictly below the upper bound of the length
    // range.
    Value maxIndex = lengthRange.upper - info.intOne;
    bool belowLength = maxIndex != const MaxIntValue()
        && indexRange.upper.min(maxIndex) == indexRange.upper;

    // Check if the index is strictly below the lower bound of the length
    // range.
    belowLength = belowLength
        || (indexRange.upper != lengthRange.lower
            && indexRange.upper.min(lengthRange.lower) == indexRange.upper);
    if (indexRange.isPositive && belowLength) {
      check.block.rewrite(check, check.index);
      check.block.remove(check);
    } else if (indexRange.isNegative || lengthRange < indexRange) {
      check.staticChecks = HBoundsCheck.ALWAYS_FALSE;
      // The check is always false, and whatever instruction it
      // dominates is dead code.
      return indexRange;
    } else if (indexRange.isPositive) {
      check.staticChecks = HBoundsCheck.ALWAYS_ABOVE_ZERO;
    } else if (belowLength) {
      check.staticChecks = HBoundsCheck.ALWAYS_BELOW_LENGTH;
    }

    if (indexRange.isPositive) {
      // If the test passes, we know the lower bound of the length is
      // greater or equal than the lower bound of the index.
      Value low = lengthRange.lower.max(indexRange.lower);
      if (low != const UnknownValue()) {
        HInstruction instruction =
            createRangeConversion(next, check.length);
        ranges[instruction] = info.newNormalizedRange(low, lengthRange.upper);
      }
    }

    // Update the range of the index if using the maximum index
    // narrows it. Use that new range for this instruction as well.
    Range newIndexRange = indexRange.intersection(
        info.newNormalizedRange(info.intZero, maxIndex));
    if (indexRange == newIndexRange) return indexRange;
    // Explicitly attach the range information to the index instruction,
    // which may be used by other instructions.  Returning the new range will
    // attach it to this instruction.
    HInstruction instruction = createRangeConversion(next, check.index);
    ranges[instruction] = newIndexRange;
    return newIndexRange;
  }

  Range visitRelational(HRelational relational) {
    HInstruction right = relational.right;
    HInstruction left = relational.left;
    if (!left.isInteger(compiler)) return info.newUnboundRange();
    if (!right.isInteger(compiler)) return info.newUnboundRange();
    BinaryOperation operation = relational.operation(constantSystem);
    Range rightRange = ranges[relational.right];
    Range leftRange = ranges[relational.left];

    if (relational is HIdentity) {
      handleEqualityCheck(relational);
    } else if (operation.apply(leftRange, rightRange)) {
      relational.block.rewrite(
          relational, graph.addConstantBool(true, compiler));
      relational.block.remove(relational);
    } else if (reverseOperation(operation).apply(leftRange, rightRange)) {
      relational.block.rewrite(
          relational, graph.addConstantBool(false, compiler));
      relational.block.remove(relational);
    }
    return info.newUnboundRange();
  }

  void handleEqualityCheck(HRelational node) {
    Range right = ranges[node.right];
    Range left = ranges[node.left];
    if (left.isSingleValue && right.isSingleValue && left == right) {
      node.block.rewrite(
          node, graph.addConstantBool(true, compiler));
      node.block.remove(node);
    }
  }

  Range handleInvokeModulo(HInvokeDynamicMethod invoke) {
    HInstruction left = invoke.inputs[1];
    HInstruction right = invoke.inputs[2];
    Range divisor = ranges[right];
    if (divisor != null) {
      // For Integer values we can be precise in the upper bound,
      // so special case those.
      if (left.isInteger(compiler) && right.isInteger(compiler)) {
        if (divisor.isPositive) {
          return info.newNormalizedRange(info.intZero, divisor.upper -
              info.intOne);
        } else if (divisor.isNegative) {
          return info.newNormalizedRange(info.intZero, info.newNegateValue(
              divisor.lower) - info.intOne);
        }
      } else if (left.isNumber(compiler) && right.isNumber(compiler)) {
        if (divisor.isPositive) {
          return info.newNormalizedRange(info.intZero, divisor.upper);
        } else if (divisor.isNegative) {
          return info.newNormalizedRange(info.intZero, info.newNegateValue(
              divisor.lower));
        }
      }
    }
    return info.newUnboundRange();
  }

  Range visitInvokeDynamicMethod(HInvokeDynamicMethod invoke) {
    if ((invoke.inputs.length == 3) && (invoke.selector.name == "%"))
      return handleInvokeModulo(invoke);
    return super.visitInvokeDynamicMethod(invoke);
  }

  Range handleBinaryOperation(HBinaryArithmetic instruction) {
    if (!instruction.isInteger(compiler)) return info.newUnboundRange();
    return instruction.operation(constantSystem).apply(
        ranges[instruction.left], ranges[instruction.right]);
  }

  Range visitAdd(HAdd add) {
    return handleBinaryOperation(add);
  }

  Range visitSubtract(HSubtract sub) {
    return handleBinaryOperation(sub);
  }

  Range visitBitAnd(HBitAnd node) {
    if (!node.isInteger(compiler)) return info.newUnboundRange();
    HInstruction right = node.right;
    HInstruction left = node.left;
    if (left.isInteger(compiler) && right.isInteger(compiler)) {
      return ranges[left] & ranges[right];
    }

    Range tryComputeRange(HInstruction instruction) {
      Range range = ranges[instruction];
      if (range.isPositive) {
        return info.newNormalizedRange(info.intZero, range.upper);
      } else if (range.isNegative) {
        return info.newNormalizedRange(range.lower, info.intZero);
      }
      return info.newUnboundRange();
    }

    if (left.isInteger(compiler)) {
      return tryComputeRange(left);
    } else if (right.isInteger(compiler)) {
      return tryComputeRange(right);
    }
    return info.newUnboundRange();
  }

  Range visitCheck(HCheck instruction) {
    if (ranges[instruction.checkedInput] == null) {
      return visitInstruction(instruction);
    }
    return ranges[instruction.checkedInput];
  }

  HInstruction createRangeConversion(HInstruction cursor,
                                     HInstruction instruction) {
    JavaScriptBackend backend = compiler.backend;
    HRangeConversion newInstruction =
        new HRangeConversion(instruction, backend.intType);
    conversions.add(newInstruction);
    cursor.block.addBefore(cursor, newInstruction);
    // Update the users of the instruction dominated by [cursor] to
    // use the new instruction, that has an narrower range.
    instruction.replaceAllUsersDominatedBy(cursor, newInstruction);
    return newInstruction;
  }

  static BinaryOperation reverseOperation(BinaryOperation operation) {
    if (operation == const LessOperation()) {
      return const GreaterEqualOperation();
    } else if (operation == const LessEqualOperation()) {
      return const GreaterOperation();
    } else if (operation == const GreaterOperation()) {
      return const LessEqualOperation();
    } else if (operation == const GreaterEqualOperation()) {
      return const LessOperation();
    } else {
      return null;
    }
  }

  Range computeConstrainedRange(BinaryOperation operation,
                                Range leftRange,
                                Range rightRange) {
    Range range;
    if (operation == const LessOperation()) {
      range = info.newNormalizedRange(
          const MinIntValue(), rightRange.upper - info.intOne);
    } else if (operation == const LessEqualOperation()) {
      range = info.newNormalizedRange(const MinIntValue(), rightRange.upper);
    } else if (operation == const GreaterOperation()) {
      range = info.newNormalizedRange(
          rightRange.lower + info.intOne, const MaxIntValue());
    } else if (operation == const GreaterEqualOperation()) {
      range = info.newNormalizedRange(rightRange.lower, const MaxIntValue());
    } else {
      range = info.newUnboundRange();
    }
    return range.intersection(leftRange);
  }

  Range visitConditionalBranch(HConditionalBranch branch) {
    var condition = branch.condition;
    // TODO(ngeoffray): Handle complex conditions.
    if (condition is !HRelational) return info.newUnboundRange();
    if (condition is HIdentity) return info.newUnboundRange();
    HInstruction right = condition.right;
    HInstruction left = condition.left;
    if (!left.isInteger(compiler)) return info.newUnboundRange();
    if (!right.isInteger(compiler)) return info.newUnboundRange();

    Range rightRange = ranges[right];
    Range leftRange = ranges[left];
    Operation operation = condition.operation(constantSystem);
    Operation reverse = reverseOperation(operation);
    // Only update the true branch if this block is the only
    // predecessor.
    if (branch.trueBranch.predecessors.length == 1) {
      assert(branch.trueBranch.predecessors[0] == branch.block);
      // Update the true branch to use narrower ranges for [left] and
      // [right].
      Range range = computeConstrainedRange(operation, leftRange, rightRange);
      if (leftRange != range) {
        HInstruction instruction =
            createRangeConversion(branch.trueBranch.first, left);
        ranges[instruction] = range;
      }

      range = computeConstrainedRange(reverse, rightRange, leftRange);
      if (rightRange != range) {
        HInstruction instruction =
            createRangeConversion(branch.trueBranch.first, right);
        ranges[instruction] = range;
      }
    }

    // Only update the false branch if this block is the only
    // predecessor.
    if (branch.falseBranch.predecessors.length == 1) {
      assert(branch.falseBranch.predecessors[0] == branch.block);
      // Update the false branch to use narrower ranges for [left] and
      // [right].
      Range range = computeConstrainedRange(reverse, leftRange, rightRange);
      if (leftRange != range) {
        HInstruction instruction =
            createRangeConversion(branch.falseBranch.first, left);
        ranges[instruction] = range;
      }

      range = computeConstrainedRange(operation, rightRange, leftRange);
      if (rightRange != range) {
        HInstruction instruction =
            createRangeConversion(branch.falseBranch.first, right);
        ranges[instruction] = range;
      }
    }

    return info.newUnboundRange();
  }

  Range visitRangeConversion(HRangeConversion conversion) {
    return ranges[conversion];
  }
}

/**
 * Tries to find a range for the update instruction of a loop phi.
 */
class LoopUpdateRecognizer extends HBaseVisitor {
  final Compiler compiler;
  final Map<HInstruction, Range> ranges;
  final ValueRangeInfo info;
  LoopUpdateRecognizer(this.compiler, this.ranges, this.info);

  Range run(HPhi loopPhi) {
    // Create a marker range for the loop phi, so that if the update
    // uses the loop phi, it has a range to use.
    ranges[loopPhi] = info.newMarkerRange();
    Range updateRange = visit(loopPhi.inputs[1]);
    ranges[loopPhi] = null;
    if (updateRange == null) return null;
    Range startRange = ranges[loopPhi.inputs[0]];
    // If the lower (respectively upper) value is the marker, we know
    // the loop does not change it, so we can just use the
    // [startRange]'s lower (upper) value. Otherwise the lower (upper) value
    // is the minimum of the [startRange]'s lower (upper) and the
    // [updateRange]'s lower (upper).
    Value low = updateRange.lower is MarkerValue
        ? startRange.lower
        : updateRange.lower.min(startRange.lower);
    Value up = updateRange.upper is MarkerValue
        ? startRange.upper
        : updateRange.upper.max(startRange.upper);
    return info.newNormalizedRange(low, up);
  }

  Range visit(HInstruction instruction) {
    if (!instruction.isInteger(compiler)) return null;
    if (ranges[instruction] != null) return ranges[instruction];
    return instruction.accept(this);
  }

  Range visitPhi(HPhi phi) {
    // If the update of a loop phi involves another loop phi, we give
    // up.
    if (phi.block.isLoopHeader()) return null;
    Range phiRange;
    for (HInstruction input in phi.inputs) {
      Range inputRange = visit(input);
      if (inputRange == null) return null;
      if (phiRange == null) {
        phiRange = inputRange;
      } else {
        phiRange = phiRange.union(inputRange);
      }
    }
    return phiRange;
  }

  Range visitCheck(HCheck instruction) {
    return visit(instruction.checkedInput);
  }

  Range visitAdd(HAdd operation) {
    return handleBinaryOperation(operation);
  }

  Range visitSubtract(HSubtract operation) {
    return handleBinaryOperation(operation);
  }

  Range handleBinaryOperation(HBinaryArithmetic instruction) {
    Range leftRange = visit(instruction.left);
    Range rightRange = visit(instruction.right);
    if (leftRange == null || rightRange == null) return null;
    BinaryOperation operation = instruction.operation(info.constantSystem);
    return operation.apply(leftRange, rightRange);
  }
}
