mirror of
https://github.com/dart-lang/sdk
synced 2024-11-05 18:22:09 +00:00
4b9bba55b4
R=ahe@google.com BUG= Review-Url: https://codereview.chromium.org/2833073002 .
3098 lines
105 KiB
C++
3098 lines
105 KiB
C++
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include "vm/flow_graph_allocator.h"
|
|
|
|
#include "vm/bit_vector.h"
|
|
#include "vm/intermediate_language.h"
|
|
#include "vm/il_printer.h"
|
|
#include "vm/flow_graph.h"
|
|
#include "vm/flow_graph_compiler.h"
|
|
#include "vm/log.h"
|
|
#include "vm/parser.h"
|
|
#include "vm/stack_frame.h"
|
|
|
|
namespace dart {
|
|
|
|
#if defined(DEBUG)
|
|
#define TRACE_ALLOC(statement) \
|
|
do { \
|
|
if (FLAG_trace_ssa_allocator) statement; \
|
|
} while (0)
|
|
#else
|
|
#define TRACE_ALLOC(statement)
|
|
#endif
|
|
|
|
|
|
static const intptr_t kNoVirtualRegister = -1;
|
|
static const intptr_t kTempVirtualRegister = -2;
|
|
static const intptr_t kIllegalPosition = -1;
|
|
static const intptr_t kMaxPosition = 0x7FFFFFFF;
|
|
static const intptr_t kPairVirtualRegisterOffset = 1;
|
|
|
|
// Definitions which have pair representations
|
|
// (kPairOfTagged) use two virtual register names.
|
|
// At SSA index allocation time each definition reserves two SSA indexes,
|
|
// the second index is only used for pairs. This function maps from the first
|
|
// SSA index to the second.
|
|
static intptr_t ToSecondPairVreg(intptr_t vreg) {
|
|
// Map vreg to its pair vreg.
|
|
return vreg + kPairVirtualRegisterOffset;
|
|
}
|
|
|
|
|
|
static intptr_t MinPosition(intptr_t a, intptr_t b) {
|
|
return (a < b) ? a : b;
|
|
}
|
|
|
|
|
|
static bool IsInstructionStartPosition(intptr_t pos) {
|
|
return (pos & 1) == 0;
|
|
}
|
|
|
|
|
|
static bool IsInstructionEndPosition(intptr_t pos) {
|
|
return (pos & 1) == 1;
|
|
}
|
|
|
|
|
|
static intptr_t ToInstructionStart(intptr_t pos) {
|
|
return (pos & ~1);
|
|
}
|
|
|
|
|
|
static intptr_t ToInstructionEnd(intptr_t pos) {
|
|
return (pos | 1);
|
|
}
|
|
|
|
|
|
FlowGraphAllocator::FlowGraphAllocator(const FlowGraph& flow_graph,
|
|
bool intrinsic_mode)
|
|
: flow_graph_(flow_graph),
|
|
reaching_defs_(flow_graph),
|
|
value_representations_(flow_graph.max_virtual_register_number()),
|
|
block_order_(flow_graph.reverse_postorder()),
|
|
postorder_(flow_graph.postorder()),
|
|
liveness_(flow_graph),
|
|
vreg_count_(flow_graph.max_virtual_register_number()),
|
|
live_ranges_(flow_graph.max_virtual_register_number()),
|
|
cpu_regs_(),
|
|
fpu_regs_(),
|
|
blocked_cpu_registers_(),
|
|
blocked_fpu_registers_(),
|
|
number_of_registers_(0),
|
|
registers_(),
|
|
blocked_registers_(),
|
|
cpu_spill_slot_count_(0),
|
|
intrinsic_mode_(intrinsic_mode) {
|
|
for (intptr_t i = 0; i < vreg_count_; i++) {
|
|
live_ranges_.Add(NULL);
|
|
}
|
|
for (intptr_t i = 0; i < vreg_count_; i++) {
|
|
value_representations_.Add(kNoRepresentation);
|
|
}
|
|
|
|
// All registers are marked as "not blocked" (array initialized to false).
|
|
// Mark the unavailable ones as "blocked" (true).
|
|
for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) {
|
|
if ((kDartAvailableCpuRegs & (1 << i)) == 0) {
|
|
blocked_cpu_registers_[i] = true;
|
|
}
|
|
}
|
|
|
|
// FpuTMP is used as scratch by optimized code and parallel move resolver.
|
|
blocked_fpu_registers_[FpuTMP] = true;
|
|
|
|
// Block additional registers needed preserved when generating intrinsics.
|
|
// TODO(fschneider): Handle saving and restoring these registers when
|
|
// generating intrinsic code.
|
|
if (intrinsic_mode) {
|
|
blocked_cpu_registers_[ARGS_DESC_REG] = true;
|
|
#if !defined(TARGET_ARCH_IA32)
|
|
// Need to preserve CODE_REG to be able to store the PC marker
|
|
// and load the pool pointer.
|
|
blocked_cpu_registers_[CODE_REG] = true;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
static void DeepLiveness(MaterializeObjectInstr* mat, BitVector* live_in) {
|
|
if (mat->was_visited_for_liveness()) {
|
|
return;
|
|
}
|
|
mat->mark_visited_for_liveness();
|
|
|
|
for (intptr_t i = 0; i < mat->InputCount(); i++) {
|
|
if (!mat->InputAt(i)->BindsToConstant()) {
|
|
Definition* defn = mat->InputAt(i)->definition();
|
|
MaterializeObjectInstr* inner_mat = defn->AsMaterializeObject();
|
|
if (inner_mat != NULL) {
|
|
DeepLiveness(inner_mat, live_in);
|
|
} else {
|
|
intptr_t idx = defn->ssa_temp_index();
|
|
live_in->Add(idx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SSALivenessAnalysis::ComputeInitialSets() {
|
|
const intptr_t block_count = postorder_.length();
|
|
for (intptr_t i = 0; i < block_count; i++) {
|
|
BlockEntryInstr* block = postorder_[i];
|
|
|
|
BitVector* kill = kill_[i];
|
|
BitVector* live_in = live_in_[i];
|
|
|
|
// Iterate backwards starting at the last instruction.
|
|
for (BackwardInstructionIterator it(block); !it.Done(); it.Advance()) {
|
|
Instruction* current = it.Current();
|
|
|
|
// Initialize location summary for instruction.
|
|
current->InitializeLocationSummary(zone(), true); // opt
|
|
#if defined(TARGET_ARCH_DBC)
|
|
// TODO(vegorov) remove this once we have ported all necessary
|
|
// instructions to DBC.
|
|
if (!current->HasLocs()) {
|
|
const char* msg = "SSALivenessAnalysis::ComputeInitialSets";
|
|
NOT_IN_PRODUCT(msg = current->ToCString());
|
|
graph_entry_->parsed_function().Bailout("SSALivenessAnalysis", msg);
|
|
}
|
|
#endif
|
|
|
|
LocationSummary* locs = current->locs();
|
|
#if defined(DEBUG)
|
|
locs->DiscoverWritableInputs();
|
|
#endif
|
|
|
|
// Handle definitions.
|
|
Definition* current_def = current->AsDefinition();
|
|
if ((current_def != NULL) && current_def->HasSSATemp()) {
|
|
kill->Add(current_def->ssa_temp_index());
|
|
live_in->Remove(current_def->ssa_temp_index());
|
|
if (current_def->HasPairRepresentation()) {
|
|
kill->Add(ToSecondPairVreg(current_def->ssa_temp_index()));
|
|
live_in->Remove(ToSecondPairVreg(current_def->ssa_temp_index()));
|
|
}
|
|
}
|
|
|
|
// Handle uses.
|
|
ASSERT(locs->input_count() == current->InputCount());
|
|
for (intptr_t j = 0; j < current->InputCount(); j++) {
|
|
Value* input = current->InputAt(j);
|
|
|
|
ASSERT(!locs->in(j).IsConstant() || input->BindsToConstant());
|
|
if (locs->in(j).IsConstant()) continue;
|
|
|
|
live_in->Add(input->definition()->ssa_temp_index());
|
|
if (input->definition()->HasPairRepresentation()) {
|
|
live_in->Add(ToSecondPairVreg(input->definition()->ssa_temp_index()));
|
|
}
|
|
}
|
|
|
|
// Add non-argument uses from the deoptimization environment (pushed
|
|
// arguments are not allocated by the register allocator).
|
|
if (current->env() != NULL) {
|
|
for (Environment::DeepIterator env_it(current->env()); !env_it.Done();
|
|
env_it.Advance()) {
|
|
Definition* defn = env_it.CurrentValue()->definition();
|
|
if (defn->IsMaterializeObject()) {
|
|
// MaterializeObject instruction is not in the graph.
|
|
// Treat its inputs as part of the environment.
|
|
DeepLiveness(defn->AsMaterializeObject(), live_in);
|
|
} else if (!defn->IsPushArgument() && !defn->IsConstant()) {
|
|
live_in->Add(defn->ssa_temp_index());
|
|
if (defn->HasPairRepresentation()) {
|
|
live_in->Add(ToSecondPairVreg(defn->ssa_temp_index()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle phis.
|
|
if (block->IsJoinEntry()) {
|
|
JoinEntryInstr* join = block->AsJoinEntry();
|
|
for (PhiIterator it(join); !it.Done(); it.Advance()) {
|
|
PhiInstr* phi = it.Current();
|
|
ASSERT(phi != NULL);
|
|
kill->Add(phi->ssa_temp_index());
|
|
live_in->Remove(phi->ssa_temp_index());
|
|
if (phi->HasPairRepresentation()) {
|
|
kill->Add(ToSecondPairVreg(phi->ssa_temp_index()));
|
|
live_in->Remove(ToSecondPairVreg(phi->ssa_temp_index()));
|
|
}
|
|
|
|
// If a phi input is not defined by the corresponding predecessor it
|
|
// must be marked live-in for that predecessor.
|
|
for (intptr_t k = 0; k < phi->InputCount(); k++) {
|
|
Value* val = phi->InputAt(k);
|
|
if (val->BindsToConstant()) continue;
|
|
|
|
BlockEntryInstr* pred = block->PredecessorAt(k);
|
|
const intptr_t use = val->definition()->ssa_temp_index();
|
|
if (!kill_[pred->postorder_number()]->Contains(use)) {
|
|
live_in_[pred->postorder_number()]->Add(use);
|
|
}
|
|
if (phi->HasPairRepresentation()) {
|
|
const intptr_t second_use = ToSecondPairVreg(use);
|
|
if (!kill_[pred->postorder_number()]->Contains(second_use)) {
|
|
live_in_[pred->postorder_number()]->Add(second_use);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (block->IsCatchBlockEntry()) {
|
|
// Process initial definitions.
|
|
CatchBlockEntryInstr* catch_entry = block->AsCatchBlockEntry();
|
|
for (intptr_t i = 0; i < catch_entry->initial_definitions()->length();
|
|
i++) {
|
|
Definition* def = (*catch_entry->initial_definitions())[i];
|
|
const intptr_t vreg = def->ssa_temp_index();
|
|
kill_[catch_entry->postorder_number()]->Add(vreg);
|
|
live_in_[catch_entry->postorder_number()]->Remove(vreg);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process initial definitions, ie, constants and incoming parameters.
|
|
for (intptr_t i = 0; i < graph_entry_->initial_definitions()->length(); i++) {
|
|
Definition* def = (*graph_entry_->initial_definitions())[i];
|
|
const intptr_t vreg = def->ssa_temp_index();
|
|
kill_[graph_entry_->postorder_number()]->Add(vreg);
|
|
live_in_[graph_entry_->postorder_number()]->Remove(vreg);
|
|
}
|
|
}
|
|
|
|
|
|
UsePosition* LiveRange::AddUse(intptr_t pos, Location* location_slot) {
|
|
ASSERT(location_slot != NULL);
|
|
ASSERT((first_use_interval_->start_ <= pos) &&
|
|
(pos <= first_use_interval_->end_));
|
|
if (uses_ != NULL) {
|
|
if ((uses_->pos() == pos) && (uses_->location_slot() == location_slot)) {
|
|
return uses_;
|
|
} else if (uses_->pos() < pos) {
|
|
// If an instruction at position P is using the same value both as
|
|
// a fixed register input and a non-fixed input (in this order) we will
|
|
// add uses both at position P-1 and *then* P which will make
|
|
// uses_ unsorted unless we account for it here.
|
|
UsePosition* insert_after = uses_;
|
|
while ((insert_after->next() != NULL) &&
|
|
(insert_after->next()->pos() < pos)) {
|
|
insert_after = insert_after->next();
|
|
}
|
|
|
|
UsePosition* insert_before = insert_after->next();
|
|
while (insert_before != NULL && (insert_before->pos() == pos)) {
|
|
if (insert_before->location_slot() == location_slot) {
|
|
return insert_before;
|
|
}
|
|
insert_before = insert_before->next();
|
|
}
|
|
|
|
insert_after->set_next(
|
|
new UsePosition(pos, insert_after->next(), location_slot));
|
|
return insert_after->next();
|
|
}
|
|
}
|
|
uses_ = new UsePosition(pos, uses_, location_slot);
|
|
return uses_;
|
|
}
|
|
|
|
|
|
void LiveRange::AddSafepoint(intptr_t pos, LocationSummary* locs) {
|
|
ASSERT(IsInstructionStartPosition(pos));
|
|
SafepointPosition* safepoint =
|
|
new SafepointPosition(ToInstructionEnd(pos), locs);
|
|
|
|
if (first_safepoint_ == NULL) {
|
|
ASSERT(last_safepoint_ == NULL);
|
|
first_safepoint_ = last_safepoint_ = safepoint;
|
|
} else {
|
|
ASSERT(last_safepoint_ != NULL);
|
|
// We assume that safepoints list is sorted by position and that
|
|
// safepoints are added in this order.
|
|
ASSERT(last_safepoint_->pos() < pos);
|
|
last_safepoint_->set_next(safepoint);
|
|
last_safepoint_ = safepoint;
|
|
}
|
|
}
|
|
|
|
|
|
void LiveRange::AddHintedUse(intptr_t pos,
|
|
Location* location_slot,
|
|
Location* hint) {
|
|
ASSERT(hint != NULL);
|
|
AddUse(pos, location_slot)->set_hint(hint);
|
|
}
|
|
|
|
|
|
void LiveRange::AddUseInterval(intptr_t start, intptr_t end) {
|
|
ASSERT(start < end);
|
|
|
|
// Live ranges are being build by visiting instructions in post-order.
|
|
// This implies that use intervals will be prepended in a monotonically
|
|
// decreasing order.
|
|
if (first_use_interval() != NULL) {
|
|
// If the first use interval and the use interval we are adding
|
|
// touch then we can just extend the first interval to cover their
|
|
// union.
|
|
if (start > first_use_interval()->start()) {
|
|
// The only case when we can add intervals with start greater than
|
|
// start of an already created interval is BlockLocation.
|
|
ASSERT(vreg() == kNoVirtualRegister);
|
|
ASSERT(end <= first_use_interval()->end());
|
|
return;
|
|
} else if (start == first_use_interval()->start()) {
|
|
// Grow first interval if necessary.
|
|
if (end <= first_use_interval()->end()) return;
|
|
first_use_interval_->end_ = end;
|
|
return;
|
|
} else if (end == first_use_interval()->start()) {
|
|
first_use_interval()->start_ = start;
|
|
return;
|
|
}
|
|
|
|
ASSERT(end < first_use_interval()->start());
|
|
}
|
|
|
|
first_use_interval_ = new UseInterval(start, end, first_use_interval_);
|
|
if (last_use_interval_ == NULL) {
|
|
ASSERT(first_use_interval_->next() == NULL);
|
|
last_use_interval_ = first_use_interval_;
|
|
}
|
|
}
|
|
|
|
|
|
void LiveRange::DefineAt(intptr_t pos) {
|
|
// Live ranges are being build by visiting instructions in post-order.
|
|
// This implies that use intervals will be prepended in a monotonically
|
|
// decreasing order.
|
|
// When we encounter a use of a value inside a block we optimistically
|
|
// expand the first use interval to cover the block from the start
|
|
// to the last use in the block and then we shrink it if we encounter
|
|
// definition of the value inside the same block.
|
|
if (first_use_interval_ == NULL) {
|
|
// Definition without a use.
|
|
first_use_interval_ = new UseInterval(pos, pos + 1, NULL);
|
|
last_use_interval_ = first_use_interval_;
|
|
} else {
|
|
// Shrink the first use interval. It was optimistically expanded to
|
|
// cover the block from the start to the last use in the block.
|
|
ASSERT(first_use_interval_->start_ <= pos);
|
|
first_use_interval_->start_ = pos;
|
|
}
|
|
}
|
|
|
|
|
|
LiveRange* FlowGraphAllocator::GetLiveRange(intptr_t vreg) {
|
|
if (live_ranges_[vreg] == NULL) {
|
|
Representation rep = value_representations_[vreg];
|
|
ASSERT(rep != kNoRepresentation);
|
|
live_ranges_[vreg] = new LiveRange(vreg, rep);
|
|
}
|
|
return live_ranges_[vreg];
|
|
}
|
|
|
|
|
|
LiveRange* FlowGraphAllocator::MakeLiveRangeForTemporary() {
|
|
// Representation does not matter for temps.
|
|
Representation ignored = kNoRepresentation;
|
|
LiveRange* range = new LiveRange(kTempVirtualRegister, ignored);
|
|
#if defined(DEBUG)
|
|
temporaries_.Add(range);
|
|
#endif
|
|
return range;
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::BlockRegisterLocation(Location loc,
|
|
intptr_t from,
|
|
intptr_t to,
|
|
bool* blocked_registers,
|
|
LiveRange** blocking_ranges) {
|
|
if (blocked_registers[loc.register_code()]) {
|
|
return;
|
|
}
|
|
|
|
if (blocking_ranges[loc.register_code()] == NULL) {
|
|
Representation ignored = kNoRepresentation;
|
|
LiveRange* range = new LiveRange(kNoVirtualRegister, ignored);
|
|
blocking_ranges[loc.register_code()] = range;
|
|
range->set_assigned_location(loc);
|
|
#if defined(DEBUG)
|
|
temporaries_.Add(range);
|
|
#endif
|
|
}
|
|
|
|
blocking_ranges[loc.register_code()]->AddUseInterval(from, to);
|
|
}
|
|
|
|
|
|
// Block location from the start of the instruction to its end.
|
|
void FlowGraphAllocator::BlockLocation(Location loc,
|
|
intptr_t from,
|
|
intptr_t to) {
|
|
if (loc.IsRegister()) {
|
|
BlockRegisterLocation(loc, from, to, blocked_cpu_registers_, cpu_regs_);
|
|
#if defined(TARGET_ARCH_DBC)
|
|
last_used_register_ =
|
|
Utils::Maximum(last_used_register_, loc.register_code());
|
|
#endif
|
|
} else if (loc.IsFpuRegister()) {
|
|
BlockRegisterLocation(loc, from, to, blocked_fpu_registers_, fpu_regs_);
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
|
|
void LiveRange::Print() {
|
|
if (first_use_interval() == NULL) {
|
|
return;
|
|
}
|
|
|
|
THR_Print(" live range v%" Pd " [%" Pd ", %" Pd ") in ", vreg(), Start(),
|
|
End());
|
|
assigned_location().Print();
|
|
if (spill_slot_.HasStackIndex()) {
|
|
intptr_t stack_slot = spill_slot_.stack_index();
|
|
THR_Print(" allocated spill slot: %" Pd "", stack_slot);
|
|
}
|
|
THR_Print("\n");
|
|
|
|
SafepointPosition* safepoint = first_safepoint();
|
|
while (safepoint != NULL) {
|
|
THR_Print(" Safepoint [%" Pd "]: ", safepoint->pos());
|
|
safepoint->locs()->stack_bitmap()->Print();
|
|
THR_Print("\n");
|
|
safepoint = safepoint->next();
|
|
}
|
|
|
|
UsePosition* use_pos = uses_;
|
|
for (UseInterval* interval = first_use_interval_; interval != NULL;
|
|
interval = interval->next()) {
|
|
THR_Print(" use interval [%" Pd ", %" Pd ")\n", interval->start(),
|
|
interval->end());
|
|
while ((use_pos != NULL) && (use_pos->pos() <= interval->end())) {
|
|
THR_Print(" use at %" Pd "", use_pos->pos());
|
|
if (use_pos->location_slot() != NULL) {
|
|
THR_Print(" as ");
|
|
use_pos->location_slot()->Print();
|
|
}
|
|
THR_Print("\n");
|
|
use_pos = use_pos->next();
|
|
}
|
|
}
|
|
|
|
if (next_sibling() != NULL) {
|
|
next_sibling()->Print();
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::PrintLiveRanges() {
|
|
#if defined(DEBUG)
|
|
for (intptr_t i = 0; i < temporaries_.length(); i++) {
|
|
temporaries_[i]->Print();
|
|
}
|
|
#endif
|
|
|
|
for (intptr_t i = 0; i < live_ranges_.length(); i++) {
|
|
if (live_ranges_[i] != NULL) {
|
|
live_ranges_[i]->Print();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Returns true if all uses of the given range inside the given loop
|
|
// have Any allocation policy.
|
|
static bool HasOnlyUnconstrainedUsesInLoop(LiveRange* range,
|
|
BlockInfo* loop_header) {
|
|
const intptr_t boundary = loop_header->last_block()->end_pos();
|
|
|
|
UsePosition* use = range->first_use();
|
|
while ((use != NULL) && (use->pos() < boundary)) {
|
|
if (!use->location_slot()->Equals(Location::Any())) {
|
|
return false;
|
|
}
|
|
use = use->next();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Returns true if all uses of the given range have Any allocation policy.
|
|
static bool HasOnlyUnconstrainedUses(LiveRange* range) {
|
|
UsePosition* use = range->first_use();
|
|
while (use != NULL) {
|
|
if (!use->location_slot()->Equals(Location::Any())) {
|
|
return false;
|
|
}
|
|
use = use->next();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::BuildLiveRanges() {
|
|
const intptr_t block_count = postorder_.length();
|
|
ASSERT(postorder_.Last()->IsGraphEntry());
|
|
BitVector* current_interference_set = NULL;
|
|
Zone* zone = flow_graph_.zone();
|
|
for (intptr_t i = 0; i < (block_count - 1); i++) {
|
|
BlockEntryInstr* block = postorder_[i];
|
|
|
|
BlockInfo* block_info = BlockInfoAt(block->start_pos());
|
|
|
|
// For every SSA value that is live out of this block, create an interval
|
|
// that covers the whole block. It will be shortened if we encounter a
|
|
// definition of this value in this block.
|
|
for (BitVector::Iterator it(liveness_.GetLiveOutSetAt(i)); !it.Done();
|
|
it.Advance()) {
|
|
LiveRange* range = GetLiveRange(it.Current());
|
|
range->AddUseInterval(block->start_pos(), block->end_pos());
|
|
}
|
|
|
|
BlockInfo* loop_header = block_info->loop_header();
|
|
if ((loop_header != NULL) && (loop_header->last_block() == block)) {
|
|
current_interference_set =
|
|
new (zone) BitVector(zone, flow_graph_.max_virtual_register_number());
|
|
ASSERT(loop_header->backedge_interference() == NULL);
|
|
// All values flowing into the loop header are live at the back-edge and
|
|
// can interfere with phi moves.
|
|
current_interference_set->AddAll(
|
|
liveness_.GetLiveInSet(loop_header->entry()));
|
|
loop_header->set_backedge_interference(current_interference_set);
|
|
}
|
|
|
|
// Connect outgoing phi-moves that were created in NumberInstructions
|
|
// and find last instruction that contributes to liveness.
|
|
Instruction* current =
|
|
ConnectOutgoingPhiMoves(block, current_interference_set);
|
|
|
|
// Now process all instructions in reverse order.
|
|
while (current != block) {
|
|
// Skip parallel moves that we insert while processing instructions.
|
|
if (!current->IsParallelMove()) {
|
|
ProcessOneInstruction(block, current, current_interference_set);
|
|
}
|
|
current = current->previous();
|
|
}
|
|
|
|
|
|
// Check if any values live into the loop can be spilled for free.
|
|
if (block_info->is_loop_header()) {
|
|
current_interference_set = NULL;
|
|
for (BitVector::Iterator it(liveness_.GetLiveInSetAt(i)); !it.Done();
|
|
it.Advance()) {
|
|
LiveRange* range = GetLiveRange(it.Current());
|
|
if (HasOnlyUnconstrainedUsesInLoop(range, block_info)) {
|
|
range->MarkHasOnlyUnconstrainedUsesInLoop(block_info->loop_id());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (block->IsJoinEntry()) {
|
|
ConnectIncomingPhiMoves(block->AsJoinEntry());
|
|
} else if (block->IsCatchBlockEntry()) {
|
|
// Process initial definitions.
|
|
CatchBlockEntryInstr* catch_entry = block->AsCatchBlockEntry();
|
|
|
|
ProcessEnvironmentUses(catch_entry, catch_entry); // For lazy deopt
|
|
|
|
for (intptr_t i = 0; i < catch_entry->initial_definitions()->length();
|
|
i++) {
|
|
Definition* defn = (*catch_entry->initial_definitions())[i];
|
|
LiveRange* range = GetLiveRange(defn->ssa_temp_index());
|
|
range->DefineAt(catch_entry->start_pos()); // Defined at block entry.
|
|
ProcessInitialDefinition(defn, range, catch_entry);
|
|
}
|
|
// Block the two fixed registers used by CatchBlockEntryInstr from the
|
|
// block start to until the end of the instruction so that they are
|
|
// preserved.
|
|
intptr_t start = catch_entry->start_pos();
|
|
#if !defined(TARGET_ARCH_DBC)
|
|
const Register exception_reg = kExceptionObjectReg;
|
|
const Register stacktrace_reg = kStackTraceObjectReg;
|
|
#else
|
|
const intptr_t exception_reg =
|
|
LocalVarIndex(0, catch_entry->exception_var().index());
|
|
const intptr_t stacktrace_reg =
|
|
LocalVarIndex(0, catch_entry->stacktrace_var().index());
|
|
#endif
|
|
BlockLocation(Location::RegisterLocation(exception_reg), start,
|
|
ToInstructionEnd(start));
|
|
BlockLocation(Location::RegisterLocation(stacktrace_reg), start,
|
|
ToInstructionEnd(start));
|
|
}
|
|
}
|
|
|
|
// Process incoming parameters and constants. Do this after all other
|
|
// instructions so that safepoints for all calls have already been found.
|
|
GraphEntryInstr* graph_entry = flow_graph_.graph_entry();
|
|
for (intptr_t i = 0; i < graph_entry->initial_definitions()->length(); i++) {
|
|
Definition* defn = (*graph_entry->initial_definitions())[i];
|
|
ASSERT(!defn->HasPairRepresentation());
|
|
LiveRange* range = GetLiveRange(defn->ssa_temp_index());
|
|
range->AddUseInterval(graph_entry->start_pos(), graph_entry->end_pos());
|
|
range->DefineAt(graph_entry->start_pos());
|
|
ProcessInitialDefinition(defn, range, graph_entry);
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::SplitInitialDefinitionAt(LiveRange* range,
|
|
intptr_t pos) {
|
|
if (range->End() > pos) {
|
|
LiveRange* tail = range->SplitAt(pos);
|
|
CompleteRange(tail, Location::kRegister);
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::ProcessInitialDefinition(Definition* defn,
|
|
LiveRange* range,
|
|
BlockEntryInstr* block) {
|
|
#if defined(TARGET_ARCH_DBC)
|
|
if (block->IsCatchBlockEntry()) {
|
|
if (defn->IsParameter()) {
|
|
// This must be in sync with FlowGraphCompiler::CatchEntryRegForVariable.
|
|
ParameterInstr* param = defn->AsParameter();
|
|
intptr_t slot_index = param->index();
|
|
AssignSafepoints(defn, range);
|
|
range->finger()->Initialize(range);
|
|
slot_index = kNumberOfCpuRegisters - 1 - slot_index;
|
|
range->set_assigned_location(Location::RegisterLocation(slot_index));
|
|
SplitInitialDefinitionAt(range, block->lifetime_position() + 2);
|
|
ConvertAllUses(range);
|
|
BlockLocation(Location::RegisterLocation(slot_index), 0, kMaxPosition);
|
|
} else {
|
|
ConstantInstr* constant = defn->AsConstant();
|
|
ASSERT(constant != NULL);
|
|
range->set_assigned_location(Location::Constant(constant));
|
|
range->set_spill_slot(Location::Constant(constant));
|
|
AssignSafepoints(defn, range);
|
|
range->finger()->Initialize(range);
|
|
UsePosition* use =
|
|
range->finger()->FirstRegisterBeneficialUse(block->start_pos());
|
|
if (use != NULL) {
|
|
LiveRange* tail = SplitBetween(range, block->start_pos(), use->pos());
|
|
// Parameters and constants are tagged, so allocated to CPU registers.
|
|
ASSERT(constant->representation() == kTagged);
|
|
CompleteRange(tail, Location::kRegister);
|
|
}
|
|
ConvertAllUses(range);
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Save the range end because it may change below.
|
|
intptr_t range_end = range->End();
|
|
if (defn->IsParameter()) {
|
|
ParameterInstr* param = defn->AsParameter();
|
|
// Assert that copied and non-copied parameters are mutually exclusive.
|
|
// This might change in the future and, if so, the index will be wrong.
|
|
ASSERT((flow_graph_.num_copied_params() == 0) ||
|
|
(flow_graph_.num_non_copied_params() == 0));
|
|
intptr_t slot_index = param->index();
|
|
ASSERT((param->base_reg() == FPREG) || (param->base_reg() == SPREG));
|
|
if (param->base_reg() == FPREG) {
|
|
// Slot index for the leftmost copied parameter is 0.
|
|
// Slot index for the rightmost fixed parameter is -1.
|
|
slot_index -= flow_graph_.num_non_copied_params();
|
|
}
|
|
|
|
#if defined(TARGET_ARCH_DBC)
|
|
ASSERT(param->base_reg() == FPREG);
|
|
if (slot_index >= 0) {
|
|
AssignSafepoints(defn, range);
|
|
range->finger()->Initialize(range);
|
|
range->set_assigned_location(Location::RegisterLocation(slot_index));
|
|
SplitInitialDefinitionAt(range, kNormalEntryPos);
|
|
ConvertAllUses(range);
|
|
return;
|
|
}
|
|
#endif // defined(TARGET_ARCH_DBC)
|
|
range->set_assigned_location(
|
|
Location::StackSlot(slot_index, param->base_reg()));
|
|
range->set_spill_slot(Location::StackSlot(slot_index, param->base_reg()));
|
|
|
|
} else if (defn->IsCurrentContext()) {
|
|
#if !defined(TARGET_ARCH_DBC)
|
|
const Register context_reg = CTX;
|
|
#else
|
|
const intptr_t context_reg = flow_graph_.num_copied_params();
|
|
#endif
|
|
|
|
AssignSafepoints(defn, range);
|
|
range->finger()->Initialize(range);
|
|
range->set_assigned_location(Location::RegisterLocation(context_reg));
|
|
if (range->End() > kNormalEntryPos) {
|
|
LiveRange* tail = range->SplitAt(kNormalEntryPos);
|
|
CompleteRange(tail, Location::kRegister);
|
|
}
|
|
ConvertAllUses(range);
|
|
return;
|
|
} else {
|
|
ConstantInstr* constant = defn->AsConstant();
|
|
ASSERT(constant != NULL);
|
|
range->set_assigned_location(Location::Constant(constant));
|
|
range->set_spill_slot(Location::Constant(constant));
|
|
}
|
|
AssignSafepoints(defn, range);
|
|
range->finger()->Initialize(range);
|
|
UsePosition* use =
|
|
range->finger()->FirstRegisterBeneficialUse(block->start_pos());
|
|
if (use != NULL) {
|
|
LiveRange* tail = SplitBetween(range, block->start_pos(), use->pos());
|
|
// Parameters and constants are tagged, so allocated to CPU registers.
|
|
CompleteRange(tail, Location::kRegister);
|
|
}
|
|
ConvertAllUses(range);
|
|
if (defn->IsParameter() && (range->spill_slot().stack_index() >= 0)) {
|
|
// Parameters above the frame pointer consume spill slots and are marked
|
|
// in stack maps.
|
|
spill_slots_.Add(range_end);
|
|
quad_spill_slots_.Add(false);
|
|
untagged_spill_slots_.Add(false);
|
|
// Note, all incoming parameters are assumed to be tagged.
|
|
MarkAsObjectAtSafepoints(range);
|
|
} else if (defn->IsConstant() && block->IsCatchBlockEntry()) {
|
|
// Constants at catch block entries consume spill slots.
|
|
spill_slots_.Add(range_end);
|
|
quad_spill_slots_.Add(false);
|
|
untagged_spill_slots_.Add(false);
|
|
}
|
|
}
|
|
|
|
|
|
static Location::Kind RegisterKindFromPolicy(Location loc) {
|
|
if (loc.policy() == Location::kRequiresFpuRegister) {
|
|
return Location::kFpuRegister;
|
|
} else {
|
|
return Location::kRegister;
|
|
}
|
|
}
|
|
|
|
|
|
static Location::Kind RegisterKindForResult(Instruction* instr) {
|
|
const Representation rep = instr->representation();
|
|
#if !defined(TARGET_ARCH_DBC)
|
|
if ((rep == kUnboxedDouble) || (rep == kUnboxedFloat32x4) ||
|
|
(rep == kUnboxedInt32x4) || (rep == kUnboxedFloat64x2)) {
|
|
return Location::kFpuRegister;
|
|
} else {
|
|
return Location::kRegister;
|
|
}
|
|
#else
|
|
// DBC supports only unboxed doubles and does not have distinguished FPU
|
|
// registers.
|
|
ASSERT((rep != kUnboxedFloat32x4) && (rep != kUnboxedInt32x4) &&
|
|
(rep != kUnboxedFloat64x2));
|
|
return Location::kRegister;
|
|
#endif
|
|
}
|
|
|
|
|
|
//
|
|
// When describing shape of live ranges in comments below we are going to use
|
|
// the following notation:
|
|
//
|
|
// B block entry
|
|
// g g' start and end of goto instruction
|
|
// i i' start and end of any other instruction
|
|
// j j' start and end of any other instruction
|
|
|
|
// - body of a use interval
|
|
// [ start of a use interval
|
|
// ) end of a use interval
|
|
// * use
|
|
//
|
|
// For example diagram
|
|
//
|
|
// i i'
|
|
// value --*--)
|
|
//
|
|
// can be read as: use interval for value starts somewhere before instruction
|
|
// and extends until currently processed instruction, there is a use of value
|
|
// at the start of the instruction.
|
|
//
|
|
|
|
Instruction* FlowGraphAllocator::ConnectOutgoingPhiMoves(
|
|
BlockEntryInstr* block,
|
|
BitVector* interfere_at_backedge) {
|
|
Instruction* last = block->last_instruction();
|
|
|
|
GotoInstr* goto_instr = last->AsGoto();
|
|
if (goto_instr == NULL) return last;
|
|
|
|
// If we have a parallel move here then the successor block must be a
|
|
// join with phis. The phi inputs contribute uses to each predecessor
|
|
// block (and the phi outputs contribute definitions in the successor
|
|
// block).
|
|
if (!goto_instr->HasParallelMove()) return goto_instr->previous();
|
|
ParallelMoveInstr* parallel_move = goto_instr->parallel_move();
|
|
|
|
// All uses are recorded at the position of parallel move preceding goto.
|
|
const intptr_t pos = goto_instr->lifetime_position();
|
|
|
|
JoinEntryInstr* join = goto_instr->successor();
|
|
ASSERT(join != NULL);
|
|
|
|
// Search for the index of the current block in the predecessors of
|
|
// the join.
|
|
const intptr_t pred_index = join->IndexOfPredecessor(block);
|
|
|
|
// Record the corresponding phi input use for each phi.
|
|
intptr_t move_index = 0;
|
|
for (PhiIterator it(join); !it.Done(); it.Advance()) {
|
|
PhiInstr* phi = it.Current();
|
|
Value* val = phi->InputAt(pred_index);
|
|
MoveOperands* move = parallel_move->MoveOperandsAt(move_index++);
|
|
|
|
ConstantInstr* constant = val->definition()->AsConstant();
|
|
if (constant != NULL) {
|
|
move->set_src(Location::Constant(constant));
|
|
continue;
|
|
}
|
|
|
|
// Expected shape of live ranges:
|
|
//
|
|
// g g'
|
|
// value --*
|
|
//
|
|
intptr_t vreg = val->definition()->ssa_temp_index();
|
|
LiveRange* range = GetLiveRange(vreg);
|
|
if (interfere_at_backedge != NULL) interfere_at_backedge->Add(vreg);
|
|
|
|
range->AddUseInterval(block->start_pos(), pos);
|
|
range->AddHintedUse(
|
|
pos, move->src_slot(),
|
|
GetLiveRange(phi->ssa_temp_index())->assigned_location_slot());
|
|
move->set_src(Location::PrefersRegister());
|
|
|
|
if (val->definition()->HasPairRepresentation()) {
|
|
move = parallel_move->MoveOperandsAt(move_index++);
|
|
vreg = ToSecondPairVreg(vreg);
|
|
range = GetLiveRange(vreg);
|
|
if (interfere_at_backedge != NULL) {
|
|
interfere_at_backedge->Add(vreg);
|
|
}
|
|
range->AddUseInterval(block->start_pos(), pos);
|
|
range->AddHintedUse(pos, move->src_slot(),
|
|
GetLiveRange(ToSecondPairVreg(phi->ssa_temp_index()))
|
|
->assigned_location_slot());
|
|
move->set_src(Location::PrefersRegister());
|
|
}
|
|
}
|
|
|
|
// Begin backward iteration with the instruction before the parallel
|
|
// move.
|
|
return goto_instr->previous();
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::ConnectIncomingPhiMoves(JoinEntryInstr* join) {
|
|
// For join blocks we need to add destinations of phi resolution moves
|
|
// to phi's live range so that register allocator will fill them with moves.
|
|
|
|
// All uses are recorded at the start position in the block.
|
|
const intptr_t pos = join->start_pos();
|
|
const bool is_loop_header = BlockInfoAt(join->start_pos())->is_loop_header();
|
|
intptr_t move_idx = 0;
|
|
for (PhiIterator it(join); !it.Done(); it.Advance()) {
|
|
PhiInstr* phi = it.Current();
|
|
ASSERT(phi != NULL);
|
|
const intptr_t vreg = phi->ssa_temp_index();
|
|
ASSERT(vreg >= 0);
|
|
const bool is_pair_phi = phi->HasPairRepresentation();
|
|
|
|
// Expected shape of live range:
|
|
//
|
|
// B
|
|
// phi [--------
|
|
//
|
|
LiveRange* range = GetLiveRange(vreg);
|
|
range->DefineAt(pos); // Shorten live range.
|
|
if (is_loop_header) range->mark_loop_phi();
|
|
|
|
if (is_pair_phi) {
|
|
LiveRange* second_range = GetLiveRange(ToSecondPairVreg(vreg));
|
|
second_range->DefineAt(pos); // Shorten live range.
|
|
if (is_loop_header) second_range->mark_loop_phi();
|
|
}
|
|
|
|
for (intptr_t pred_idx = 0; pred_idx < phi->InputCount(); pred_idx++) {
|
|
BlockEntryInstr* pred = join->PredecessorAt(pred_idx);
|
|
GotoInstr* goto_instr = pred->last_instruction()->AsGoto();
|
|
ASSERT((goto_instr != NULL) && (goto_instr->HasParallelMove()));
|
|
MoveOperands* move =
|
|
goto_instr->parallel_move()->MoveOperandsAt(move_idx);
|
|
move->set_dest(Location::PrefersRegister());
|
|
range->AddUse(pos, move->dest_slot());
|
|
if (is_pair_phi) {
|
|
LiveRange* second_range = GetLiveRange(ToSecondPairVreg(vreg));
|
|
MoveOperands* second_move =
|
|
goto_instr->parallel_move()->MoveOperandsAt(move_idx + 1);
|
|
second_move->set_dest(Location::PrefersRegister());
|
|
second_range->AddUse(pos, second_move->dest_slot());
|
|
}
|
|
}
|
|
|
|
// All phi resolution moves are connected. Phi's live range is
|
|
// complete.
|
|
AssignSafepoints(phi, range);
|
|
CompleteRange(range, RegisterKindForResult(phi));
|
|
if (is_pair_phi) {
|
|
LiveRange* second_range = GetLiveRange(ToSecondPairVreg(vreg));
|
|
AssignSafepoints(phi, second_range);
|
|
CompleteRange(second_range, RegisterKindForResult(phi));
|
|
}
|
|
|
|
move_idx += is_pair_phi ? 2 : 1;
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::ProcessEnvironmentUses(BlockEntryInstr* block,
|
|
Instruction* current) {
|
|
ASSERT(current->env() != NULL);
|
|
Environment* env = current->env();
|
|
while (env != NULL) {
|
|
// Any value mentioned in the deoptimization environment should survive
|
|
// until the end of instruction but it does not need to be in the register.
|
|
// Expected shape of live range:
|
|
//
|
|
// i i'
|
|
// value -----*
|
|
//
|
|
|
|
if (env->Length() == 0) {
|
|
env = env->outer();
|
|
continue;
|
|
}
|
|
|
|
const intptr_t block_start_pos = block->start_pos();
|
|
const intptr_t use_pos = current->lifetime_position() + 1;
|
|
|
|
Location* locations = flow_graph_.zone()->Alloc<Location>(env->Length());
|
|
|
|
for (intptr_t i = 0; i < env->Length(); ++i) {
|
|
Value* value = env->ValueAt(i);
|
|
Definition* def = value->definition();
|
|
if (def->HasPairRepresentation()) {
|
|
locations[i] = Location::Pair(Location::Any(), Location::Any());
|
|
} else {
|
|
locations[i] = Location::Any();
|
|
}
|
|
|
|
if (def->IsPushArgument()) {
|
|
// Frame size is unknown until after allocation.
|
|
locations[i] = Location::NoLocation();
|
|
continue;
|
|
}
|
|
|
|
ConstantInstr* constant = def->AsConstant();
|
|
if (constant != NULL) {
|
|
locations[i] = Location::Constant(constant);
|
|
continue;
|
|
}
|
|
|
|
MaterializeObjectInstr* mat = def->AsMaterializeObject();
|
|
if (mat != NULL) {
|
|
// MaterializeObject itself produces no value. But its uses
|
|
// are treated as part of the environment: allocated locations
|
|
// will be used when building deoptimization data.
|
|
locations[i] = Location::NoLocation();
|
|
ProcessMaterializationUses(block, block_start_pos, use_pos, mat);
|
|
continue;
|
|
}
|
|
|
|
if (def->HasPairRepresentation()) {
|
|
PairLocation* location_pair = locations[i].AsPairLocation();
|
|
{
|
|
// First live range.
|
|
LiveRange* range = GetLiveRange(def->ssa_temp_index());
|
|
range->AddUseInterval(block_start_pos, use_pos);
|
|
range->AddUse(use_pos, location_pair->SlotAt(0));
|
|
}
|
|
{
|
|
// Second live range.
|
|
LiveRange* range =
|
|
GetLiveRange(ToSecondPairVreg(def->ssa_temp_index()));
|
|
range->AddUseInterval(block_start_pos, use_pos);
|
|
range->AddUse(use_pos, location_pair->SlotAt(1));
|
|
}
|
|
} else {
|
|
LiveRange* range = GetLiveRange(def->ssa_temp_index());
|
|
range->AddUseInterval(block_start_pos, use_pos);
|
|
range->AddUse(use_pos, &locations[i]);
|
|
}
|
|
}
|
|
|
|
env->set_locations(locations);
|
|
env = env->outer();
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::ProcessMaterializationUses(
|
|
BlockEntryInstr* block,
|
|
const intptr_t block_start_pos,
|
|
const intptr_t use_pos,
|
|
MaterializeObjectInstr* mat) {
|
|
// Materialization can occur several times in the same environment.
|
|
// Check if we already processed this one.
|
|
if (mat->locations() != NULL) {
|
|
return; // Already processed.
|
|
}
|
|
|
|
// Initialize location for every input of the MaterializeObject instruction.
|
|
Location* locations = flow_graph_.zone()->Alloc<Location>(mat->InputCount());
|
|
mat->set_locations(locations);
|
|
|
|
for (intptr_t i = 0; i < mat->InputCount(); ++i) {
|
|
Definition* def = mat->InputAt(i)->definition();
|
|
|
|
ConstantInstr* constant = def->AsConstant();
|
|
if (constant != NULL) {
|
|
locations[i] = Location::Constant(constant);
|
|
continue;
|
|
}
|
|
|
|
if (def->HasPairRepresentation()) {
|
|
locations[i] = Location::Pair(Location::Any(), Location::Any());
|
|
PairLocation* location_pair = locations[i].AsPairLocation();
|
|
{
|
|
// First live range.
|
|
LiveRange* range = GetLiveRange(def->ssa_temp_index());
|
|
range->AddUseInterval(block_start_pos, use_pos);
|
|
range->AddUse(use_pos, location_pair->SlotAt(0));
|
|
}
|
|
{
|
|
// Second live range.
|
|
LiveRange* range =
|
|
GetLiveRange(ToSecondPairVreg(def->ssa_temp_index()));
|
|
range->AddUseInterval(block_start_pos, use_pos);
|
|
range->AddUse(use_pos, location_pair->SlotAt(1));
|
|
}
|
|
} else if (def->IsMaterializeObject()) {
|
|
locations[i] = Location::NoLocation();
|
|
ProcessMaterializationUses(block, block_start_pos, use_pos,
|
|
def->AsMaterializeObject());
|
|
} else {
|
|
locations[i] = Location::Any();
|
|
LiveRange* range = GetLiveRange(def->ssa_temp_index());
|
|
range->AddUseInterval(block_start_pos, use_pos);
|
|
range->AddUse(use_pos, &locations[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::ProcessOneInput(BlockEntryInstr* block,
|
|
intptr_t pos,
|
|
Location* in_ref,
|
|
Value* input,
|
|
intptr_t vreg,
|
|
RegisterSet* live_registers) {
|
|
ASSERT(in_ref != NULL);
|
|
ASSERT(!in_ref->IsPairLocation());
|
|
ASSERT(input != NULL);
|
|
ASSERT(block != NULL);
|
|
LiveRange* range = GetLiveRange(vreg);
|
|
if (in_ref->IsMachineRegister()) {
|
|
// Input is expected in a fixed register. Expected shape of
|
|
// live ranges:
|
|
//
|
|
// j' i i'
|
|
// value --*
|
|
// register [-----)
|
|
//
|
|
if (live_registers != NULL) {
|
|
live_registers->Add(*in_ref, range->representation());
|
|
}
|
|
MoveOperands* move = AddMoveAt(pos - 1, *in_ref, Location::Any());
|
|
BlockLocation(*in_ref, pos - 1, pos + 1);
|
|
range->AddUseInterval(block->start_pos(), pos - 1);
|
|
range->AddHintedUse(pos - 1, move->src_slot(), in_ref);
|
|
} else if (in_ref->IsUnallocated()) {
|
|
if (in_ref->policy() == Location::kWritableRegister) {
|
|
// Writable unallocated input. Expected shape of
|
|
// live ranges:
|
|
//
|
|
// i i'
|
|
// value --*
|
|
// temp [--)
|
|
MoveOperands* move = AddMoveAt(pos, Location::RequiresRegister(),
|
|
Location::PrefersRegister());
|
|
|
|
// Add uses to the live range of the input.
|
|
range->AddUseInterval(block->start_pos(), pos);
|
|
range->AddUse(pos, move->src_slot());
|
|
|
|
// Create live range for the temporary.
|
|
LiveRange* temp = MakeLiveRangeForTemporary();
|
|
temp->AddUseInterval(pos, pos + 1);
|
|
temp->AddHintedUse(pos, in_ref, move->src_slot());
|
|
temp->AddUse(pos, move->dest_slot());
|
|
*in_ref = Location::RequiresRegister();
|
|
CompleteRange(temp, RegisterKindFromPolicy(*in_ref));
|
|
} else {
|
|
// Normal unallocated input. Expected shape of
|
|
// live ranges:
|
|
//
|
|
// i i'
|
|
// value -----*
|
|
//
|
|
range->AddUseInterval(block->start_pos(), pos + 1);
|
|
range->AddUse(pos + 1, in_ref);
|
|
}
|
|
} else {
|
|
ASSERT(in_ref->IsConstant());
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::ProcessOneOutput(BlockEntryInstr* block,
|
|
intptr_t pos,
|
|
Location* out,
|
|
Definition* def,
|
|
intptr_t vreg,
|
|
bool output_same_as_first_input,
|
|
Location* in_ref,
|
|
Definition* input,
|
|
intptr_t input_vreg,
|
|
BitVector* interference_set) {
|
|
ASSERT(out != NULL);
|
|
ASSERT(!out->IsPairLocation());
|
|
ASSERT(def != NULL);
|
|
ASSERT(block != NULL);
|
|
|
|
LiveRange* range =
|
|
vreg >= 0 ? GetLiveRange(vreg) : MakeLiveRangeForTemporary();
|
|
|
|
// Process output and finalize its liverange.
|
|
if (out->IsMachineRegister()) {
|
|
// Fixed output location. Expected shape of live range:
|
|
//
|
|
// i i' j j'
|
|
// register [--)
|
|
// output [-------
|
|
//
|
|
BlockLocation(*out, pos, pos + 1);
|
|
|
|
if (range->vreg() == kTempVirtualRegister) return;
|
|
|
|
// We need to emit move connecting fixed register with another location
|
|
// that will be allocated for this output's live range.
|
|
// Special case: fixed output followed by a fixed input last use.
|
|
UsePosition* use = range->first_use();
|
|
|
|
// If the value has no uses we don't need to allocate it.
|
|
if (use == NULL) return;
|
|
|
|
if (use->pos() == (pos + 1)) {
|
|
ASSERT(use->location_slot()->IsUnallocated());
|
|
*(use->location_slot()) = *out;
|
|
|
|
// Remove first use. It was allocated.
|
|
range->set_first_use(range->first_use()->next());
|
|
}
|
|
|
|
// Shorten live range to the point of definition, this might make the range
|
|
// empty (if the only use immediately follows). If range is not empty add
|
|
// move from a fixed register to an unallocated location.
|
|
range->DefineAt(pos + 1);
|
|
if (range->Start() == range->End()) return;
|
|
|
|
MoveOperands* move = AddMoveAt(pos + 1, Location::Any(), *out);
|
|
range->AddHintedUse(pos + 1, move->dest_slot(), out);
|
|
} else if (output_same_as_first_input) {
|
|
ASSERT(in_ref != NULL);
|
|
ASSERT(input != NULL);
|
|
// Output register will contain a value of the first input at instruction's
|
|
// start. Expected shape of live ranges:
|
|
//
|
|
// i i'
|
|
// input #0 --*
|
|
// output [----
|
|
//
|
|
ASSERT(in_ref->Equals(Location::RequiresRegister()) ||
|
|
in_ref->Equals(Location::RequiresFpuRegister()));
|
|
*out = *in_ref;
|
|
// Create move that will copy value between input and output.
|
|
MoveOperands* move =
|
|
AddMoveAt(pos, Location::RequiresRegister(), Location::Any());
|
|
|
|
// Add uses to the live range of the input.
|
|
LiveRange* input_range = GetLiveRange(input_vreg);
|
|
input_range->AddUseInterval(block->start_pos(), pos);
|
|
input_range->AddUse(pos, move->src_slot());
|
|
|
|
// Shorten output live range to the point of definition and add both input
|
|
// and output uses slots to be filled by allocator.
|
|
range->DefineAt(pos);
|
|
range->AddHintedUse(pos, out, move->src_slot());
|
|
range->AddUse(pos, move->dest_slot());
|
|
range->AddUse(pos, in_ref);
|
|
|
|
if ((interference_set != NULL) && (range->vreg() >= 0) &&
|
|
interference_set->Contains(range->vreg())) {
|
|
interference_set->Add(input->ssa_temp_index());
|
|
}
|
|
} else {
|
|
// Normal unallocated location that requires a register. Expected shape of
|
|
// live range:
|
|
//
|
|
// i i'
|
|
// output [-------
|
|
//
|
|
ASSERT(out->Equals(Location::RequiresRegister()) ||
|
|
out->Equals(Location::RequiresFpuRegister()));
|
|
|
|
// Shorten live range to the point of definition and add use to be filled by
|
|
// allocator.
|
|
range->DefineAt(pos);
|
|
range->AddUse(pos, out);
|
|
}
|
|
|
|
AssignSafepoints(def, range);
|
|
CompleteRange(range, RegisterKindForResult(def));
|
|
}
|
|
|
|
|
|
// Create and update live ranges corresponding to instruction's inputs,
|
|
// temporaries and output.
|
|
void FlowGraphAllocator::ProcessOneInstruction(BlockEntryInstr* block,
|
|
Instruction* current,
|
|
BitVector* interference_set) {
|
|
LocationSummary* locs = current->locs();
|
|
|
|
Definition* def = current->AsDefinition();
|
|
if ((def != NULL) && (def->AsConstant() != NULL)) {
|
|
ASSERT(!def->HasPairRepresentation());
|
|
LiveRange* range = (def->ssa_temp_index() != -1)
|
|
? GetLiveRange(def->ssa_temp_index())
|
|
: NULL;
|
|
|
|
// Drop definitions of constants that have no uses.
|
|
if ((range == NULL) || (range->first_use() == NULL)) {
|
|
locs->set_out(0, Location::NoLocation());
|
|
return;
|
|
}
|
|
|
|
// If this constant has only unconstrained uses convert them all
|
|
// to use the constant directly and drop this definition.
|
|
// TODO(vegorov): improve allocation when we have enough registers to keep
|
|
// constants used in the loop in them.
|
|
if (HasOnlyUnconstrainedUses(range)) {
|
|
ConstantInstr* constant_instr = def->AsConstant();
|
|
range->set_assigned_location(Location::Constant(constant_instr));
|
|
range->set_spill_slot(Location::Constant(constant_instr));
|
|
range->finger()->Initialize(range);
|
|
ConvertAllUses(range);
|
|
|
|
locs->set_out(0, Location::NoLocation());
|
|
return;
|
|
}
|
|
}
|
|
|
|
const intptr_t pos = current->lifetime_position();
|
|
ASSERT(IsInstructionStartPosition(pos));
|
|
|
|
ASSERT(locs->input_count() == current->InputCount());
|
|
|
|
// Normalize same-as-first-input output if input is specified as
|
|
// fixed register.
|
|
if (locs->out(0).IsUnallocated() &&
|
|
(locs->out(0).policy() == Location::kSameAsFirstInput)) {
|
|
if (locs->in(0).IsPairLocation()) {
|
|
// Pair input, pair output.
|
|
PairLocation* in_pair = locs->in(0).AsPairLocation();
|
|
ASSERT(in_pair->At(0).IsMachineRegister() ==
|
|
in_pair->At(1).IsMachineRegister());
|
|
if (in_pair->At(0).IsMachineRegister() &&
|
|
in_pair->At(1).IsMachineRegister()) {
|
|
locs->set_out(0, Location::Pair(in_pair->At(0), in_pair->At(1)));
|
|
}
|
|
} else if (locs->in(0).IsMachineRegister()) {
|
|
// Single input, single output.
|
|
locs->set_out(0, locs->in(0));
|
|
}
|
|
}
|
|
|
|
const bool output_same_as_first_input =
|
|
locs->out(0).IsUnallocated() &&
|
|
(locs->out(0).policy() == Location::kSameAsFirstInput);
|
|
|
|
// Output is same as first input which is a pair.
|
|
if (output_same_as_first_input && locs->in(0).IsPairLocation()) {
|
|
// Make out into a PairLocation.
|
|
locs->set_out(0, Location::Pair(Location::RequiresRegister(),
|
|
Location::RequiresRegister()));
|
|
}
|
|
// Add uses from the deoptimization environment.
|
|
if (current->env() != NULL) ProcessEnvironmentUses(block, current);
|
|
|
|
// Process inputs.
|
|
// Skip the first input if output is specified with kSameAsFirstInput policy,
|
|
// they will be processed together at the very end.
|
|
{
|
|
for (intptr_t j = output_same_as_first_input ? 1 : 0;
|
|
j < locs->input_count(); j++) {
|
|
// Determine if we are dealing with a value pair, and if so, whether
|
|
// the location is the first register or second register.
|
|
Value* input = current->InputAt(j);
|
|
Location* in_ref = locs->in_slot(j);
|
|
RegisterSet* live_registers = NULL;
|
|
if (locs->HasCallOnSlowPath()) {
|
|
live_registers = locs->live_registers();
|
|
}
|
|
if (in_ref->IsPairLocation()) {
|
|
ASSERT(input->definition()->HasPairRepresentation());
|
|
PairLocation* pair = in_ref->AsPairLocation();
|
|
const intptr_t vreg = input->definition()->ssa_temp_index();
|
|
// Each element of the pair is assigned it's own virtual register number
|
|
// and is allocated its own LiveRange.
|
|
ProcessOneInput(block, pos, pair->SlotAt(0), input, vreg,
|
|
live_registers);
|
|
ProcessOneInput(block, pos, pair->SlotAt(1), input,
|
|
ToSecondPairVreg(vreg), live_registers);
|
|
} else {
|
|
ProcessOneInput(block, pos, in_ref, input,
|
|
input->definition()->ssa_temp_index(), live_registers);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process temps.
|
|
for (intptr_t j = 0; j < locs->temp_count(); j++) {
|
|
// Expected shape of live range:
|
|
//
|
|
// i i'
|
|
// [--)
|
|
//
|
|
|
|
Location temp = locs->temp(j);
|
|
// We do not support pair locations for temporaries.
|
|
ASSERT(!temp.IsPairLocation());
|
|
if (temp.IsMachineRegister()) {
|
|
BlockLocation(temp, pos, pos + 1);
|
|
} else if (temp.IsUnallocated()) {
|
|
LiveRange* range = MakeLiveRangeForTemporary();
|
|
range->AddUseInterval(pos, pos + 1);
|
|
range->AddUse(pos, locs->temp_slot(j));
|
|
CompleteRange(range, RegisterKindFromPolicy(temp));
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
// Block all allocatable registers for calls.
|
|
// Note that on DBC registers are always essentially spilled so
|
|
// we don't need to block anything.
|
|
#if !defined(TARGET_ARCH_DBC)
|
|
if (locs->always_calls()) {
|
|
// Expected shape of live range:
|
|
//
|
|
// i i'
|
|
// [--)
|
|
//
|
|
// The stack bitmap describes the position i.
|
|
for (intptr_t reg = 0; reg < kNumberOfCpuRegisters; reg++) {
|
|
BlockLocation(Location::RegisterLocation(static_cast<Register>(reg)), pos,
|
|
pos + 1);
|
|
}
|
|
|
|
for (intptr_t reg = 0; reg < kNumberOfFpuRegisters; reg++) {
|
|
BlockLocation(
|
|
Location::FpuRegisterLocation(static_cast<FpuRegister>(reg)), pos,
|
|
pos + 1);
|
|
}
|
|
|
|
|
|
#if defined(DEBUG)
|
|
// Verify that temps, inputs and output were specified as fixed
|
|
// locations. Every register is blocked now so attempt to
|
|
// allocate will not succeed.
|
|
for (intptr_t j = 0; j < locs->temp_count(); j++) {
|
|
ASSERT(!locs->temp(j).IsPairLocation());
|
|
ASSERT(!locs->temp(j).IsUnallocated());
|
|
}
|
|
|
|
for (intptr_t j = 0; j < locs->input_count(); j++) {
|
|
if (locs->in(j).IsPairLocation()) {
|
|
PairLocation* pair = locs->in_slot(j)->AsPairLocation();
|
|
ASSERT(!pair->At(0).IsUnallocated());
|
|
ASSERT(!pair->At(1).IsUnallocated());
|
|
} else {
|
|
ASSERT(!locs->in(j).IsUnallocated());
|
|
}
|
|
}
|
|
|
|
if (locs->out(0).IsPairLocation()) {
|
|
PairLocation* pair = locs->out_slot(0)->AsPairLocation();
|
|
ASSERT(!pair->At(0).IsUnallocated());
|
|
ASSERT(!pair->At(1).IsUnallocated());
|
|
} else {
|
|
ASSERT(!locs->out(0).IsUnallocated());
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
if (locs->can_call()) {
|
|
safepoints_.Add(current);
|
|
}
|
|
|
|
if (def == NULL) {
|
|
ASSERT(locs->out(0).IsInvalid());
|
|
return;
|
|
}
|
|
|
|
if (locs->out(0).IsInvalid()) {
|
|
ASSERT(def->ssa_temp_index() < 0);
|
|
return;
|
|
}
|
|
|
|
ASSERT(locs->output_count() == 1);
|
|
Location* out = locs->out_slot(0);
|
|
if (out->IsPairLocation()) {
|
|
ASSERT(def->HasPairRepresentation());
|
|
PairLocation* pair = out->AsPairLocation();
|
|
if (output_same_as_first_input) {
|
|
ASSERT(locs->in_slot(0)->IsPairLocation());
|
|
PairLocation* in_pair = locs->in_slot(0)->AsPairLocation();
|
|
Definition* input = current->InputAt(0)->definition();
|
|
ASSERT(input->HasPairRepresentation());
|
|
// Each element of the pair is assigned it's own virtual register number
|
|
// and is allocated its own LiveRange.
|
|
ProcessOneOutput(block, pos, // BlockEntry, seq.
|
|
pair->SlotAt(0), def, // (output) Location, Definition.
|
|
def->ssa_temp_index(), // (output) virtual register.
|
|
true, // output mapped to first input.
|
|
in_pair->SlotAt(0), input, // (input) Location, Def.
|
|
input->ssa_temp_index(), // (input) virtual register.
|
|
interference_set);
|
|
ProcessOneOutput(
|
|
block, pos, pair->SlotAt(1), def,
|
|
ToSecondPairVreg(def->ssa_temp_index()), true, in_pair->SlotAt(1),
|
|
input, ToSecondPairVreg(input->ssa_temp_index()), interference_set);
|
|
} else {
|
|
// Each element of the pair is assigned it's own virtual register number
|
|
// and is allocated its own LiveRange.
|
|
ProcessOneOutput(block, pos, pair->SlotAt(0), def, def->ssa_temp_index(),
|
|
false, // output is not mapped to first input.
|
|
NULL, NULL, -1, // First input not needed.
|
|
interference_set);
|
|
ProcessOneOutput(block, pos, pair->SlotAt(1), def,
|
|
ToSecondPairVreg(def->ssa_temp_index()), false, NULL,
|
|
NULL, -1, interference_set);
|
|
}
|
|
} else {
|
|
if (output_same_as_first_input) {
|
|
Location* in_ref = locs->in_slot(0);
|
|
Definition* input = current->InputAt(0)->definition();
|
|
ASSERT(!in_ref->IsPairLocation());
|
|
ProcessOneOutput(block, pos, // BlockEntry, Instruction, seq.
|
|
out, def, // (output) Location, Definition.
|
|
def->ssa_temp_index(), // (output) virtual register.
|
|
true, // output mapped to first input.
|
|
in_ref, input, // (input) Location, Def.
|
|
input->ssa_temp_index(), // (input) virtual register.
|
|
interference_set);
|
|
} else {
|
|
ProcessOneOutput(block, pos, out, def, def->ssa_temp_index(),
|
|
false, // output is not mapped to first input.
|
|
NULL, NULL, -1, // First input not needed.
|
|
interference_set);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static ParallelMoveInstr* CreateParallelMoveBefore(Instruction* instr,
|
|
intptr_t pos) {
|
|
ASSERT(pos > 0);
|
|
Instruction* prev = instr->previous();
|
|
ParallelMoveInstr* move = prev->AsParallelMove();
|
|
if ((move == NULL) || (move->lifetime_position() != pos)) {
|
|
move = new ParallelMoveInstr();
|
|
prev->LinkTo(move);
|
|
move->LinkTo(instr);
|
|
move->set_lifetime_position(pos);
|
|
}
|
|
return move;
|
|
}
|
|
|
|
|
|
static ParallelMoveInstr* CreateParallelMoveAfter(Instruction* instr,
|
|
intptr_t pos) {
|
|
Instruction* next = instr->next();
|
|
if (next->IsParallelMove() && (next->lifetime_position() == pos)) {
|
|
return next->AsParallelMove();
|
|
}
|
|
return CreateParallelMoveBefore(next, pos);
|
|
}
|
|
|
|
|
|
// Linearize the control flow graph. The chosen order will be used by the
|
|
// linear-scan register allocator. Number most instructions with a pair of
|
|
// numbers representing lifetime positions. Introduce explicit parallel
|
|
// move instructions in the predecessors of join nodes. The moves are used
|
|
// for phi resolution.
|
|
void FlowGraphAllocator::NumberInstructions() {
|
|
intptr_t pos = 0;
|
|
|
|
// The basic block order is reverse postorder.
|
|
const intptr_t block_count = postorder_.length();
|
|
for (intptr_t i = block_count - 1; i >= 0; i--) {
|
|
BlockEntryInstr* block = postorder_[i];
|
|
BlockInfo* info = new BlockInfo(block);
|
|
|
|
instructions_.Add(block);
|
|
block_info_.Add(info);
|
|
block->set_start_pos(pos);
|
|
block->set_lifetime_position(pos);
|
|
pos += 2;
|
|
|
|
for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
|
|
Instruction* current = it.Current();
|
|
// Do not assign numbers to parallel move instructions.
|
|
if (!current->IsParallelMove()) {
|
|
instructions_.Add(current);
|
|
block_info_.Add(info);
|
|
current->set_lifetime_position(pos);
|
|
pos += 2;
|
|
}
|
|
}
|
|
block->set_end_pos(pos);
|
|
}
|
|
|
|
// Create parallel moves in join predecessors. This must be done after
|
|
// all instructions are numbered.
|
|
for (intptr_t i = block_count - 1; i >= 0; i--) {
|
|
BlockEntryInstr* block = postorder_[i];
|
|
|
|
// For join entry predecessors create phi resolution moves if
|
|
// necessary. They will be populated by the register allocator.
|
|
JoinEntryInstr* join = block->AsJoinEntry();
|
|
if (join != NULL) {
|
|
intptr_t move_count = 0;
|
|
for (PhiIterator it(join); !it.Done(); it.Advance()) {
|
|
move_count += it.Current()->HasPairRepresentation() ? 2 : 1;
|
|
}
|
|
for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
|
|
// Insert the move between the last two instructions of the
|
|
// predecessor block (all such blocks have at least two instructions:
|
|
// the block entry and goto instructions.)
|
|
Instruction* last = block->PredecessorAt(i)->last_instruction();
|
|
ASSERT(last->IsGoto());
|
|
|
|
ParallelMoveInstr* move = last->AsGoto()->GetParallelMove();
|
|
|
|
// Populate the ParallelMove with empty moves.
|
|
for (intptr_t j = 0; j < move_count; j++) {
|
|
move->AddMove(Location::NoLocation(), Location::NoLocation());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Discover structural (reducible) loops nesting structure.
|
|
void FlowGraphAllocator::DiscoverLoops() {
|
|
// This algorithm relies on the assumption that we emit blocks in reverse
|
|
// postorder, so postorder number can be used to identify loop nesting.
|
|
//
|
|
// TODO(vegorov): consider using a generic algorithm to correctly discover
|
|
// both headers of reducible and irreducible loops.
|
|
BlockInfo* current_loop = NULL;
|
|
|
|
intptr_t loop_id = 0; // All loop headers have a unique id.
|
|
|
|
const intptr_t block_count = postorder_.length();
|
|
for (intptr_t i = 0; i < block_count; i++) {
|
|
BlockEntryInstr* block = postorder_[i];
|
|
GotoInstr* goto_instr = block->last_instruction()->AsGoto();
|
|
if (goto_instr != NULL) {
|
|
JoinEntryInstr* successor = goto_instr->successor();
|
|
if (successor->postorder_number() > i) {
|
|
// This is back-edge.
|
|
BlockInfo* successor_info = BlockInfoAt(successor->lifetime_position());
|
|
ASSERT(successor_info->entry() == successor);
|
|
if (!successor_info->is_loop_header() &&
|
|
((current_loop == NULL) ||
|
|
(current_loop->entry()->postorder_number() >
|
|
successor_info->entry()->postorder_number()))) {
|
|
ASSERT(successor_info != current_loop);
|
|
|
|
successor_info->mark_loop_header();
|
|
successor_info->set_loop_id(loop_id++);
|
|
successor_info->set_last_block(block);
|
|
// For loop header loop information points to the outer loop.
|
|
successor_info->set_loop(current_loop);
|
|
current_loop = successor_info;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (current_loop != NULL) {
|
|
BlockInfo* current_info = BlockInfoAt(block->lifetime_position());
|
|
if (current_info == current_loop) {
|
|
ASSERT(current_loop->is_loop_header());
|
|
current_loop = current_info->loop();
|
|
} else {
|
|
current_info->set_loop(current_loop);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Instruction* FlowGraphAllocator::InstructionAt(intptr_t pos) const {
|
|
return instructions_[pos / 2];
|
|
}
|
|
|
|
|
|
BlockInfo* FlowGraphAllocator::BlockInfoAt(intptr_t pos) const {
|
|
return block_info_[pos / 2];
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::IsBlockEntry(intptr_t pos) const {
|
|
return IsInstructionStartPosition(pos) && InstructionAt(pos)->IsBlockEntry();
|
|
}
|
|
|
|
|
|
void AllocationFinger::Initialize(LiveRange* range) {
|
|
first_pending_use_interval_ = range->first_use_interval();
|
|
first_register_use_ = range->first_use();
|
|
first_register_beneficial_use_ = range->first_use();
|
|
first_hinted_use_ = range->first_use();
|
|
}
|
|
|
|
|
|
bool AllocationFinger::Advance(const intptr_t start) {
|
|
UseInterval* a = first_pending_use_interval_;
|
|
while (a != NULL && a->end() <= start)
|
|
a = a->next();
|
|
first_pending_use_interval_ = a;
|
|
return (first_pending_use_interval_ == NULL);
|
|
}
|
|
|
|
|
|
Location AllocationFinger::FirstHint() {
|
|
UsePosition* use = first_hinted_use_;
|
|
|
|
while (use != NULL) {
|
|
if (use->HasHint()) return use->hint();
|
|
use = use->next();
|
|
}
|
|
|
|
return Location::NoLocation();
|
|
}
|
|
|
|
|
|
static UsePosition* FirstUseAfter(UsePosition* use, intptr_t after) {
|
|
while ((use != NULL) && (use->pos() < after)) {
|
|
use = use->next();
|
|
}
|
|
return use;
|
|
}
|
|
|
|
|
|
UsePosition* AllocationFinger::FirstRegisterUse(intptr_t after) {
|
|
for (UsePosition* use = FirstUseAfter(first_register_use_, after);
|
|
use != NULL; use = use->next()) {
|
|
Location* loc = use->location_slot();
|
|
if (loc->IsUnallocated() &&
|
|
((loc->policy() == Location::kRequiresRegister) ||
|
|
(loc->policy() == Location::kRequiresFpuRegister))) {
|
|
first_register_use_ = use;
|
|
return use;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
UsePosition* AllocationFinger::FirstRegisterBeneficialUse(intptr_t after) {
|
|
for (UsePosition* use = FirstUseAfter(first_register_beneficial_use_, after);
|
|
use != NULL; use = use->next()) {
|
|
Location* loc = use->location_slot();
|
|
if (loc->IsUnallocated() && loc->IsRegisterBeneficial()) {
|
|
first_register_beneficial_use_ = use;
|
|
return use;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
UsePosition* AllocationFinger::FirstInterferingUse(intptr_t after) {
|
|
if (IsInstructionEndPosition(after)) {
|
|
// If after is a position at the end of the instruction disregard
|
|
// any use occurring at it.
|
|
after += 1;
|
|
}
|
|
return FirstRegisterUse(after);
|
|
}
|
|
|
|
|
|
void AllocationFinger::UpdateAfterSplit(intptr_t first_use_after_split_pos) {
|
|
if ((first_register_use_ != NULL) &&
|
|
(first_register_use_->pos() >= first_use_after_split_pos)) {
|
|
first_register_use_ = NULL;
|
|
}
|
|
|
|
if ((first_register_beneficial_use_ != NULL) &&
|
|
(first_register_beneficial_use_->pos() >= first_use_after_split_pos)) {
|
|
first_register_beneficial_use_ = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
intptr_t UseInterval::Intersect(UseInterval* other) {
|
|
if (this->start() <= other->start()) {
|
|
if (other->start() < this->end()) return other->start();
|
|
} else if (this->start() < other->end()) {
|
|
return this->start();
|
|
}
|
|
return kIllegalPosition;
|
|
}
|
|
|
|
|
|
static intptr_t FirstIntersection(UseInterval* a, UseInterval* u) {
|
|
while (a != NULL && u != NULL) {
|
|
const intptr_t pos = a->Intersect(u);
|
|
if (pos != kIllegalPosition) return pos;
|
|
|
|
if (a->start() < u->start()) {
|
|
a = a->next();
|
|
} else {
|
|
u = u->next();
|
|
}
|
|
}
|
|
|
|
return kMaxPosition;
|
|
}
|
|
|
|
|
|
template <typename PositionType>
|
|
PositionType* SplitListOfPositions(PositionType** head,
|
|
intptr_t split_pos,
|
|
bool split_at_start) {
|
|
PositionType* last_before_split = NULL;
|
|
PositionType* pos = *head;
|
|
if (split_at_start) {
|
|
while ((pos != NULL) && (pos->pos() < split_pos)) {
|
|
last_before_split = pos;
|
|
pos = pos->next();
|
|
}
|
|
} else {
|
|
while ((pos != NULL) && (pos->pos() <= split_pos)) {
|
|
last_before_split = pos;
|
|
pos = pos->next();
|
|
}
|
|
}
|
|
|
|
if (last_before_split == NULL) {
|
|
*head = NULL;
|
|
} else {
|
|
last_before_split->set_next(NULL);
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
|
|
LiveRange* LiveRange::SplitAt(intptr_t split_pos) {
|
|
if (Start() == split_pos) return this;
|
|
|
|
UseInterval* interval = finger_.first_pending_use_interval();
|
|
if (interval == NULL) {
|
|
finger_.Initialize(this);
|
|
interval = finger_.first_pending_use_interval();
|
|
}
|
|
|
|
ASSERT(split_pos < End());
|
|
|
|
// Corner case. Split position can be inside the a lifetime hole or at its
|
|
// end. We need to start over to find the previous interval.
|
|
if (split_pos <= interval->start()) interval = first_use_interval_;
|
|
|
|
UseInterval* last_before_split = NULL;
|
|
while (interval->end() <= split_pos) {
|
|
last_before_split = interval;
|
|
interval = interval->next();
|
|
}
|
|
|
|
const bool split_at_start = (interval->start() == split_pos);
|
|
|
|
UseInterval* first_after_split = interval;
|
|
if (!split_at_start && interval->Contains(split_pos)) {
|
|
first_after_split =
|
|
new UseInterval(split_pos, interval->end(), interval->next());
|
|
interval->end_ = split_pos;
|
|
interval->next_ = first_after_split;
|
|
last_before_split = interval;
|
|
}
|
|
|
|
ASSERT(last_before_split != NULL);
|
|
ASSERT(last_before_split->next() == first_after_split);
|
|
ASSERT(last_before_split->end() <= split_pos);
|
|
ASSERT(split_pos <= first_after_split->start());
|
|
|
|
UsePosition* first_use_after_split =
|
|
SplitListOfPositions(&uses_, split_pos, split_at_start);
|
|
|
|
SafepointPosition* first_safepoint_after_split =
|
|
SplitListOfPositions(&first_safepoint_, split_pos, split_at_start);
|
|
|
|
UseInterval* last_use_interval = (last_before_split == last_use_interval_)
|
|
? first_after_split
|
|
: last_use_interval_;
|
|
next_sibling_ = new LiveRange(vreg(), representation(), first_use_after_split,
|
|
first_after_split, last_use_interval,
|
|
first_safepoint_after_split, next_sibling_);
|
|
|
|
TRACE_ALLOC(THR_Print(" split sibling [%" Pd ", %" Pd ")\n",
|
|
next_sibling_->Start(), next_sibling_->End()));
|
|
|
|
last_use_interval_ = last_before_split;
|
|
last_use_interval_->next_ = NULL;
|
|
|
|
if (first_use_after_split != NULL) {
|
|
finger_.UpdateAfterSplit(first_use_after_split->pos());
|
|
}
|
|
|
|
return next_sibling_;
|
|
}
|
|
|
|
|
|
LiveRange* FlowGraphAllocator::SplitBetween(LiveRange* range,
|
|
intptr_t from,
|
|
intptr_t to) {
|
|
TRACE_ALLOC(THR_Print("split v%" Pd " [%" Pd ", %" Pd ") between [%" Pd
|
|
", %" Pd ")\n",
|
|
range->vreg(), range->Start(), range->End(), from, to));
|
|
|
|
intptr_t split_pos = kIllegalPosition;
|
|
|
|
BlockInfo* split_block = BlockInfoAt(to);
|
|
if (from < split_block->entry()->lifetime_position()) {
|
|
// Interval [from, to) spans multiple blocks.
|
|
|
|
// If last block is inside a loop prefer splitting at outermost loop's
|
|
// header.
|
|
BlockInfo* loop_header = split_block->loop();
|
|
while ((loop_header != NULL) &&
|
|
(from < loop_header->entry()->lifetime_position())) {
|
|
split_block = loop_header;
|
|
loop_header = loop_header->loop();
|
|
}
|
|
|
|
// Split at block's start.
|
|
split_pos = split_block->entry()->lifetime_position();
|
|
} else {
|
|
// Interval [from, to) is contained inside a single block.
|
|
|
|
// Split at position corresponding to the end of the previous
|
|
// instruction.
|
|
split_pos = ToInstructionStart(to) - 1;
|
|
}
|
|
|
|
ASSERT(split_pos != kIllegalPosition);
|
|
ASSERT(from < split_pos);
|
|
|
|
return range->SplitAt(split_pos);
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::SpillBetween(LiveRange* range,
|
|
intptr_t from,
|
|
intptr_t to) {
|
|
ASSERT(from < to);
|
|
TRACE_ALLOC(THR_Print("spill v%" Pd " [%" Pd ", %" Pd ") "
|
|
"between [%" Pd ", %" Pd ")\n",
|
|
range->vreg(), range->Start(), range->End(), from, to));
|
|
LiveRange* tail = range->SplitAt(from);
|
|
|
|
if (tail->Start() < to) {
|
|
// There is an intersection of tail and [from, to).
|
|
LiveRange* tail_tail = SplitBetween(tail, tail->Start(), to);
|
|
Spill(tail);
|
|
AddToUnallocated(tail_tail);
|
|
} else {
|
|
// No intersection between tail and [from, to).
|
|
AddToUnallocated(tail);
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::SpillAfter(LiveRange* range, intptr_t from) {
|
|
TRACE_ALLOC(THR_Print("spill v%" Pd " [%" Pd ", %" Pd ") after %" Pd "\n",
|
|
range->vreg(), range->Start(), range->End(), from));
|
|
|
|
// When spilling the value inside the loop check if this spill can
|
|
// be moved outside.
|
|
BlockInfo* block_info = BlockInfoAt(from);
|
|
if (block_info->is_loop_header() || (block_info->loop() != NULL)) {
|
|
BlockInfo* loop_header =
|
|
block_info->is_loop_header() ? block_info : block_info->loop();
|
|
|
|
if ((range->Start() <= loop_header->entry()->start_pos()) &&
|
|
RangeHasOnlyUnconstrainedUsesInLoop(range, loop_header->loop_id())) {
|
|
ASSERT(loop_header->entry()->start_pos() <= from);
|
|
from = loop_header->entry()->start_pos();
|
|
TRACE_ALLOC(
|
|
THR_Print(" moved spill position to loop header %" Pd "\n", from));
|
|
}
|
|
}
|
|
|
|
LiveRange* tail = range->SplitAt(from);
|
|
Spill(tail);
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AllocateSpillSlotFor(LiveRange* range) {
|
|
#if defined(TARGET_ARCH_DBC)
|
|
// There is no need to support spilling on DBC because we have a lot of
|
|
// registers and registers and spill-slots have the same performance
|
|
// characteristics.
|
|
flow_graph_.parsed_function().Bailout("FlowGraphAllocator", "SPILL");
|
|
UNREACHABLE();
|
|
#endif
|
|
|
|
ASSERT(range->spill_slot().IsInvalid());
|
|
|
|
// Compute range start and end.
|
|
LiveRange* last_sibling = range;
|
|
while (last_sibling->next_sibling() != NULL) {
|
|
last_sibling = last_sibling->next_sibling();
|
|
}
|
|
|
|
const intptr_t start = range->Start();
|
|
const intptr_t end = last_sibling->End();
|
|
|
|
// During fpu register allocation spill slot indices are computed in terms of
|
|
// double (64bit) stack slots. We treat quad stack slot (128bit) as a
|
|
// consecutive pair of two double spill slots.
|
|
// Special care is taken to never allocate the same index to both
|
|
// double and quad spill slots as it complicates disambiguation during
|
|
// parallel move resolution.
|
|
const bool need_quad = (register_kind_ == Location::kFpuRegister) &&
|
|
((range->representation() == kUnboxedFloat32x4) ||
|
|
(range->representation() == kUnboxedInt32x4) ||
|
|
(range->representation() == kUnboxedFloat64x2));
|
|
const bool need_untagged = (register_kind_ == Location::kRegister) &&
|
|
((range->representation() == kUntagged));
|
|
|
|
// Search for a free spill slot among allocated: the value in it should be
|
|
// dead and its type should match (e.g. it should not be a part of the quad if
|
|
// we are allocating normal double slot).
|
|
// For CPU registers we need to take reserved slots for try-catch into
|
|
// account.
|
|
intptr_t idx = register_kind_ == Location::kRegister
|
|
? flow_graph_.graph_entry()->fixed_slot_count()
|
|
: 0;
|
|
for (; idx < spill_slots_.length(); idx++) {
|
|
if ((need_quad == quad_spill_slots_[idx]) &&
|
|
(need_untagged == untagged_spill_slots_[idx]) &&
|
|
(spill_slots_[idx] <= start)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (idx == spill_slots_.length()) {
|
|
// No free spill slot found. Allocate a new one.
|
|
spill_slots_.Add(0);
|
|
quad_spill_slots_.Add(need_quad);
|
|
untagged_spill_slots_.Add(need_untagged);
|
|
if (need_quad) { // Allocate two double stack slots if we need quad slot.
|
|
spill_slots_.Add(0);
|
|
quad_spill_slots_.Add(need_quad);
|
|
untagged_spill_slots_.Add(need_untagged);
|
|
}
|
|
}
|
|
|
|
// Set spill slot expiration boundary to the live range's end.
|
|
spill_slots_[idx] = end;
|
|
if (need_quad) {
|
|
ASSERT(quad_spill_slots_[idx] && quad_spill_slots_[idx + 1]);
|
|
idx++; // Use the higher index it corresponds to the lower stack address.
|
|
spill_slots_[idx] = end;
|
|
} else {
|
|
ASSERT(!quad_spill_slots_[idx]);
|
|
}
|
|
|
|
// Assign spill slot to the range.
|
|
if (register_kind_ == Location::kRegister) {
|
|
range->set_spill_slot(Location::StackSlot(idx));
|
|
} else {
|
|
// We use the index of the slot with the lowest address as an index for the
|
|
// FPU register spill slot. In terms of indexes this relation is inverted:
|
|
// so we have to take the highest index.
|
|
const intptr_t slot_idx = cpu_spill_slot_count_ + idx * kDoubleSpillFactor +
|
|
(kDoubleSpillFactor - 1);
|
|
|
|
Location location;
|
|
if ((range->representation() == kUnboxedFloat32x4) ||
|
|
(range->representation() == kUnboxedInt32x4) ||
|
|
(range->representation() == kUnboxedFloat64x2)) {
|
|
ASSERT(need_quad);
|
|
location = Location::QuadStackSlot(slot_idx);
|
|
} else {
|
|
ASSERT((range->representation() == kUnboxedDouble));
|
|
location = Location::DoubleStackSlot(slot_idx);
|
|
}
|
|
range->set_spill_slot(location);
|
|
}
|
|
|
|
spilled_.Add(range);
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::MarkAsObjectAtSafepoints(LiveRange* range) {
|
|
intptr_t stack_index = range->spill_slot().stack_index();
|
|
ASSERT(stack_index >= 0);
|
|
|
|
while (range != NULL) {
|
|
for (SafepointPosition* safepoint = range->first_safepoint();
|
|
safepoint != NULL; safepoint = safepoint->next()) {
|
|
// Mark the stack slot as having an object.
|
|
safepoint->locs()->SetStackBit(stack_index);
|
|
}
|
|
range = range->next_sibling();
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::Spill(LiveRange* range) {
|
|
LiveRange* parent = GetLiveRange(range->vreg());
|
|
if (parent->spill_slot().IsInvalid()) {
|
|
AllocateSpillSlotFor(parent);
|
|
if (range->representation() == kTagged) {
|
|
MarkAsObjectAtSafepoints(parent);
|
|
}
|
|
}
|
|
range->set_assigned_location(parent->spill_slot());
|
|
ConvertAllUses(range);
|
|
}
|
|
|
|
|
|
intptr_t FlowGraphAllocator::FirstIntersectionWithAllocated(
|
|
intptr_t reg,
|
|
LiveRange* unallocated) {
|
|
intptr_t intersection = kMaxPosition;
|
|
for (intptr_t i = 0; i < registers_[reg]->length(); i++) {
|
|
LiveRange* allocated = (*registers_[reg])[i];
|
|
if (allocated == NULL) continue;
|
|
|
|
UseInterval* allocated_head =
|
|
allocated->finger()->first_pending_use_interval();
|
|
if (allocated_head->start() >= intersection) continue;
|
|
|
|
const intptr_t pos = FirstIntersection(
|
|
unallocated->finger()->first_pending_use_interval(), allocated_head);
|
|
if (pos < intersection) intersection = pos;
|
|
}
|
|
return intersection;
|
|
}
|
|
|
|
|
|
void ReachingDefs::AddPhi(PhiInstr* phi) {
|
|
if (phi->reaching_defs() == NULL) {
|
|
Zone* zone = flow_graph_.zone();
|
|
phi->set_reaching_defs(
|
|
new (zone) BitVector(zone, flow_graph_.max_virtual_register_number()));
|
|
|
|
// Compute initial set reaching defs set.
|
|
bool depends_on_phi = false;
|
|
for (intptr_t i = 0; i < phi->InputCount(); i++) {
|
|
Definition* input = phi->InputAt(i)->definition();
|
|
if (input->IsPhi()) {
|
|
depends_on_phi = true;
|
|
}
|
|
phi->reaching_defs()->Add(input->ssa_temp_index());
|
|
if (phi->HasPairRepresentation()) {
|
|
phi->reaching_defs()->Add(ToSecondPairVreg(input->ssa_temp_index()));
|
|
}
|
|
}
|
|
|
|
// If this phi depends on another phi then we need fix point iteration.
|
|
if (depends_on_phi) phis_.Add(phi);
|
|
}
|
|
}
|
|
|
|
|
|
void ReachingDefs::Compute() {
|
|
// Transitively collect all phis that are used by the given phi.
|
|
for (intptr_t i = 0; i < phis_.length(); i++) {
|
|
PhiInstr* phi = phis_[i];
|
|
|
|
// Add all phis that affect this phi to the list.
|
|
for (intptr_t i = 0; i < phi->InputCount(); i++) {
|
|
PhiInstr* input_phi = phi->InputAt(i)->definition()->AsPhi();
|
|
if (input_phi != NULL) {
|
|
AddPhi(input_phi);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Propagate values until fix point is reached.
|
|
bool changed;
|
|
do {
|
|
changed = false;
|
|
for (intptr_t i = 0; i < phis_.length(); i++) {
|
|
PhiInstr* phi = phis_[i];
|
|
for (intptr_t i = 0; i < phi->InputCount(); i++) {
|
|
PhiInstr* input_phi = phi->InputAt(i)->definition()->AsPhi();
|
|
if (input_phi != NULL) {
|
|
if (phi->reaching_defs()->AddAll(input_phi->reaching_defs())) {
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (changed);
|
|
|
|
phis_.Clear();
|
|
}
|
|
|
|
|
|
BitVector* ReachingDefs::Get(PhiInstr* phi) {
|
|
if (phi->reaching_defs() == NULL) {
|
|
ASSERT(phis_.is_empty());
|
|
AddPhi(phi);
|
|
Compute();
|
|
}
|
|
return phi->reaching_defs();
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::AllocateFreeRegister(LiveRange* unallocated) {
|
|
intptr_t candidate = kNoRegister;
|
|
intptr_t free_until = 0;
|
|
|
|
// If hint is available try hint first.
|
|
// TODO(vegorov): ensure that phis are hinted on the back edge.
|
|
Location hint = unallocated->finger()->FirstHint();
|
|
if (hint.IsMachineRegister()) {
|
|
if (!blocked_registers_[hint.register_code()]) {
|
|
free_until =
|
|
FirstIntersectionWithAllocated(hint.register_code(), unallocated);
|
|
candidate = hint.register_code();
|
|
}
|
|
|
|
TRACE_ALLOC(THR_Print("found hint %s for v%" Pd ": free until %" Pd "\n",
|
|
hint.Name(), unallocated->vreg(), free_until));
|
|
} else {
|
|
for (intptr_t reg = 0; reg < NumberOfRegisters(); ++reg) {
|
|
if (!blocked_registers_[reg] && (registers_[reg]->length() == 0)) {
|
|
candidate = reg;
|
|
free_until = kMaxPosition;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(0 <= kMaxPosition);
|
|
if (free_until != kMaxPosition) {
|
|
for (intptr_t reg = 0; reg < NumberOfRegisters(); ++reg) {
|
|
if (blocked_registers_[reg] || (reg == candidate)) continue;
|
|
const intptr_t intersection =
|
|
FirstIntersectionWithAllocated(reg, unallocated);
|
|
if (intersection > free_until) {
|
|
candidate = reg;
|
|
free_until = intersection;
|
|
if (free_until == kMaxPosition) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// All registers are blocked by active ranges.
|
|
if (free_until <= unallocated->Start()) return false;
|
|
|
|
// We have a very good candidate (either hinted to us or completely free).
|
|
// If we are in a loop try to reduce number of moves on the back edge by
|
|
// searching for a candidate that does not interfere with phis on the back
|
|
// edge.
|
|
BlockInfo* loop_header = BlockInfoAt(unallocated->Start())->loop_header();
|
|
if ((unallocated->vreg() >= 0) && (loop_header != NULL) &&
|
|
(free_until >= loop_header->last_block()->end_pos()) &&
|
|
loop_header->backedge_interference()->Contains(unallocated->vreg())) {
|
|
GrowableArray<bool> used_on_backedge(number_of_registers_);
|
|
for (intptr_t i = 0; i < number_of_registers_; i++) {
|
|
used_on_backedge.Add(false);
|
|
}
|
|
|
|
for (PhiIterator it(loop_header->entry()->AsJoinEntry()); !it.Done();
|
|
it.Advance()) {
|
|
PhiInstr* phi = it.Current();
|
|
ASSERT(phi->is_alive());
|
|
const intptr_t phi_vreg = phi->ssa_temp_index();
|
|
LiveRange* range = GetLiveRange(phi_vreg);
|
|
if (range->assigned_location().kind() == register_kind_) {
|
|
const intptr_t reg = range->assigned_location().register_code();
|
|
if (!reaching_defs_.Get(phi)->Contains(unallocated->vreg())) {
|
|
used_on_backedge[reg] = true;
|
|
}
|
|
}
|
|
if (phi->HasPairRepresentation()) {
|
|
const intptr_t second_phi_vreg = ToSecondPairVreg(phi_vreg);
|
|
LiveRange* second_range = GetLiveRange(second_phi_vreg);
|
|
if (second_range->assigned_location().kind() == register_kind_) {
|
|
const intptr_t reg =
|
|
second_range->assigned_location().register_code();
|
|
if (!reaching_defs_.Get(phi)->Contains(unallocated->vreg())) {
|
|
used_on_backedge[reg] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (used_on_backedge[candidate]) {
|
|
TRACE_ALLOC(THR_Print("considering %s for v%" Pd
|
|
": has interference on the back edge"
|
|
" {loop [%" Pd ", %" Pd ")}\n",
|
|
MakeRegisterLocation(candidate).Name(),
|
|
unallocated->vreg(),
|
|
loop_header->entry()->start_pos(),
|
|
loop_header->last_block()->end_pos()));
|
|
for (intptr_t reg = 0; reg < NumberOfRegisters(); ++reg) {
|
|
if (blocked_registers_[reg] || (reg == candidate) ||
|
|
used_on_backedge[reg]) {
|
|
continue;
|
|
}
|
|
|
|
const intptr_t intersection =
|
|
FirstIntersectionWithAllocated(reg, unallocated);
|
|
if (intersection >= free_until) {
|
|
candidate = reg;
|
|
free_until = intersection;
|
|
TRACE_ALLOC(THR_Print(
|
|
"found %s for v%" Pd " with no interference on the back edge\n",
|
|
MakeRegisterLocation(candidate).Name(), candidate));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TRACE_ALLOC(THR_Print("assigning free register "));
|
|
TRACE_ALLOC(MakeRegisterLocation(candidate).Print());
|
|
TRACE_ALLOC(THR_Print(" to v%" Pd "\n", unallocated->vreg()));
|
|
|
|
if (free_until != kMaxPosition) {
|
|
// There was an intersection. Split unallocated.
|
|
TRACE_ALLOC(THR_Print(" splitting at %" Pd "\n", free_until));
|
|
LiveRange* tail = unallocated->SplitAt(free_until);
|
|
AddToUnallocated(tail);
|
|
}
|
|
|
|
registers_[candidate]->Add(unallocated);
|
|
unallocated->set_assigned_location(MakeRegisterLocation(candidate));
|
|
#if defined(TARGET_ARCH_DBC)
|
|
last_used_register_ = Utils::Maximum(last_used_register_, candidate);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::RangeHasOnlyUnconstrainedUsesInLoop(LiveRange* range,
|
|
intptr_t loop_id) {
|
|
if (range->vreg() >= 0) {
|
|
LiveRange* parent = GetLiveRange(range->vreg());
|
|
return parent->HasOnlyUnconstrainedUsesInLoop(loop_id);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::IsCheapToEvictRegisterInLoop(BlockInfo* loop,
|
|
intptr_t reg) {
|
|
const intptr_t loop_start = loop->entry()->start_pos();
|
|
const intptr_t loop_end = loop->last_block()->end_pos();
|
|
|
|
for (intptr_t i = 0; i < registers_[reg]->length(); i++) {
|
|
LiveRange* allocated = (*registers_[reg])[i];
|
|
|
|
UseInterval* interval = allocated->finger()->first_pending_use_interval();
|
|
if (interval->Contains(loop_start)) {
|
|
if (!RangeHasOnlyUnconstrainedUsesInLoop(allocated, loop->loop_id())) {
|
|
return false;
|
|
}
|
|
} else if (interval->start() < loop_end) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::HasCheapEvictionCandidate(LiveRange* phi_range) {
|
|
ASSERT(phi_range->is_loop_phi());
|
|
|
|
BlockInfo* loop_header = BlockInfoAt(phi_range->Start());
|
|
ASSERT(loop_header->is_loop_header());
|
|
ASSERT(phi_range->Start() == loop_header->entry()->start_pos());
|
|
|
|
for (intptr_t reg = 0; reg < NumberOfRegisters(); ++reg) {
|
|
if (blocked_registers_[reg]) continue;
|
|
if (IsCheapToEvictRegisterInLoop(loop_header, reg)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AllocateAnyRegister(LiveRange* unallocated) {
|
|
// If a loop phi has no register uses we might still want to allocate it
|
|
// to the register to reduce amount of memory moves on the back edge.
|
|
// This is possible if there is a register blocked by a range that can be
|
|
// cheaply evicted i.e. it has no register beneficial uses inside the
|
|
// loop.
|
|
UsePosition* register_use =
|
|
unallocated->finger()->FirstRegisterUse(unallocated->Start());
|
|
if ((register_use == NULL) &&
|
|
!(unallocated->is_loop_phi() && HasCheapEvictionCandidate(unallocated))) {
|
|
Spill(unallocated);
|
|
return;
|
|
}
|
|
|
|
intptr_t candidate = kNoRegister;
|
|
intptr_t free_until = 0;
|
|
intptr_t blocked_at = kMaxPosition;
|
|
|
|
for (int reg = 0; reg < NumberOfRegisters(); ++reg) {
|
|
if (blocked_registers_[reg]) continue;
|
|
if (UpdateFreeUntil(reg, unallocated, &free_until, &blocked_at)) {
|
|
candidate = reg;
|
|
}
|
|
}
|
|
|
|
const intptr_t register_use_pos =
|
|
(register_use != NULL) ? register_use->pos() : unallocated->Start();
|
|
if (free_until < register_use_pos) {
|
|
// Can't acquire free register. Spill until we really need one.
|
|
ASSERT(unallocated->Start() < ToInstructionStart(register_use_pos));
|
|
SpillBetween(unallocated, unallocated->Start(), register_use->pos());
|
|
return;
|
|
}
|
|
|
|
ASSERT(candidate != kNoRegister);
|
|
|
|
TRACE_ALLOC(THR_Print("assigning blocked register "));
|
|
TRACE_ALLOC(MakeRegisterLocation(candidate).Print());
|
|
TRACE_ALLOC(THR_Print(" to live range v%" Pd " until %" Pd "\n",
|
|
unallocated->vreg(), blocked_at));
|
|
|
|
if (blocked_at < unallocated->End()) {
|
|
// Register is blocked before the end of the live range. Split the range
|
|
// at latest at blocked_at position.
|
|
LiveRange* tail =
|
|
SplitBetween(unallocated, unallocated->Start(), blocked_at + 1);
|
|
AddToUnallocated(tail);
|
|
}
|
|
|
|
AssignNonFreeRegister(unallocated, candidate);
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::UpdateFreeUntil(intptr_t reg,
|
|
LiveRange* unallocated,
|
|
intptr_t* cur_free_until,
|
|
intptr_t* cur_blocked_at) {
|
|
intptr_t free_until = kMaxPosition;
|
|
intptr_t blocked_at = kMaxPosition;
|
|
const intptr_t start = unallocated->Start();
|
|
|
|
for (intptr_t i = 0; i < registers_[reg]->length(); i++) {
|
|
LiveRange* allocated = (*registers_[reg])[i];
|
|
|
|
UseInterval* first_pending_use_interval =
|
|
allocated->finger()->first_pending_use_interval();
|
|
if (first_pending_use_interval->Contains(start)) {
|
|
// This is an active interval.
|
|
if (allocated->vreg() < 0) {
|
|
// This register blocked by an interval that
|
|
// can't be spilled.
|
|
return false;
|
|
}
|
|
|
|
UsePosition* use = allocated->finger()->FirstInterferingUse(start);
|
|
if ((use != NULL) && ((ToInstructionStart(use->pos()) - start) <= 1)) {
|
|
// This register is blocked by interval that is used
|
|
// as register in the current instruction and can't
|
|
// be spilled.
|
|
return false;
|
|
}
|
|
|
|
const intptr_t use_pos = (use != NULL) ? use->pos() : allocated->End();
|
|
|
|
if (use_pos < free_until) free_until = use_pos;
|
|
} else {
|
|
// This is inactive interval.
|
|
const intptr_t intersection = FirstIntersection(
|
|
first_pending_use_interval, unallocated->first_use_interval());
|
|
if (intersection != kMaxPosition) {
|
|
if (intersection < free_until) free_until = intersection;
|
|
if (allocated->vreg() == kNoVirtualRegister) blocked_at = intersection;
|
|
}
|
|
}
|
|
|
|
if (free_until <= *cur_free_until) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ASSERT(free_until > *cur_free_until);
|
|
*cur_free_until = free_until;
|
|
*cur_blocked_at = blocked_at;
|
|
return true;
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::RemoveEvicted(intptr_t reg, intptr_t first_evicted) {
|
|
intptr_t to = first_evicted;
|
|
intptr_t from = first_evicted + 1;
|
|
while (from < registers_[reg]->length()) {
|
|
LiveRange* allocated = (*registers_[reg])[from++];
|
|
if (allocated != NULL) (*registers_[reg])[to++] = allocated;
|
|
}
|
|
registers_[reg]->TruncateTo(to);
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AssignNonFreeRegister(LiveRange* unallocated,
|
|
intptr_t reg) {
|
|
intptr_t first_evicted = -1;
|
|
for (intptr_t i = registers_[reg]->length() - 1; i >= 0; i--) {
|
|
LiveRange* allocated = (*registers_[reg])[i];
|
|
if (allocated->vreg() < 0) continue; // Can't be evicted.
|
|
if (EvictIntersection(allocated, unallocated)) {
|
|
// If allocated was not spilled convert all pending uses.
|
|
if (allocated->assigned_location().IsMachineRegister()) {
|
|
ASSERT(allocated->End() <= unallocated->Start());
|
|
ConvertAllUses(allocated);
|
|
}
|
|
(*registers_[reg])[i] = NULL;
|
|
first_evicted = i;
|
|
}
|
|
}
|
|
|
|
// Remove evicted ranges from the array.
|
|
if (first_evicted != -1) RemoveEvicted(reg, first_evicted);
|
|
|
|
registers_[reg]->Add(unallocated);
|
|
unallocated->set_assigned_location(MakeRegisterLocation(reg));
|
|
#if defined(TARGET_ARCH_DBC)
|
|
last_used_register_ = Utils::Maximum(last_used_register_, reg);
|
|
#endif
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::EvictIntersection(LiveRange* allocated,
|
|
LiveRange* unallocated) {
|
|
UseInterval* first_unallocated =
|
|
unallocated->finger()->first_pending_use_interval();
|
|
const intptr_t intersection = FirstIntersection(
|
|
allocated->finger()->first_pending_use_interval(), first_unallocated);
|
|
if (intersection == kMaxPosition) return false;
|
|
|
|
const intptr_t spill_position = first_unallocated->start();
|
|
UsePosition* use = allocated->finger()->FirstInterferingUse(spill_position);
|
|
if (use == NULL) {
|
|
// No register uses after this point.
|
|
SpillAfter(allocated, spill_position);
|
|
} else {
|
|
const intptr_t restore_position =
|
|
(spill_position < intersection) ? MinPosition(intersection, use->pos())
|
|
: use->pos();
|
|
|
|
SpillBetween(allocated, spill_position, restore_position);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
MoveOperands* FlowGraphAllocator::AddMoveAt(intptr_t pos,
|
|
Location to,
|
|
Location from) {
|
|
ASSERT(!IsBlockEntry(pos));
|
|
|
|
if (pos < kNormalEntryPos) {
|
|
ASSERT(pos > 0);
|
|
// Parallel moves added to the GraphEntry (B0) will be added at the start
|
|
// of the normal entry (B1)
|
|
BlockEntryInstr* entry = InstructionAt(kNormalEntryPos)->AsBlockEntry();
|
|
return entry->GetParallelMove()->AddMove(to, from);
|
|
}
|
|
|
|
Instruction* instr = InstructionAt(pos);
|
|
|
|
ParallelMoveInstr* parallel_move = NULL;
|
|
if (IsInstructionStartPosition(pos)) {
|
|
parallel_move = CreateParallelMoveBefore(instr, pos);
|
|
} else {
|
|
parallel_move = CreateParallelMoveAfter(instr, pos);
|
|
}
|
|
|
|
return parallel_move->AddMove(to, from);
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::ConvertUseTo(UsePosition* use, Location loc) {
|
|
ASSERT(!loc.IsPairLocation());
|
|
ASSERT(use->location_slot() != NULL);
|
|
Location* slot = use->location_slot();
|
|
ASSERT(slot->IsUnallocated());
|
|
TRACE_ALLOC(THR_Print(" use at %" Pd " converted to ", use->pos()));
|
|
TRACE_ALLOC(loc.Print());
|
|
TRACE_ALLOC(THR_Print("\n"));
|
|
*slot = loc;
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::ConvertAllUses(LiveRange* range) {
|
|
if (range->vreg() == kNoVirtualRegister) return;
|
|
|
|
const Location loc = range->assigned_location();
|
|
ASSERT(!loc.IsInvalid());
|
|
|
|
TRACE_ALLOC(THR_Print("range [%" Pd ", %" Pd ") "
|
|
"for v%" Pd " has been allocated to ",
|
|
range->Start(), range->End(), range->vreg()));
|
|
TRACE_ALLOC(loc.Print());
|
|
TRACE_ALLOC(THR_Print(":\n"));
|
|
|
|
for (UsePosition* use = range->first_use(); use != NULL; use = use->next()) {
|
|
ConvertUseTo(use, loc);
|
|
}
|
|
|
|
// Add live registers at all safepoints for instructions with slow-path
|
|
// code.
|
|
if (loc.IsMachineRegister()) {
|
|
for (SafepointPosition* safepoint = range->first_safepoint();
|
|
safepoint != NULL; safepoint = safepoint->next()) {
|
|
#if !defined(TARGET_ARCH_DBC)
|
|
if (!safepoint->locs()->always_calls()) {
|
|
ASSERT(safepoint->locs()->can_call());
|
|
safepoint->locs()->live_registers()->Add(loc, range->representation());
|
|
}
|
|
#else
|
|
if (range->representation() == kTagged) {
|
|
safepoint->locs()->SetStackBit(loc.reg());
|
|
}
|
|
#endif // !defined(TARGET_ARCH_DBC)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AdvanceActiveIntervals(const intptr_t start) {
|
|
for (intptr_t reg = 0; reg < NumberOfRegisters(); reg++) {
|
|
if (registers_[reg]->is_empty()) continue;
|
|
|
|
intptr_t first_evicted = -1;
|
|
for (intptr_t i = registers_[reg]->length() - 1; i >= 0; i--) {
|
|
LiveRange* range = (*registers_[reg])[i];
|
|
if (range->finger()->Advance(start)) {
|
|
ConvertAllUses(range);
|
|
(*registers_[reg])[i] = NULL;
|
|
first_evicted = i;
|
|
}
|
|
}
|
|
|
|
if (first_evicted != -1) RemoveEvicted(reg, first_evicted);
|
|
}
|
|
}
|
|
|
|
|
|
bool LiveRange::Contains(intptr_t pos) const {
|
|
if (!CanCover(pos)) return false;
|
|
|
|
for (UseInterval* interval = first_use_interval_; interval != NULL;
|
|
interval = interval->next()) {
|
|
if (interval->Contains(pos)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AssignSafepoints(Definition* defn, LiveRange* range) {
|
|
for (intptr_t i = safepoints_.length() - 1; i >= 0; i--) {
|
|
Instruction* safepoint_instr = safepoints_[i];
|
|
if (safepoint_instr == defn) {
|
|
// The value is not live until after the definition is fully executed,
|
|
// don't assign the safepoint inside the definition itself to
|
|
// definition's liverange.
|
|
continue;
|
|
}
|
|
|
|
const intptr_t pos = safepoint_instr->lifetime_position();
|
|
if (range->End() <= pos) break;
|
|
|
|
if (range->Contains(pos)) {
|
|
range->AddSafepoint(pos, safepoint_instr->locs());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static inline bool ShouldBeAllocatedBefore(LiveRange* a, LiveRange* b) {
|
|
// TODO(vegorov): consider first hint position when ordering live ranges.
|
|
return a->Start() <= b->Start();
|
|
}
|
|
|
|
|
|
static void AddToSortedListOfRanges(GrowableArray<LiveRange*>* list,
|
|
LiveRange* range) {
|
|
range->finger()->Initialize(range);
|
|
|
|
if (list->is_empty()) {
|
|
list->Add(range);
|
|
return;
|
|
}
|
|
|
|
for (intptr_t i = list->length() - 1; i >= 0; i--) {
|
|
if (ShouldBeAllocatedBefore(range, (*list)[i])) {
|
|
list->InsertAt(i + 1, range);
|
|
return;
|
|
}
|
|
}
|
|
list->InsertAt(0, range);
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AddToUnallocated(LiveRange* range) {
|
|
AddToSortedListOfRanges(&unallocated_, range);
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::CompleteRange(LiveRange* range, Location::Kind kind) {
|
|
switch (kind) {
|
|
case Location::kRegister:
|
|
AddToSortedListOfRanges(&unallocated_cpu_, range);
|
|
break;
|
|
|
|
case Location::kFpuRegister:
|
|
AddToSortedListOfRanges(&unallocated_xmm_, range);
|
|
break;
|
|
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
|
|
#if defined(DEBUG)
|
|
bool FlowGraphAllocator::UnallocatedIsSorted() {
|
|
for (intptr_t i = unallocated_.length() - 1; i >= 1; i--) {
|
|
LiveRange* a = unallocated_[i];
|
|
LiveRange* b = unallocated_[i - 1];
|
|
if (!ShouldBeAllocatedBefore(a, b)) {
|
|
UNREACHABLE();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
void FlowGraphAllocator::PrepareForAllocation(
|
|
Location::Kind register_kind,
|
|
intptr_t number_of_registers,
|
|
const GrowableArray<LiveRange*>& unallocated,
|
|
LiveRange** blocking_ranges,
|
|
bool* blocked_registers) {
|
|
register_kind_ = register_kind;
|
|
number_of_registers_ = number_of_registers;
|
|
|
|
blocked_registers_.Clear();
|
|
registers_.Clear();
|
|
for (intptr_t i = 0; i < number_of_registers_; i++) {
|
|
blocked_registers_.Add(false);
|
|
registers_.Add(new ZoneGrowableArray<LiveRange*>);
|
|
}
|
|
ASSERT(unallocated_.is_empty());
|
|
unallocated_.AddArray(unallocated);
|
|
|
|
for (intptr_t reg = 0; reg < number_of_registers; reg++) {
|
|
blocked_registers_[reg] = blocked_registers[reg];
|
|
ASSERT(registers_[reg]->is_empty());
|
|
|
|
LiveRange* range = blocking_ranges[reg];
|
|
if (range != NULL) {
|
|
range->finger()->Initialize(range);
|
|
registers_[reg]->Add(range);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AllocateUnallocatedRanges() {
|
|
#if defined(DEBUG)
|
|
ASSERT(UnallocatedIsSorted());
|
|
#endif
|
|
|
|
while (!unallocated_.is_empty()) {
|
|
LiveRange* range = unallocated_.RemoveLast();
|
|
const intptr_t start = range->Start();
|
|
TRACE_ALLOC(THR_Print("Processing live range for v%" Pd " "
|
|
"starting at %" Pd "\n",
|
|
range->vreg(), start));
|
|
|
|
// TODO(vegorov): eagerly spill liveranges without register uses.
|
|
AdvanceActiveIntervals(start);
|
|
|
|
if (!AllocateFreeRegister(range)) {
|
|
if (intrinsic_mode_) {
|
|
// No spilling when compiling intrinsics.
|
|
// TODO(fschneider): Handle spilling in intrinsics. For now, the
|
|
// IR has to be built so that there are enough free registers.
|
|
UNREACHABLE();
|
|
}
|
|
AllocateAnyRegister(range);
|
|
}
|
|
}
|
|
|
|
// All allocation decisions were done.
|
|
ASSERT(unallocated_.is_empty());
|
|
|
|
// Finish allocation.
|
|
AdvanceActiveIntervals(kMaxPosition);
|
|
TRACE_ALLOC(THR_Print("Allocation completed\n"));
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::TargetLocationIsSpillSlot(LiveRange* range,
|
|
Location target) {
|
|
if (target.IsStackSlot() || target.IsDoubleStackSlot() ||
|
|
target.IsConstant()) {
|
|
ASSERT(GetLiveRange(range->vreg())->spill_slot().Equals(target));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::ConnectSplitSiblings(LiveRange* parent,
|
|
BlockEntryInstr* source_block,
|
|
BlockEntryInstr* target_block) {
|
|
TRACE_ALLOC(THR_Print("Connect v%" Pd " on the edge B%" Pd " -> B%" Pd "\n",
|
|
parent->vreg(), source_block->block_id(),
|
|
target_block->block_id()));
|
|
if (parent->next_sibling() == NULL) {
|
|
// Nothing to connect. The whole range was allocated to the same location.
|
|
TRACE_ALLOC(THR_Print("range v%" Pd " has no siblings\n", parent->vreg()));
|
|
return;
|
|
}
|
|
|
|
const intptr_t source_pos = source_block->end_pos() - 1;
|
|
ASSERT(IsInstructionEndPosition(source_pos));
|
|
|
|
const intptr_t target_pos = target_block->start_pos();
|
|
|
|
Location target;
|
|
Location source;
|
|
|
|
#if defined(DEBUG)
|
|
LiveRange* source_cover = NULL;
|
|
LiveRange* target_cover = NULL;
|
|
#endif
|
|
|
|
LiveRange* range = parent;
|
|
while ((range != NULL) && (source.IsInvalid() || target.IsInvalid())) {
|
|
if (range->CanCover(source_pos)) {
|
|
ASSERT(source.IsInvalid());
|
|
source = range->assigned_location();
|
|
#if defined(DEBUG)
|
|
source_cover = range;
|
|
#endif
|
|
}
|
|
if (range->CanCover(target_pos)) {
|
|
ASSERT(target.IsInvalid());
|
|
target = range->assigned_location();
|
|
#if defined(DEBUG)
|
|
target_cover = range;
|
|
#endif
|
|
}
|
|
|
|
range = range->next_sibling();
|
|
}
|
|
|
|
TRACE_ALLOC(THR_Print("connecting v%" Pd " between [%" Pd ", %" Pd ") {%s} "
|
|
"to [%" Pd ", %" Pd ") {%s}\n",
|
|
parent->vreg(), source_cover->Start(),
|
|
source_cover->End(), source.Name(),
|
|
target_cover->Start(), target_cover->End(),
|
|
target.Name()));
|
|
|
|
// Siblings were allocated to the same register.
|
|
if (source.Equals(target)) return;
|
|
|
|
// Values are eagerly spilled. Spill slot already contains appropriate value.
|
|
if (TargetLocationIsSpillSlot(parent, target)) {
|
|
return;
|
|
}
|
|
|
|
Instruction* last = source_block->last_instruction();
|
|
if ((last->SuccessorCount() == 1) && !source_block->IsGraphEntry()) {
|
|
ASSERT(last->IsGoto());
|
|
last->AsGoto()->GetParallelMove()->AddMove(target, source);
|
|
} else {
|
|
target_block->GetParallelMove()->AddMove(target, source);
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::ResolveControlFlow() {
|
|
// Resolve linear control flow between touching split siblings
|
|
// inside basic blocks.
|
|
for (intptr_t vreg = 0; vreg < live_ranges_.length(); vreg++) {
|
|
LiveRange* range = live_ranges_[vreg];
|
|
if (range == NULL) continue;
|
|
|
|
while (range->next_sibling() != NULL) {
|
|
LiveRange* sibling = range->next_sibling();
|
|
TRACE_ALLOC(THR_Print("connecting [%" Pd ", %" Pd ") [", range->Start(),
|
|
range->End()));
|
|
TRACE_ALLOC(range->assigned_location().Print());
|
|
TRACE_ALLOC(THR_Print("] to [%" Pd ", %" Pd ") [", sibling->Start(),
|
|
sibling->End()));
|
|
TRACE_ALLOC(sibling->assigned_location().Print());
|
|
TRACE_ALLOC(THR_Print("]\n"));
|
|
if ((range->End() == sibling->Start()) &&
|
|
!TargetLocationIsSpillSlot(range, sibling->assigned_location()) &&
|
|
!range->assigned_location().Equals(sibling->assigned_location()) &&
|
|
!IsBlockEntry(range->End())) {
|
|
AddMoveAt(sibling->Start(), sibling->assigned_location(),
|
|
range->assigned_location());
|
|
}
|
|
range = sibling;
|
|
}
|
|
}
|
|
|
|
// Resolve non-linear control flow across branches.
|
|
for (intptr_t i = 1; i < block_order_.length(); i++) {
|
|
BlockEntryInstr* block = block_order_[i];
|
|
BitVector* live = liveness_.GetLiveInSet(block);
|
|
for (BitVector::Iterator it(live); !it.Done(); it.Advance()) {
|
|
LiveRange* range = GetLiveRange(it.Current());
|
|
for (intptr_t j = 0; j < block->PredecessorCount(); j++) {
|
|
ConnectSplitSiblings(range, block->PredecessorAt(j), block);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Eagerly spill values.
|
|
// TODO(vegorov): if value is spilled on the cold path (e.g. by the call)
|
|
// this will cause spilling to occur on the fast path (at the definition).
|
|
for (intptr_t i = 0; i < spilled_.length(); i++) {
|
|
LiveRange* range = spilled_[i];
|
|
if (range->assigned_location().IsStackSlot() ||
|
|
range->assigned_location().IsDoubleStackSlot() ||
|
|
range->assigned_location().IsConstant()) {
|
|
ASSERT(range->assigned_location().Equals(range->spill_slot()));
|
|
} else {
|
|
AddMoveAt(range->Start() + 1, range->spill_slot(),
|
|
range->assigned_location());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static Representation RepresentationForRange(Representation definition_rep) {
|
|
if (definition_rep == kUnboxedMint) {
|
|
// kUnboxedMint is split into two ranges, each of which are kUntagged.
|
|
return kUntagged;
|
|
} else if (definition_rep == kUnboxedUint32) {
|
|
// kUnboxedUint32 is untagged.
|
|
return kUntagged;
|
|
}
|
|
return definition_rep;
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::CollectRepresentations() {
|
|
// Parameters.
|
|
GraphEntryInstr* graph_entry = flow_graph_.graph_entry();
|
|
for (intptr_t i = 0; i < graph_entry->initial_definitions()->length(); ++i) {
|
|
Definition* def = (*graph_entry->initial_definitions())[i];
|
|
value_representations_[def->ssa_temp_index()] =
|
|
RepresentationForRange(def->representation());
|
|
ASSERT(!def->HasPairRepresentation());
|
|
}
|
|
|
|
for (BlockIterator it = flow_graph_.reverse_postorder_iterator(); !it.Done();
|
|
it.Advance()) {
|
|
BlockEntryInstr* block = it.Current();
|
|
|
|
// Catch entry.
|
|
if (block->IsCatchBlockEntry()) {
|
|
CatchBlockEntryInstr* catch_entry = block->AsCatchBlockEntry();
|
|
for (intptr_t i = 0; i < catch_entry->initial_definitions()->length();
|
|
++i) {
|
|
Definition* def = (*catch_entry->initial_definitions())[i];
|
|
ASSERT(!def->HasPairRepresentation());
|
|
value_representations_[def->ssa_temp_index()] =
|
|
RepresentationForRange(def->representation());
|
|
}
|
|
}
|
|
// Phis.
|
|
if (block->IsJoinEntry()) {
|
|
JoinEntryInstr* join = block->AsJoinEntry();
|
|
for (PhiIterator it(join); !it.Done(); it.Advance()) {
|
|
PhiInstr* phi = it.Current();
|
|
ASSERT(phi != NULL && phi->ssa_temp_index() >= 0);
|
|
value_representations_[phi->ssa_temp_index()] =
|
|
RepresentationForRange(phi->representation());
|
|
if (phi->HasPairRepresentation()) {
|
|
value_representations_[ToSecondPairVreg(phi->ssa_temp_index())] =
|
|
RepresentationForRange(phi->representation());
|
|
}
|
|
}
|
|
}
|
|
// Normal instructions.
|
|
for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
|
|
instr_it.Advance()) {
|
|
Definition* def = instr_it.Current()->AsDefinition();
|
|
if ((def != NULL) && (def->ssa_temp_index() >= 0)) {
|
|
const intptr_t vreg = def->ssa_temp_index();
|
|
value_representations_[vreg] =
|
|
RepresentationForRange(def->representation());
|
|
if (def->HasPairRepresentation()) {
|
|
value_representations_[ToSecondPairVreg(vreg)] =
|
|
RepresentationForRange(def->representation());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AllocateRegisters() {
|
|
CollectRepresentations();
|
|
|
|
liveness_.Analyze();
|
|
|
|
NumberInstructions();
|
|
|
|
DiscoverLoops();
|
|
|
|
#if defined(TARGET_ARCH_DBC)
|
|
last_used_register_ = -1;
|
|
#endif
|
|
|
|
BuildLiveRanges();
|
|
|
|
if (FLAG_print_ssa_liveranges) {
|
|
const Function& function = flow_graph_.function();
|
|
THR_Print("-- [before ssa allocator] ranges [%s] ---------\n",
|
|
function.ToFullyQualifiedCString());
|
|
PrintLiveRanges();
|
|
THR_Print("----------------------------------------------\n");
|
|
|
|
THR_Print("-- [before ssa allocator] ir [%s] -------------\n",
|
|
function.ToFullyQualifiedCString());
|
|
if (FLAG_support_il_printer) {
|
|
#ifndef PRODUCT
|
|
FlowGraphPrinter printer(flow_graph_, true);
|
|
printer.PrintBlocks();
|
|
#endif
|
|
}
|
|
THR_Print("----------------------------------------------\n");
|
|
}
|
|
|
|
PrepareForAllocation(Location::kRegister, kNumberOfCpuRegisters,
|
|
unallocated_cpu_, cpu_regs_, blocked_cpu_registers_);
|
|
AllocateUnallocatedRanges();
|
|
#if defined(TARGET_ARCH_DBC)
|
|
const intptr_t last_used_cpu_register = last_used_register_;
|
|
last_used_register_ = -1;
|
|
#endif
|
|
|
|
cpu_spill_slot_count_ = spill_slots_.length();
|
|
spill_slots_.Clear();
|
|
quad_spill_slots_.Clear();
|
|
untagged_spill_slots_.Clear();
|
|
|
|
PrepareForAllocation(Location::kFpuRegister, kNumberOfFpuRegisters,
|
|
unallocated_xmm_, fpu_regs_, blocked_fpu_registers_);
|
|
#if defined(TARGET_ARCH_DBC)
|
|
// For DBC all registers should have been allocated in the first pass.
|
|
ASSERT(unallocated_.is_empty());
|
|
#endif
|
|
|
|
AllocateUnallocatedRanges();
|
|
#if defined(TARGET_ARCH_DBC)
|
|
const intptr_t last_used_fpu_register = last_used_register_;
|
|
ASSERT(last_used_fpu_register == -1); // Not supported right now.
|
|
#endif
|
|
|
|
ResolveControlFlow();
|
|
|
|
GraphEntryInstr* entry = block_order_[0]->AsGraphEntry();
|
|
ASSERT(entry != NULL);
|
|
intptr_t double_spill_slot_count = spill_slots_.length() * kDoubleSpillFactor;
|
|
entry->set_spill_slot_count(cpu_spill_slot_count_ + double_spill_slot_count);
|
|
|
|
#if defined(TARGET_ARCH_DBC)
|
|
// Spilling is unsupported on DBC.
|
|
if (entry->spill_slot_count() != 0) {
|
|
UNREACHABLE();
|
|
}
|
|
|
|
// We store number of used DBC registers in the spill slot count to avoid
|
|
// introducing a separate field. It has roughly the same meaning:
|
|
// number of used registers determines how big of a frame to reserve for
|
|
// this function on DBC stack.
|
|
entry->set_spill_slot_count(Utils::Maximum(
|
|
(last_used_cpu_register + 1) + (last_used_fpu_register + 1),
|
|
flow_graph_.num_copied_params()));
|
|
#endif
|
|
|
|
if (FLAG_print_ssa_liveranges) {
|
|
const Function& function = flow_graph_.function();
|
|
|
|
THR_Print("-- [after ssa allocator] ranges [%s] ---------\n",
|
|
function.ToFullyQualifiedCString());
|
|
PrintLiveRanges();
|
|
THR_Print("----------------------------------------------\n");
|
|
|
|
THR_Print("-- [after ssa allocator] ir [%s] -------------\n",
|
|
function.ToFullyQualifiedCString());
|
|
if (FLAG_support_il_printer) {
|
|
#ifndef PRODUCT
|
|
FlowGraphPrinter printer(flow_graph_, true);
|
|
printer.PrintBlocks();
|
|
#endif
|
|
}
|
|
THR_Print("----------------------------------------------\n");
|
|
}
|
|
}
|
|
|
|
|
|
} // namespace dart
|