| // 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. |
| |
| import 'dart:collection' show UnmodifiableMapView; |
| |
| import '../constants/constant_system.dart' as constant_system; |
| import '../constants/values.dart'; |
| import '../js_model/js_world.dart' show JClosedWorld; |
| import '../tracer.dart'; |
| import 'nodes.dart'; |
| import 'optimize.dart' show OptimizationPhase, SsaOptimizerTask; |
| |
| bool _debug = false; |
| |
| class ValueRangeInfo { |
| late final IntValue intZero; |
| late final IntValue intOne; |
| |
| late final Value maxIntValue; |
| late final Value minIntValue; |
| late final Value unknownValue; |
| |
| ValueRangeInfo() { |
| intZero = newIntValue(BigInt.zero); |
| intOne = newIntValue(BigInt.one); |
| |
| maxIntValue = MaxIntValue(this); |
| minIntValue = MinIntValue(this); |
| unknownValue = UnknownValue(this); |
| } |
| |
| IntValue newIntValue(BigInt value) { |
| return IntValue(value, this); |
| } |
| |
| InstructionValue newInstructionValue(HInstruction instruction) { |
| return InstructionValue(instruction, this); |
| } |
| |
| PositiveValue newPositiveValue(HInstruction instruction) { |
| return PositiveValue(instruction, this); |
| } |
| |
| Value newAddValue(Value left, Value right) { |
| return AddValue(left, right, this); |
| } |
| |
| Value newSubtractValue(Value left, Value right) { |
| return SubtractValue(left, right, this); |
| } |
| |
| Value newNegateValue(Value value) { |
| return NegateValue(value, this); |
| } |
| |
| Range newUnboundRange() { |
| return Range.unbound(this); |
| } |
| |
| Range newNormalizedRange(Value low, Value up) { |
| return Range.normalize(low, up, this); |
| } |
| |
| Value newMarkerValue({required bool isLower, required bool isPositive}) { |
| return MarkerValue(isLower, isPositive, 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) => info.unknownValue; |
| Value operator -(Value other) => info.unknownValue; |
| Value operator -() => info.unknownValue; |
| Value operator &(Value other) => info.unknownValue; |
| |
| Value min(Value other) { |
| if (this == other) return this; |
| if (other == info.minIntValue) return other; |
| if (other == info.maxIntValue) return this; |
| Value value = this - other; |
| if (value.isPositive) return other; |
| if (value.isNegative) return this; |
| return info.unknownValue; |
| } |
| |
| Value max(Value other) { |
| if (this == other) return this; |
| if (other == info.minIntValue) return this; |
| if (other == info.maxIntValue) return other; |
| Value value = this - other; |
| if (value.isPositive) return this; |
| if (value.isNegative) return other; |
| return info.unknownValue; |
| } |
| |
| Value replaceMarkers(Value lowerBound, Value upperBound) { |
| return this; |
| } |
| |
| bool get isNegative => false; |
| bool get isPositive => false; |
| bool get isZero => false; |
| } |
| |
| /// An [IntValue] contains a constant integer value. |
| class IntValue extends Value { |
| final BigInt value; |
| |
| const IntValue(this.value, super.info); |
| |
| @override |
| Value operator +(Value other) { |
| if (other.isZero) return this; |
| if (other is IntValue) { |
| final constant = constant_system.add.fold( |
| constant_system.createInt(value), |
| constant_system.createInt(other.value), |
| ); |
| if (constant is IntConstantValue) { |
| return info.newIntValue(constant.intValue); |
| } |
| return info.unknownValue; |
| } |
| return other + this; |
| } |
| |
| @override |
| Value operator -(Value other) { |
| if (other.isZero) return this; |
| if (other is IntValue) { |
| final constant = constant_system.subtract.fold( |
| constant_system.createInt(value), |
| constant_system.createInt(other.value), |
| ); |
| if (constant is IntConstantValue) { |
| return info.newIntValue(constant.intValue); |
| } |
| return info.unknownValue; |
| } |
| return -other + this; |
| } |
| |
| @override |
| Value operator -() { |
| if (isZero) return this; |
| final constant = constant_system.negate.fold( |
| constant_system.createInt(value), |
| ); |
| if (constant is IntConstantValue) { |
| return info.newIntValue(constant.intValue); |
| } |
| return info.unknownValue; |
| } |
| |
| @override |
| Value operator &(Value other) { |
| if (other is IntValue) { |
| final constant = |
| constant_system.bitAnd.fold( |
| constant_system.createInt(value), |
| constant_system.createInt(other.value), |
| ) |
| as IntConstantValue; |
| return info.newIntValue(constant.intValue); |
| } |
| return info.unknownValue; |
| } |
| |
| @override |
| Value min(Value other) { |
| if (other is IntValue) { |
| return value < other.value ? this : other; |
| } |
| return other.min(this); |
| } |
| |
| @override |
| Value max(Value other) { |
| if (other is IntValue) { |
| return value < other.value ? other : this; |
| } |
| return other.max(this); |
| } |
| |
| @override |
| bool operator ==(other) { |
| if (other is! IntValue) return false; |
| return value == other.value; |
| } |
| |
| @override |
| int get hashCode => throw UnsupportedError('IntValue.hashCode'); |
| |
| @override |
| String toString() => 'Int($value)'; |
| @override |
| bool get isNegative => value < BigInt.zero; |
| @override |
| bool get isPositive => value >= BigInt.zero; |
| @override |
| bool get isZero => value == BigInt.zero; |
| } |
| |
| /// The [MaxIntValue] represents the maximum value an integer can have, |
| /// which is currently +infinity. |
| class MaxIntValue extends Value { |
| MaxIntValue(super.info); |
| @override |
| Value operator +(Value other) => this; |
| @override |
| Value operator -(Value other) => this; |
| @override |
| Value operator -() => info.minIntValue; |
| @override |
| Value min(Value other) => other; |
| @override |
| Value max(Value other) => this; |
| @override |
| String toString() => 'Max'; |
| @override |
| bool get isNegative => false; |
| @override |
| bool get isPositive => true; |
| } |
| |
| /// The [MinIntValue] represents the minimum value an integer can have, |
| /// which is currently -infinity. |
| class MinIntValue extends Value { |
| MinIntValue(super.info); |
| @override |
| Value operator +(Value other) => this; |
| @override |
| Value operator -(Value other) => this; |
| @override |
| Value operator -() => info.maxIntValue; |
| @override |
| Value min(Value other) => this; |
| @override |
| Value max(Value other) => other; |
| @override |
| String toString() => 'Min'; |
| @override |
| bool get isNegative => true; |
| @override |
| 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 { |
| UnknownValue(super.info); |
| @override |
| Value operator +(Value other) => info.unknownValue; |
| @override |
| Value operator -(Value other) => info.unknownValue; |
| @override |
| Value operator -() => info.unknownValue; |
| @override |
| Value min(Value other) => info.unknownValue; |
| @override |
| Value max(Value other) => info.unknownValue; |
| @override |
| bool get isNegative => false; |
| @override |
| bool get isPositive => false; |
| @override |
| String toString() => 'Unknown'; |
| } |
| |
| abstract class VariableValue extends Value { |
| VariableValue(super.info); |
| |
| @override |
| bool operator ==(other) => throw UnsupportedError('VariableValue.=='); |
| |
| @override |
| int get hashCode => throw UnsupportedError('VariableValue.hashCode'); |
| |
| @override |
| 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 VariableValue) { |
| return info.newAddValue(this, other); |
| } |
| return other + this; |
| } |
| |
| @override |
| 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 VariableValue) { |
| return info.newSubtractValue(this, other); |
| } |
| return -other + this; |
| } |
| |
| @override |
| Value operator -() { |
| return info.newNegateValue(this); |
| } |
| |
| @override |
| bool get isNegative => false; |
| @override |
| bool get isPositive => false; |
| |
| @override |
| String toString() => throw UnsupportedError('VariableValue.toString'); |
| } |
| |
| /// The [MarkerValue] class is a symbolc variable used to recognize ranges of |
| /// loop updates. |
| class MarkerValue extends VariableValue { |
| /// There are two marker values in the marker range - a lower value and an |
| /// upper value. [isLower] is true for the lower marker value. |
| final bool isLower; |
| @override |
| final bool isPositive; |
| |
| MarkerValue(this.isLower, this.isPositive, super.info); |
| |
| @override |
| int get hashCode => isLower.hashCode; |
| |
| @override |
| bool operator ==(other) { |
| return other is MarkerValue && isLower == other.isLower; |
| } |
| |
| @override |
| Value replaceMarkers(Value lowerBound, Value upperBound) { |
| return isLower ? lowerBound : upperBound; |
| } |
| |
| @override |
| String toString() => |
| 'Marker(${isLower ? "lower" : "upper"}${isPositive ? ",>=0" : ""})'; |
| } |
| |
| /// A symbolic value representing an [HInstruction]. |
| class InstructionValue extends VariableValue { |
| final HInstruction instruction; |
| InstructionValue(this.instruction, super.info); |
| |
| @override |
| bool operator ==(other) { |
| if (other is! InstructionValue) return false; |
| return instruction == other.instruction; |
| } |
| |
| @override |
| int get hashCode => throw UnsupportedError('InstructionValue.hashCode'); |
| |
| @override |
| String toString() => 'Instruction($instruction)'; |
| } |
| |
| /// Special value for instructions whose type is a positive integer. |
| class PositiveValue extends InstructionValue { |
| PositiveValue(super.instruction, super.info); |
| @override |
| bool get isPositive => true; |
| } |
| |
| /// Represents a binary operation on two [Value]s, where the operation did not |
| /// yield a canonical value. |
| abstract class BinaryOperationValue extends Value { |
| final Value left; |
| final Value right; |
| BinaryOperationValue(this.left, this.right, super.info); |
| } |
| |
| class AddValue extends BinaryOperationValue { |
| AddValue(super.left, super.right, super.info); |
| |
| @override |
| bool operator ==(other) { |
| if (other is! AddValue) return false; |
| return (left == other.left && right == other.right) || |
| (left == other.right && right == other.left); |
| } |
| |
| @override |
| int get hashCode => throw UnsupportedError('AddValue.hashCode'); |
| |
| @override |
| Value operator -() => -left - right; |
| |
| @override |
| Value operator +(Value other) { |
| if (other.isZero) return this; |
| Value value = left + other; |
| if (value != info.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 != info.unknownValue && value is! BinaryOperationValue) { |
| return left + value; |
| } |
| return info.unknownValue; |
| } |
| |
| @override |
| Value operator -(Value other) { |
| if (other.isZero) return this; |
| Value value = left - other; |
| if (value != info.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 != info.unknownValue && value is! BinaryOperationValue) { |
| return left + value; |
| } |
| return info.unknownValue; |
| } |
| |
| @override |
| Value replaceMarkers(Value lowerBound, Value upperBound) { |
| final newLeft = left.replaceMarkers(lowerBound, upperBound); |
| final newRight = right.replaceMarkers(lowerBound, upperBound); |
| if (left == newLeft && right == newRight) return this; |
| return newLeft + newRight; |
| } |
| |
| @override |
| bool get isNegative => left.isNegative && right.isNegative; |
| @override |
| bool get isPositive => left.isPositive && right.isPositive; |
| @override |
| String toString() => '$left + $right'; |
| } |
| |
| class SubtractValue extends BinaryOperationValue { |
| SubtractValue(super.left, super.right, super.info); |
| |
| @override |
| bool operator ==(other) { |
| if (other is! SubtractValue) return false; |
| return left == other.left && right == other.right; |
| } |
| |
| @override |
| int get hashCode => throw UnsupportedError('SubtractValue.hashCode'); |
| |
| @override |
| Value operator -() => right - left; |
| |
| @override |
| Value operator +(Value other) { |
| if (other.isZero) return this; |
| Value value = left + other; |
| if (value != info.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 != info.unknownValue && value is! BinaryOperationValue) { |
| return left + value; |
| } |
| return info.unknownValue; |
| } |
| |
| @override |
| Value operator -(Value other) { |
| if (other.isZero) return this; |
| Value value = left - other; |
| if (value != info.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 != info.unknownValue && value is! BinaryOperationValue) { |
| return left - value; |
| } |
| return info.unknownValue; |
| } |
| |
| @override |
| Value replaceMarkers(Value lowerBound, Value upperBound) { |
| final newLeft = left.replaceMarkers(lowerBound, upperBound); |
| final newRight = right.replaceMarkers(lowerBound, upperBound); |
| if (left == newLeft && right == newRight) return this; |
| return newLeft - newRight; |
| } |
| |
| @override |
| bool get isNegative => left.isNegative && right.isPositive; |
| @override |
| bool get isPositive => left.isPositive && right.isNegative; |
| @override |
| String toString() => '$left - $right'; |
| } |
| |
| class NegateValue extends Value { |
| final Value value; |
| NegateValue(this.value, super.info); |
| |
| @override |
| bool operator ==(other) { |
| if (other is! NegateValue) return false; |
| return value == other.value; |
| } |
| |
| @override |
| int get hashCode => throw UnsupportedError('Negate.hashCode'); |
| |
| @override |
| 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 VariableValue) { |
| return info.newSubtractValue(other, value); |
| } |
| return other - value; |
| } |
| |
| @override |
| Value operator &(Value other) => info.unknownValue; |
| |
| @override |
| 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 VariableValue) { |
| return info.newSubtractValue(this, other); |
| } |
| if (other is NegateValue) return this + other.value; |
| return -other - value; |
| } |
| |
| @override |
| Value operator -() => value; |
| |
| @override |
| Value replaceMarkers(Value lowerBound, Value upperBound) { |
| final newValue = value.replaceMarkers(lowerBound, upperBound); |
| if (value == newValue) return this; |
| return -newValue; |
| } |
| |
| @override |
| bool get isNegative => value.isPositive; |
| @override |
| bool get isPositive => value.isNegative; |
| @override |
| 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 != info.unknownValue); |
| assert(upper != info.unknownValue); |
| } |
| |
| Range.unbound(ValueRangeInfo info) |
| : this(info.minIntValue, info.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, ValueRangeInfo info) |
| : this( |
| low == info.unknownValue ? info.minIntValue : low, |
| up == info.unknownValue ? info.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 == info.unknownValue) { |
| if (lower is IntValue) { |
| low = lower; |
| } else if (other.lower is IntValue) { |
| low = other.lower; |
| } else { |
| low = lower; |
| } |
| } |
| if (up == info.unknownValue) { |
| if (upper is IntValue) { |
| up = upper; |
| } else if (other.upper is IntValue) { |
| up = other.upper; |
| } else { |
| up = upper; |
| } |
| } |
| return info.newNormalizedRange(low, up); |
| } |
| |
| static Range add(Range a, Range b) => a + b; |
| static Range subtract(Range a, Range b) => a - b; |
| |
| 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 == info.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 = info.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(); |
| } |
| } |
| |
| Range replaceMarkers(Value lowerBound, Value upperBound) { |
| final newLower = lower.replaceMarkers(lowerBound, upperBound); |
| final newUpper = upper.replaceMarkers(lowerBound, upperBound); |
| if (lower == newLower && upper == newUpper) return this; |
| return info.newNormalizedRange(newLower, newUpper); |
| } |
| |
| @override |
| bool operator ==(other) { |
| if (other is! Range) return false; |
| return other.lower == lower && other.upper == upper; |
| } |
| |
| @override |
| int get hashCode => throw 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; |
| |
| @override |
| String toString() => '[$lower, $upper]'; |
| } |
| |
| typedef BinaryRangeOperation = Range Function(Range, Range); |
| |
| /// 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<Range> |
| implements OptimizationPhase { |
| @override |
| String get name => 'SsaValueRangeAnalyzer'; |
| |
| /// 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 = []; |
| |
| /// List of [HBoundsCheck] instructions collected during visit. |
| final List<HBoundsCheck> boundsChecks = []; |
| |
| /// Value ranges for integer instructions. This map gets populated by |
| /// the dominator tree visit. |
| final Map<HInstruction, Range> ranges = {}; |
| |
| final JClosedWorld closedWorld; |
| final ValueRangeInfo info = ValueRangeInfo(); |
| final SsaOptimizerTask optimizer; |
| final Tracer tracer; |
| |
| late HGraph graph; |
| |
| SsaValueRangeAnalyzer(this.closedWorld, this.optimizer, this.tracer); |
| |
| @override |
| void visitGraph(HGraph graph) { |
| // Example debugging code: |
| // |
| // _DEBUG = graph.element.toString().contains('(main)'); |
| |
| this.graph = graph; |
| visitDominatorTree(graph); |
| |
| tracer.traceGraph('$name.analysis', graph); |
| |
| optimizeBoundsChecks(); |
| |
| // 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(); |
| |
| // TODO(herhut): Find a cleaner way to pass around ranges. |
| optimizer.ranges = ranges; |
| } |
| |
| @override |
| bool validPostcondition(HGraph graph) => true; |
| |
| void optimizeBoundsChecks() { |
| for (final check in boundsChecks) { |
| if (check.staticChecks == StaticBoundsChecks.alwaysTrue) { |
| final block = check.block!; |
| block.rewrite(check, check.index); |
| block.remove(check); |
| } |
| } |
| } |
| |
| void removeRangeConversion() { |
| for (final instruction in conversions) { |
| final block = instruction.block!; |
| block.rewrite(instruction, instruction.inputs[0]); |
| block.remove(instruction); |
| } |
| } |
| |
| @override |
| void visitBasicBlock(HBasicBlock node) { |
| void visit(HInstruction instruction) { |
| Range range = instruction.accept(this); |
| if (instruction is! HControlFlow && |
| instruction |
| .isInteger(closedWorld.abstractValueDomain) |
| .isDefinitelyTrue) { |
| ranges[instruction] = range; |
| } |
| } |
| |
| node.forEachPhi(visit); |
| node.forEachInstruction(visit); |
| } |
| |
| @override |
| Range visitInstruction(HInstruction instruction) { |
| if (instruction |
| .isPositiveInteger(closedWorld.abstractValueDomain) |
| .isDefinitelyTrue) { |
| return info.newNormalizedRange( |
| info.intZero, |
| info.newPositiveValue(instruction), |
| ); |
| } else if (instruction |
| .isInteger(closedWorld.abstractValueDomain) |
| .isDefinitelyTrue) { |
| InstructionValue value = info.newInstructionValue(instruction); |
| return info.newNormalizedRange(value, value); |
| } else { |
| return info.newUnboundRange(); |
| } |
| } |
| |
| @override |
| Range visitControlFlow(HControlFlow node) { |
| return info.newUnboundRange(); |
| } |
| |
| @override |
| Range visitPhi(HPhi node) { |
| if (node.isInteger(closedWorld.abstractValueDomain).isPotentiallyFalse) { |
| 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 (node.inputs.any( |
| (i) => i.isInteger(closedWorld.abstractValueDomain).isPotentiallyFalse, |
| )) { |
| return info.newUnboundRange(); |
| } |
| if (node.block!.isLoopHeader()) { |
| final range = LoopUpdateRecognizer( |
| closedWorld, |
| UnmodifiableMapView(ranges), |
| info, |
| ).run(node); |
| if (range == null) return info.newUnboundRange(); |
| return range; |
| } |
| |
| Range range = ranges[node.inputs[0]]!; |
| for (int i = 1; i < node.inputs.length; i++) { |
| range = range.union(ranges[node.inputs[i]]!); |
| } |
| return range; |
| } |
| |
| @override |
| Range visitConstant(HConstant node) { |
| ConstantValue constant = node.constant; |
| if (constant is DeferredGlobalConstantValue) { |
| constant = constant.referenced; |
| } |
| if (constant is! IntConstantValue || |
| node.isInteger(closedWorld.abstractValueDomain).isPotentiallyFalse) { |
| return info.newUnboundRange(); |
| } |
| NumConstantValue constantNum = constant; |
| if (constantNum.isPositiveInfinity || constantNum.isNegativeInfinity) { |
| return info.newUnboundRange(); |
| } |
| if (constantNum.isMinusZero) { |
| constantNum = IntConstantValue(BigInt.zero); |
| } |
| |
| BigInt intValue = constantNum is IntConstantValue |
| ? constantNum.intValue |
| : BigInt.from(constantNum.doubleValue.toInt()); |
| Value value = info.newIntValue(intValue); |
| return info.newNormalizedRange(value, value); |
| } |
| |
| @override |
| Range visitFieldGet(HFieldGet node) { |
| return visitInstruction(node); |
| } |
| |
| @override |
| Range visitGetLength(HGetLength node) { |
| PositiveValue value = info.newPositiveValue(node); |
| // 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); |
| } |
| |
| @override |
| Range visitBoundsCheck(HBoundsCheck node) { |
| boundsChecks.add(node); |
| // Save the next instruction, in case the check gets removed. |
| final next = node.next; |
| Range? indexRange = ranges[node.index]; |
| final lengthRange = ranges[node.length]; |
| if (indexRange == null) { |
| indexRange = info.newUnboundRange(); |
| assert( |
| node.index |
| .isInteger(closedWorld.abstractValueDomain) |
| .isPotentiallyFalse, |
| ); |
| } |
| if (lengthRange == null) { |
| // We might have lost the length range due to a type conversion that |
| // asserts a non-integer type. In such a case, the program will never |
| // get to this point anyway, so no need to try and refine ranges. |
| return indexRange; |
| } |
| assert( |
| node.length.isInteger(closedWorld.abstractValueDomain).isDefinitelyTrue, |
| ); |
| |
| // Check if the index is strictly below the upper bound of the length |
| // range. |
| Value maxIndex = lengthRange.upper - info.intOne; |
| bool belowLength = |
| maxIndex != info.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) { |
| node.staticChecks = StaticBoundsChecks.alwaysTrue; |
| } else if (indexRange.isNegative || lengthRange < indexRange) { |
| node.staticChecks = StaticBoundsChecks.alwaysFalse; |
| // The check is always false, and whatever instruction it |
| // dominates is dead code. |
| return indexRange; |
| } else if (indexRange.isPositive) { |
| node.staticChecks = StaticBoundsChecks.alwaysAboveZero; |
| } else if (belowLength) { |
| node.staticChecks = StaticBoundsChecks.alwaysBelowLength; |
| } |
| |
| 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 != info.unknownValue && node.length is! HConstant) { |
| HInstruction instruction = createRangeConversion(next!, node.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!, node.index); |
| ranges[instruction] = newIndexRange; |
| return newIndexRange; |
| } |
| |
| @override |
| Range visitRelational(HRelational node) { |
| HInstruction right = node.right; |
| HInstruction left = node.left; |
| if (left.isInteger(closedWorld.abstractValueDomain).isPotentiallyFalse) { |
| return info.newUnboundRange(); |
| } |
| if (right.isInteger(closedWorld.abstractValueDomain).isPotentiallyFalse) { |
| return info.newUnboundRange(); |
| } |
| constant_system.BinaryOperation operation = node.operation(); |
| Range rightRange = ranges[node.right]!; |
| Range leftRange = ranges[node.left]!; |
| |
| if (node is HIdentity) { |
| handleEqualityCheck(node); |
| } else if (applyRelationalOperation(operation, leftRange, rightRange)) { |
| final block = node.block!; |
| block.rewrite(node, graph.addConstantBool(true, closedWorld)); |
| block.remove(node); |
| } else if (applyRelationalOperation( |
| negateOperation(operation), |
| leftRange, |
| rightRange, |
| )) { |
| final block = node.block!; |
| block.rewrite(node, graph.addConstantBool(false, closedWorld)); |
| block.remove(node); |
| } |
| return info.newUnboundRange(); |
| } |
| |
| bool applyRelationalOperation( |
| constant_system.BinaryOperation operation, |
| Range left, |
| Range right, |
| ) { |
| if (operation == const constant_system.LessOperation()) { |
| return left < right; |
| } |
| if (operation == const constant_system.LessEqualOperation()) { |
| return left <= right; |
| } |
| if (operation == const constant_system.GreaterOperation()) { |
| return left > right; |
| } |
| if (operation == const constant_system.GreaterEqualOperation()) { |
| return left >= right; |
| } |
| |
| throw StateError('Unknown relational operation: $operation, $left, $right'); |
| } |
| |
| void handleEqualityCheck(HRelational node) { |
| Range right = ranges[node.right]!; |
| Range left = ranges[node.left]!; |
| if (left.isSingleValue && right.isSingleValue && left == right) { |
| final block = node.block!; |
| block.rewrite(node, graph.addConstantBool(true, closedWorld)); |
| block.remove(node); |
| } |
| } |
| |
| Range handleInvokeModulo(HInvokeDynamicMethod invoke) { |
| HInstruction left = invoke.inputs[1]; |
| HInstruction right = invoke.inputs[2]; |
| final divisor = ranges[right]; |
| if (divisor != null) { |
| // For Integer values we can be precise in the upper bound, so special |
| // case those. |
| if (left.isInteger(closedWorld.abstractValueDomain).isDefinitelyTrue && |
| right.isInteger(closedWorld.abstractValueDomain).isDefinitelyTrue) { |
| 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(closedWorld.abstractValueDomain) |
| .isDefinitelyTrue && |
| right.isNumber(closedWorld.abstractValueDomain).isDefinitelyTrue) { |
| 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(); |
| } |
| |
| @override |
| Range visitRemainder(HRemainder node) { |
| HInstruction left = node.inputs[0]; |
| HInstruction right = node.inputs[1]; |
| final dividend = ranges[left]; |
| // If both operands are >=0, the result is >= 0 and bounded by the divisor. |
| if ((dividend != null && dividend.isPositive) || |
| left |
| .isPositiveInteger(closedWorld.abstractValueDomain) |
| .isDefinitelyTrue) { |
| final divisor = ranges[right]; |
| if (divisor != null) { |
| if (divisor.isPositive) { |
| // For Integer values we can be precise in the upper bound. |
| if (left |
| .isInteger(closedWorld.abstractValueDomain) |
| .isDefinitelyTrue && |
| right |
| .isInteger(closedWorld.abstractValueDomain) |
| .isDefinitelyTrue) { |
| return info.newNormalizedRange( |
| info.intZero, |
| divisor.upper - info.intOne, |
| ); |
| } |
| if (left.isNumber(closedWorld.abstractValueDomain).isDefinitelyTrue && |
| right |
| .isNumber(closedWorld.abstractValueDomain) |
| .isDefinitelyTrue) { |
| return info.newNormalizedRange(info.intZero, divisor.upper); |
| } |
| } |
| } |
| } |
| return info.newUnboundRange(); |
| } |
| |
| @override |
| Range visitInvokeDynamicMethod(HInvokeDynamicMethod node) { |
| if ((node.inputs.length == 3) && (node.selector.name == "%")) { |
| return handleInvokeModulo(node); |
| } |
| return super.visitInvokeDynamicMethod(node); |
| } |
| |
| Range handleBinaryOperation( |
| HBinaryArithmetic instruction, |
| BinaryRangeOperation operation, |
| ) { |
| if (instruction |
| .isInteger(closedWorld.abstractValueDomain) |
| .isPotentiallyFalse) { |
| return info.newUnboundRange(); |
| } |
| return operation(ranges[instruction.left]!, ranges[instruction.right]!); |
| } |
| |
| @override |
| Range visitAdd(HAdd node) { |
| return handleBinaryOperation(node, Range.add); |
| } |
| |
| @override |
| Range visitSubtract(HSubtract node) { |
| return handleBinaryOperation(node, Range.subtract); |
| } |
| |
| @override |
| Range visitBitAnd(HBitAnd node) { |
| if (node.isInteger(closedWorld.abstractValueDomain).isPotentiallyFalse) { |
| return info.newUnboundRange(); |
| } |
| HInstruction right = node.right; |
| HInstruction left = node.left; |
| if (left.isInteger(closedWorld.abstractValueDomain).isDefinitelyTrue && |
| right.isInteger(closedWorld.abstractValueDomain).isDefinitelyTrue) { |
| return ranges[left]! & ranges[right]!; |
| } |
| |
| Range tryComputeRange(HInstruction instruction) { |
| final 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(closedWorld.abstractValueDomain).isDefinitelyTrue) { |
| return tryComputeRange(left); |
| } |
| if (right.isInteger(closedWorld.abstractValueDomain).isDefinitelyTrue) { |
| return tryComputeRange(right); |
| } |
| return info.newUnboundRange(); |
| } |
| |
| @override |
| Range visitCheck(HCheck node) { |
| final range = ranges[node.checkedInput]; |
| return range ?? visitInstruction(node); |
| } |
| |
| HInstruction createRangeConversion( |
| HInstruction cursor, |
| HInstruction instruction, |
| ) { |
| HRangeConversion newInstruction = HRangeConversion( |
| instruction, |
| closedWorld.abstractValueDomain.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 constant_system.BinaryOperation negateOperation( |
| constant_system.BinaryOperation operation, |
| ) { |
| if (operation == const constant_system.LessOperation()) { |
| return const constant_system.GreaterEqualOperation(); |
| } else if (operation == const constant_system.LessEqualOperation()) { |
| return const constant_system.GreaterOperation(); |
| } else if (operation == const constant_system.GreaterOperation()) { |
| return const constant_system.LessEqualOperation(); |
| } else if (operation == const constant_system.GreaterEqualOperation()) { |
| return const constant_system.LessOperation(); |
| } |
| throw ArgumentError('Cannot negate $operation'); |
| } |
| |
| static constant_system.BinaryOperation? flipOperation( |
| constant_system.BinaryOperation operation, |
| ) { |
| if (operation == const constant_system.LessOperation()) { |
| return const constant_system.GreaterOperation(); |
| } else if (operation == const constant_system.LessEqualOperation()) { |
| return const constant_system.GreaterEqualOperation(); |
| } else if (operation == const constant_system.GreaterOperation()) { |
| return const constant_system.LessOperation(); |
| } else if (operation == const constant_system.GreaterEqualOperation()) { |
| return const constant_system.LessEqualOperation(); |
| } else { |
| return null; |
| } |
| } |
| |
| Range computeConstrainedRange( |
| constant_system.BinaryOperation operation, |
| Range leftRange, |
| Range rightRange, |
| ) { |
| Range range; |
| if (operation == const constant_system.LessOperation()) { |
| range = info.newNormalizedRange( |
| info.minIntValue, |
| rightRange.upper - info.intOne, |
| ); |
| } else if (operation == const constant_system.LessEqualOperation()) { |
| range = info.newNormalizedRange(info.minIntValue, rightRange.upper); |
| } else if (operation == const constant_system.GreaterOperation()) { |
| range = info.newNormalizedRange( |
| rightRange.lower + info.intOne, |
| info.maxIntValue, |
| ); |
| } else if (operation == const constant_system.GreaterEqualOperation()) { |
| range = info.newNormalizedRange(rightRange.lower, info.maxIntValue); |
| } else { |
| range = info.newUnboundRange(); |
| } |
| return range.intersection(leftRange); |
| } |
| |
| @override |
| Range visitConditionalBranch(HConditionalBranch node) { |
| HInstruction condition = node.condition; |
| // TODO(ngeoffray): Handle complex conditions. |
| if (condition is HRelational) { |
| if (condition is HIdentity) return info.newUnboundRange(); |
| HInstruction right = condition.right; |
| HInstruction left = condition.left; |
| if (left.isInteger(closedWorld.abstractValueDomain).isPotentiallyFalse) { |
| return info.newUnboundRange(); |
| } |
| if (right.isInteger(closedWorld.abstractValueDomain).isPotentiallyFalse) { |
| return info.newUnboundRange(); |
| } |
| |
| final rightRange = ranges[right]!; |
| final leftRange = ranges[left]!; |
| constant_system.BinaryOperation operation = condition.operation(); |
| constant_system.BinaryOperation mirrorOp = flipOperation(operation)!; |
| // Only update the true branch if this block is the only |
| // predecessor. |
| if (node.trueBranch.predecessors.length == 1) { |
| assert(node.trueBranch.predecessors[0] == node.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( |
| node.trueBranch.first!, |
| left, |
| ); |
| ranges[instruction] = range; |
| } |
| |
| range = computeConstrainedRange(mirrorOp, rightRange, leftRange); |
| if (rightRange != range) { |
| HInstruction instruction = createRangeConversion( |
| node.trueBranch.first!, |
| right, |
| ); |
| ranges[instruction] = range; |
| } |
| } |
| |
| // Only update the false branch if this block is the only |
| // predecessor. |
| if (node.falseBranch.predecessors.length == 1) { |
| assert(node.falseBranch.predecessors[0] == node.block); |
| constant_system.BinaryOperation reverse = negateOperation(operation); |
| constant_system.BinaryOperation reversedMirror = flipOperation( |
| reverse, |
| )!; |
| // Update the false branch to use narrower ranges for [left] and |
| // [right]. |
| Range range = computeConstrainedRange(reverse, leftRange, rightRange); |
| if (leftRange != range) { |
| HInstruction instruction = createRangeConversion( |
| node.falseBranch.first!, |
| left, |
| ); |
| ranges[instruction] = range; |
| } |
| |
| range = computeConstrainedRange(reversedMirror, rightRange, leftRange); |
| if (rightRange != range) { |
| HInstruction instruction = createRangeConversion( |
| node.falseBranch.first!, |
| right, |
| ); |
| ranges[instruction] = range; |
| } |
| } |
| |
| return info.newUnboundRange(); |
| } |
| return info.newUnboundRange(); |
| } |
| |
| @override |
| Range visitRangeConversion(HRangeConversion node) { |
| return ranges[node]!; |
| } |
| } |
| |
| /// Tries to find a range for the update instruction of a loop phi. |
| class LoopUpdateRecognizer extends HBaseVisitor<Range?> { |
| final JClosedWorld closedWorld; |
| // Ranges from outside the loop, which never contain a marker value. |
| final UnmodifiableMapView<HInstruction, Range> ranges; |
| final ValueRangeInfo info; |
| |
| // Ranges inside the loop which may contain marker values specific to the loop |
| // phi. |
| final Map<HInstruction, Range?> temporaryRanges = {}; |
| |
| LoopUpdateRecognizer(this.closedWorld, this.ranges, this.info); |
| |
| Range? run(HPhi loopPhi) { |
| // Create a marker range for the loop phi. This is the symbolic initial |
| // value of the loop variable for one iteration. |
| bool isPositive = loopPhi |
| .isPositiveInteger(closedWorld.abstractValueDomain) |
| .isDefinitelyTrue; |
| final lowerMarker = info.newMarkerValue( |
| isLower: true, |
| isPositive: isPositive, |
| ); |
| final upperMarker = info.newMarkerValue( |
| isLower: false, |
| isPositive: isPositive, |
| ); |
| final markerRange = info.newNormalizedRange(lowerMarker, upperMarker); |
| |
| // Compute the update range as a function of the initial marker range. |
| temporaryRanges[loopPhi] = markerRange; |
| final updateRange = visit(loopPhi.inputs[1]); |
| if (updateRange == null) return null; |
| |
| // Use 'union' to compare the marker with the loop update to find out if the |
| // lower or upper value did not change. |
| final deltaRange = markerRange.union(updateRange); |
| |
| // If the lower (respectively upper) value is the marker, we know the loop |
| // does not change it, so we can use the [startRange]'s lower (upper) value. |
| // Otherwise the lower (upper) value changes and needs to be widened to the |
| // minimum (maximum) value. |
| final startRange = ranges[loopPhi.inputs[0]]!; |
| |
| Value lowerLimit = isPositive ? info.intZero : info.minIntValue; |
| Value upperLimit = info.maxIntValue; |
| Value lowerBound = deltaRange.lower == lowerMarker |
| ? startRange.lower |
| : lowerLimit; |
| Value upperBound = deltaRange.upper == upperMarker |
| ? startRange.upper |
| : upperLimit; |
| |
| // Widen the update range and union with the start range. |
| final widened = updateRange.replaceMarkers(lowerBound, upperBound); |
| final result = startRange.union(widened); |
| |
| if (_debug) { |
| print( |
| '------- ${loopPhi.sourceElement}' |
| '\n marker $markerRange' |
| '\n update $updateRange' |
| '\n delta $deltaRange' |
| '\n start $startRange' |
| '\n widened $widened' |
| '\n result= $result', |
| ); |
| } |
| |
| return result; |
| } |
| |
| Range? visit(HInstruction instruction) { |
| if (instruction |
| .isInteger(closedWorld.abstractValueDomain) |
| .isPotentiallyFalse) { |
| return null; |
| } |
| Range? result = ranges[instruction]; |
| if (result != null) return result; |
| return temporaryRanges[instruction] ??= instruction.accept(this); |
| } |
| |
| @override |
| Range? visitPhi(HPhi node) { |
| // If the update of a loop phi involves another loop phi, we give up. |
| if (node.block!.isLoopHeader()) return null; |
| Range? phiRange; |
| for (HInstruction input in node.inputs) { |
| final inputRange = visit(input); |
| if (inputRange == null) return null; |
| if (phiRange == null) { |
| phiRange = inputRange; |
| } else { |
| phiRange = phiRange.union(inputRange); |
| } |
| } |
| return phiRange; |
| } |
| |
| @override |
| Range? visitCheck(HCheck node) { |
| return visit(node.checkedInput); |
| } |
| |
| @override |
| Range? visitAdd(HAdd node) { |
| return handleBinaryOperation(node, Range.add); |
| } |
| |
| @override |
| Range? visitSubtract(HSubtract node) { |
| return handleBinaryOperation(node, Range.subtract); |
| } |
| |
| Range? handleBinaryOperation( |
| HBinaryArithmetic instruction, |
| BinaryRangeOperation operation, |
| ) { |
| final leftRange = visit(instruction.left); |
| final rightRange = visit(instruction.right); |
| if (leftRange == null || rightRange == null) return null; |
| return operation(leftRange, rightRange); |
| } |
| } |