Index: src/compiler/register-allocator.cc |
diff --git a/src/compiler/register-allocator.cc b/src/compiler/register-allocator.cc |
index efcdcb42e64eb79ef0546c0215c1974feb5bb6c8..5197b945fb8fc8e19cdb0597263712dec55e833d 100644 |
--- a/src/compiler/register-allocator.cc |
+++ b/src/compiler/register-allocator.cc |
@@ -342,6 +342,11 @@ UsePositionHintType UsePosition::HintTypeForOperand( |
return UsePositionHintType::kNone; |
} |
+void UsePosition::SetHint(UsePosition* use_pos) { |
+ DCHECK_NOT_NULL(use_pos); |
+ hint_ = use_pos; |
+ flags_ = HintTypeField::update(flags_, UsePositionHintType::kUsePos); |
+} |
void UsePosition::ResolveHint(UsePosition* use_pos) { |
DCHECK_NOT_NULL(use_pos); |
@@ -581,7 +586,9 @@ void LiveRange::AdvanceLastProcessedMarker( |
LiveRange* LiveRange::SplitAt(LifetimePosition position, Zone* zone) { |
int new_id = TopLevel()->GetNextChildId(); |
LiveRange* child = new (zone) LiveRange(new_id, representation(), TopLevel()); |
- DetachAt(position, child, zone); |
+ // If we split, we do so because we're about to switch registers or move |
+ // to/from a slot, so there's no value in connecting hints. |
+ DetachAt(position, child, zone, DoNotConnectHints); |
child->top_level_ = TopLevel(); |
child->next_ = next_; |
@@ -589,9 +596,9 @@ LiveRange* LiveRange::SplitAt(LifetimePosition position, Zone* zone) { |
return child; |
} |
- |
UsePosition* LiveRange::DetachAt(LifetimePosition position, LiveRange* result, |
- Zone* zone) { |
+ Zone* zone, |
+ HintConnectionOption connect_hints) { |
DCHECK(Start() < position); |
DCHECK(End() > position); |
DCHECK(result->IsEmpty()); |
@@ -670,6 +677,10 @@ UsePosition* LiveRange::DetachAt(LifetimePosition position, LiveRange* result, |
last_processed_use_ = nullptr; |
current_interval_ = nullptr; |
+ if (connect_hints == ConnectHints && use_before != nullptr && |
+ use_after != nullptr) { |
+ use_after->SetHint(use_before); |
+ } |
#ifdef DEBUG |
VerifyChildStructure(); |
result->VerifyChildStructure(); |
@@ -912,17 +923,21 @@ void TopLevelLiveRange::Splinter(LifetimePosition start, LifetimePosition end, |
if (end >= End()) { |
DCHECK(start > Start()); |
- DetachAt(start, &splinter_temp, zone); |
+ DetachAt(start, &splinter_temp, zone, ConnectHints); |
next_ = nullptr; |
} else { |
DCHECK(start < End() && Start() < end); |
const int kInvalidId = std::numeric_limits<int>::max(); |
- UsePosition* last = DetachAt(start, &splinter_temp, zone); |
+ UsePosition* last = DetachAt(start, &splinter_temp, zone, ConnectHints); |
LiveRange end_part(kInvalidId, this->representation(), nullptr); |
- last_in_splinter = splinter_temp.DetachAt(end, &end_part, zone); |
+ // The last chunk exits the deferred region, and we don't want to connect |
+ // hints here, because the non-deferred region shouldn't be affected |
+ // by allocation decisions on the deferred path. |
+ last_in_splinter = |
+ splinter_temp.DetachAt(end, &end_part, zone, DoNotConnectHints); |
next_ = end_part.next_; |
last_interval_->set_next(end_part.first_interval_); |
@@ -2401,7 +2416,13 @@ void RegisterAllocator::SplitAndSpillRangesDefinedByMemoryOperand() { |
if (next_pos.IsGapPosition()) { |
next_pos = next_pos.NextStart(); |
} |
- UsePosition* pos = range->NextUsePositionRegisterIsBeneficial(next_pos); |
+ |
+ // With splinters, we can be more strict and skip over positions |
+ // not strictly needing registers. |
+ UsePosition* pos = |
+ range->IsSplinter() |
+ ? range->NextRegisterPosition(next_pos) |
+ : range->NextUsePositionRegisterIsBeneficial(next_pos); |
// If the range already has a spill operand and it doesn't need a |
// register immediately, split it and spill the first part of the range. |
if (pos == nullptr) { |
@@ -2634,14 +2655,28 @@ void LinearScanAllocator::AllocateRegisters() { |
DCHECK(!current->HasRegisterAssigned() && !current->spilled()); |
- bool result = TryAllocateFreeReg(current); |
- if (!result) AllocateBlockedReg(current); |
- if (current->HasRegisterAssigned()) { |
- AddToActive(current); |
- } |
+ ProcessCurrentRange(current); |
} |
} |
+bool LinearScanAllocator::TrySplitAndSpillSplinter(LiveRange* range) { |
+ DCHECK(range->TopLevel()->IsSplinter()); |
+ // If there was no hint, apply regular (hot path) heuristics. |
+ if (range->FirstHintPosition() == nullptr) return false; |
+ // If we can spill the whole range, great. Otherwise, split above the |
+ // first use needing a register and spill the top part. |
+ const UsePosition* next_reg = range->NextRegisterPosition(range->Start()); |
+ if (next_reg == nullptr) { |
+ Spill(range); |
+ return true; |
+ } else if (next_reg->pos().PrevStart() > range->Start()) { |
+ LiveRange* tail = SplitRangeAt(range, next_reg->pos().PrevStart()); |
+ AddToUnhandledSorted(tail); |
+ Spill(range); |
+ return true; |
+ } |
+ return false; |
+} |
void LinearScanAllocator::SetLiveRangeAssignedRegister(LiveRange* range, |
int reg) { |
@@ -2757,35 +2792,88 @@ void LinearScanAllocator::InactiveToActive(LiveRange* range) { |
range->TopLevel()->vreg(), range->relative_id()); |
} |
- |
-bool LinearScanAllocator::TryAllocateFreeReg(LiveRange* current) { |
+void LinearScanAllocator::FindFreeRegistersForRange( |
+ LiveRange* range, Vector<LifetimePosition> positions) { |
int num_regs = num_registers(); |
- int num_codes = num_allocatable_registers(); |
- const int* codes = allocatable_register_codes(); |
+ DCHECK_GE(positions.length(), num_regs); |
- LifetimePosition free_until_pos[RegisterConfiguration::kMaxFPRegisters]; |
for (int i = 0; i < num_regs; i++) { |
- free_until_pos[i] = LifetimePosition::MaxPosition(); |
+ positions[i] = LifetimePosition::MaxPosition(); |
} |
for (LiveRange* cur_active : active_live_ranges()) { |
int cur_reg = cur_active->assigned_register(); |
- free_until_pos[cur_reg] = LifetimePosition::GapFromInstructionIndex(0); |
+ positions[cur_reg] = LifetimePosition::GapFromInstructionIndex(0); |
TRACE("Register %s is free until pos %d (1)\n", RegisterName(cur_reg), |
LifetimePosition::GapFromInstructionIndex(0).value()); |
} |
for (LiveRange* cur_inactive : inactive_live_ranges()) { |
- DCHECK(cur_inactive->End() > current->Start()); |
- LifetimePosition next_intersection = |
- cur_inactive->FirstIntersection(current); |
+ DCHECK(cur_inactive->End() > range->Start()); |
+ LifetimePosition next_intersection = cur_inactive->FirstIntersection(range); |
if (!next_intersection.IsValid()) continue; |
int cur_reg = cur_inactive->assigned_register(); |
- free_until_pos[cur_reg] = Min(free_until_pos[cur_reg], next_intersection); |
+ positions[cur_reg] = Min(positions[cur_reg], next_intersection); |
TRACE("Register %s is free until pos %d (2)\n", RegisterName(cur_reg), |
- Min(free_until_pos[cur_reg], next_intersection).value()); |
- } |
- |
+ Min(positions[cur_reg], next_intersection).value()); |
+ } |
+} |
+ |
+// High-level register allocation summary: |
+// |
+// For regular, or hot (i.e. not splinter) ranges, we attempt to first |
+// allocate first the preferred (hint) register. If that is not possible, |
+// we find a register that's free, and allocate that. If that's not possible, |
+// we search for a register to steal from a range that was allocated. The |
+// goal is to optimize for throughput by avoiding register-to-memory |
+// moves, which are expensive. |
+// |
+// For splinters, the goal is to minimize the number of moves. First we try |
+// to allocate the preferred register (more discussion follows). Failing that, |
+// we bail out and spill as far as we can, unless the first use is at start, |
+// case in which we apply the same behavior as we do for regular ranges. |
+// If there is no hint, we apply the hot-path behavior. |
+// |
+// For the splinter, the hint register may come from: |
+// |
+// - the hot path (we set it at splintering time with SetHint). In this case, if |
+// we cannot offer the hint register, spilling is better because it's at most |
+// 1 move, while trying to find and offer another register is at least 1 move. |
+// |
+// - a constraint. If we cannot offer that register, it's because there is some |
+// interference. So offering the hint register up to the interference would |
+// result |
+// in a move at the interference, plus a move to satisfy the constraint. This is |
+// also the number of moves if we spill, with the potential of the range being |
+// already spilled and thus saving a move (the spill). |
+// Note that this can only be an input constraint, if it were an output one, |
+// the range wouldn't be a splinter because it means it'd be defined in a |
+// deferred |
+// block, and we don't mark those as splinters (they live in deferred blocks |
+// only). |
+// |
+// - a phi. The same analysis as in the case of the input constraint applies. |
+// |
+void LinearScanAllocator::ProcessCurrentRange(LiveRange* current) { |
+ LifetimePosition free_until_pos_buff[RegisterConfiguration::kMaxFPRegisters]; |
+ Vector<LifetimePosition> free_until_pos( |
+ free_until_pos_buff, RegisterConfiguration::kMaxFPRegisters); |
+ FindFreeRegistersForRange(current, free_until_pos); |
+ if (!TryAllocatePreferredReg(current, free_until_pos)) { |
+ if (current->TopLevel()->IsSplinter()) { |
+ if (TrySplitAndSpillSplinter(current)) return; |
+ } |
+ if (!TryAllocateFreeReg(current, free_until_pos)) { |
+ AllocateBlockedReg(current); |
+ } |
+ } |
+ if (current->HasRegisterAssigned()) { |
+ AddToActive(current); |
+ } |
+} |
+ |
+bool LinearScanAllocator::TryAllocatePreferredReg( |
+ LiveRange* current, const Vector<LifetimePosition>& free_until_pos) { |
int hint_register; |
if (current->FirstHintPosition(&hint_register) != nullptr) { |
TRACE( |
@@ -2803,6 +2891,14 @@ bool LinearScanAllocator::TryAllocateFreeReg(LiveRange* current) { |
return true; |
} |
} |
+ return false; |
+} |
+ |
+bool LinearScanAllocator::TryAllocateFreeReg( |
+ LiveRange* current, const Vector<LifetimePosition>& free_until_pos) { |
+ int num_codes = num_allocatable_registers(); |
+ const int* codes = allocatable_register_codes(); |
+ DCHECK_GE(free_until_pos.length(), num_codes); |
// Find the register which stays free for the longest time. |
int reg = codes[0]; |
@@ -2837,7 +2933,6 @@ bool LinearScanAllocator::TryAllocateFreeReg(LiveRange* current) { |
return true; |
} |
- |
void LinearScanAllocator::AllocateBlockedReg(LiveRange* current) { |
UsePosition* register_use = current->NextRegisterPosition(current->Start()); |
if (register_use == nullptr) { |