Index: src/compiler/register-allocator.cc |
diff --git a/src/compiler/register-allocator.cc b/src/compiler/register-allocator.cc |
index 08b71f2e40488181657a064418bff797d9186b9c..d1ad6b248525a9e70b47b7b1655f6780520c7348 100644 |
--- a/src/compiler/register-allocator.cc |
+++ b/src/compiler/register-allocator.cc |
@@ -47,6 +47,17 @@ const InstructionBlock* GetContainingLoop(const InstructionSequence* sequence, |
} |
+unsigned GetContainingLoopCount(const InstructionSequence* sequence, |
+ const InstructionBlock* block) { |
+ unsigned ret = 0; |
+ for (auto cursor = GetContainingLoop(sequence, block); cursor != nullptr; |
+ cursor = GetContainingLoop(sequence, cursor)) { |
+ ++ret; |
+ } |
+ return ret; |
+} |
+ |
+ |
const InstructionBlock* GetInstructionBlock(const InstructionSequence* code, |
LifetimePosition pos) { |
return code->GetInstructionBlock(pos.ToInstructionIndex()); |
@@ -110,6 +121,22 @@ int GetByteWidth(MachineType machine_type) { |
} |
} |
+ |
+int GetHintedRegister(LiveRange* range) { |
+ int reg; |
+ const UsePosition* hint = range->FirstHintPosition(®); |
+ if (hint != nullptr) { |
+ if (hint->HasOperand()) { |
+ if (hint->operand()->IsDoubleStackSlot() || |
+ hint->operand()->IsStackSlot()) { |
+ return -1; |
+ } |
+ } |
+ return reg; |
+ } |
+ return -1; |
+} |
+ |
} // namespace |
@@ -143,6 +170,60 @@ bool UsePosition::HasHint() const { |
} |
+void UsePosition::dump_hint(std::ostream& os, |
+ const RegisterConfiguration* config) { |
+ if (hint_ == nullptr) { |
+ os << "H:nil"; |
+ return; |
+ } |
+ switch (HintTypeField::decode(flags_)) { |
+ case UsePositionHintType::kNone: |
+ os << "H:N"; |
+ return; |
+ case UsePositionHintType::kUnresolved: |
+ os << "H:U"; |
+ return; |
+ case UsePositionHintType::kUsePos: { |
+ auto use_pos = reinterpret_cast<UsePosition*>(hint_); |
+ int assigned_register = AssignedRegisterField::decode(use_pos->flags_); |
+ if (assigned_register == kUnassignedRegister) { |
+ os << "H:Use(R ?"; |
+ } else { |
+ os << "H:Use(R " << assigned_register; |
+ } |
+ if (use_pos->HasOperand()) { |
+ PrintableInstructionOperand pio{config, *use_pos->operand()}; |
+ os << " | " << pio; |
+ } |
+ os << ")"; |
+ return; |
+ } |
+ case UsePositionHintType::kOperand: { |
+ auto operand = reinterpret_cast<InstructionOperand*>(hint_); |
+ int assigned_register = AllocatedOperand::cast(operand)->index(); |
+ PrintableInstructionOperand pio{config, *operand}; |
+ os << "H:Op(R" << assigned_register << " | " << pio << ")"; |
+ return; |
+ } |
+ case UsePositionHintType::kPhi: { |
+ auto phi = reinterpret_cast<RegisterAllocationData::PhiMapValue*>(hint_); |
+ int assigned_register = phi->assigned_register(); |
+ PrintableInstructionOperand pio{config, phi->phi()->output()}; |
+ if (assigned_register == kUnassignedRegister) { |
+ os << "H:Phi(R x"; |
+ |
+ } else { |
+ os << "H:Phi(R" << assigned_register; |
+ } |
+ os << " | " << pio; |
+ os << ")"; |
+ return; |
+ } |
+ } |
+ UNREACHABLE(); |
+} |
+ |
+ |
bool UsePosition::HintRegister(int* register_index) const { |
if (hint_ == nullptr) return false; |
switch (HintTypeField::decode(flags_)) { |
@@ -264,11 +345,13 @@ LiveRange::LiveRange(int id, MachineType machine_type) |
spills_at_definition_(nullptr), |
current_interval_(nullptr), |
last_processed_use_(nullptr), |
- current_hint_position_(nullptr) { |
+ current_hint_position_(nullptr), |
+ group_(nullptr) { |
Jarin
2015/06/12 04:09:11
Initialize size_ and weight_ here, please. It is s
|
DCHECK(AllocatedOperand::IsSupportedMachineType(machine_type)); |
bits_ = SpillTypeField::encode(SpillType::kNoSpillType) | |
AssignedRegisterField::encode(kUnassignedRegister) | |
MachineTypeField::encode(machine_type); |
+ InvalidateWeightAndSize(); |
} |
@@ -424,9 +507,10 @@ UsePosition* LiveRange::NextRegisterPosition(LifetimePosition start) const { |
} |
-bool LiveRange::CanBeSpilled(LifetimePosition pos) const { |
- // We cannot spill a live range that has a use requiring a register |
- // at the current or the immediate next position. |
+bool LiveRange::CanBeSplit(LifetimePosition pos) const { |
+ // We cannot split a live range that has a use requiring a register |
+ // at the current or the immediate next position, because there would be |
+ // no gap where to insert a parallel move. |
auto use_pos = NextRegisterPosition(pos); |
if (use_pos == nullptr) return true; |
return use_pos->pos() > pos.NextStart().End(); |
@@ -572,6 +656,7 @@ void LiveRange::SplitAt(LifetimePosition position, LiveRange* result, |
result->parent_ = (parent_ == nullptr) ? this : parent_; |
result->next_ = next_; |
next_ = result; |
+ InvalidateWeightAndSize(); |
#ifdef DEBUG |
Verify(); |
@@ -763,6 +848,81 @@ LifetimePosition LiveRange::FirstIntersection(LiveRange* other) const { |
} |
+LifetimePosition LiveRange::GetFirstSplittablePosition() { |
+ for (auto c = first_pos(); c != nullptr; c = c->next()) { |
Jarin
2015/06/12 04:09:11
Use the real type here.
|
+ LifetimePosition pos; |
+ if (CanBeSpilled(c->pos())) { |
Jarin
2015/06/12 04:09:11
I suppose you wanted to use your new CanBeSplit me
|
+ pos = c->pos(); |
+ } else { |
+ auto after = c->pos().NextStart(); |
+ if (Covers(after) && CanBeSpilled(after)) { |
+ pos = after; |
+ } |
+ } |
+ if (pos.IsValid() && Start() < pos && pos < End()) { |
+ return pos; |
+ } |
+ } |
+ return LifetimePosition::Invalid(); |
+} |
+ |
+ |
+void LiveRange::RecalculateSize() { |
+ size_ = 0; |
+ for (auto cursor = first_interval(); cursor != nullptr; |
+ cursor = cursor->next()) { |
+ size_ += cursor->end().value() - cursor->start().value(); |
+ } |
+ |
+ DCHECK_NE(0U, static_cast<unsigned>(size_)); |
+} |
+ |
+ |
+const float LiveRange::kMaxWeight = FLT_MAX; |
+const float LiveRange::kUseInLoopWeightMultiplier = 10.0f; |
+const float LiveRange::kPreferredRegisterWeightMultiplier = 10.0f; |
+const float LiveRange::kInvalidWeight = -1.0f; |
+ |
+ |
+void LiveRange::RecalculateWeight(InstructionSequence* code) { |
+ LifetimePosition start = Start(); |
+ |
+ CHECK(!spilled()); |
+ |
+ if (IsFixed()) { |
+ weight_ = kMaxWeight; |
+ return; |
+ } |
+ |
+ if (first_interval()->next() == nullptr) { |
+ bool can_be_split_or_spilled = |
+ CanBeSpilled(start) || GetFirstSplittablePosition().IsValid(); |
+ if (!can_be_split_or_spilled) { |
+ weight_ = kMaxWeight; |
+ return; |
+ } |
+ } |
+ |
+ float use_count = 0.0; |
+ auto* pos = first_pos(); |
+ |
+ for (; pos != nullptr; pos = pos->next()) { |
+ auto loop_count = GetContainingLoopCount( |
Jarin
2015/06/12 04:09:10
Use the real type here.
|
+ code, code->GetInstructionBlock(pos->pos().ToInstructionIndex())); |
+ use_count += |
+ std::pow(kUseInLoopWeightMultiplier, static_cast<float>(loop_count)); |
+ } |
+ |
+ |
+ if (GetHintedRegister(this) >= 0 && |
+ GetHintedRegister(this) == assigned_register()) { |
+ use_count *= kPreferredRegisterWeightMultiplier; |
+ } |
+ |
+ weight_ = use_count / static_cast<float>(GetSize()); |
+} |
+ |
+ |
static bool AreUseIntervalsIntersecting(UseInterval* interval1, |
UseInterval* interval2) { |
while (interval1 != nullptr && interval2 != nullptr) { |
@@ -782,35 +942,75 @@ static bool AreUseIntervalsIntersecting(UseInterval* interval1, |
} |
+void PrintIntervals(std::ostream& os, UseInterval* interval) { |
+ while (interval != nullptr) { |
+ os << '[' << interval->start() << ", " << interval->end() << ')' |
+ << std::endl; |
+ interval = interval->next(); |
+ } |
+} |
+ |
std::ostream& operator<<(std::ostream& os, |
const PrintableLiveRange& printable_range) { |
+ PrintableInstructionOperand pio; |
+ pio.register_configuration_ = printable_range.register_configuration_; |
+ |
const LiveRange* range = printable_range.range_; |
os << "Range: " << range->id() << " "; |
if (range->is_phi()) os << "phi "; |
if (range->is_non_loop_phi()) os << "nlphi "; |
+ if (range->HasRegisterAssigned()) |
+ os << "R: " << range->assigned_register() << " "; |
+ if (range->HasSpillOperand()) { |
+ pio.op_ = *(range->GetSpillOperand()); |
+ os << "SOp: " << pio << " "; |
+ } |
+ if (range->HasSpillRange()) { |
+ os << "SR: "; |
+ if (range->GetSpillRange()->IsSlotAssigned()) { |
+ os << range->GetSpillRange()->assigned_slot() << " "; |
+ } else { |
+ os << "x "; |
+ } |
+ } |
os << "{" << std::endl; |
auto interval = range->first_interval(); |
auto use_pos = range->first_pos(); |
- PrintableInstructionOperand pio; |
- pio.register_configuration_ = printable_range.register_configuration_; |
while (use_pos != nullptr) { |
- pio.op_ = *use_pos->operand(); |
- os << pio << use_pos->pos() << " "; |
+ os << "["; |
+ if (use_pos->HasOperand()) { |
+ pio.op_ = *use_pos->operand(); |
+ os << pio << use_pos->pos() << " "; |
+ } else { |
+ os << "<no_op> "; |
+ } |
+ use_pos->dump_hint(os, printable_range.register_configuration_); |
+ os << "] "; |
use_pos = use_pos->next(); |
} |
os << std::endl; |
- while (interval != nullptr) { |
- os << '[' << interval->start() << ", " << interval->end() << ')' |
- << std::endl; |
- interval = interval->next(); |
- } |
+ PrintIntervals(os, interval); |
os << "}"; |
return os; |
} |
+std::ostream& operator<<(std::ostream& os, SpillRange* range) { |
+ if (range->IsSlotAssigned()) { |
+ os << "Slot: " << range->assigned_slot(); |
+ } else { |
+ os << "Unassigned Slot."; |
+ } |
+ os << std::endl; |
+ os << "{"; |
+ PrintIntervals(os, range->interval()); |
+ os << "}" << std::endl; |
+ return os; |
+} |
+ |
+ |
SpillRange::SpillRange(LiveRange* parent, Zone* zone) |
: live_ranges_(zone), assigned_slot_(kUnassignedSlot) { |
DCHECK(!parent->IsChild()); |
@@ -1013,8 +1213,8 @@ LiveRange* RegisterAllocationData::NewChildRangeFor(LiveRange* range) { |
RegisterAllocationData::PhiMapValue* RegisterAllocationData::InitializePhiMap( |
const InstructionBlock* block, PhiInstruction* phi) { |
- auto map_value = new (allocation_zone()) |
- RegisterAllocationData::PhiMapValue(phi, block, allocation_zone()); |
+ auto map_value = |
+ new (allocation_zone()) PhiMapValue(phi, block, allocation_zone()); |
auto res = |
phi_map_.insert(std::make_pair(phi->virtual_register(), map_value)); |
DCHECK(res.second); |
@@ -1844,6 +2044,8 @@ LifetimePosition RegisterAllocator::FindOptimalSpillingPos( |
void RegisterAllocator::Spill(LiveRange* range) { |
DCHECK(!range->spilled()); |
+ stats_.spills++; |
+ |
TRACE("Spilling live range %d\n", range->id()); |
auto first = range->TopLevel(); |
if (first->HasNoSpillType()) { |
@@ -1871,10 +2073,12 @@ LinearScanAllocator::LinearScanAllocator(RegisterAllocationData* data, |
void LinearScanAllocator::AllocateRegisters() { |
+ stats_.reset(); |
DCHECK(unhandled_live_ranges().empty()); |
DCHECK(active_live_ranges().empty()); |
DCHECK(inactive_live_ranges().empty()); |
- |
+ TRACE("Begin allocating function %s with the Linear Allocator\n", |
+ data()->debug_name()); |
for (auto range : data()->live_ranges()) { |
if (range == nullptr) continue; |
if (range->kind() == mode()) { |
@@ -1951,15 +2155,22 @@ void LinearScanAllocator::AllocateRegisters() { |
DCHECK(!current->HasRegisterAssigned() && !current->spilled()); |
bool result = TryAllocateFreeReg(current); |
- if (!result) AllocateBlockedReg(current); |
+ if (!result) { |
+ TRACE("Failed to allocate a free reg for %d\n", current->id()); |
+ AllocateBlockedReg(current); |
+ } |
if (current->HasRegisterAssigned()) { |
AddToActive(current); |
+ } else { |
+ TRACE("Failed to assign register to %d\n", current->id()); |
} |
} |
+ TRACE("End allocating function %s with the Linear Allocator\n", |
+ data()->debug_name()); |
} |
-const char* LinearScanAllocator::RegisterName(int allocation_index) const { |
+const char* RegisterAllocator::RegisterName(int allocation_index) const { |
if (mode() == GENERAL_REGISTERS) { |
return data()->config()->general_register_name(allocation_index); |
} else { |
@@ -2127,6 +2338,10 @@ bool LinearScanAllocator::TryAllocateFreeReg(LiveRange* current) { |
if (pos < current->End()) { |
// Register reg is available at the range start but becomes blocked before |
// the range end. Split current at position where it becomes blocked. |
+ TRACE( |
+ "Register %d is available at the range start but becomes blocked " |
+ "before range %d end\n", |
+ reg, current->id()); |
auto tail = SplitRangeAt(current, pos); |
AddToUnhandledSorted(tail); |
} |
@@ -2199,6 +2414,8 @@ void LinearScanAllocator::AllocateBlockedReg(LiveRange* current) { |
if (pos < register_use->pos()) { |
// All registers are blocked before the first use that requires a register. |
// Spill starting part of live range up to that use. |
+ TRACE("All registers are blocked before the first use for %d\n", |
+ current->id()); |
SpillBetween(current, current->Start(), register_use->pos()); |
return; |
} |
@@ -2206,6 +2423,8 @@ void LinearScanAllocator::AllocateBlockedReg(LiveRange* current) { |
if (block_pos[reg] < current->End()) { |
// Register becomes blocked before the current range end. Split before that |
// position. |
+ TRACE("Register %d becomes blocked before end of range %d\n", reg, |
+ current->id()); |
LiveRange* tail = |
SplitBetween(current, current->Start(), block_pos[reg].Start()); |
AddToUnhandledSorted(tail); |
@@ -2234,6 +2453,8 @@ void LinearScanAllocator::SplitAndSpillIntersecting(LiveRange* current) { |
auto next_pos = range->NextRegisterPosition(current->Start()); |
auto spill_pos = FindOptimalSpillingPos(range, split_pos); |
if (next_pos == nullptr) { |
+ TRACE("SplitAndSpillIntersecting (1). Range %d, for %d\n", range->id(), |
+ current->id()); |
SpillAfter(range, spill_pos); |
} else { |
// When spilling between spill_pos and next_pos ensure that the range |
@@ -2244,6 +2465,8 @@ void LinearScanAllocator::SplitAndSpillIntersecting(LiveRange* current) { |
// live-ranges: ranges are allocated in order of their start positions, |
// ranges are retired from active/inactive when the start of the |
// current live-range is larger than their end. |
+ TRACE("SplitAndSpillIntersecting (2). Range %d, for %d\n", range->id(), |
+ current->id()); |
SpillBetweenUntil(range, spill_pos, current->Start(), next_pos->pos()); |
} |
ActiveToHandled(range); |
@@ -2259,9 +2482,13 @@ void LinearScanAllocator::SplitAndSpillIntersecting(LiveRange* current) { |
if (next_intersection.IsValid()) { |
UsePosition* next_pos = range->NextRegisterPosition(current->Start()); |
if (next_pos == nullptr) { |
+ TRACE("SplitAndSpillIntersecting (3). Range %d, for %d\n", |
+ range->id(), current->id()); |
SpillAfter(range, split_pos); |
} else { |
next_intersection = Min(next_intersection, next_pos->pos()); |
+ TRACE("SplitAndSpillIntersecting (4). Range %d, for %d\n", |
+ range->id(), current->id()); |
SpillBetween(range, split_pos, next_intersection); |
} |
InactiveToHandled(range); |
@@ -2398,77 +2625,196 @@ void LinearScanAllocator::SpillBetweenUntil(LiveRange* range, |
} |
+conflict_iterator& conflict_iterator::operator++() { |
+ DCHECK(storage_ != nullptr); |
+ |
+ if (pos_ == storage_->end()) { |
Jarin
2015/06/12 04:09:11
The iterator should always be in canonical form, n
|
+ // We're at the end - ensure we are in a canonical "end of iterator" form. |
+ Invalidate(); |
+ return *this; |
+ } |
+ DCHECK(QueryIntersectsAllocatedInterval()); |
+ ++pos_; |
+ if (pos_ == storage_->end()) { |
+ // Advancing got us at the end of storage, so the iterator becomes "end". |
+ Invalidate(); |
+ return *this; |
+ } |
+ if (!QueryIntersectsAllocatedInterval()) { |
+ query_ = query_->next(); |
+ if (query_ == nullptr) { |
+ Invalidate(); |
+ return *this; |
+ } |
+ // We may discover it is better to linearly skip over non-conflicting |
+ // use intervals rather than re-do the seeking in InitializeForNewQuery. |
+ // No profiling data yet on this one. |
+ InitializeForNewQuery(); |
+ } |
+ return *this; |
+} |
+ |
+ |
+bool operator==(const conflict_iterator& first, |
+ const conflict_iterator& second) { |
+ DCHECK_EQ(first.storage_, second.storage_); |
+ |
+ return first.query_ == second.query_ && first.pos_ == second.pos_; |
+} |
+ |
+ |
+bool operator!=(const conflict_iterator& first, |
+ const conflict_iterator& second) { |
+ return !(first == second); |
+} |
+ |
+ |
+void conflict_iterator::InitializeForNewQuery() { |
+ DCHECK(storage_ != nullptr); |
+ DCHECK(query_ != nullptr); |
+ // If empty or the last element starts before the query, we have no conflicts. |
+ if (storage_->empty() || storage_->rbegin()->end <= query_->start()) { |
+ Invalidate(); |
+ return; |
+ } |
+ |
+ for (; query_ != nullptr; query_ = query_->next()) { |
+ LifetimePosition q_start = query_->start(); |
+ LifetimePosition q_end = query_->end(); |
+ if (storage_->begin()->start >= q_end) { |
+ // Skip over queried use intervals that end before the first stored |
+ // element. |
+ continue; |
+ } |
+ |
+ pos_ = storage_->upper_bound(AsAllocatedInterval(q_start)); |
+ // pos_ is either at the end (no start strictly greater than q_start) or |
+ // at some position with the aforementioned property. In either case, the |
+ // allocated interval before this one may intersect our query: |
+ // either because, although it starts before this query's start, it ends |
+ // after; or because it starts exactly at the query start. So unless we're |
+ // right at the beginning of the storage - meaning the first allocated |
+ // interval is also starting after this query's start - see what's behind. |
+ if (pos_ != storage_->begin()) { |
+ --pos_; |
+ if (QueryIntersectsAllocatedInterval()) break; |
+ // The interval behind wasn't intersecting, so move back. |
+ ++pos_; |
+ } |
+ if (pos_ != storage_->end() && QueryIntersectsAllocatedInterval()) break; |
+ } |
+ |
+ // If we got here because we couldn't find an intersection and got to the last |
+ // use interval query, we have no conflicts. |
+ if (query_ == nullptr) { |
+ Invalidate(); |
+ return; |
+ } |
+ |
+ SkipMatchedQueries(); |
+} |
+ |
+ |
+// Advance query_ at the last use interval that still intersects/overlaps with |
+// the current conflict, so that operator++ can pick up from there. |
+void conflict_iterator::SkipMatchedQueries() { |
+ DCHECK(query_ != nullptr); |
+ DCHECK(QueryIntersectsAllocatedInterval()); |
+ |
+ for (; query_->next() != nullptr; query_ = query_->next()) { |
+ if (!NextQueryIntersectsAllocatedInterval()) { |
+ break; |
+ } |
+ } |
+ DCHECK(query_ != nullptr); |
+ DCHECK(QueryIntersectsAllocatedInterval()); |
+ DCHECK(query_->next() == nullptr || !NextQueryIntersectsAllocatedInterval()); |
+} |
+ |
+ |
+// Collection of live ranges allocated to the same register. |
+// It supports efficiently finding all conflicts for a given, non-allocated |
+// range. See AllocatedInterval. |
+// Allocated live ranges do not intersect. At most, individual use intervals |
+// touch. We store, for a live range, an AllocatedInterval corresponding to each |
+// of that range's UseIntervals. We keep the list of AllocatedIntervals sorted |
+// by starts. Then, given the non-intersecting property, we know that |
+// consecutive AllocatedIntervals have the property that the "smaller"'s end is |
+// less or equal to the "larger"'s start. |
+// This allows for quick (logarithmic complexity) identification of the first |
+// AllocatedInterval to conflict with a given LiveRange, and then for efficient |
+// traversal of conflicts. See also conflict_iterator. |
class CoalescedLiveRanges : public ZoneObject { |
public: |
explicit CoalescedLiveRanges(Zone* zone) : storage_(zone) {} |
- LiveRange* Find(UseInterval* query) { |
- ZoneSplayTree<Config>::Locator locator; |
+ void clear() { storage_.clear(); } |
- if (storage_.Find(GetKey(query), &locator)) { |
- return locator.value(); |
- } |
- return nullptr; |
+ |
+ conflict_iterator conflicts_begin(LiveRange* query) { |
+ return GetFirstConflict(query->first_interval()); |
} |
- // TODO(mtrofin): Change to void returning if we do not care if the interval |
- // was previously added. |
- bool Insert(LiveRange* range) { |
- auto* interval = range->first_interval(); |
- while (interval != nullptr) { |
- if (!Insert(interval, range)) return false; |
- interval = interval->next(); |
+ conflict_iterator conflicts_end() { |
+ return conflict_iterator::EndIteratorForStorage(&storage_); |
+ } |
+ |
+ // We assume it was determined that this range does not conflict with |
+ // contained ranges. |
+ void insert(LiveRange* range) { |
+ for (auto interval = range->first_interval(); interval != nullptr; |
+ interval = interval->next()) { |
+ storage_.insert({interval->start(), interval->end(), range}); |
} |
- return true; |
} |
- bool Remove(LiveRange* range) { |
- bool ret = false; |
- auto* segment = range->first_interval(); |
- while (segment != nullptr) { |
- ret |= Remove(segment); |
- segment = segment->next(); |
+ void remove(LiveRange* range) { |
+ for (auto interval = range->first_interval(); interval != nullptr; |
+ interval = interval->next()) { |
+ storage_.erase({interval->start(), interval->end(), range}); |
} |
- return ret; |
} |
- bool IsEmpty() { return storage_.is_empty(); } |
+ bool empty() const { return storage_.empty(); } |
- private: |
- struct Config { |
- typedef std::pair<int, int> Key; |
- typedef LiveRange* Value; |
- static const Key kNoKey; |
- static Value NoValue() { return nullptr; } |
- static int Compare(const Key& a, const Key& b) { |
- if (a.second <= b.first) return -1; |
- if (a.first >= b.second) return 1; |
- return 0; |
+ bool IsValid() { |
+ LifetimePosition last_end = LifetimePosition::GapFromInstructionIndex(0); |
+ for (auto i : storage_) { |
+ if (i.start < last_end) { |
+ return false; |
+ } |
+ last_end = i.end; |
} |
- }; |
- |
- Config::Key GetKey(UseInterval* interval) { |
- if (interval == nullptr) return std::make_pair(0, 0); |
- return std::make_pair(interval->start().value(), interval->end().value()); |
+ return true; |
} |
- // TODO(mtrofin): Change to void returning if we do not care if the interval |
- // was previously added. |
- bool Insert(UseInterval* interval, LiveRange* range) { |
- ZoneSplayTree<Config>::Locator locator; |
- bool ret = storage_.Insert(GetKey(interval), &locator); |
- if (ret) locator.set_value(range); |
- return ret; |
+ private: |
+ conflict_iterator GetFirstConflict(UseInterval* query) { |
+ return conflict_iterator(query, &storage_); |
} |
- bool Remove(UseInterval* key) { return storage_.Remove(GetKey(key)); } |
- |
- ZoneSplayTree<Config> storage_; |
+ ZoneSet<AllocatedInterval, AllocatedInterval::Comparer> storage_; |
Jarin
2015/06/12 04:09:11
storage_ -> intervals_, perhaps?
|
DISALLOW_COPY_AND_ASSIGN(CoalescedLiveRanges); |
}; |
-const std::pair<int, int> CoalescedLiveRanges::Config::kNoKey = {0, 0}; |
+std::ostream& operator<<(std::ostream& os, const AllocatorStats& stats) { |
+ os << "losses after eviction: " << stats.losses_after_eviction << std::endl; |
+ os << "losses, no eviction: " << stats.losses_no_eviction << std::endl; |
+ os << "spills: " << stats.spills << std::endl; |
+ os << "wins: " << stats.wins << std::endl; |
+ os << "split attempts: " << stats.good_split_attempts << std::endl; |
+ os << "split successes: " << stats.good_split_successes << std::endl; |
+ os << "splits above: " << stats.good_split_above << std::endl; |
+ os << "splits below: " << stats.good_split_below << std::endl; |
+ os << "smart splits above: " << stats.good_split_smart_above << std::endl; |
+ os << "smart splits below: " << stats.good_split_smart_below << std::endl; |
+ os << "groups allocated: " << stats.groups_allocated << std::endl; |
+ os << "groups attempted: " << stats.groups_attempted << std::endl; |
+ os << "groups succeeded: " << stats.groups_succeeded << std::endl; |
+ return os; |
+} |
+ |
GreedyAllocator::GreedyAllocator(RegisterAllocationData* data, |
RegisterKind kind, Zone* local_zone) |
@@ -2478,78 +2824,27 @@ GreedyAllocator::GreedyAllocator(RegisterAllocationData* data, |
queue_(local_zone) {} |
-unsigned GreedyAllocator::GetLiveRangeSize(LiveRange* range) { |
- auto interval = range->first_interval(); |
- if (interval == nullptr) return 0; |
- |
- unsigned size = 0; |
- while (interval != nullptr) { |
- size += (interval->end().value() - interval->start().value()); |
- interval = interval->next(); |
- } |
- |
- return size; |
-} |
- |
void GreedyAllocator::AssignRangeToRegister(int reg_id, LiveRange* range) { |
- allocations_[reg_id]->Insert(range); |
+ allocations_[reg_id]->insert(range); |
if (range->HasRegisterAssigned()) { |
DCHECK_EQ(reg_id, range->assigned_register()); |
return; |
} |
+ TRACE("Assigning %s to range %d\n", RegisterName(reg_id), range->id()); |
range->set_assigned_register(reg_id); |
range->SetUseHints(reg_id); |
if (range->is_phi()) { |
data()->GetPhiMapValueFor(range->id())->set_assigned_register(reg_id); |
} |
-} |
- |
- |
-float GreedyAllocator::CalculateSpillWeight(LiveRange* range) { |
- InstructionOperand* first_hint = nullptr; |
- if (range->FirstHintPosition() != nullptr) { |
- first_hint = range->FirstHintPosition()->operand(); |
- } |
- |
- if (range->IsFixed()) return std::numeric_limits<float>::max(); |
- bool spill; |
- if (!FindProgressingSplitPosition(range, &spill).IsValid()) |
- return std::numeric_limits<float>::max(); |
- |
- float multiplier = 1.0; |
- if (first_hint != nullptr && first_hint->IsRegister()) { |
- multiplier = 3.0; |
- } |
- |
- unsigned use_count = 0; |
- auto* pos = range->first_pos(); |
- while (pos != nullptr) { |
- use_count++; |
- pos = pos->next(); |
- } |
- |
- unsigned range_size = GetLiveRangeSize(range); |
- DCHECK_NE(0U, range_size); |
- |
- return multiplier * static_cast<float>(use_count) / |
- static_cast<float>(range_size); |
-} |
- |
- |
-float GreedyAllocator::CalculateMaxSpillWeight( |
- const ZoneSet<LiveRange*>& ranges) { |
- float max = 0.0; |
- for (auto* r : ranges) { |
- max = std::max(max, CalculateSpillWeight(r)); |
- } |
- return max; |
+ range->RecalculateWeight(code()); |
+ DCHECK(allocations_[reg_id]->IsValid()); |
} |
void GreedyAllocator::Evict(LiveRange* range) { |
- bool removed = allocations_[range->assigned_register()]->Remove(range); |
- CHECK(removed); |
+ TRACE("Evicting range %d from register %s\n", range->id(), |
+ RegisterName(range->assigned_register())); |
range->UnsetUseHints(); |
range->UnsetAssignedRegister(); |
if (range->is_phi()) { |
@@ -2558,66 +2853,6 @@ void GreedyAllocator::Evict(LiveRange* range) { |
} |
-bool GreedyAllocator::TryAllocatePhysicalRegister( |
- unsigned reg_id, LiveRange* range, ZoneSet<LiveRange*>* conflicting) { |
- auto* segment = range->first_interval(); |
- |
- auto* alloc_info = allocations_[reg_id]; |
- while (segment != nullptr) { |
- if (auto* existing = alloc_info->Find(segment)) { |
- DCHECK(existing->HasRegisterAssigned()); |
- conflicting->insert(existing); |
- } |
- segment = segment->next(); |
- } |
- if (!conflicting->empty()) return false; |
- // No conflicts means we can safely allocate this register to this range. |
- AssignRangeToRegister(reg_id, range); |
- return true; |
-} |
- |
- |
-int GreedyAllocator::GetHintedRegister(LiveRange* range) { |
- int reg; |
- if (range->FirstHintPosition(®) != nullptr) { |
- return reg; |
- } |
- return -1; |
-} |
- |
- |
-bool GreedyAllocator::TryAllocate(LiveRange* current, |
- ZoneSet<LiveRange*>* conflicting) { |
- if (current->IsFixed()) { |
- return TryAllocatePhysicalRegister(current->assigned_register(), current, |
- conflicting); |
- } |
- |
- int hinted_reg_id = GetHintedRegister(current); |
- if (hinted_reg_id >= 0) { |
- if (TryAllocatePhysicalRegister(hinted_reg_id, current, conflicting)) { |
- return true; |
- } |
- } |
- |
- ZoneSet<LiveRange*> local_conflicts(local_zone()); |
- for (unsigned candidate_reg = 0; candidate_reg < allocations_.size(); |
- candidate_reg++) { |
- if (hinted_reg_id >= 0 && |
- candidate_reg == static_cast<size_t>(hinted_reg_id)) |
- continue; |
- local_conflicts.clear(); |
- if (TryAllocatePhysicalRegister(candidate_reg, current, &local_conflicts)) { |
- conflicting->clear(); |
- return true; |
- } else { |
- conflicting->insert(local_conflicts.begin(), local_conflicts.end()); |
- } |
- } |
- return false; |
-} |
- |
- |
LiveRange* GreedyAllocator::SpillBetweenUntil(LiveRange* range, |
LifetimePosition start, |
LifetimePosition until, |
@@ -2649,14 +2884,32 @@ LiveRange* GreedyAllocator::SpillBetweenUntil(LiveRange* range, |
void GreedyAllocator::Enqueue(LiveRange* range) { |
- if (range == nullptr || range->IsEmpty()) return; |
- unsigned size = GetLiveRangeSize(range); |
- TRACE("Enqueuing range %d\n", range->id()); |
- queue_.push(std::make_pair(size, range)); |
+ unsigned size = range->GetSize(); |
+ range->RecalculateWeight(code()); |
+ TRACE("Enqueuing range %d size %d\n", range->id(), size); |
+ DCHECK(size > 0); |
+ queue_.push({size, PQueueElem(range)}); |
+} |
+ |
+ |
+// We treat groups of ranges as one, so that we try first to allocate |
+// them all to the same register. If that fails, they get processed as |
+// individual ranges. |
+void GreedyAllocator::Enqueue(LiveRangeGroup* group) { |
+ unsigned size = 0; |
+ for (auto r : group->ranges()) { |
+ size += r->GetSize(); |
+ r->RecalculateWeight(code()); |
+ } |
+ |
+ DCHECK(size > 0); |
+ TRACE("Enqueuing group of size %d\n", size); |
+ queue_.push({size, PQueueElem(group)}); |
} |
-bool GreedyAllocator::HandleSpillOperands(LiveRange* range) { |
+bool GreedyAllocator::HandleSpillOperands(LiveRange* range, |
+ LiveRange** remaining) { |
auto position = range->Start(); |
TRACE("Processing interval %d start=%d\n", range->id(), position.value()); |
@@ -2675,24 +2928,314 @@ bool GreedyAllocator::HandleSpillOperands(LiveRange* range) { |
} else if (pos->pos() > range->Start().NextStart()) { |
// Do not spill live range eagerly if use position that can benefit from |
// the register is too close to the start of live range. |
- auto* reminder = SpillBetweenUntil(range, position, position, pos->pos()); |
- Enqueue(reminder); |
+ *remaining = SpillBetweenUntil(range, position, position, pos->pos()); |
return true; |
} |
} |
- return TryReuseSpillForPhi(range); |
+ return false; |
} |
-void GreedyAllocator::AllocateRegisters() { |
+// TODO(mtrofin): consider using CoalescedLiveRanges for grouping. |
+bool LiveRangeGroup::CanAdd(LiveRange* r) { |
+ for (auto member : ranges()) { |
+ if (member->FirstIntersection(r).IsValid()) { |
+ return false; |
+ } |
+ } |
+ return true; |
+} |
+ |
+ |
+void LiveRangeGroup::TryMerge(LiveRangeGroup* other) { |
+ DCHECK(other != nullptr); |
+ |
+ bool can_merge = true; |
+ for (auto r : ranges()) { |
+ if (!other->CanAdd(r)) { |
+ can_merge = false; |
+ break; |
+ } |
+ } |
+ if (can_merge) { |
+ for (auto r : ranges()) { |
+ other->ranges().insert(r); |
+ r->SetGroup(other); |
+ } |
+ } |
+} |
+ |
+ |
+void TryGroup(LiveRange* r1, LiveRange* r2, Zone* local_zone) { |
+ if (r1->group() == nullptr) { |
+ if (r2->group() == nullptr) { |
+ if (!r1->FirstIntersection(r2).IsValid()) { |
+ LiveRangeGroup* grp = new (local_zone) LiveRangeGroup(local_zone); |
+ grp->ranges().insert(r1); |
+ grp->ranges().insert(r2); |
+ r1->SetGroup(grp); |
+ r2->SetGroup(grp); |
+ } |
+ return; |
+ } |
+ return TryGroup(r2, r1, local_zone); |
+ } |
+ DCHECK(r1->group() != nullptr); |
+ if (r2->group() == nullptr) { |
+ if (r1->group()->CanAdd(r2)) { |
+ r1->group()->ranges().insert(r2); |
+ r2->SetGroup(r1->group()); |
+ } |
+ return; |
+ } |
+ r1->group()->TryMerge(r2->group()); |
+} |
+ |
+ |
+void GreedyAllocator::GroupAndEnqueue() { |
+ // Group phi inputs to the output. Ideally, they get all allocated to the same |
+ // register, avoiding moves. |
+ for (auto r : data()->live_ranges()) { |
+ if (r == nullptr || r->IsEmpty() || r->kind() != mode()) continue; |
+ if (r->is_phi()) { |
+ auto phi_map = data()->GetPhiMapValueFor(r->id()); |
+ auto phi = phi_map->phi(); |
+ auto inputs = phi->operands(); |
+ for (auto i : inputs) { |
+ LiveRange* in_range = data()->live_ranges()[i]; |
+ TryGroup(r, in_range, local_zone()); |
+ } |
+ } |
+ } |
+ |
+ ZoneSet<LiveRangeGroup*> seen_groups(local_zone()); |
for (auto range : data()->live_ranges()) { |
- if (range == nullptr) continue; |
- if (range->kind() == mode()) { |
- DCHECK(!range->HasRegisterAssigned() && !range->spilled()); |
- TRACE("Enqueueing live range %d to priority queue \n", range->id()); |
+ if (range == nullptr || range->IsEmpty() || range->spilled() || |
+ range->kind() != mode()) |
+ continue; |
+ |
+ if (range->group() != nullptr) { |
Jarin
2015/06/12 04:09:11
Package the range->group() == nullptr check into t
|
+ auto grp = range->group(); |
Jarin
2015/06/12 04:09:11
grp -> group
|
+ if (seen_groups.count(grp) > 0) continue; |
+ seen_groups.insert(grp); |
+ Enqueue(grp); |
+ if (FLAG_trace_alloc) { |
+ OFStream os(stdout); |
+ os << "group: " << std::endl; |
+ PrintableLiveRange plr; |
+ plr.register_configuration_ = data()->config(); |
+ for (auto r : grp->ranges()) { |
+ plr.range_ = r; |
+ os << plr; |
+ } |
+ os << std::endl; |
+ } |
+ } else { |
Enqueue(range); |
} |
} |
+} |
+ |
+ |
+void GreedyAllocator::EvictAll(int reg, |
+ const conflict_iterator& first_conflict) { |
+ for (conflict_iterator c = first_conflict; !c.IsEnd();) { |
+ auto range = *c; |
+ while (!c.IsEnd() && *c == range) ++c; |
Jarin
2015/06/12 04:09:11
When can it happen that the iterator gives the sam
|
+ |
+ DCHECK(range->HasRegisterAssigned()); |
+ CHECK(!range->IsFixed()); |
+ allocations_[reg]->remove(range); |
+ Evict(range); |
+ Enqueue(range); |
+ } |
+} |
+ |
+ |
+void GreedyAllocator::AllocateRange(LiveRange* current) { |
+ TRACE("Processing interval %d of size %d\n", current->id(), |
+ current->GetSize()); |
+ |
+ LiveRange* remaining = nullptr; |
+ if (HandleSpillOperands(current, &remaining)) { |
+ if (remaining != nullptr) Enqueue(remaining); |
+ return; |
+ } |
+ |
+ // TODO(mtrofin): Does the linear algo's hinting mechanism even matter, |
+ // now that we have groups? |
Jarin
2015/06/12 04:09:11
Does it make any performance difference if you kee
|
+ int hint_reg = GetHintedRegister(current); |
+ float my_weight = current->GetWeight(); |
+ if (hint_reg >= 0) { |
+ auto first_conflict = allocations_[hint_reg]->conflicts_begin(current); |
+ if (first_conflict.IsEnd()) { |
+ AssignRangeToRegister(hint_reg, current); |
+ return; |
+ } |
+ float max_weight = CalculateMaxSpillWeight( |
+ first_conflict, allocations_[hint_reg]->conflicts_end()); |
+ if (max_weight < my_weight) { |
+ EvictAll(hint_reg, first_conflict); |
+ AssignRangeToRegister(hint_reg, current); |
+ return; |
+ } |
+ } |
+ |
+ int free_reg = -1; |
+ conflict_iterator all_conflicts[RegisterConfiguration::kMaxDoubleRegisters]; |
+ for (int i = 0; i < num_registers(); i++) { |
+ auto conflict = allocations_[i]->conflicts_begin(current); |
+ if (conflict.IsEnd()) { |
+ free_reg = i; |
+ break; |
+ } |
+ all_conflicts[i] = conflict; |
+ } |
+ |
+ if (free_reg >= 0) { |
+ AssignRangeToRegister(free_reg, current); |
+ return; |
+ } |
+ free_reg = FindRegisterToEvictForRange(all_conflicts, my_weight); |
Jarin
2015/06/12 04:09:11
What happens if you do not evict here? (And spill
|
+ if (free_reg >= 0) { |
+ EvictAll(free_reg, all_conflicts[free_reg]); |
+ AssignRangeToRegister(free_reg, current); |
+ return; |
+ } |
+ HandleBlockedRange(current); |
+} |
+ |
+template <typename TIter> |
+float GreedyAllocator::CalculateMaxSpillWeight(const TIter& begin, |
+ const TIter& end) { |
+ float ret = 0.0; |
+ for (auto s = begin; s != end; ++s) { |
+ ret = Max(ret, (*s)->GetWeight()); |
+ if (ret == LiveRange::kMaxWeight) return ret; |
+ } |
+ return ret; |
+} |
+ |
+ |
+bool GreedyAllocator::TryAllocateGroup(LiveRangeGroup* grp) { |
Jarin
2015/06/12 04:09:11
Do you have any performance numbers on group alloc
|
+ for (int i = 0; i < num_registers(); i++) { |
+ if (TryAllocateGroupAtRegister(i, grp)) { |
+ return true; |
+ } |
+ } |
+ return false; |
+} |
+ |
+ |
+bool GreedyAllocator::TryAllocateGroupAtRegister(unsigned reg, |
+ LiveRangeGroup* grp) { |
+ auto ranges = grp->ranges(); |
+ for (auto r : ranges) { |
+ auto first_conflict = allocations_[reg]->conflicts_begin(r); |
Jarin
2015/06/12 04:09:10
Could we have a method on CoalescedLiveRanges that
|
+ if (!first_conflict.IsEnd()) { |
+ return false; |
+ } |
+ } |
+ for (auto r : ranges) { |
+ AssignRangeToRegister(reg, r); |
+ } |
+ return true; |
+} |
+ |
+ |
+int GreedyAllocator::FindRegisterToEvictForRange( |
+ conflict_iterator all_conflicts[RegisterConfiguration::kMaxDoubleRegisters], |
Jarin
2015/06/12 04:09:11
Sized array argument is a bit esoteric construct i
|
+ float competing) { |
+ int ret = -1; |
+ float smallest_weight = LiveRange::kMaxWeight; |
+ for (int i = 0; i < num_registers(); ++i) { |
+ float w = CalculateMaxSpillWeight(all_conflicts[i], |
Jarin
2015/06/12 04:09:11
w -> weight
|
+ allocations_[i]->conflicts_end()); |
+ if (w >= competing) continue; |
+ if (w < smallest_weight) { |
+ smallest_weight = w; |
+ ret = i; |
+ } |
+ } |
+ return ret; |
+} |
+ |
+ |
+int GreedyAllocator::FindRegisterToEvictForGroup(LiveRangeGroup* grp, |
+ float competing) { |
+ int ret = -1; |
+ auto ranges = grp->ranges(); |
+ float smallest_weight = LiveRange::kMaxWeight; |
+ for (int i = 0; i < num_registers(); ++i) { |
+ float grp_counter_weight = 0.0; |
Jarin
2015/06/12 04:09:10
grp -> group.
|
+ for (auto r : ranges) { |
+ auto first_conflict = allocations_[i]->conflicts_begin(r); |
+ if (first_conflict.IsEnd()) continue; |
+ auto w = CalculateMaxSpillWeight(first_conflict, |
Jarin
2015/06/12 04:09:11
Do not use auto here.
|
+ allocations_[i]->conflicts_end()); |
+ grp_counter_weight = Max(grp_counter_weight, w); |
+ if (grp_counter_weight >= competing) break; |
+ } |
+ if (grp_counter_weight >= competing) continue; |
Jarin
2015/06/12 04:09:10
Could you fold the grp_counter_weight >= competing
|
+ if (grp_counter_weight < smallest_weight) { |
+ smallest_weight = grp_counter_weight; |
+ ret = i; |
+ } |
+ } |
+ return ret; |
+} |
+ |
+ |
+// TODO(mtrofin): improved code reuse with AllocateRange? |
+void GreedyAllocator::AllocateGroup(LiveRangeGroup* grp) { |
Jarin
2015/06/12 04:09:11
grp -> group
|
+ // Modify the group ranges content after handling spill operands |
+ for (auto iter = grp->ranges().begin(), end = grp->ranges().end(); |
+ iter != end;) { |
+ auto iter_backup = iter; |
+ auto range = *iter++; |
+ LiveRange* remainder = nullptr; |
+ if (HandleSpillOperands(range, &remainder)) { |
+ grp->ranges().erase(iter_backup); |
+ if (remainder != nullptr) { |
+ grp->ranges().insert(remainder); |
+ remainder->RecalculateWeight(code()); |
+ } |
+ } |
+ } |
+ |
+ float grp_weight = -1.0; |
Jarin
2015/06/12 04:09:10
grp_weight -> group_weight
|
+ if (TryAllocateGroup(grp)) { |
+ stats_.groups_allocated++; |
+ return; |
+ } |
+ |
+ if (grp_weight < 0.0) { |
Jarin
2015/06/12 04:09:10
If you really insist on having the same types for
|
+ grp_weight = |
+ CalculateMaxSpillWeight(grp->ranges().begin(), grp->ranges().end()); |
+ } |
+ |
+ int reg_to_free = FindRegisterToEvictForGroup(grp, grp_weight); |
+ if (reg_to_free >= 0) { |
+ for (auto r : grp->ranges()) { |
+ EvictAll(reg_to_free, allocations_[reg_to_free]->conflicts_begin(r)); |
+ AssignRangeToRegister(reg_to_free, r); |
+ } |
+ return; |
+ } |
+ |
+ for (auto r : grp->ranges()) { |
+ Enqueue(r); |
+ } |
+} |
+ |
+ |
+void GreedyAllocator::AllocateRegisters() { |
+ stats_.reset(); |
+ CHECK(queue_.empty()); |
+ CHECK(allocations_.empty()); |
+ |
+ TRACE("Begin allocating function %s with the Greedy Allocator\n", |
+ data()->debug_name()); |
allocations_.resize(num_registers()); |
for (int i = 0; i < num_registers(); i++) { |
@@ -2703,138 +3246,225 @@ void GreedyAllocator::AllocateRegisters() { |
if (current != nullptr) { |
DCHECK_EQ(mode(), current->kind()); |
int reg_nr = current->assigned_register(); |
- bool inserted = allocations_[reg_nr]->Insert(current); |
- CHECK(inserted); |
+ allocations_[reg_nr]->insert(current); |
+ current->RecalculateWeight(code()); |
} |
} |
+ GroupAndEnqueue(); |
+ |
while (!queue_.empty()) { |
- auto current_pair = queue_.top(); |
+ std::pair<unsigned, PQueueElem> current_pair = queue_.top(); |
queue_.pop(); |
- auto current = current_pair.second; |
- if (HandleSpillOperands(current)) { |
- continue; |
- } |
- bool spill = false; |
- ZoneSet<LiveRange*> conflicting(local_zone()); |
- if (!TryAllocate(current, &conflicting)) { |
- DCHECK(!conflicting.empty()); |
- float this_weight = std::numeric_limits<float>::max(); |
- LifetimePosition split_pos = |
- FindProgressingSplitPosition(current, &spill); |
- if (split_pos.IsValid()) { |
- this_weight = CalculateSpillWeight(current); |
- } |
- |
- bool evicted = false; |
- for (auto* conflict : conflicting) { |
- if (CalculateSpillWeight(conflict) < this_weight) { |
- Evict(conflict); |
- Enqueue(conflict); |
- evicted = true; |
- } |
- } |
- if (evicted) { |
- conflicting.clear(); |
- TryAllocate(current, &conflicting); |
- } |
- if (!conflicting.empty()) { |
- DCHECK(!current->IsFixed() || current->CanBeSpilled(current->Start())); |
- DCHECK(split_pos.IsValid()); |
- AllocateBlockedRange(current, split_pos, spill); |
- } |
+ auto element = current_pair.second; |
+ if (element.IsGroup()) { |
+ AllocateGroup(element.get_group()); |
+ } else { |
+ auto current = element.get_range(); |
Jarin
2015/06/12 04:09:11
No need for the intermediate variable.
|
+ AllocateRange(current); |
} |
} |
for (size_t i = 0; i < allocations_.size(); ++i) { |
- if (!allocations_[i]->IsEmpty()) { |
+ if (!allocations_[i]->empty()) { |
data()->MarkAllocated(mode(), static_cast<int>(i)); |
} |
} |
+ allocations_.clear(); |
+ |
+ for (auto r : data()->live_ranges()) { |
+ if (r == nullptr || r->IsEmpty() || r->kind() != mode()) continue; |
+ if (!r->spilled()) continue; |
Jarin
2015/06/12 04:09:11
Fold into the previous if?
|
+ auto top = r->TopLevel(); |
Jarin
2015/06/12 04:09:10
Use the real type here.
|
+ if (top->group() != nullptr) { |
+ if (!top->HasSpillRange()) continue; |
+ auto top_sp_range = top->GetSpillRange(); |
+ CHECK(top_sp_range != nullptr); |
Jarin
2015/06/12 04:09:11
DCHECK?
|
+ for (auto m : top->group()->ranges()) { |
+ if (!m->HasSpillRange()) continue; |
+ auto m_sp_range = m->TopLevel()->GetSpillRange(); |
+ if (m_sp_range == top_sp_range) continue; |
+ bool merged = top_sp_range->TryMerge(m_sp_range); |
+ CHECK(merged); |
+ } |
+ } |
+ } |
+ TRACE("End allocating function %s with the Greedy Allocator\n", |
+ data()->debug_name()); |
} |
-LifetimePosition GreedyAllocator::GetSplittablePos(LifetimePosition pos) { |
- auto ret = pos.PrevStart().End(); |
- if (IsBlockBoundary(code(), pos.Start())) { |
- ret = pos.Start(); |
- } |
- DCHECK(ret <= pos); |
- return ret; |
-} |
+void GreedyAllocator::HandleBlockedRange(LiveRange* current) { |
+ // Make the best possible decision for splitting this range. The resulting |
+ // chunks may have a better chance at allocation, or, if not, will eventually |
+ // be unsplittable and "fit". |
-LifetimePosition GreedyAllocator::FindProgressingSplitPosition( |
- LiveRange* range, bool* is_spill_pos) { |
- auto start = range->Start(); |
- auto end = range->End(); |
+ // TODO(mtrofin): more tuning. Is the ordering the one we want? |
+ auto start = current->Start(); |
Jarin
2015/06/12 04:09:10
Explicit types, please.
|
+ auto end = current->End(); |
+ |
+ UsePosition* next_reg_use = |
+ current->NextUsePositionRegisterIsBeneficial(start); |
- UsePosition* next_reg_use = range->first_pos(); |
- while (next_reg_use != nullptr && |
- next_reg_use->type() != UsePositionType::kRequiresRegister) { |
- next_reg_use = next_reg_use->next(); |
+ if (current->is_phi()) { |
+ CHECK(next_reg_use != nullptr && next_reg_use->pos() == start); |
+ // If the range does not need register soon, spill it to the merged |
+ // spill range. |
+ auto next_pos = start; |
+ if (next_pos.IsGapPosition()) next_pos = next_pos.NextStart(); |
+ auto pos = current->NextUsePositionRegisterIsBeneficial(next_pos); |
+ if (pos == nullptr) { |
+ Spill(current); |
+ return; |
+ } else if (pos->pos() > start.NextStart()) { |
+ Enqueue(SpillBetweenUntil(current, start, start, pos->pos())); |
+ return; |
+ } |
} |
if (next_reg_use == nullptr) { |
- *is_spill_pos = true; |
- auto ret = FindOptimalSpillingPos(range, start); |
- DCHECK(ret.IsValid()); |
- return ret; |
+ auto pos = FindOptimalSpillingPos(current, start); |
+ DCHECK(pos.IsValid()); |
+ auto tail = SplitRangeAt(current, pos); |
+ Spill(tail); |
+ if (tail != current) { |
+ Enqueue(current); |
+ } |
+ return; |
} |
- *is_spill_pos = false; |
- auto reg_pos = next_reg_use->pos(); |
- auto correct_pos = GetSplittablePos(reg_pos); |
- if (start < correct_pos && correct_pos < end) { |
- return correct_pos; |
- } |
+ if (TrySplitAroundCalls(current)) return; |
+ if (TrySplitBeforeLoops(current)) return; |
+ if (TrySplitAfterLoops(current)) return; |
- if (correct_pos >= end) { |
- return LifetimePosition::Invalid(); |
- } |
- // Correct_pos must be at or before start. Find the next use position. |
- next_reg_use = next_reg_use->next(); |
- auto reference = reg_pos; |
- while (next_reg_use != nullptr) { |
- auto pos = next_reg_use->pos(); |
- // Skip over tight successive uses. |
- if (reference.NextStart() < pos) { |
- break; |
+ if (current->CanBeSpilled(start)) { |
+ UsePosition* next_mandatory_use = nullptr; |
+ for (next_mandatory_use = current->first_pos(); |
+ next_mandatory_use != nullptr; |
+ next_mandatory_use = next_mandatory_use->next()) { |
+ if (next_mandatory_use->type() == UsePositionType::kRequiresRegister) |
+ break; |
} |
- reference = pos; |
- next_reg_use = next_reg_use->next(); |
+ if (next_mandatory_use == nullptr) { |
+ Spill(current); |
+ } else { |
+ Enqueue( |
+ SpillBetweenUntil(current, start, start, next_mandatory_use->pos())); |
+ } |
+ return; |
} |
- if (next_reg_use == nullptr) { |
- // While there may not be another use, we may still have space in the range |
- // to clip off. |
- correct_pos = reference.NextStart(); |
- if (start < correct_pos && correct_pos < end) { |
- return correct_pos; |
- } |
- return LifetimePosition::Invalid(); |
+ if (current->first_interval()->next() != nullptr) { |
+ auto tail = SplitRangeAt(current, current->first_interval()->end()); |
+ DCHECK(tail != current); |
+ Enqueue(tail); |
+ Enqueue(current); |
+ return; |
} |
- correct_pos = GetSplittablePos(next_reg_use->pos()); |
- if (start < correct_pos && correct_pos < end) { |
- DCHECK(reference < correct_pos); |
- return correct_pos; |
+ auto pos_to_split = current->GetFirstSplittablePosition(); |
+ CHECK(pos_to_split.IsValid() && start < pos_to_split && pos_to_split < end); |
+ auto tail = SplitRangeAt(current, pos_to_split); |
+ CHECK(tail != current); |
+ Enqueue(tail); |
+ Enqueue(current); |
+} |
+ |
+ |
+bool GreedyAllocator::TrySplitAroundCalls(LiveRange* range) { |
+ // TODO(mtrofin): should we just split around all calls? |
+ auto start = range->Start(); |
+ auto end = range->End(); |
+ for (auto i = range->first_interval(); i != nullptr; i = i->next()) { |
+ for (int instr_pos = i->start().ToInstructionIndex(); |
+ instr_pos < i->end().ToInstructionIndex(); instr_pos++) { |
+ auto instr = code()->InstructionAt(instr_pos); |
+ if (instr->IsCall()) { |
+ auto pos = LifetimePosition::GapFromInstructionIndex(instr_pos); |
+ if (start >= pos || pos >= end) continue; |
+ auto tail = SplitRangeAt(range, pos); |
+ DCHECK(tail != range); |
+ Enqueue(tail); |
+ Enqueue(range); |
Jarin
2015/06/12 04:09:11
I am wondering whether queuing a group for tail an
|
+ return true; |
+ } |
+ } |
} |
- return LifetimePosition::Invalid(); |
+ return false; |
} |
-void GreedyAllocator::AllocateBlockedRange(LiveRange* current, |
- LifetimePosition pos, bool spill) { |
- auto tail = SplitRangeAt(current, pos); |
- if (spill) { |
- Spill(tail); |
- } else { |
- Enqueue(tail); |
+bool GreedyAllocator::TrySplitBeforeLoops(LiveRange* range) { |
+ auto start = range->Start(); |
+ auto end = range->End(); |
+ for (auto pos = range->first_pos(); pos != nullptr; pos = pos->next()) { |
+ if (!pos->RegisterIsBeneficial()) continue; |
+ const InstructionBlock* block = |
+ code()->GetInstructionBlock(pos->pos().ToInstructionIndex()); |
+ if (block->IsLoopHeader() || block->loop_header().IsValid()) { |
+ block = block->IsLoopHeader() ? block : GetContainingLoop(code(), block); |
+ auto split_pos = start; |
+ while (block != nullptr) { |
+ auto before_loop_start = LifetimePosition::GapFromInstructionIndex( |
+ block->first_instruction_index() - 1); |
+ |
+ if (range->Covers(before_loop_start)) { |
+ split_pos = before_loop_start; |
+ } |
+ |
+ // Try hoisting out to an outer loop. |
+ block = GetContainingLoop(code(), block); |
+ } |
+ if (start < split_pos && split_pos < end) { |
+ auto tail = SplitRangeAt(range, split_pos); |
+ Enqueue(tail); |
+ Enqueue(range); |
+ return true; |
+ } |
+ } |
} |
- if (tail != current) { |
- Enqueue(current); |
+ return false; |
+} |
+ |
+ |
+bool GreedyAllocator::TrySplitAfterLoops(LiveRange* range) { |
+ auto start = range->Start(); |
+ auto end = range->End(); |
+ for (auto pos = range->first_pos(); pos != nullptr; pos = pos->next()) { |
+ if (!pos->RegisterIsBeneficial()) continue; |
+ const InstructionBlock* block = |
+ code()->GetInstructionBlock(pos->pos().ToInstructionIndex()); |
+ if (block->IsLoopHeader() || block->loop_header().IsValid()) { |
+ auto header = block->IsLoopHeader() |
+ ? block |
+ : code()->InstructionBlockAt(block->loop_header()); |
+ |
+ auto split_pos = start; |
+ while (header != nullptr) { |
+ if (header->loop_end().ToSize() >= |
+ static_cast<size_t>(code()->InstructionBlockCount())) |
+ break; |
+ auto loop_end = code()->InstructionBlockAt(header->loop_end()); |
+ auto after_loop_start = LifetimePosition::GapFromInstructionIndex( |
+ loop_end->last_instruction_index() + 1); |
+ |
+ if (range->Covers(after_loop_start)) { |
+ split_pos = after_loop_start; |
+ } |
+ |
+ // Try hoisting out to an outer loop. |
+ header = GetContainingLoop(code(), header); |
+ } |
+ if (start < split_pos && split_pos < end) { |
+ auto tail = SplitRangeAt(range, split_pos); |
+ Enqueue(tail); |
+ Enqueue(range); |
+ return true; |
+ } |
+ } |
} |
+ return false; |
} |
@@ -2857,91 +3487,6 @@ void SpillSlotLocator::LocateSpillSlots() { |
} |
-bool GreedyAllocator::TryReuseSpillForPhi(LiveRange* range) { |
- if (range->IsChild() || !range->is_phi()) return false; |
- DCHECK(!range->HasSpillOperand()); |
- |
- auto phi_map_value = data()->GetPhiMapValueFor(range->id()); |
- auto phi = phi_map_value->phi(); |
- auto block = phi_map_value->block(); |
- // Count the number of spilled operands. |
- size_t spilled_count = 0; |
- LiveRange* first_op = nullptr; |
- for (size_t i = 0; i < phi->operands().size(); i++) { |
- int op = phi->operands()[i]; |
- LiveRange* op_range = LiveRangeFor(op); |
- if (!op_range->HasSpillRange()) continue; |
- auto pred = code()->InstructionBlockAt(block->predecessors()[i]); |
- auto pred_end = LifetimePosition::InstructionFromInstructionIndex( |
- pred->last_instruction_index()); |
- while (op_range != nullptr && !op_range->CanCover(pred_end)) { |
- op_range = op_range->next(); |
- } |
- if (op_range != nullptr && op_range->spilled()) { |
- spilled_count++; |
- if (first_op == nullptr) { |
- first_op = op_range->TopLevel(); |
- } |
- } |
- } |
- |
- // Only continue if more than half of the operands are spilled. |
- if (spilled_count * 2 <= phi->operands().size()) { |
- return false; |
- } |
- |
- // Try to merge the spilled operands and count the number of merged spilled |
- // operands. |
- DCHECK(first_op != nullptr); |
- auto first_op_spill = first_op->GetSpillRange(); |
- size_t num_merged = 1; |
- for (size_t i = 1; i < phi->operands().size(); i++) { |
- int op = phi->operands()[i]; |
- auto op_range = LiveRangeFor(op); |
- if (!op_range->HasSpillRange()) continue; |
- auto op_spill = op_range->GetSpillRange(); |
- if (op_spill == first_op_spill || first_op_spill->TryMerge(op_spill)) { |
- num_merged++; |
- } |
- } |
- |
- // Only continue if enough operands could be merged to the |
- // same spill slot. |
- if (num_merged * 2 <= phi->operands().size() || |
- AreUseIntervalsIntersecting(first_op_spill->interval(), |
- range->first_interval())) { |
- return false; |
- } |
- |
- // If the range does not need register soon, spill it to the merged |
- // spill range. |
- auto next_pos = range->Start(); |
- if (next_pos.IsGapPosition()) next_pos = next_pos.NextStart(); |
- auto pos = range->NextUsePositionRegisterIsBeneficial(next_pos); |
- if (pos == nullptr) { |
- auto spill_range = |
- range->TopLevel()->HasSpillRange() |
- ? range->TopLevel()->GetSpillRange() |
- : data()->AssignSpillRangeToLiveRange(range->TopLevel()); |
- bool merged = first_op_spill->TryMerge(spill_range); |
- CHECK(merged); |
- Spill(range); |
- return true; |
- } else if (pos->pos() > range->Start().NextStart()) { |
- auto spill_range = |
- range->TopLevel()->HasSpillRange() |
- ? range->TopLevel()->GetSpillRange() |
- : data()->AssignSpillRangeToLiveRange(range->TopLevel()); |
- bool merged = first_op_spill->TryMerge(spill_range); |
- CHECK(merged); |
- Enqueue( |
- SpillBetweenUntil(range, range->Start(), range->Start(), pos->pos())); |
- return true; |
- } |
- return false; |
-} |
- |
- |
OperandAssigner::OperandAssigner(RegisterAllocationData* data) : data_(data) {} |
@@ -3377,7 +3922,6 @@ void LiveRangeConnector::ConnectRanges(Zone* local_zone) { |
moves->push_back(move); |
} |
if (done) break; |
- // Reset state. |
to_eliminate.clear(); |
to_insert.clear(); |
moves = it->first.first; |