blob: 683b75d49fa756701c3eb061f485a1168fc91515 [file] [log] [blame]
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/compiler/greedy-allocator.h"
#include "src/compiler/register-allocator.h"
namespace v8 {
namespace internal {
namespace compiler {
#define TRACE(...) \
do { \
if (FLAG_trace_alloc) PrintF(__VA_ARGS__); \
} while (false)
const float GreedyAllocator::kAllocatedRangeMultiplier = 10.0;
namespace {
void UpdateOperands(LiveRange* range, RegisterAllocationData* data) {
int reg_id = range->assigned_register();
range->SetUseHints(reg_id);
if (range->IsTopLevel() && range->TopLevel()->is_phi()) {
data->GetPhiMapValueFor(range->TopLevel())->set_assigned_register(reg_id);
}
}
void UnsetOperands(LiveRange* range, RegisterAllocationData* data) {
range->UnsetUseHints();
if (range->IsTopLevel() && range->TopLevel()->is_phi()) {
data->GetPhiMapValueFor(range->TopLevel())->UnsetAssignedRegister();
}
}
LiveRange* Split(LiveRange* range, RegisterAllocationData* data,
LifetimePosition pos) {
DCHECK(range->Start() < pos && pos < range->End());
DCHECK(pos.IsStart() || pos.IsGapPosition() ||
(data->code()
->GetInstructionBlock(pos.ToInstructionIndex())
->last_instruction_index() != pos.ToInstructionIndex()));
LiveRange* result = range->SplitAt(pos, data->allocation_zone());
return result;
}
} // namespace
AllocationCandidate AllocationScheduler::GetNext() {
DCHECK(!queue_.empty());
AllocationCandidate ret = queue_.top();
queue_.pop();
return ret;
}
void AllocationScheduler::Schedule(LiveRange* range) {
TRACE("Scheduling live range %d:%d.\n", range->TopLevel()->vreg(),
range->relative_id());
queue_.push(AllocationCandidate(range));
}
void AllocationScheduler::Schedule(LiveRangeGroup* group) {
queue_.push(AllocationCandidate(group));
}
GreedyAllocator::GreedyAllocator(RegisterAllocationData* data,
RegisterKind kind, Zone* local_zone)
: RegisterAllocator(data, kind),
local_zone_(local_zone),
allocations_(local_zone),
scheduler_(local_zone),
groups_(local_zone) {}
void GreedyAllocator::AssignRangeToRegister(int reg_id, LiveRange* range) {
TRACE("Assigning register %s to live range %d:%d\n", RegisterName(reg_id),
range->TopLevel()->vreg(), range->relative_id());
DCHECK(!range->HasRegisterAssigned());
AllocateRegisterToRange(reg_id, range);
TRACE("Assigning %s to range %d%d.\n", RegisterName(reg_id),
range->TopLevel()->vreg(), range->relative_id());
range->set_assigned_register(reg_id);
UpdateOperands(range, data());
}
void GreedyAllocator::PreallocateFixedRanges() {
allocations_.resize(num_registers());
for (int i = 0; i < num_registers(); i++) {
allocations_[i] = new (local_zone()) CoalescedLiveRanges(local_zone());
}
for (LiveRange* fixed_range : GetFixedRegisters()) {
if (fixed_range != nullptr) {
DCHECK_EQ(mode(), fixed_range->kind());
DCHECK(fixed_range->TopLevel()->IsFixed());
int reg_nr = fixed_range->assigned_register();
EnsureValidRangeWeight(fixed_range);
AllocateRegisterToRange(reg_nr, fixed_range);
}
}
}
void GreedyAllocator::GroupLiveRanges() {
CoalescedLiveRanges grouper(local_zone());
for (TopLevelLiveRange* range : data()->live_ranges()) {
grouper.clear();
// Skip splinters, because we do not want to optimize for them, and moves
// due to assigning them to different registers occur in deferred blocks.
if (!CanProcessRange(range) || range->IsSplinter() || !range->is_phi()) {
continue;
}
// A phi can't be a memory operand, so it couldn't have been split.
DCHECK(!range->spilled());
// Maybe this phi range is itself an input to another phi which was already
// processed.
LiveRangeGroup* latest_grp = range->group() != nullptr
? range->group()
: new (local_zone())
LiveRangeGroup(local_zone());
// Populate the grouper.
if (range->group() == nullptr) {
grouper.AllocateRange(range);
} else {
for (LiveRange* member : range->group()->ranges()) {
grouper.AllocateRange(member);
}
}
for (int j : data()->GetPhiMapValueFor(range)->phi()->operands()) {
// skip output also in input, which may happen for loops.
if (j == range->vreg()) continue;
TopLevelLiveRange* other_top = data()->live_ranges()[j];
if (other_top->IsSplinter()) continue;
// If the other was a memory operand, it might have been split.
// So get the unsplit part.
LiveRange* other =
other_top->next() == nullptr ? other_top : other_top->next();
if (other->spilled()) continue;
LiveRangeGroup* other_group = other->group();
if (other_group != nullptr) {
bool can_merge = true;
for (LiveRange* member : other_group->ranges()) {
if (grouper.GetConflicts(member).Current() != nullptr) {
can_merge = false;
break;
}
}
// If each member doesn't conflict with the current group, then since
// the members don't conflict with eachother either, we can merge them.
if (can_merge) {
latest_grp->ranges().insert(latest_grp->ranges().end(),
other_group->ranges().begin(),
other_group->ranges().end());
for (LiveRange* member : other_group->ranges()) {
grouper.AllocateRange(member);
member->set_group(latest_grp);
}
// Clear the other range, so we avoid scheduling it.
other_group->ranges().clear();
}
} else if (grouper.GetConflicts(other).Current() == nullptr) {
grouper.AllocateRange(other);
latest_grp->ranges().push_back(other);
other->set_group(latest_grp);
}
}
if (latest_grp->ranges().size() > 0 && range->group() == nullptr) {
latest_grp->ranges().push_back(range);
DCHECK(latest_grp->ranges().size() > 1);
groups().push_back(latest_grp);
range->set_group(latest_grp);
}
}
}
void GreedyAllocator::ScheduleAllocationCandidates() {
for (LiveRangeGroup* group : groups()) {
if (group->ranges().size() > 0) {
// We shouldn't have added single-range groups.
DCHECK(group->ranges().size() != 1);
scheduler().Schedule(group);
}
}
for (LiveRange* range : data()->live_ranges()) {
if (CanProcessRange(range)) {
for (LiveRange* child = range; child != nullptr; child = child->next()) {
if (!child->spilled() && child->group() == nullptr) {
scheduler().Schedule(child);
}
}
}
}
}
void GreedyAllocator::TryAllocateCandidate(
const AllocationCandidate& candidate) {
if (candidate.is_group()) {
TryAllocateGroup(candidate.group());
} else {
TryAllocateLiveRange(candidate.live_range());
}
}
void GreedyAllocator::TryAllocateGroup(LiveRangeGroup* group) {
float group_weight = 0.0;
for (LiveRange* member : group->ranges()) {
EnsureValidRangeWeight(member);
group_weight = Max(group_weight, member->weight());
}
float eviction_weight = group_weight;
int eviction_reg = -1;
int free_reg = -1;
for (int i = 0; i < num_allocatable_registers(); ++i) {
int reg = allocatable_register_code(i);
float weight = GetMaximumConflictingWeight(reg, group, group_weight);
if (weight == LiveRange::kInvalidWeight) {
free_reg = reg;
break;
}
if (weight < eviction_weight) {
eviction_weight = weight;
eviction_reg = reg;
}
}
if (eviction_reg < 0 && free_reg < 0) {
for (LiveRange* member : group->ranges()) {
scheduler().Schedule(member);
}
return;
}
if (free_reg < 0) {
DCHECK(eviction_reg >= 0);
for (LiveRange* member : group->ranges()) {
EvictAndRescheduleConflicts(eviction_reg, member);
}
free_reg = eviction_reg;
}
DCHECK(free_reg >= 0);
for (LiveRange* member : group->ranges()) {
AssignRangeToRegister(free_reg, member);
}
}
void GreedyAllocator::TryAllocateLiveRange(LiveRange* range) {
// TODO(mtrofin): once we introduce groups, we'll want to first try and
// allocate at the preferred register.
TRACE("Attempting to allocate live range %d:%d.\n", range->TopLevel()->vreg(),
range->relative_id());
int free_reg = -1;
int evictable_reg = -1;
int hinted_reg = -1;
EnsureValidRangeWeight(range);
float competing_weight = range->weight();
DCHECK(competing_weight != LiveRange::kInvalidWeight);
// Can we allocate at the hinted register?
if (range->FirstHintPosition(&hinted_reg) != nullptr) {
DCHECK(hinted_reg >= 0);
float max_conflict_weight =
GetMaximumConflictingWeight(hinted_reg, range, competing_weight);
if (max_conflict_weight == LiveRange::kInvalidWeight) {
free_reg = hinted_reg;
} else if (max_conflict_weight < range->weight()) {
evictable_reg = hinted_reg;
}
}
if (free_reg < 0 && evictable_reg < 0) {
// There was no hinted reg, or we cannot allocate there.
float smallest_weight = LiveRange::kMaxWeight;
// Seek either the first free register, or, from the set of registers
// where the maximum conflict is lower than the candidate's weight, the one
// with the smallest such weight.
for (int i = 0; i < num_allocatable_registers(); i++) {
int reg = allocatable_register_code(i);
// Skip unnecessarily re-visiting the hinted register, if any.
if (reg == hinted_reg) continue;
float max_conflict_weight =
GetMaximumConflictingWeight(reg, range, competing_weight);
if (max_conflict_weight == LiveRange::kInvalidWeight) {
free_reg = reg;
break;
}
if (max_conflict_weight < range->weight() &&
max_conflict_weight < smallest_weight) {
smallest_weight = max_conflict_weight;
evictable_reg = reg;
}
}
}
// We have a free register, so we use it.
if (free_reg >= 0) {
TRACE("Found free register %s for live range %d:%d.\n",
RegisterName(free_reg), range->TopLevel()->vreg(),
range->relative_id());
AssignRangeToRegister(free_reg, range);
return;
}
// We found a register to perform evictions, so we evict and allocate our
// candidate.
if (evictable_reg >= 0) {
TRACE("Found evictable register %s for live range %d:%d.\n",
RegisterName(free_reg), range->TopLevel()->vreg(),
range->relative_id());
EvictAndRescheduleConflicts(evictable_reg, range);
AssignRangeToRegister(evictable_reg, range);
return;
}
// The range needs to be split or spilled.
SplitOrSpillBlockedRange(range);
}
void GreedyAllocator::EvictAndRescheduleConflicts(unsigned reg_id,
const LiveRange* range) {
auto conflicts = current_allocations(reg_id)->GetConflicts(range);
for (LiveRange* conflict = conflicts.Current(); conflict != nullptr;
conflict = conflicts.RemoveCurrentAndGetNext()) {
DCHECK(conflict->HasRegisterAssigned());
CHECK(!conflict->TopLevel()->IsFixed());
conflict->UnsetAssignedRegister();
UnsetOperands(conflict, data());
UpdateWeightAtEviction(conflict);
scheduler().Schedule(conflict);
TRACE("Evicted range %d%d.\n", conflict->TopLevel()->vreg(),
conflict->relative_id());
}
}
void GreedyAllocator::AllocateRegisters() {
CHECK(scheduler().empty());
CHECK(allocations_.empty());
TRACE("Begin allocating function %s with the Greedy Allocator\n",
data()->debug_name());
SplitAndSpillRangesDefinedByMemoryOperand(true);
GroupLiveRanges();
ScheduleAllocationCandidates();
PreallocateFixedRanges();
while (!scheduler().empty()) {
AllocationCandidate candidate = scheduler().GetNext();
TryAllocateCandidate(candidate);
}
for (size_t i = 0; i < allocations_.size(); ++i) {
if (!allocations_[i]->empty()) {
data()->MarkAllocated(mode(), static_cast<int>(i));
}
}
allocations_.clear();
TryReuseSpillRangesForGroups();
TRACE("End allocating function %s with the Greedy Allocator\n",
data()->debug_name());
}
void GreedyAllocator::TryReuseSpillRangesForGroups() {
for (TopLevelLiveRange* top : data()->live_ranges()) {
if (!CanProcessRange(top) || !top->is_phi() || top->group() == nullptr) {
continue;
}
SpillRange* spill_range = nullptr;
for (LiveRange* member : top->group()->ranges()) {
if (!member->TopLevel()->HasSpillRange()) continue;
SpillRange* member_range = member->TopLevel()->GetSpillRange();
if (spill_range == nullptr) {
spill_range = member_range;
} else {
// This may not always succeed, because we group non-conflicting ranges
// that may have been splintered, and the splinters may cause conflicts
// in the spill ranges.
// TODO(mtrofin): should the splinters own their own spill ranges?
spill_range->TryMerge(member_range);
}
}
}
}
float GreedyAllocator::GetMaximumConflictingWeight(
unsigned reg_id, const LiveRange* range, float competing_weight) const {
float ret = LiveRange::kInvalidWeight;
auto conflicts = current_allocations(reg_id)->GetConflicts(range);
for (LiveRange* conflict = conflicts.Current(); conflict != nullptr;
conflict = conflicts.GetNext()) {
DCHECK_NE(conflict->weight(), LiveRange::kInvalidWeight);
if (competing_weight <= conflict->weight()) return LiveRange::kMaxWeight;
ret = Max(ret, conflict->weight());
DCHECK(ret < LiveRange::kMaxWeight);
}
return ret;
}
float GreedyAllocator::GetMaximumConflictingWeight(unsigned reg_id,
const LiveRangeGroup* group,
float group_weight) const {
float ret = LiveRange::kInvalidWeight;
for (LiveRange* member : group->ranges()) {
float member_conflict_weight =
GetMaximumConflictingWeight(reg_id, member, group_weight);
if (member_conflict_weight == LiveRange::kMaxWeight) {
return LiveRange::kMaxWeight;
}
if (member_conflict_weight > group_weight) return LiveRange::kMaxWeight;
ret = Max(member_conflict_weight, ret);
}
return ret;
}
void GreedyAllocator::EnsureValidRangeWeight(LiveRange* range) {
// The live range weight will be invalidated when ranges are created or split.
// Otherwise, it is consistently updated when the range is allocated or
// unallocated.
if (range->weight() != LiveRange::kInvalidWeight) return;
if (range->TopLevel()->IsFixed()) {
range->set_weight(LiveRange::kMaxWeight);
return;
}
if (!IsProgressPossible(range)) {
range->set_weight(LiveRange::kMaxWeight);
return;
}
float use_count = 0.0;
for (auto pos = range->first_pos(); pos != nullptr; pos = pos->next()) {
++use_count;
}
range->set_weight(use_count / static_cast<float>(range->GetSize()));
}
void GreedyAllocator::SpillRangeAsLastResort(LiveRange* range) {
LifetimePosition start = range->Start();
CHECK(range->CanBeSpilled(start));
DCHECK(range->NextRegisterPosition(start) == nullptr);
Spill(range);
}
LiveRange* GreedyAllocator::GetRemainderAfterSplittingAroundFirstCall(
LiveRange* range) {
LiveRange* ret = range;
for (UseInterval* interval = range->first_interval(); interval != nullptr;
interval = interval->next()) {
LifetimePosition start = interval->start();
LifetimePosition end = interval->end();
// If the interval starts at instruction end, then the first instruction
// in the interval is the next one.
int first_full_instruction = (start.IsGapPosition() || start.IsStart())
? start.ToInstructionIndex()
: start.ToInstructionIndex() + 1;
// If the interval ends in a gap or at instruction start, then the last
// instruction is the previous one.
int last_full_instruction = (end.IsGapPosition() || end.IsStart())
? end.ToInstructionIndex() - 1
: end.ToInstructionIndex();
for (int instruction_index = first_full_instruction;
instruction_index <= last_full_instruction; ++instruction_index) {
if (!code()->InstructionAt(instruction_index)->IsCall()) continue;
LifetimePosition before =
GetSplitPositionForInstruction(range, instruction_index);
LiveRange* second_part =
before.IsValid() ? Split(range, data(), before) : range;
if (range != second_part) scheduler().Schedule(range);
LifetimePosition after =
FindSplitPositionAfterCall(second_part, instruction_index);
if (after.IsValid()) {
ret = Split(second_part, data(), after);
} else {
ret = nullptr;
}
Spill(second_part);
return ret;
}
}
return ret;
}
bool GreedyAllocator::TrySplitAroundCalls(LiveRange* range) {
bool modified = false;
while (range != nullptr) {
LiveRange* remainder = GetRemainderAfterSplittingAroundFirstCall(range);
// If we performed no modification, we're done.
if (remainder == range) {
break;
}
// We performed a modification.
modified = true;
range = remainder;
}
// If we have a remainder and we made modifications, it means the remainder
// has no calls and we should schedule it for further processing. If we made
// no modifications, we will just return false, because we want the algorithm
// to make progress by trying some other heuristic.
if (modified && range != nullptr) {
DCHECK(!range->spilled());
DCHECK(!range->HasRegisterAssigned());
scheduler().Schedule(range);
}
return modified;
}
LifetimePosition GreedyAllocator::FindSplitPositionAfterCall(
const LiveRange* range, int call_index) {
LifetimePosition after_call =
Max(range->Start(),
LifetimePosition::GapFromInstructionIndex(call_index + 1));
UsePosition* next_use = range->NextRegisterPosition(after_call);
if (!next_use) return LifetimePosition::Invalid();
LifetimePosition split_pos = FindOptimalSplitPos(after_call, next_use->pos());
split_pos =
GetSplitPositionForInstruction(range, split_pos.ToInstructionIndex());
return split_pos;
}
LifetimePosition GreedyAllocator::FindSplitPositionBeforeLoops(
LiveRange* range) {
LifetimePosition end = range->End();
if (end.ToInstructionIndex() >= code()->LastInstructionIndex()) {
end =
LifetimePosition::GapFromInstructionIndex(end.ToInstructionIndex() - 1);
}
LifetimePosition pos = FindOptimalSplitPos(range->Start(), end);
pos = GetSplitPositionForInstruction(range, pos.ToInstructionIndex());
return pos;
}
void GreedyAllocator::SplitOrSpillBlockedRange(LiveRange* range) {
if (TrySplitAroundCalls(range)) return;
LifetimePosition pos = FindSplitPositionBeforeLoops(range);
if (!pos.IsValid()) pos = GetLastResortSplitPosition(range);
if (pos.IsValid()) {
LiveRange* tail = Split(range, data(), pos);
DCHECK(tail != range);
scheduler().Schedule(tail);
scheduler().Schedule(range);
return;
}
SpillRangeAsLastResort(range);
}
// Basic heuristic for advancing the algorithm, if any other splitting heuristic
// failed.
LifetimePosition GreedyAllocator::GetLastResortSplitPosition(
const LiveRange* range) {
LifetimePosition previous = range->Start();
for (UsePosition *pos = range->NextRegisterPosition(previous); pos != nullptr;
previous = previous.NextFullStart(),
pos = range->NextRegisterPosition(previous)) {
LifetimePosition optimal = FindOptimalSplitPos(previous, pos->pos());
LifetimePosition before =
GetSplitPositionForInstruction(range, optimal.ToInstructionIndex());
if (before.IsValid()) return before;
LifetimePosition after = GetSplitPositionForInstruction(
range, pos->pos().ToInstructionIndex() + 1);
if (after.IsValid()) return after;
}
return LifetimePosition::Invalid();
}
bool GreedyAllocator::IsProgressPossible(const LiveRange* range) {
return range->CanBeSpilled(range->Start()) ||
GetLastResortSplitPosition(range).IsValid();
}
} // namespace compiler
} // namespace internal
} // namespace v8