| // 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_ |