/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ART_COMPILER_OPTIMIZING_SSA_LIVENESS_ANALYSIS_H_ #define ART_COMPILER_OPTIMIZING_SSA_LIVENESS_ANALYSIS_H_ #include "nodes.h" namespace art { class CodeGenerator; class BlockInfo : public ArenaObject { public: BlockInfo(ArenaAllocator* allocator, const HBasicBlock& block, size_t number_of_ssa_values) : block_(block), live_in_(allocator, number_of_ssa_values, false), live_out_(allocator, number_of_ssa_values, false), kill_(allocator, number_of_ssa_values, false) { live_in_.ClearAllBits(); live_out_.ClearAllBits(); kill_.ClearAllBits(); } private: const HBasicBlock& block_; ArenaBitVector live_in_; ArenaBitVector live_out_; ArenaBitVector kill_; friend class SsaLivenessAnalysis; DISALLOW_COPY_AND_ASSIGN(BlockInfo); }; /** * A live range contains the start and end of a range where an instruction * is live. */ class LiveRange : public ArenaObject { public: LiveRange(size_t start, size_t end, LiveRange* next) : start_(start), end_(end), next_(next) { DCHECK_LT(start, end); DCHECK(next_ == nullptr || next_->GetStart() > GetEnd()); } size_t GetStart() const { return start_; } size_t GetEnd() const { return end_; } LiveRange* GetNext() const { return next_; } bool IntersectsWith(const LiveRange& other) { return (start_ >= other.start_ && start_ < other.end_) || (other.start_ >= start_ && other.start_ < end_); } bool IsBefore(const LiveRange& other) { return end_ <= other.start_; } void Dump(std::ostream& stream) { stream << "[" << start_ << ", " << end_ << ")"; } private: size_t start_; size_t end_; LiveRange* next_; friend class LiveInterval; DISALLOW_COPY_AND_ASSIGN(LiveRange); }; /** * A use position represents a live interval use at a given position. */ class UsePosition : public ArenaObject { public: UsePosition(HInstruction* user, size_t input_index, bool is_environment, size_t position, UsePosition* next) : user_(user), input_index_(input_index), is_environment_(is_environment), position_(position), next_(next) { DCHECK(user->AsPhi() != nullptr || GetPosition() == user->GetLifetimePosition() + 1); DCHECK(next_ == nullptr || next->GetPosition() >= GetPosition()); } size_t GetPosition() const { return position_; } UsePosition* GetNext() const { return next_; } HInstruction* GetUser() const { return user_; } bool GetIsEnvironment() const { return is_environment_; } size_t GetInputIndex() const { return input_index_; } void Dump(std::ostream& stream) const { stream << position_; } private: HInstruction* const user_; const size_t input_index_; const bool is_environment_; const size_t position_; UsePosition* const next_; DISALLOW_COPY_AND_ASSIGN(UsePosition); }; /** * An interval is a list of disjoint live ranges where an instruction is live. * Each instruction that has uses gets an interval. */ class LiveInterval : public ArenaObject { public: LiveInterval(ArenaAllocator* allocator, Primitive::Type type, HInstruction* defined_by = nullptr) : allocator_(allocator), first_range_(nullptr), last_range_(nullptr), first_use_(nullptr), type_(type), next_sibling_(nullptr), parent_(this), register_(kNoRegister), spill_slot_(kNoSpillSlot), is_fixed_(false), defined_by_(defined_by) {} static LiveInterval* MakeFixedInterval(ArenaAllocator* allocator, int reg, Primitive::Type type) { LiveInterval* interval = new (allocator) LiveInterval(allocator, type); interval->SetRegister(reg); interval->is_fixed_ = true; return interval; } bool IsFixed() const { return is_fixed_; } void AddUse(HInstruction* instruction, size_t input_index, bool is_environment) { // Set the use within the instruction. // TODO: Use the instruction's location to know whether the instruction can die // at entry, or needs to say alive within the user. size_t position = instruction->GetLifetimePosition() + 1; size_t start_block_position = instruction->GetBlock()->GetLifetimeStart(); size_t end_block_position = instruction->GetBlock()->GetLifetimeEnd(); if (first_range_ == nullptr) { // First time we see a use of that interval. first_range_ = last_range_ = new (allocator_) LiveRange(start_block_position, position, nullptr); } else if (first_range_->GetStart() == start_block_position) { // There is a use later in the same block. DCHECK_LE(position, first_range_->GetEnd()); } else if (first_range_->GetStart() == end_block_position) { // Last use is in the following block. first_range_->start_ = start_block_position; } else { // There is a hole in the interval. Create a new range. first_range_ = new (allocator_) LiveRange(start_block_position, position, first_range_); } first_use_ = new (allocator_) UsePosition( instruction, input_index, is_environment, position, first_use_); } void AddPhiUse(HInstruction* instruction, size_t input_index, HBasicBlock* block) { DCHECK(instruction->AsPhi() != nullptr); first_use_ = new (allocator_) UsePosition( instruction, input_index, false, block->GetLifetimeEnd(), first_use_); } void AddRange(size_t start, size_t end) { if (first_range_ == nullptr) { first_range_ = last_range_ = new (allocator_) LiveRange(start, end, first_range_); } else if (first_range_->GetStart() == end) { // There is a use in the following block. first_range_->start_ = start; } else { // There is a hole in the interval. Create a new range. first_range_ = new (allocator_) LiveRange(start, end, first_range_); } } void AddLoopRange(size_t start, size_t end) { DCHECK(first_range_ != nullptr); while (first_range_ != nullptr && first_range_->GetEnd() < end) { DCHECK_LE(start, first_range_->GetStart()); first_range_ = first_range_->GetNext(); } if (first_range_ == nullptr) { // Uses are only in the loop. first_range_ = last_range_ = new (allocator_) LiveRange(start, end, nullptr); } else { // There are uses after the loop. first_range_->start_ = start; } } bool HasSpillSlot() const { return spill_slot_ != kNoSpillSlot; } void SetSpillSlot(int slot) { spill_slot_ = slot; } int GetSpillSlot() const { return spill_slot_; } void SetFrom(size_t from) { if (first_range_ != nullptr) { first_range_->start_ = from; } else { // Instruction without uses. DCHECK(!defined_by_->HasUses()); DCHECK(from == defined_by_->GetLifetimePosition()); first_range_ = last_range_ = new (allocator_) LiveRange(from, from + 2, nullptr); } } LiveInterval* GetParent() const { return parent_; } LiveRange* GetFirstRange() const { return first_range_; } int GetRegister() const { return register_; } void SetRegister(int reg) { register_ = reg; } void ClearRegister() { register_ = kNoRegister; } bool HasRegister() const { return register_ != kNoRegister; } bool IsDeadAt(size_t position) const { return last_range_->GetEnd() <= position; } bool Covers(size_t position) const { LiveRange* current = first_range_; while (current != nullptr) { if (position >= current->GetStart() && position < current->GetEnd()) { return true; } current = current->GetNext(); } return false; } /** * Returns the first intersection of this interval with `other`. */ size_t FirstIntersectionWith(LiveInterval* other) const { // Advance both intervals and find the first matching range start in // this interval. LiveRange* my_range = first_range_; LiveRange* other_range = other->first_range_; do { if (my_range->IntersectsWith(*other_range)) { return std::max(my_range->GetStart(), other_range->GetStart()); } else if (my_range->IsBefore(*other_range)) { my_range = my_range->GetNext(); if (my_range == nullptr) { return kNoLifetime; } } else { DCHECK(other_range->IsBefore(*my_range)); other_range = other_range->GetNext(); if (other_range == nullptr) { return kNoLifetime; } } } while (true); } size_t GetStart() const { return first_range_->GetStart(); } size_t GetEnd() const { return last_range_->GetEnd(); } size_t FirstRegisterUseAfter(size_t position) const { if (position == GetStart() && defined_by_ != nullptr) { Location location = defined_by_->GetLocations()->Out(); // This interval is the first interval of the instruction. If the output // of the instruction requires a register, we return the position of that instruction // as the first register use. if (location.IsUnallocated()) { if ((location.GetPolicy() == Location::kRequiresRegister) || (location.GetPolicy() == Location::kSameAsFirstInput && defined_by_->GetLocations()->InAt(0).GetPolicy() == Location::kRequiresRegister)) { return position; } } } UsePosition* use = first_use_; while (use != nullptr) { size_t use_position = use->GetPosition(); if (use_position >= position && !use->GetIsEnvironment()) { Location location = use->GetUser()->GetLocations()->InAt(use->GetInputIndex()); if (location.IsUnallocated() && location.GetPolicy() == Location::kRequiresRegister) { return use_position; } } use = use->GetNext(); } return kNoLifetime; } size_t FirstRegisterUse() const { return FirstRegisterUseAfter(GetStart()); } UsePosition* GetFirstUse() const { return first_use_; } Primitive::Type GetType() const { return type_; } HInstruction* GetDefinedBy() const { return defined_by_; } /** * Split this interval at `position`. This interval is changed to: * [start ... position). * * The new interval covers: * [position ... end) */ LiveInterval* SplitAt(size_t position) { DCHECK(!is_fixed_); DCHECK_GT(position, GetStart()); if (last_range_->GetEnd() <= position) { // This range dies before `position`, no need to split. return nullptr; } LiveInterval* new_interval = new (allocator_) LiveInterval(allocator_, type_); new_interval->next_sibling_ = next_sibling_; next_sibling_ = new_interval; new_interval->parent_ = parent_; new_interval->first_use_ = first_use_; LiveRange* current = first_range_; LiveRange* previous = nullptr; // Iterate over the ranges, and either find a range that covers this position, or // a two ranges in between this position (that is, the position is in a lifetime hole). do { if (position >= current->GetEnd()) { // Move to next range. previous = current; current = current->next_; } else if (position <= current->GetStart()) { // If the previous range did not cover this position, we know position is in // a lifetime hole. We can just break the first_range_ and last_range_ links // and return the new interval. DCHECK(previous != nullptr); DCHECK(current != first_range_); new_interval->last_range_ = last_range_; last_range_ = previous; previous->next_ = nullptr; new_interval->first_range_ = current; return new_interval; } else { // This range covers position. We create a new last_range_ for this interval // that covers last_range_->Start() and position. We also shorten the current // range and make it the first range of the new interval. DCHECK(position < current->GetEnd() && position > current->GetStart()); new_interval->last_range_ = last_range_; last_range_ = new (allocator_) LiveRange(current->start_, position, nullptr); if (previous != nullptr) { previous->next_ = last_range_; } else { first_range_ = last_range_; } new_interval->first_range_ = current; current->start_ = position; return new_interval; } } while (current != nullptr); LOG(FATAL) << "Unreachable"; return nullptr; } bool StartsBefore(LiveInterval* other) const { return GetStart() <= other->GetStart(); } bool StartsAfter(LiveInterval* other) const { return GetStart() >= other->GetStart(); } void Dump(std::ostream& stream) const { stream << "ranges: { "; LiveRange* current = first_range_; do { current->Dump(stream); stream << " "; } while ((current = current->GetNext()) != nullptr); stream << "}, uses: { "; UsePosition* use = first_use_; if (use != nullptr) { do { use->Dump(stream); stream << " "; } while ((use = use->GetNext()) != nullptr); } stream << "}"; } LiveInterval* GetNextSibling() const { return next_sibling_; } private: ArenaAllocator* const allocator_; // Ranges of this interval. We need a quick access to the last range to test // for liveness (see `IsDeadAt`). LiveRange* first_range_; LiveRange* last_range_; // Uses of this interval. Note that this linked list is shared amongst siblings. UsePosition* first_use_; // The instruction type this interval corresponds to. const Primitive::Type type_; // Live interval that is the result of a split. LiveInterval* next_sibling_; // The first interval from which split intervals come from. LiveInterval* parent_; // The register allocated to this interval. int register_; // The spill slot allocated to this interval. int spill_slot_; // Whether the interval is for a fixed register. bool is_fixed_; // The instruction represented by this interval. HInstruction* const defined_by_; static constexpr int kNoRegister = -1; static constexpr int kNoSpillSlot = -1; DISALLOW_COPY_AND_ASSIGN(LiveInterval); }; class SsaLivenessAnalysis : public ValueObject { public: SsaLivenessAnalysis(const HGraph& graph, CodeGenerator* codegen) : graph_(graph), codegen_(codegen), linear_post_order_(graph.GetArena(), graph.GetBlocks().Size()), block_infos_(graph.GetArena(), graph.GetBlocks().Size()), instructions_from_ssa_index_(graph.GetArena(), 0), instructions_from_lifetime_position_(graph.GetArena(), 0), number_of_ssa_values_(0) { block_infos_.SetSize(graph.GetBlocks().Size()); } void Analyze(); BitVector* GetLiveInSet(const HBasicBlock& block) const { return &block_infos_.Get(block.GetBlockId())->live_in_; } BitVector* GetLiveOutSet(const HBasicBlock& block) const { return &block_infos_.Get(block.GetBlockId())->live_out_; } BitVector* GetKillSet(const HBasicBlock& block) const { return &block_infos_.Get(block.GetBlockId())->kill_; } const GrowableArray& GetLinearPostOrder() const { return linear_post_order_; } HInstruction* GetInstructionFromSsaIndex(size_t index) const { return instructions_from_ssa_index_.Get(index); } HInstruction* GetInstructionFromPosition(size_t index) const { return instructions_from_lifetime_position_.Get(index); } size_t GetMaxLifetimePosition() const { return instructions_from_lifetime_position_.Size() * 2 - 1; } size_t GetNumberOfSsaValues() const { return number_of_ssa_values_; } private: // Linearize the graph so that: // (1): a block is always after its dominator, // (2): blocks of loops are contiguous. // This creates a natural and efficient ordering when visualizing live ranges. void LinearizeGraph(); // Give an SSA number to each instruction that defines a value used by another instruction, // and setup the lifetime information of each instruction and block. void NumberInstructions(); // Compute live ranges of instructions, as well as live_in, live_out and kill sets. void ComputeLiveness(); // Compute the live ranges of instructions, as well as the initial live_in, live_out and // kill sets, that do not take into account backward branches. void ComputeLiveRanges(); // After computing the initial sets, this method does a fixed point // calculation over the live_in and live_out set to take into account // backwards branches. void ComputeLiveInAndLiveOutSets(); // Update the live_in set of the block and returns whether it has changed. bool UpdateLiveIn(const HBasicBlock& block); // Update the live_out set of the block and returns whether it has changed. bool UpdateLiveOut(const HBasicBlock& block); const HGraph& graph_; CodeGenerator* const codegen_; GrowableArray linear_post_order_; GrowableArray block_infos_; // Temporary array used when computing live_in, live_out, and kill sets. GrowableArray instructions_from_ssa_index_; // Temporary array used when inserting moves in the graph. GrowableArray instructions_from_lifetime_position_; size_t number_of_ssa_values_; DISALLOW_COPY_AND_ASSIGN(SsaLivenessAnalysis); }; class HLinearOrderIterator : public ValueObject { public: explicit HLinearOrderIterator(const SsaLivenessAnalysis& liveness) : post_order_(liveness.GetLinearPostOrder()), index_(liveness.GetLinearPostOrder().Size()) {} bool Done() const { return index_ == 0; } HBasicBlock* Current() const { return post_order_.Get(index_ -1); } void Advance() { --index_; DCHECK_GE(index_, 0U); } private: const GrowableArray& post_order_; size_t index_; DISALLOW_COPY_AND_ASSIGN(HLinearOrderIterator); }; class HLinearPostOrderIterator : public ValueObject { public: explicit HLinearPostOrderIterator(const SsaLivenessAnalysis& liveness) : post_order_(liveness.GetLinearPostOrder()), index_(0) {} bool Done() const { return index_ == post_order_.Size(); } HBasicBlock* Current() const { return post_order_.Get(index_); } void Advance() { ++index_; } private: const GrowableArray& post_order_; size_t index_; DISALLOW_COPY_AND_ASSIGN(HLinearPostOrderIterator); }; } // namespace art #endif // ART_COMPILER_OPTIMIZING_SSA_LIVENESS_ANALYSIS_H_