blob: 7779e419434fbe4f87e15b9fdebd72083b448ae1 [file] [log] [blame] [edit]
// 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);
}
}