summaryrefslogtreecommitdiffstats
path: root/compiler/optimizing/ssa_liveness_analysis.h
diff options
context:
space:
mode:
authorNicolas Geoffray <ngeoffray@google.com>2014-05-22 12:50:17 +0100
committerNicolas Geoffray <ngeoffray@google.com>2014-05-26 11:31:38 +0100
commita7062e05e6048c7f817d784a5b94e3122e25b1ec (patch)
treea5d6b64ae6d5352f761fc2547bda863281adbe40 /compiler/optimizing/ssa_liveness_analysis.h
parent8b5b1e5593ffa77c393e4172b71a3d5a821d2ed8 (diff)
downloadart-a7062e05e6048c7f817d784a5b94e3122e25b1ec.zip
art-a7062e05e6048c7f817d784a5b94e3122e25b1ec.tar.gz
art-a7062e05e6048c7f817d784a5b94e3122e25b1ec.tar.bz2
Add a linear scan register allocator to the optimizing compiler.
This is a "by-the-book" implementation. It currently only deals with allocating registers, with no hint optimizations. The changes remaining to make it functional are: - Allocate spill slots. - Resolution and placements of Move instructions. - Connect it to the code generator. Change-Id: Ie0b2f6ba1b98da85425be721ce4afecd6b4012a4
Diffstat (limited to 'compiler/optimizing/ssa_liveness_analysis.h')
-rw-r--r--compiler/optimizing/ssa_liveness_analysis.h326
1 files changed, 293 insertions, 33 deletions
diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h
index 2d91436..4d56e1f 100644
--- a/compiler/optimizing/ssa_liveness_analysis.h
+++ b/compiler/optimizing/ssa_liveness_analysis.h
@@ -48,21 +48,68 @@ class BlockInfo : public ArenaObject {
* A live range contains the start and end of a range where an instruction
* is live.
*/
-class LiveRange : public ValueObject {
+class LiveRange : public ArenaObject {
public:
- LiveRange(size_t start, size_t end) : start_(start), end_(end) {
+ 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);
};
-static constexpr int kDefaultNumberOfRanges = 3;
+/**
+ * A use position represents a live interval use at a given position.
+ */
+class UsePosition : public ArenaObject {
+ public:
+ UsePosition(HInstruction* user, size_t position, UsePosition* next)
+ : user_(user), position_(position), next_(next) {
+ DCHECK(user->AsPhi() != nullptr || GetPosition() == user->GetLifetimePosition());
+ DCHECK(next_ == nullptr || next->GetPosition() >= GetPosition());
+ }
+
+ size_t GetPosition() const { return position_; }
+
+ UsePosition* GetNext() const { return next_; }
+
+ HInstruction* GetUser() const { return user_; }
+
+ void Dump(std::ostream& stream) {
+ stream << position_;
+ }
+
+ private:
+ HInstruction* const user_;
+ 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.
@@ -70,67 +117,276 @@ static constexpr int kDefaultNumberOfRanges = 3;
*/
class LiveInterval : public ArenaObject {
public:
- explicit LiveInterval(ArenaAllocator* allocator) : ranges_(allocator, kDefaultNumberOfRanges) {}
+ LiveInterval(ArenaAllocator* allocator, Primitive::Type type)
+ : allocator_(allocator),
+ first_range_(nullptr),
+ last_range_(nullptr),
+ first_use_(nullptr),
+ type_(type),
+ next_sibling_(nullptr),
+ register_(kNoRegister) {}
void AddUse(HInstruction* instruction) {
size_t position = instruction->GetLifetimePosition();
size_t start_block_position = instruction->GetBlock()->GetLifetimeStart();
size_t end_block_position = instruction->GetBlock()->GetLifetimeEnd();
- if (ranges_.IsEmpty()) {
+ if (first_range_ == nullptr) {
// First time we see a use of that interval.
- ranges_.Add(LiveRange(start_block_position, position));
- } else if (ranges_.Peek().GetStart() == start_block_position) {
+ 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, ranges_.Peek().GetEnd());
- } else if (ranges_.Peek().GetStart() == end_block_position + 1) {
- // Last use is in a following block.
- LiveRange existing = ranges_.Pop();
- ranges_.Add(LiveRange(start_block_position, existing.GetEnd()));
+ 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.
- ranges_.Add(LiveRange(start_block_position, position));
+ first_range_ = new (allocator_) LiveRange(start_block_position, position, first_range_);
}
+ first_use_ = new (allocator_) UsePosition(instruction, position, first_use_);
+ }
+
+ void AddPhiUse(HInstruction* instruction, HBasicBlock* block) {
+ DCHECK(instruction->AsPhi() != nullptr);
+ first_use_ = new (allocator_) UsePosition(instruction, block->GetLifetimeEnd(), first_use_);
}
void AddRange(size_t start, size_t end) {
- if (ranges_.IsEmpty()) {
- ranges_.Add(LiveRange(start, end));
- } else if (ranges_.Peek().GetStart() == end + 1) {
+ 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.
- LiveRange existing = ranges_.Pop();
- ranges_.Add(LiveRange(start, existing.GetEnd()));
+ first_range_->start_ = start;
} else {
// There is a hole in the interval. Create a new range.
- ranges_.Add(LiveRange(start, end));
+ first_range_ = new (allocator_) LiveRange(start, end, first_range_);
}
}
void AddLoopRange(size_t start, size_t end) {
- DCHECK(!ranges_.IsEmpty());
- while (!ranges_.IsEmpty() && ranges_.Peek().GetEnd() < end) {
- DCHECK_LE(start, ranges_.Peek().GetStart());
- ranges_.Pop();
+ DCHECK(first_range_ != nullptr);
+ while (first_range_ != nullptr && first_range_->GetEnd() < end) {
+ DCHECK_LE(start, first_range_->GetStart());
+ first_range_ = first_range_->GetNext();
}
- if (ranges_.IsEmpty()) {
+ if (first_range_ == nullptr) {
// Uses are only in the loop.
- ranges_.Add(LiveRange(start, end));
+ first_range_ = last_range_ = new (allocator_) LiveRange(start, end, nullptr);
} else {
// There are uses after the loop.
- LiveRange range = ranges_.Pop();
- ranges_.Add(LiveRange(start, range.GetEnd()));
+ first_range_->start_ = start;
}
}
void SetFrom(size_t from) {
- DCHECK(!ranges_.IsEmpty());
- LiveRange existing = ranges_.Pop();
- ranges_.Add(LiveRange(from, existing.GetEnd()));
+ DCHECK(first_range_ != nullptr);
+ first_range_->start_ = from;
+ }
+
+ 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) {
+ return last_range_->GetEnd() <= position;
+ }
+
+ bool Covers(size_t position) {
+ LiveRange* current = first_range_;
+ while (current != nullptr) {
+ if (position >= current->GetStart() && position < current->GetEnd()) {
+ return true;
+ }
+ current = current->GetNext();
+ }
+ return false;
}
- const GrowableArray<LiveRange>& GetRanges() const { return ranges_; }
+ /**
+ * Returns the first intersection of this interval with `other`.
+ */
+ size_t FirstIntersectionWith(LiveInterval* other) {
+ // We only call this method if there is a lifetime hole in this interval
+ // at the start of `other`.
+ DCHECK(!Covers(other->GetStart()));
+ DCHECK_LE(GetStart(), other->GetStart());
+ // Move to the range in this interval that starts after the other interval.
+ size_t other_start = other->GetStart();
+ LiveRange* my_range = first_range_;
+ while (my_range != nullptr) {
+ if (my_range->GetStart() >= other_start) {
+ break;
+ } else {
+ my_range = my_range->GetNext();
+ }
+ }
+ if (my_range == nullptr) {
+ return kNoLifetime;
+ }
+
+ // Advance both intervals and find the first matching range start in
+ // this interval.
+ 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 FirstRegisterUseAfter(size_t position) const {
+ UsePosition* use = first_use_;
+ while (use != nullptr) {
+ size_t use_position = use->GetPosition();
+ // TODO: Once we plug the Locations builder of the code generator
+ // to the register allocator, this method must be adjusted. We
+ // test if there is an environment, because these are currently the only
+ // instructions that could have more uses than the number of registers.
+ if (use_position >= position && !use->GetUser()->NeedsEnvironment()) {
+ return use_position;
+ }
+ use = use->GetNext();
+ }
+ return kNoLifetime;
+ }
+
+ size_t FirstRegisterUse() const {
+ return FirstRegisterUseAfter(GetStart());
+ }
+
+ Primitive::Type GetType() const {
+ return type_;
+ }
+
+ /**
+ * Split this interval at `position`. This interval is changed to:
+ * [start ... position).
+ *
+ * The new interval covers:
+ * [position ... end)
+ */
+ LiveInterval* SplitAt(size_t position) {
+ DCHECK(next_sibling_ == nullptr);
+ 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_);
+ next_sibling_ = new_interval;
+
+ 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:
- GrowableArray<LiveRange> ranges_;
+ 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 register allocated to this interval.
+ int register_;
+
+ static constexpr int kNoRegister = -1;
DISALLOW_COPY_AND_ASSIGN(LiveInterval);
};
@@ -164,10 +420,14 @@ class SsaLivenessAnalysis : public ValueObject {
return linear_post_order_;
}
- HInstruction* GetInstructionFromSsaIndex(size_t index) {
+ HInstruction* GetInstructionFromSsaIndex(size_t index) const {
return instructions_from_ssa_index_.Get(index);
}
+ size_t GetNumberOfSsaValues() const {
+ return number_of_ssa_values_;
+ }
+
private:
// Linearize the graph so that:
// (1): a block is always after its dominator,