blob: 17202d265c7d73e554908279b13dd0c2dddaabde [file] [log] [blame] [edit]
// Copyright (c) 2014, 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.
#ifndef RUNTIME_VM_COMPILER_BACKEND_RANGE_ANALYSIS_H_
#define RUNTIME_VM_COMPILER_BACKEND_RANGE_ANALYSIS_H_
#if defined(DART_PRECOMPILED_RUNTIME)
#error "AOT runtime should not use compiler sources (including header files)"
#endif // defined(DART_PRECOMPILED_RUNTIME)
#include "vm/compiler/backend/flow_graph.h"
#include "vm/compiler/backend/il.h"
namespace dart {
// How to interpret values with kTagged representation.
enum class TaggedMode {
kTaggedNotAllowed,
kTaggedIsSmi,
};
class RangeBoundary : public ValueObject {
public:
#define FOR_EACH_RANGE_BOUNDARY_KIND(V) \
V(Unknown) \
V(Symbol) \
V(Constant)
#define KIND_DEFN(name) k##name,
enum Kind { FOR_EACH_RANGE_BOUNDARY_KIND(KIND_DEFN) };
#undef KIND_DEFN
RangeBoundary() : kind_(kUnknown), value_(0), offset_(0) {}
RangeBoundary(const RangeBoundary& other)
: ValueObject(),
kind_(other.kind_),
value_(other.value_),
offset_(other.offset_) {}
explicit RangeBoundary(int64_t val)
: kind_(kConstant), value_(val), offset_(0) {}
RangeBoundary& operator=(const RangeBoundary& other) {
kind_ = other.kind_;
value_ = other.value_;
offset_ = other.offset_;
return *this;
}
// Construct a RangeBoundary for a constant value.
static RangeBoundary FromConstant(int64_t val) { return RangeBoundary(val); }
// Construct a RangeBoundary from a definition and offset.
static RangeBoundary FromDefinition(Definition* defn, int64_t offs = 0);
static bool IsValidOffsetForSymbolicRangeBoundary(int64_t offset) {
if ((offset > (kMaxInt64 - compiler::target::kSmiMax)) ||
(offset < (kMinInt64 - compiler::target::kSmiMin))) {
// Avoid creating symbolic range boundaries which can wrap around.
return false;
}
return true;
}
// Construct a RangeBoundary for the constant MinSmi value.
static RangeBoundary MinSmi() {
return FromConstant(compiler::target::kSmiMin);
}
// Construct a RangeBoundary for the constant MaxSmi value.
static RangeBoundary MaxSmi() {
return FromConstant(compiler::target::kSmiMax);
}
static RangeBoundary MinInt64() { return FromConstant(kMinInt64); }
static RangeBoundary MaxInt64() { return FromConstant(kMaxInt64); }
// Given two boundaries a and b, select one of them as c so that
//
// inf {[a, ...) ^ [b, ...)} >= inf {c}
//
static RangeBoundary IntersectionMin(RangeBoundary a, RangeBoundary b);
// Given two boundaries a and b, select one of them as c so that
//
// sup {(..., a] ^ (..., b]} <= sup {c}
//
static RangeBoundary IntersectionMax(RangeBoundary a, RangeBoundary b);
// Given two boundaries a and b compute boundary c such that
//
// inf {[a, ...) U [b, ...)} >= inf {c}
//
// Try to select c such that it is as close to inf {[a, ...) U [b, ...)}
// as possible.
static RangeBoundary JoinMin(RangeBoundary a,
RangeBoundary b,
const Range& full_range);
// Given two boundaries a and b compute boundary c such that
//
// sup {(..., a] U (..., b]} <= sup {c}
//
// Try to select c such that it is as close to sup {(..., a] U (..., b]}
// as possible.
static RangeBoundary JoinMax(RangeBoundary a,
RangeBoundary b,
const Range& full_range);
// Returns true when this is a constant that is outside of Smi range.
bool OverflowedSmi() const {
return IsConstant() && !compiler::target::IsSmi(ConstantValue());
}
bool Overflowed(const Range& full_range) const {
ASSERT(IsConstant());
return !Equals(Clamp(full_range));
}
// Clamp constant boundary to the given [full_range].
RangeBoundary Clamp(const Range& full_range) const;
bool IsLessOrEqual(const RangeBoundary& other) const {
ASSERT(other.IsConstant());
return IsConstant() && (ConstantValue() <= other.ConstantValue());
}
bool IsGreaterOrEqual(const RangeBoundary& other) const {
ASSERT(other.IsConstant());
return IsConstant() && (ConstantValue() >= other.ConstantValue());
}
intptr_t kind() const { return kind_; }
// Kind tests.
bool IsUnknown() const { return kind_ == kUnknown; }
bool IsConstant() const { return kind_ == kConstant; }
bool IsSymbol() const { return kind_ == kSymbol; }
// Returns the value of a kConstant RangeBoundary.
int64_t ConstantValue() const;
// Returns the Definition associated with a kSymbol RangeBoundary.
Definition* symbol() const {
ASSERT(IsSymbol());
return reinterpret_cast<Definition*>(value_);
}
// Offset from symbol.
int64_t offset() const { return offset_; }
// Computes the LowerBound of this. Three cases:
// IsConstant() -> value().
// IsSymbol() -> lower bound computed from definition + offset.
RangeBoundary LowerBound() const;
// Computes the UpperBound of this. Three cases:
// IsConstant() -> value().
// IsSymbol() -> upper bound computed from definition + offset.
RangeBoundary UpperBound() const;
void PrintTo(BaseTextBuffer* f) const;
const char* ToCString() const;
static bool WillAddOverflow(const RangeBoundary& a, const RangeBoundary& b);
static RangeBoundary Add(const RangeBoundary& a, const RangeBoundary& b);
static bool WillSubOverflow(const RangeBoundary& a, const RangeBoundary& b);
static RangeBoundary Sub(const RangeBoundary& a, const RangeBoundary& b);
static bool WillShlOverflow(const RangeBoundary& a, int64_t shift_count);
static RangeBoundary Shl(const RangeBoundary& value_boundary,
int64_t shift_count);
static RangeBoundary Shr(const RangeBoundary& value_boundary,
int64_t shift_count);
// Attempts to calculate a + b when:
// a is a symbol and b is a constant OR
// a is a constant and b is a symbol
// returns true if it succeeds, output is in result.
static bool SymbolicAdd(const RangeBoundary& a,
const RangeBoundary& b,
RangeBoundary* result);
// Attempts to calculate a - b when:
// a is a symbol and b is a constant
// returns true if it succeeds, output is in result.
static bool SymbolicSub(const RangeBoundary& a,
const RangeBoundary& b,
RangeBoundary* result);
bool Equals(const RangeBoundary& other) const;
int64_t UpperBound(const Range& full_range) const {
return UpperBound().Clamp(full_range).ConstantValue();
}
int64_t LowerBound(const Range& full_range) const {
return LowerBound().Clamp(full_range).ConstantValue();
}
void Write(FlowGraphSerializer* s) const;
explicit RangeBoundary(FlowGraphDeserializer* d);
private:
RangeBoundary(Kind kind, int64_t value, int64_t offset)
: kind_(kind), value_(value), offset_(offset) {}
Kind kind_;
int64_t value_;
int64_t offset_;
};
class Range : public ZoneAllocated {
public:
Range() : min_(), max_() {}
Range(RangeBoundary min, RangeBoundary max) : min_(min), max_(max) {
ASSERT(min_.IsUnknown() == max_.IsUnknown());
}
Range(const Range& other)
: ZoneAllocated(), min_(other.min_), max_(other.max_) {}
Range& operator=(const Range& other) {
min_ = other.min_;
max_ = other.max_;
return *this;
}
static bool IsUnknown(const Range* other) {
if (other == nullptr) {
return true;
}
return other->min().IsUnknown();
}
static Range Smi() {
return Range(RangeBoundary::MinSmi(), RangeBoundary::MaxSmi());
}
static Range Int64() {
return Range(RangeBoundary::MinInt64(), RangeBoundary::MaxInt64());
}
static Range Full(Representation rep,
TaggedMode tagged_mode = TaggedMode::kTaggedNotAllowed);
void PrintTo(BaseTextBuffer* f) const;
static const char* ToCString(const Range* range);
bool Equals(const Range* other) {
ASSERT(min_.IsUnknown() == max_.IsUnknown());
if (other == nullptr) {
return min_.IsUnknown();
}
return min_.Equals(other->min_) && max_.Equals(other->max_);
}
const RangeBoundary& min() const { return min_; }
const RangeBoundary& max() const { return max_; }
void set_min(const RangeBoundary& value) { min_ = value; }
void set_max(const RangeBoundary& value) { max_ = value; }
static RangeBoundary ConstantMinSmi(const Range* range) {
return ConstantMin(range, Range::Smi());
}
static RangeBoundary ConstantMaxSmi(const Range* range) {
return ConstantMax(range, Range::Smi());
}
static RangeBoundary ConstantMin(const Range* range) {
return ConstantMin(range, Range::Int64());
}
static RangeBoundary ConstantMax(const Range* range) {
return ConstantMax(range, Range::Int64());
}
static RangeBoundary ConstantMin(const Range* range,
const Range& full_range) {
if (range == nullptr) {
return full_range.min();
}
return range->min().LowerBound().Clamp(full_range);
}
static RangeBoundary ConstantMax(const Range* range,
const Range& full_range) {
if (range == nullptr) {
return full_range.max();
}
return range->max().UpperBound().Clamp(full_range);
}
// [0, +inf]
bool IsPositive() const;
// [-inf, -1]
bool IsNegative() const;
// [-inf, val].
bool OnlyLessThanOrEqualTo(int64_t val) const;
// [val, +inf].
bool OnlyGreaterThanOrEqualTo(int64_t val) const;
// Inclusive.
bool IsWithin(int64_t min_int, int64_t max_int) const;
// Inclusive.
bool IsWithin(const Range* other) const;
// Inclusive.
bool Overlaps(int64_t min_int, int64_t max_int) const;
bool IsUnsatisfiable() const;
bool IsSingleton() const {
return min_.IsConstant() && max_.IsConstant() &&
min_.ConstantValue() == max_.ConstantValue();
}
int64_t Singleton() const {
ASSERT(IsSingleton());
return min_.ConstantValue();
}
Range Intersect(const Range* other) const {
return Range(RangeBoundary::IntersectionMin(min(), other->min()),
RangeBoundary::IntersectionMax(max(), other->max()));
}
// Returns true if this range fits without truncation into
// the given representation.
static bool Fits(Range* range,
Representation rep,
TaggedMode tagged_mode = TaggedMode::kTaggedNotAllowed) {
if (range == nullptr) return false;
const Range& other = Range::Full(rep, tagged_mode);
return range->IsWithin(&other);
}
// Clamp this to be within size.
void Clamp(const Range& full_range);
// Clamp this to be within size and eliminate symbols.
void ClampToConstant(const Range& full_range);
static void Add(const Range* left_range,
const Range* right_range,
RangeBoundary* min,
RangeBoundary* max,
Definition* left_defn);
static void Sub(const Range* left_range,
const Range* right_range,
RangeBoundary* min,
RangeBoundary* max,
Definition* left_defn);
static void Mul(const Range* left_range,
const Range* right_range,
RangeBoundary* min,
RangeBoundary* max);
static void TruncDiv(const Range* left_range,
const Range* right_range,
RangeBoundary* min,
RangeBoundary* max);
static void Mod(const Range* right_range,
RangeBoundary* min,
RangeBoundary* max);
static void Shr(const Range* left_range,
const Range* right_range,
RangeBoundary* min,
RangeBoundary* max);
static void Ushr(const Range* left_range,
const Range* right_range,
RangeBoundary* min,
RangeBoundary* max);
static void Shl(const Range* left_range,
const Range* right_range,
RangeBoundary* min,
RangeBoundary* max);
static void And(const Range* left_range,
const Range* right_range,
RangeBoundary* min,
RangeBoundary* max);
static void BitwiseOp(const Range* left_range,
const Range* right_range,
RangeBoundary* min,
RangeBoundary* max);
// Both the a and b ranges are >= 0.
static bool OnlyPositiveOrZero(const Range& a, const Range& b);
// Both the a and b ranges are <= 0.
static bool OnlyNegativeOrZero(const Range& a, const Range& b);
// Return the maximum absolute value included in range.
static int64_t ConstantAbsMax(const Range* range);
// Return the minimum absolute value included in range.
static int64_t ConstantAbsMin(const Range* range);
static void BinaryOp(const Token::Kind op,
const Range* left_range,
const Range* right_range,
Definition* left_defn,
Range* result);
void Write(FlowGraphSerializer* s) const;
explicit Range(FlowGraphDeserializer* d);
private:
RangeBoundary min_;
RangeBoundary max_;
};
class RangeUtils : public AllStatic {
public:
static bool IsWithin(const Range* range, int64_t min, int64_t max) {
return !Range::IsUnknown(range) && range->IsWithin(min, max);
}
static bool IsWithin(const Range* range, const Range* other) {
return !Range::IsUnknown(range) && range->IsWithin(other);
}
static bool IsPositive(Range* range) {
return !Range::IsUnknown(range) && range->IsPositive();
}
static bool IsNegative(Range* range) {
return !Range::IsUnknown(range) && range->IsNegative();
}
static bool Overlaps(Range* range, intptr_t min, intptr_t max) {
return Range::IsUnknown(range) || range->Overlaps(min, max);
}
static bool CanBeZero(Range* range) { return Overlaps(range, 0, 0); }
static bool OnlyLessThanOrEqualTo(Range* range, intptr_t value) {
return !Range::IsUnknown(range) && range->OnlyLessThanOrEqualTo(value);
}
static bool IsSingleton(Range* range) {
return !Range::IsUnknown(range) && range->IsSingleton();
}
};
// Range analysis for integer values.
class RangeAnalysis : public ValueObject {
public:
explicit RangeAnalysis(FlowGraph* flow_graph)
: flow_graph_(flow_graph), smi_range_(Range::Smi()) {}
// Infer ranges for all values and remove overflow checks from binary smi
// operations when proven redundant.
void Analyze();
static bool IsIntegerDefinition(Definition* defn) {
return defn->Type()->IsInt();
}
void AssignRangesRecursively(Definition* defn);
private:
enum JoinOperator { NONE, WIDEN, NARROW };
static char OpPrefix(JoinOperator op);
// Collect all integer values (smi or int), all 64-bit binary
// and shift operations, and all check bounds.
void CollectValues();
// Iterate over smi values and constrain them at branch successors.
// Additionally constraint values after CheckSmi instructions.
void InsertConstraints();
// Iterate over uses of the given definition and discover branches that
// constrain it. Insert appropriate Constraint instructions at true
// and false successor and rename all dominated uses to refer to a
// Constraint instead of this definition.
void InsertConstraintsFor(Definition* defn);
// Create a constraint for defn, insert it after given instruction and
// rename all uses that are dominated by it.
ConstraintInstr* InsertConstraintFor(Value* use,
Definition* defn,
Range* constraint,
Instruction* after);
bool ConstrainValueAfterBranch(Value* use, Definition* defn);
void ConstrainValueAfterCheckBound(Value* use,
CheckBoundBaseInstr* check,
Definition* defn);
// Infer ranges for integer (smi or mint) definitions.
void InferRanges();
// Collect integer definition in the reverse postorder.
void CollectDefinitions(BitVector* set);
// Recompute ranges of all definitions until they stop changing.
// Apply the given JoinOperator when computing Phi ranges.
void Iterate(JoinOperator op, intptr_t max_iterations);
bool InferRange(JoinOperator op, Definition* defn, intptr_t iteration);
// Based on computed ranges find and eliminate redundant CheckArrayBound
// instructions.
void EliminateRedundantBoundsChecks();
// Find unsatisfiable constraints and mark corresponding blocks unreachable.
void MarkUnreachableBlocks();
// Convert Int64 operations that stay within Int32 range into Int32
// operations.
void NarrowInt64OperationsToInt32();
// Remove artificial Constraint instructions and replace them with actual
// unconstrained definitions.
void RemoveConstraints();
Range* ConstraintRange(Token::Kind op,
Definition* boundary,
const Range& full_range);
Zone* zone() const { return flow_graph_->zone(); }
const Range& smi_range() const { return smi_range_; }
FlowGraph* flow_graph_;
Range smi_range_;
// All values that are known to be smi or mint.
GrowableArray<Definition*> values_;
// All 64-bit binary operations.
GrowableArray<BinaryInt64OpInstr*> binary_int64_ops_;
GrowableArray<ComparisonInstr*> int64_comparisons_;
// All CheckArrayBound/GenericCheckBound instructions.
GrowableArray<CheckBoundBaseInstr*> bounds_checks_;
// All Constraints inserted during InsertConstraints phase. They are treated
// as smi values.
GrowableArray<ConstraintInstr*> constraints_;
// List of integer (smi or mint) definitions including constraints sorted
// in the reverse postorder.
GrowableArray<Definition*> definitions_;
DISALLOW_COPY_AND_ASSIGN(RangeAnalysis);
};
// Replaces Mint IL instructions with Uint32 IL instructions
// when possible. Uses output of RangeAnalysis.
class IntegerInstructionSelector : public ValueObject {
public:
explicit IntegerInstructionSelector(FlowGraph* flow_graph);
void Select();
private:
bool IsPotentialUint32Definition(Definition* def);
void FindPotentialUint32Definitions();
bool IsUint32NarrowingDefinition(Definition* def);
void FindUint32NarrowingDefinitions();
bool AllUsesAreUint32Narrowing(Value* list_head);
bool CanBecomeUint32(Definition* def);
void Propagate();
bool IsUint32Use(Value* use);
void NarrowUint32Uses();
Definition* ConstructReplacementFor(Definition* def);
void ReplaceInstructions();
Zone* zone() const { return zone_; }
GrowableArray<Definition*> potential_uint32_defs_;
BitVector* selected_uint32_defs_;
FlowGraph* flow_graph_;
Zone* zone_;
};
} // namespace dart
#endif // RUNTIME_VM_COMPILER_BACKEND_RANGE_ANALYSIS_H_