Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1443)

Unified Diff: src/compiler/register-allocator.cc

Issue 2347563004: [turbofan] Avoid large deopt blocks (Closed)
Patch Set: [turbofan] Avoid large deopt blocks Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/compiler/register-allocator.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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) {
« no previous file with comments | « src/compiler/register-allocator.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698