Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(438)

Unified Diff: lib/compiler/implementation/ssa/value_range_analyzer.dart

Issue 10968060: Add a value range analysis phase to remove bounds checks. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: lib/compiler/implementation/ssa/value_range_analyzer.dart
===================================================================
--- lib/compiler/implementation/ssa/value_range_analyzer.dart (revision 0)
+++ lib/compiler/implementation/ssa/value_range_analyzer.dart (revision 0)
@@ -0,0 +1,620 @@
+// 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.
+
+/**
+ * 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 {
+ const Value();
+
+ Value operator +(Value other);
+ Value operator -(Value other);
+ Value operator &(Value other);
+
+ Value min(Value other) {
+ if (this == other) return this;
+ if (other == const MinValue()) return other;
+ if (other == const MaxValue()) 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 MinValue()) return this;
+ if (other == const MaxValue()) return other;
+ Value value = this - other;
+ if (value.isPositive()) return this;
+ if (value.isNegative()) return other;
+ return const UnknownValue();
+ }
+
+ bool isNegative() => false;
+ bool isPositive() => false;
+ bool isZero() => false;
+}
+
+/**
+ * An [IntValue] contains a constant integer value.
+ */
+class IntValue extends Value {
+ final int value;
+ const IntValue(this.value);
+
+ Value operator +(other) {
+ if (other is !IntValue) return other + this;
+ return new IntValue(value + other.value);
+ }
+
+ Value operator -(other) {
+ if (other is !IntValue) return other - this;
+ return new IntValue(value - other.value);
+ }
+
+ Value operator &(other) {
+ if (other is !IntValue) return this;
+ return new IntValue(value & other.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;
+ }
+
+ String toString() => 'IntValue $value';
+ bool isNegative() => value < 0;
+ bool isPositive() => value >= 0;
+ bool isZero() => value == 0;
+}
+
+/**
+ * The [MaxValue] represents the maximum value an integer can have,
+ * which is currently +infinity.
+ */
+class MaxValue extends Value {
+ const MaxValue();
+ Value operator +(Value other) => this;
+ Value operator -(Value other) => this;
+ Value operator &(Value other) {
+ if (other.isPositive()) return other;
+ if (other.isNegative()) return const IntValue(0);
+ return this;
+ }
+ Value min(Value other) => other;
+ Value max(Value other) => this;
+ String toString() => 'Max';
+ bool isNegative() => false;
+ bool isPositive() => true;
+}
+
+/**
+ * The [MinValue] represents the minimum value an integer can have,
+ * which is currently -infinity.
+ */
+class MinValue extends Value {
+ const MinValue();
+ Value operator +(Value other) => this;
+ Value operator -(Value other) => this;
+ Value operator &(Value other) {
+ if (other.isPositive()) return const IntValue(0);
+ if (other.isNegative()) return other;
Søren Gjesse 2012/09/26 14:00:15 Shouldn't this be MinValue instead of other? They
ngeoffray 2012/09/27 13:22:02 Good point. Done.
+ return this;
+ }
+ Value min(Value other) => this;
+ Value max(Value other) => other;
+ String toString() => 'Min';
+ bool isNegative() => true;
+ bool 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();
+ Value operator +(Value other) => const UnknownValue();
+ Value operator -(Value other) => const UnknownValue();
+ Value operator &(Value other) => const UnknownValue();
+ Value min(Value other) => const UnknownValue();
+ Value max(Value other) => const UnknownValue();
+ bool isNegative() => false;
+ bool isPositive() => false;
+ String toString() => 'Unknown';
+}
+
+/**
+ * A symbolic value representing an [HInstruction].
+ */
+class InstructionValue extends Value {
+ final HInstruction instruction;
+ InstructionValue(this.instruction);
+
+ bool operator ==(other) {
+ if (other is !InstructionValue) return false;
+ return this.instruction == other.instruction;
+ }
+
+ Value operator +(Value other) {
+ if (other.isZero()) return this;
+ return new OperationValue(this, other, const AddOperation());
+ }
+
+ Value operator -(Value other) {
+ if (other.isZero()) return this;
+ if (this == other) return const IntValue(0);
+ return new OperationValue(this, other, const SubtractOperation());
+ }
+
+ Value operator &(Value other) {
+ if (other is IntValue) return other & this;
+ return this;
+ }
+
+ bool isNegative() => false;
+ bool isPositive() => false;
+
+ String toString() => 'Instruction: $instruction';
+}
+
+/**
+ * Special value for instructions that represent the length of an
+ * array. The difference with an [InstructionValue] is that we know
+ * the value is positive.
+ */
+class LengthValue extends InstructionValue {
+ LengthValue(HInstruction instruction) : super(instruction);
+ bool isPositive() => true;
+ String toString() => 'Length: $instruction';
+}
+
+/**
+ * Represents a binary operation on two [Value], where the operation
+ * did not yield a canonical value.
+ */
+class OperationValue extends Value {
+ final Value left;
+ final Value right;
+ final Operation operation;
+ OperationValue(this.left, this.right, this.operation);
+
+ bool operator ==(other) {
+ if (other is !OperationValue) return false;
+ return left == other.left
+ && right == other.right
+ && operation == other.operation;
+ }
+
+ Value operator +(Value other) => const UnknownValue();
+ Value operator &(Value other) => const UnknownValue();
+
+ Value operator -(Value other) {
+ // We try to create a simple [Value] out of this operation. So we
+ // first try to substract [other] to [left]. If the result is simple
+ // enough (not unknown and not an operation), we return the result
+ // of doing the operation of this [OperationValue] on the previous
+ // result and [right].
+ //
+ // For example:
+ // OperationValue(LengthValue(i1), IntValue(42), '-') - LengthValue(i1)
+ //
+ // Will return IntValue(-42)
Søren Gjesse 2012/09/26 14:00:15 So using the fact that (a - b) - c == a - (b - c)
ngeoffray 2012/09/27 13:22:02 Added as a comment.
+ Value value = left - other;
+ if (value != const UnknownValue() && value is! OperationValue) {
+ return operation.apply(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! OperationValue) {
+ return operation.apply(left, value);
+ }
+ return const UnknownValue();
+ }
+
+ bool isNegative() => false;
+ bool isPositive() => false;
+ String toString() => '$left ${operation.name} $right';
+}
+
+/**
+ * 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;
+ const Range([this.lower = const MinValue(), this.upper = const MaxValue()]);
+ /**
+ * Checks if the given values are unknown, and creates a
+ * range that does not have any unknown values.
+ */
+ Range.normalize(Value low, Value up)
+ : lower = low == const UnknownValue() ? const MinValue() : low,
+ upper = up == const UnknownValue() ? const MaxValue() : up;
+
+ Range union(Range other) {
+ return new Range.normalize(lower.min(other.lower), upper.max(other.upper));
+ }
+
+ intersection(Range other) {
+ return new Range.normalize(lower.max(other.lower), upper.min(other.upper));
+ }
+
+ Range operator +(Range other) {
+ return new Range.normalize(lower + other.lower, upper + other.upper);
+ }
+
+ Range operator -(Range other) {
+ return new Range.normalize(lower - other.lower, upper - other.upper);
+ }
+
+ Range operator &(Range other) {
+ return new Range.normalize(lower & other.lower, upper & other.upper);
+ }
+
+ bool operator ==(other) {
+ if (other is! Range) return false;
+ return other.lower == lower && other.upper == upper;
+ }
+
+ bool isLessThan(Range other) {
+ return upper != other.lower && upper.min(other.lower) == upper;
+ }
+
+ bool isNegative() => upper.isNegative();
+ bool isPositive() => lower.isPositive();
+
+ 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 ConstantSystem constantSystem;
+ final HTypeMap types;
+ WorkItem work;
+ HGraph graph;
+
+ SsaValueRangeAnalyzer(this.constantSystem, this.types, WorkItem this.work);
+
+ 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();
+ }
+
+ 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(types)) {
+ assert(range != null);
+ ranges[instruction] = range;
+ }
+ }
+
+ block.forEachPhi(visit);
+ block.forEachInstruction(visit);
+ }
+
+ Range visitInstruction(HInstruction instruction) {
+ return const Range(const MinValue(), const MaxValue());
+ }
+
+ Range visitParameterValue(HParameterValue parameter) {
+ if (!parameter.isInteger(types)) return null;
+ Value value = new InstructionValue(parameter);
+ return new Range(value, value);
+ }
+
+ Range visitPhi(HPhi phi) {
+ if (!phi.isInteger(types)) return null;
+ if (phi.block.isLoopHeader()) {
+ Range range = tryInferLoopPhiRange(phi);
+ if (range == null) return visitInstruction(phi);
+ 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 tryInferLoopPhiRange(HPhi phi) {
+ HInstruction update = phi.inputs[1];
+ return update.accept(new LoopUpdateRecognizer(phi, ranges, types));
+ }
+
+ Range visitConstant(HConstant constant) {
+ if (!constant.isInteger(types)) return null;
+ Value value = new IntValue(constant.constant.value);
+ return new Range(value, value);
+ }
+
+ Range visitInvokeInterceptor(HInvokeInterceptor interceptor) {
+ if (!interceptor.isInteger(types)) return null;
+ if (!interceptor.isLengthGetterOnStringOrArray(types)) {
+ return visitInstruction(interceptor);
+ }
+ LengthValue value = new LengthValue(interceptor);
+ return new Range(value, value);
+ }
+
+ bool handleBoundsCheck(HBoundsCheck check) {
Søren Gjesse 2012/09/26 14:00:15 Maybe comment that this returns true is the bounds
ngeoffray 2012/09/27 13:22:02 Done.
+ Range indexRange = ranges[check.index];
+ Range lengthRange = ranges[check.length];
+ Value maxIndex = lengthRange.upper - const IntValue(1);
+ bool belowLength = maxIndex != const MaxValue()
+ && indexRange.upper.min(maxIndex) == indexRange.upper;
+ if (indexRange.isPositive() && belowLength) {
+ check.block.rewrite(check, check.index);
+ check.block.remove(check);
+ return true;
+ } else if (indexRange.isNegative() || lengthRange.isLessThan(indexRange)) {
+ check.staticChecks = HBoundsCheck.ALWAYS_FALSE;
+ } else if (indexRange.isPositive()) {
+ check.staticChecks = HBoundsCheck.ALWAYS_ABOVE_ZERO;
+ } else if (belowLength) {
+ check.staticChecks = HBoundsCheck.ALWAYS_BELOW_LENGTH;
+ }
+ return false;
+ }
+
+ Range visitBoundsCheck(HBoundsCheck check) {
+ HInstruction next = check.next;
+ Range indexRange = ranges[check.index];
+ Range lengthRange = ranges[check.length];
+ if (handleBoundsCheck(check)) return indexRange;
+ // Update the range of the index.
+ Range newIndexRange = indexRange.intersection(lengthRange);
+ if (indexRange == newIndexRange) return indexRange;
+ HInstruction instruction = createRangeConversion(check.next, check.index);
+ ranges[instruction] = newIndexRange;
+ return newIndexRange;
+ }
+
+ Range visitLess(HLess less) {
+ HInstruction right = less.right;
+ HInstruction left = less.left;
+ if (!left.isInteger(types)) return null;
+ if (!right.isInteger(types)) return null;
+ if (ranges[left].isLessThan(ranges[right])) {
+ less.block.rewrite(less, graph.addConstantBool(true, constantSystem));
+ less.block.remove(less);
+ return null;
+ }
+ if (ranges[right].isLessThan(ranges[left])) {
+ less.block.rewrite(less, graph.addConstantBool(false, constantSystem));
+ less.block.remove(less);
+ return null;
+ }
+ return null;
+ }
+
+ Range handleBinaryOperation(HBinaryArithmetic instruction) {
+ if (!instruction.isInteger(types)) return null;
+ 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(types)) return null;
+ HInstruction right = node.right;
+ HInstruction left = node.left;
+ if (left.isInteger(types) && right.isInteger(types)) {
+ return ranges[left] & ranges[right];
+ }
+
+ Range tryComputeRange(HInstruction instruction) {
+ Range range = ranges[instruction];
+ if (range.isPositive()) {
+ return new Range(const IntValue(0), range.upper);
+ } else if (range.isNegative()) {
+ return new Range(range.lower, const IntValue(0));
+ }
+ return visitInstruction(node);
+ }
+
+ if (left.isInteger(types)) {
+ return tryComputeRange(left);
+ } else if (right.isInteger(types)) {
+ return tryComputeRange(right);
+ }
+ return visitInstruction(node);
+ }
+
+ Range visitCheck(HCheck instruction) {
+ if (ranges[instruction.checkedInput] == null) {
+ return visitInstruction(instruction);
+ }
+ return ranges[instruction.checkedInput];
+ }
+
+ HInstruction createRangeConversion(HInstruction cursor,
+ HInstruction instruction) {
+ HRangeConversion newInstruction = new HRangeConversion(instruction);
+ 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.
+ Set<HInstruction> dominatedUsers = instruction.dominatedUsers(cursor);
+ for (HInstruction user in dominatedUsers) {
+ user.changeUse(instruction, newInstruction);
+ }
+ return newInstruction;
+ }
+
+ Range visitConditionalBranch(HConditionalBranch branch) {
+ var condition = branch.condition;
+ // TODO(ngeoffray): Handle more condition kinds.
+ if (condition is !HLess) return null;
+ HInstruction right = condition.right;
+ HInstruction left = condition.left;
+ if (!left.isInteger(types)) return null;
+ if (!right.isInteger(types)) return null;
+
+ // Update the true branch to use a narrower range for [left].
+ // TODO(ngeoffray): Also do it for [right].
+ HInstruction instruction =
+ createRangeConversion(branch.trueBranch.first, left);
+ Range range = new Range(
+ const MinValue(), ranges[right].upper - const IntValue(1));
+ range = range.intersection(ranges[left]);
+ ranges[instruction] = range;
+
+ // Update the false branch to use a narrower range for [left].
+ // TODO(ngeoffray): Also do it for [right].
+ instruction = createRangeConversion(branch.falseBranch.first, left);
+ range = new Range(ranges[right].lower, const MaxValue());
+ range = range.intersection(ranges[left]);
+ ranges[instruction] = range;
+
+ return null;
+ }
+
+ Range visitRangeConversion(HRangeConversion conversion) {
+ return ranges[conversion];
+ }
+}
+
+/**
+ * Recognizes a number of patterns in a loop update instruction and
+ * tries to infer a range for the loop phi.
+ */
+class LoopUpdateRecognizer extends HBaseVisitor {
+ final HPhi loopPhi;
+ final Map<HInstruction, Range> ranges;
+ final HTypeMap types;
+ LoopUpdateRecognizer(this.loopPhi, this.ranges, this.types);
+
+ Range visitAdd(HAdd operation) {
+ Range range = getRangeForRecognizableOperation(operation);
+ if (range == null) return null;
+ Range initial = ranges[loopPhi.inputs[0]];
+ if (range.isPositive()) {
+ return new Range(initial.lower, const MaxValue());
+ } else if (range.isNegative()) {
+ return new Range(const MinValue(), initial.upper);
+ }
+ return null;
+ }
+
+ Range visitSubtract(HSubtract operation) {
+ Range range = getRangeForRecognizableOperation(operation);
+ if (range == null) return null;
+ Range initial = ranges[loopPhi.inputs[0]];
+ if (range.isPositive()) {
+ return new Range(const MinValue(), initial.upper);
+ } else if (range.isNegative()) {
+ return new Range(initial.lower, const MaxValue());
+ }
+ return null;
+ }
+
+ Range visitPhi(HPhi phi) {
+ // If one of the inputs is the loop phi, then we're only
+ // interested in the other input: a loop phi feeding itself means
+ // it is not being updated.
+ if (unwrap(phi.inputs[0]) == loopPhi) return phi.inputs[1].accept(this);
+ if (unwrap(phi.inputs[1]) == loopPhi) return phi.inputs[0].accept(this);
+ assert(phi.inputs.length == 2);
+ return null;
+ }
+
+ Range getRangeForRecognizableOperation(HBinaryArithmetic operation) {
+ if (!operation.left.isInteger(types)) return null;
+ if (!operation.right.isInteger(types)) return null;
+ HInstruction left = unwrap(operation.left);
+ HInstruction right = unwrap(operation.right);
+ // We only recognize operations that operate on the loop phi.
+ bool isLeftLoopPhi = (left == loopPhi);
+ bool isRightLoopPhi = (right == loopPhi);
+ if (!isLeftLoopPhi && !isRightLoopPhi) return null;
+
+ var other = isLeftLoopPhi ? right : left;
+ // If the analysis already computed range for the update, use it.
+ if (ranges[other] != null) return ranges[other];
+
+ // We currently only handle constants in updates if the
+ // update does not have a range.
+ if (other.isConstant()) {
+ Value value = new IntValue(other.constant.value);
+ return new Range(value, value);
+ }
+ return null;
+ }
+
+ /**
+ * [HCheck] instructions may check the loop phi. Since we only
+ * recognize updates on the loop phi, we must [unwrap] the [HCheck]
+ * instruction to check if it references the loop phi.
+ */
+ HInstruction unwrap(instruction) {
+ if (instruction is HCheck) return unwrap(instruction.checkedInput);
+ // [HPhi] might have two different [HCheck] instructions as
+ // inputs, checking the same instruction.
+ if (instruction is HPhi && !instruction.block.isLoopHeader()) {
+ HInstruction result = unwrap(instruction.inputs[0]);
+ for (int i = 1; i < instruction.inputs.length; i++) {
+ if (result != unwrap(instruction.inputs[i])) return instruction;
+ }
+ return result;
+ }
+ return instruction;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698