1bd5a9b414
BUG= TEST= Review URL: https://chromiumcodereview.appspot.com//10696151 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@9563 260f80e4-7a28-3924-810f-c04153c831b5
937 lines
28 KiB
C++
937 lines
28 KiB
C++
// Copyright (c) 2012, 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_builder.h"
|
|
#include "vm/flow_graph_compiler.h"
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_FLAG(bool, print_ssa_liveness, false,
|
|
"Print liveness for ssa variables.");
|
|
DEFINE_FLAG(bool, trace_ssa_allocator, false,
|
|
"Trace register allocation over SSA.");
|
|
|
|
#ifdef DEBUG
|
|
#define TRACE_ALLOC(m) do { \
|
|
if (FLAG_trace_ssa_allocator) OS::Print m ; \
|
|
} while (0)
|
|
#else
|
|
#define TRACE_ALLOC(m)
|
|
#endif
|
|
|
|
|
|
static const intptr_t kNoVirtualRegister = -1;
|
|
static const intptr_t kTempVirtualRegister = -2;
|
|
static UseInterval* const kPermanentlyBlocked =
|
|
reinterpret_cast<UseInterval*>(-1);
|
|
static const intptr_t kIllegalPosition = -1;
|
|
static const intptr_t kMaxPosition = 0x7FFFFFFF;
|
|
|
|
|
|
FlowGraphAllocator::FlowGraphAllocator(
|
|
const GrowableArray<BlockEntryInstr*>& block_order,
|
|
FlowGraphBuilder* builder)
|
|
: builder_(builder),
|
|
block_order_(block_order),
|
|
postorder_(builder->postorder_block_entries()),
|
|
live_out_(block_order.length()),
|
|
kill_(block_order.length()),
|
|
live_in_(block_order.length()),
|
|
vreg_count_(builder->current_ssa_temp_index()),
|
|
live_ranges_(builder->current_ssa_temp_index()) {
|
|
for (intptr_t i = 0; i < vreg_count_; i++) live_ranges_.Add(NULL);
|
|
|
|
for (intptr_t reg = 0; reg < kNumberOfCpuRegisters; reg++) {
|
|
cpu_regs_[reg] = NULL;
|
|
}
|
|
|
|
cpu_regs_[CTX] = kPermanentlyBlocked;
|
|
if (TMP != kNoRegister) {
|
|
cpu_regs_[TMP] = kPermanentlyBlocked;
|
|
}
|
|
cpu_regs_[SPREG] = kPermanentlyBlocked;
|
|
cpu_regs_[FPREG] = kPermanentlyBlocked;
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::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];
|
|
|
|
if (block->IsJoinEntry()) {
|
|
JoinEntryInstr* join = block->AsJoinEntry();
|
|
if (join->phis() != NULL) {
|
|
for (intptr_t j = 0; j < join->phis()->length(); j++) {
|
|
PhiInstr* phi = (*join->phis())[j];
|
|
if (phi == NULL) continue;
|
|
kill->Add(phi->ssa_temp_index());
|
|
|
|
for (intptr_t k = 0; k < phi->InputCount(); k++) {
|
|
Value* val = phi->InputAt(k);
|
|
if (val->IsUse()) {
|
|
BlockEntryInstr* pred = block->PredecessorAt(k);
|
|
const intptr_t use = val->AsUse()->definition()->ssa_temp_index();
|
|
live_out_[pred->postorder_number()]->Add(use);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(vegorov): iterate backwards.
|
|
for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
|
|
Instruction* current = it.Current();
|
|
for (intptr_t j = 0; j < current->InputCount(); j++) {
|
|
Value* input = current->InputAt(j);
|
|
if (input->IsUse()) {
|
|
const intptr_t use = input->AsUse()->definition()->ssa_temp_index();
|
|
if (!kill->Contains(use)) live_in->Add(use);
|
|
}
|
|
}
|
|
|
|
// Add uses from the deoptimization environment.
|
|
if (current->env() != NULL) {
|
|
const GrowableArray<Value*>& values = current->env()->values();
|
|
for (intptr_t j = 0; j < values.length(); j++) {
|
|
Value* val = values[j];
|
|
if (val->IsUse()) {
|
|
const intptr_t use = val->AsUse()->definition()->ssa_temp_index();
|
|
if (!kill->Contains(use)) live_in->Add(use);
|
|
}
|
|
}
|
|
}
|
|
|
|
Definition* current_def = current->AsDefinition();
|
|
if ((current_def != NULL) && (current_def->ssa_temp_index() >= 0)) {
|
|
kill->Add(current_def->ssa_temp_index());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update initial live_in sets to match live_out sets. Has to be
|
|
// done in a separate path because of backwards branches.
|
|
for (intptr_t i = 0; i < block_count; i++) {
|
|
UpdateLiveIn(postorder_[i]);
|
|
}
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::UpdateLiveOut(BlockEntryInstr* instr) {
|
|
BitVector* live_out = live_out_[instr->postorder_number()];
|
|
bool changed = false;
|
|
Instruction* last = instr->last_instruction();
|
|
ASSERT(last != NULL);
|
|
for (intptr_t i = 0; i < last->SuccessorCount(); i++) {
|
|
BlockEntryInstr* succ = last->SuccessorAt(i);
|
|
ASSERT(succ != NULL);
|
|
if (live_out->AddAll(live_in_[succ->postorder_number()])) {
|
|
changed = true;
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::UpdateLiveIn(BlockEntryInstr* instr) {
|
|
BitVector* live_out = live_out_[instr->postorder_number()];
|
|
BitVector* kill = kill_[instr->postorder_number()];
|
|
BitVector* live_in = live_in_[instr->postorder_number()];
|
|
return live_in->KillAndAdd(kill, live_out);
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::ComputeLiveInAndLiveOutSets() {
|
|
const intptr_t block_count = postorder_.length();
|
|
bool changed;
|
|
do {
|
|
changed = false;
|
|
|
|
for (intptr_t i = 0; i < block_count; i++) {
|
|
BlockEntryInstr* block = postorder_[i];
|
|
|
|
// Live-in set depends only on kill set which does not
|
|
// change in this loop and live-out set. If live-out
|
|
// set does not change there is no need to recompute
|
|
// live-in set.
|
|
if (UpdateLiveOut(block) && UpdateLiveIn(block)) {
|
|
changed = true;
|
|
}
|
|
}
|
|
} while (changed);
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AnalyzeLiveness() {
|
|
const intptr_t block_count = postorder_.length();
|
|
for (intptr_t i = 0; i < block_count; i++) {
|
|
live_out_.Add(new BitVector(vreg_count_));
|
|
kill_.Add(new BitVector(vreg_count_));
|
|
live_in_.Add(new BitVector(vreg_count_));
|
|
}
|
|
|
|
ComputeInitialSets();
|
|
ComputeLiveInAndLiveOutSets();
|
|
}
|
|
|
|
|
|
static void PrintBitVector(const char* tag, BitVector* v) {
|
|
OS::Print("%s:", tag);
|
|
for (BitVector::Iterator it(v); !it.Done(); it.Advance()) {
|
|
OS::Print(" %d", it.Current());
|
|
}
|
|
OS::Print("\n");
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::DumpLiveness() {
|
|
const intptr_t block_count = postorder_.length();
|
|
for (intptr_t i = 0; i < block_count; i++) {
|
|
BlockEntryInstr* block = postorder_[i];
|
|
OS::Print("block @%d -> ", block->block_id());
|
|
|
|
Instruction* last = block->last_instruction();
|
|
for (intptr_t j = 0; j < last->SuccessorCount(); j++) {
|
|
BlockEntryInstr* succ = last->SuccessorAt(j);
|
|
OS::Print(" @%d", succ->block_id());
|
|
}
|
|
OS::Print("\n");
|
|
|
|
PrintBitVector(" live out", live_out_[i]);
|
|
PrintBitVector(" kill", kill_[i]);
|
|
PrintBitVector(" live in", live_in_[i]);
|
|
}
|
|
}
|
|
|
|
|
|
void UseInterval::Print() {
|
|
OS::Print(" [%d, %d) uses {", start_, end_);
|
|
for (UsePosition* use_pos = uses_;
|
|
use_pos != NULL && use_pos->pos() <= end();
|
|
use_pos = use_pos->next()) {
|
|
if (use_pos != uses_) OS::Print(", ");
|
|
OS::Print("%d", use_pos->pos());
|
|
}
|
|
OS::Print("}\n");
|
|
}
|
|
|
|
|
|
void UseInterval::AddUse(Instruction* instr,
|
|
intptr_t pos,
|
|
Location* location_slot) {
|
|
ASSERT((start_ <= pos) && (pos <= end_));
|
|
ASSERT((instr == NULL) || (instr->lifetime_position() == pos));
|
|
if ((uses_ != NULL) && (uses_->pos() == pos)) {
|
|
if ((location_slot == NULL) || (uses_->location_slot() == location_slot)) {
|
|
return;
|
|
} else if ((uses_->location_slot() == NULL) && (instr == NULL)) {
|
|
uses_->set_location_slot(location_slot);
|
|
return;
|
|
}
|
|
}
|
|
uses_ = new UsePosition(instr, pos, uses_, location_slot);
|
|
}
|
|
|
|
|
|
void LiveRange::Print() {
|
|
OS::Print("vreg %d live intervals:\n", vreg_);
|
|
for (UseInterval* interval = head_;
|
|
interval != NULL;
|
|
interval = interval->next_) {
|
|
interval->Print();
|
|
}
|
|
}
|
|
|
|
|
|
void LiveRange::AddUseInterval(intptr_t start, intptr_t end) {
|
|
if ((head_ != NULL) && (head_->start_ == end)) {
|
|
head_->start_ = start;
|
|
return;
|
|
}
|
|
|
|
head_ = new UseInterval(vreg_, start, end, head_);
|
|
}
|
|
|
|
|
|
void LiveRange::DefineAt(Instruction* instr, intptr_t pos, Location* loc) {
|
|
if (head_ != NULL) {
|
|
ASSERT(head_->start_ <= pos);
|
|
head_->start_ = pos;
|
|
} else {
|
|
// Definition without a use.
|
|
head_ = new UseInterval(vreg_, pos, pos + 1, NULL);
|
|
}
|
|
head_->AddUse(instr, pos, loc);
|
|
}
|
|
|
|
|
|
// TODO(vegorov): encode use_at_start vs. use_at_end in the location itself?
|
|
void LiveRange::UseAt(Instruction* instr,
|
|
intptr_t def, intptr_t use,
|
|
bool use_at_end,
|
|
Location* loc) {
|
|
if (head_ == NULL || head_->start_ != def) {
|
|
AddUseInterval(def, use + (use_at_end ? 1 : 0));
|
|
}
|
|
head_->AddUse(instr, use, loc);
|
|
}
|
|
|
|
|
|
LiveRange* FlowGraphAllocator::GetLiveRange(intptr_t vreg) {
|
|
if (live_ranges_[vreg] == NULL) {
|
|
live_ranges_[vreg] = new LiveRange(vreg);
|
|
}
|
|
return live_ranges_[vreg];
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::BlockLocation(Location loc, intptr_t pos) {
|
|
ASSERT(loc.IsRegister());
|
|
const Register reg = loc.reg();
|
|
UseInterval* last = cpu_regs_[reg];
|
|
if (last == kPermanentlyBlocked) return;
|
|
if ((last != NULL) && (last->start() == pos)) return;
|
|
cpu_regs_[reg] = new UseInterval(kNoVirtualRegister, pos, pos + 1, last);
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::Define(Instruction* instr,
|
|
intptr_t pos,
|
|
intptr_t vreg,
|
|
Location* loc) {
|
|
LiveRange* range = GetLiveRange(vreg);
|
|
ASSERT(loc != NULL);
|
|
if (loc->IsRegister()) {
|
|
BlockLocation(*loc, pos);
|
|
range->DefineAt(instr, pos + 1, loc);
|
|
} else if (loc->IsUnallocated()) {
|
|
range->DefineAt(instr, pos, loc);
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
|
|
AddToUnallocated(range->head());
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::UseValue(Instruction* instr,
|
|
intptr_t def_pos,
|
|
intptr_t use_pos,
|
|
intptr_t vreg,
|
|
Location* loc,
|
|
bool use_at_end) {
|
|
LiveRange* range = GetLiveRange(vreg);
|
|
if (loc == NULL) {
|
|
range->UseAt(NULL, def_pos, use_pos, true, loc);
|
|
} else if (loc->IsRegister()) {
|
|
// We have a fixed use.
|
|
BlockLocation(*loc, use_pos);
|
|
range->UseAt(instr, def_pos, use_pos, false, loc);
|
|
} else if (loc->IsUnallocated()) {
|
|
ASSERT(loc->policy() == Location::kRequiresRegister);
|
|
range->UseAt(use_at_end ? NULL : instr, def_pos, use_pos, use_at_end, loc);
|
|
}
|
|
}
|
|
|
|
|
|
static void PrintChain(UseInterval* chain) {
|
|
if (chain == kPermanentlyBlocked) {
|
|
OS::Print(" not for allocation\n");
|
|
return;
|
|
}
|
|
|
|
while (chain != NULL) {
|
|
chain->Print();
|
|
chain = chain->next();
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::PrintLiveRanges() {
|
|
for (intptr_t i = 0; i < unallocated_.length(); i++) {
|
|
OS::Print("unallocated chain for vr%d\n", unallocated_[i]->vreg());
|
|
PrintChain(unallocated_[i]);
|
|
}
|
|
|
|
for (intptr_t reg = 0; reg < kNumberOfCpuRegisters; reg++) {
|
|
OS::Print("blocking chain for %s\n",
|
|
Location::RegisterLocation(static_cast<Register>(reg)).Name());
|
|
PrintChain(cpu_regs_[reg]);
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::BuildLiveRanges() {
|
|
NumberInstructions();
|
|
|
|
const intptr_t block_count = postorder_.length();
|
|
for (intptr_t i = 0; i < block_count; i++) {
|
|
BlockEntryInstr* block = postorder_[i];
|
|
|
|
// For every SSA value that is live out of this block create an interval
|
|
// that covers the hole block. It will be shortened if we encounter a
|
|
// definition of this value in this block.
|
|
for (BitVector::Iterator it(live_out_[i]); !it.Done(); it.Advance()) {
|
|
LiveRange* range = GetLiveRange(it.Current());
|
|
range->AddUseInterval(block->start_pos(), block->end_pos());
|
|
}
|
|
|
|
// Position corresponding to the end of the last instruction in the block.
|
|
intptr_t pos = block->end_pos() - 1;
|
|
|
|
Instruction* current = block->last_instruction();
|
|
|
|
// If last instruction is a parallel move we need to perform phi resolution.
|
|
if (current->IsParallelMove()) {
|
|
ParallelMoveInstr* parallel_move = current->AsParallelMove();
|
|
JoinEntryInstr* join = current->next()->AsJoinEntry();
|
|
ASSERT(join != NULL);
|
|
|
|
// Find index of the current block in predecessors of join.
|
|
intptr_t pred_idx = -1;
|
|
for (intptr_t j = 0; j < join->PredecessorCount(); j++) {
|
|
BlockEntryInstr* pred = join->PredecessorAt(j);
|
|
if (pred == block) {
|
|
pred_idx = j;
|
|
break;
|
|
}
|
|
}
|
|
ASSERT(pred_idx != -1);
|
|
|
|
// For every phi we have a reserved phi resolution move and we need
|
|
// to either initialize its source with constant or to register a use, so
|
|
// that register allocator will populate source slot with location of
|
|
// the appropriate SSA value.
|
|
ZoneGrowableArray<PhiInstr*>* phis = join->phis();
|
|
intptr_t move_idx = 0;
|
|
for (intptr_t j = 0; j < phis->length(); j++) {
|
|
PhiInstr* phi = (*phis)[j];
|
|
if (phi == NULL) continue;
|
|
|
|
Value* val = phi->InputAt(pred_idx);
|
|
|
|
MoveOperands move = parallel_move->moves()[move_idx];
|
|
if (val->IsUse()) {
|
|
const intptr_t use = val->AsUse()->definition()->ssa_temp_index();
|
|
Location* slot = move.src_slot();
|
|
*slot = Location::RequiresRegister();
|
|
GetLiveRange(use)->head()->AddUse(NULL, pos, slot);
|
|
} else {
|
|
ASSERT(val->IsConstant());
|
|
move.set_src(Location::Constant(val->AsConstant()->value()));
|
|
}
|
|
|
|
move_idx++;
|
|
}
|
|
|
|
current = current->previous();
|
|
}
|
|
|
|
// Now process all instructions in reverse order.
|
|
// Advance position to the start of the last instruction in the block.
|
|
pos -= 1;
|
|
while (current != block) {
|
|
LocationSummary* locs = current->locs();
|
|
|
|
const bool output_same_as_first_input =
|
|
locs->out().IsUnallocated() &&
|
|
locs->out().policy() == Location::kSameAsFirstInput;
|
|
|
|
// TODO(vegorov): number of inputs should match number of input locations.
|
|
// TODO(vegorov): generic support for writable registers?
|
|
for (intptr_t j = 0; j < current->InputCount(); j++) {
|
|
Value* input = current->InputAt(j);
|
|
if (input->IsUse()) {
|
|
const intptr_t use = input->AsUse()->definition()->ssa_temp_index();
|
|
|
|
Location* in_ref = (j < locs->input_count()) ?
|
|
locs->in_slot(j) : NULL;
|
|
const bool use_at_end = (j > 0) || (in_ref == NULL) ||
|
|
!output_same_as_first_input;
|
|
UseValue(current, block->start_pos(), pos, use, in_ref, use_at_end);
|
|
}
|
|
}
|
|
|
|
// Add uses from the deoptimization environment.
|
|
// TODO(vegorov): these uses should _not_ require register but for now
|
|
// they do because we don't support spilling at all.
|
|
if (current->env() != NULL) {
|
|
const GrowableArray<Value*>& values = current->env()->values();
|
|
GrowableArray<Location>* locations = current->env()->locations();
|
|
|
|
for (intptr_t j = 0; j < values.length(); j++) {
|
|
Value* val = values[j];
|
|
if (val->IsUse()) {
|
|
locations->Add(Location::RequiresRegister());
|
|
const intptr_t use = val->AsUse()->definition()->ssa_temp_index();
|
|
UseValue(current,
|
|
block->start_pos(),
|
|
pos,
|
|
use,
|
|
&(*locations)[j],
|
|
true);
|
|
} else {
|
|
locations->Add(Location::NoLocation());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process temps.
|
|
for (intptr_t j = 0; j < locs->temp_count(); j++) {
|
|
Location temp = locs->temp(j);
|
|
if (temp.IsRegister()) {
|
|
BlockLocation(temp, pos);
|
|
} else if (temp.IsUnallocated()) {
|
|
UseInterval* temp_interval = new UseInterval(
|
|
kTempVirtualRegister, pos, pos + 1, NULL);
|
|
temp_interval->AddUse(NULL, pos, locs->temp_slot(j));
|
|
AddToUnallocated(temp_interval);
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
// Block all allocatable registers for calls.
|
|
if (locs->is_call()) {
|
|
for (intptr_t reg = 0; reg < kNumberOfCpuRegisters; reg++) {
|
|
BlockLocation(Location::RegisterLocation(static_cast<Register>(reg)),
|
|
pos);
|
|
}
|
|
}
|
|
|
|
if (locs->out().IsRegister()) {
|
|
builder_->Bailout("ssa allocator: fixed outputs are not supported");
|
|
}
|
|
|
|
Definition* def = current->AsDefinition();
|
|
if ((def != NULL) && (def->ssa_temp_index() >= 0)) {
|
|
Define(output_same_as_first_input ? current : NULL,
|
|
pos,
|
|
def->ssa_temp_index(),
|
|
locs->out_slot());
|
|
}
|
|
|
|
current = current->previous();
|
|
pos -= 2;
|
|
}
|
|
|
|
// If this block is a join we need to add destinations of phi
|
|
// resolution moves to phi's live range so that register allocator will
|
|
// fill them with moves.
|
|
if (block->IsJoinEntry() && block->AsJoinEntry()->phis() != NULL) {
|
|
ZoneGrowableArray<PhiInstr*>* phis = block->AsJoinEntry()->phis();
|
|
|
|
intptr_t move_idx = 0;
|
|
for (intptr_t j = 0; j < phis->length(); j++) {
|
|
PhiInstr* phi = (*phis)[j];
|
|
if (phi == NULL) continue;
|
|
|
|
const intptr_t def = phi->ssa_temp_index();
|
|
ASSERT(def != -1);
|
|
|
|
LiveRange* range = GetLiveRange(def);
|
|
range->DefineAt(NULL, pos, NULL);
|
|
UseInterval* interval = GetLiveRange(def)->head();
|
|
|
|
for (intptr_t k = 0; k < phi->InputCount(); k++) {
|
|
BlockEntryInstr* pred = block->PredecessorAt(k);
|
|
ASSERT(pred->last_instruction()->IsParallelMove());
|
|
|
|
Location* slot = pred->last_instruction()->AsParallelMove()->
|
|
moves()[move_idx].dest_slot();
|
|
*slot = Location::RequiresRegister();
|
|
interval->AddUse(NULL, pos, slot);
|
|
}
|
|
|
|
// All phi resolution moves are connected. Phi's live range is complete.
|
|
AddToUnallocated(interval);
|
|
|
|
move_idx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::NumberInstructions() {
|
|
intptr_t pos = 0;
|
|
|
|
const intptr_t block_count = postorder_.length();
|
|
for (intptr_t i = block_count - 1; i >= 0; i--) {
|
|
BlockEntryInstr* block = postorder_[i];
|
|
|
|
block->set_start_pos(pos);
|
|
pos += 2;
|
|
Instruction* current = block->next();
|
|
|
|
Instruction* last = block->last_instruction();
|
|
if (!last->IsParallelMove()) last = last->next();
|
|
|
|
while (current != last) {
|
|
current->set_lifetime_position(pos);
|
|
current = current->next();
|
|
pos += 2;
|
|
}
|
|
block->set_end_pos(pos);
|
|
|
|
// For join entry predecessors create phi resolution moves if
|
|
// necessary. They will be populated by the register allocator.
|
|
if (block->IsJoinEntry() && (block->AsJoinEntry()->phi_count() > 0)) {
|
|
const intptr_t phi_count = block->AsJoinEntry()->phi_count();
|
|
for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
|
|
BlockEntryInstr* pred = block->PredecessorAt(i);
|
|
ASSERT(!pred->last_instruction()->IsParallelMove());
|
|
|
|
ParallelMoveInstr* move = new ParallelMoveInstr();
|
|
move->set_next(block);
|
|
move->set_previous(pred->last_instruction());
|
|
pred->last_instruction()->set_next(move);
|
|
pred->set_last_instruction(move);
|
|
|
|
// Populate ParallelMove with empty moves.
|
|
for (intptr_t j = 0; j < phi_count; j++) {
|
|
move->AddMove(Location::NoLocation(), Location::NoLocation());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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_allocated();
|
|
} else {
|
|
u = u->next();
|
|
}
|
|
}
|
|
|
|
return kMaxPosition;
|
|
}
|
|
|
|
|
|
static Location LookAheadForHint(UseInterval* interval) {
|
|
UsePosition* use = interval->first_use();
|
|
|
|
while (use != NULL) {
|
|
if (use->HasHint()) return use->hint();
|
|
use = use->next();
|
|
}
|
|
|
|
return Location::NoLocation();
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::AllocateFreeRegister(UseInterval* unallocated) {
|
|
Register candidate = kNoRegister;
|
|
intptr_t free_until = 0;
|
|
|
|
// If hint is available try hint first.
|
|
// TODO(vegorov): ensure that phis are hinted on the backedge.
|
|
Location hint = LookAheadForHint(unallocated);
|
|
if (!hint.IsInvalid()) {
|
|
ASSERT(hint.IsRegister());
|
|
|
|
if (cpu_regs_[hint.reg()] != kPermanentlyBlocked) {
|
|
free_until = FirstIntersection(cpu_regs_[hint.reg()], unallocated);
|
|
candidate = hint.reg();
|
|
}
|
|
|
|
TRACE_ALLOC(("found hint %s for %d: free until %d\n",
|
|
hint.Name(), unallocated->vreg(), free_until));
|
|
}
|
|
|
|
if (free_until != kMaxPosition) {
|
|
for (int reg = 0; reg < kNumberOfCpuRegisters; ++reg) {
|
|
if (cpu_regs_[reg] == NULL) {
|
|
candidate = static_cast<Register>(reg);
|
|
free_until = kMaxPosition;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(0 <= kMaxPosition);
|
|
if (free_until != kMaxPosition) {
|
|
for (int reg = 0; reg < kNumberOfCpuRegisters; ++reg) {
|
|
if (cpu_regs_[reg] == kPermanentlyBlocked) continue;
|
|
if (reg == candidate) continue;
|
|
|
|
const intptr_t pos = FirstIntersection(cpu_regs_[reg], unallocated);
|
|
|
|
if (pos > free_until) {
|
|
candidate = static_cast<Register>(reg);
|
|
free_until = pos;
|
|
if (free_until == kMaxPosition) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// All registers are blocked by active ranges.
|
|
if (free_until <= unallocated->start()) return false;
|
|
|
|
AssignFreeRegister(unallocated, candidate);
|
|
return true;
|
|
}
|
|
|
|
|
|
UseInterval* UseInterval::Split(intptr_t pos) {
|
|
if (pos == start()) return this;
|
|
ASSERT(Contains(pos));
|
|
UseInterval* tail = new UseInterval(vreg(), pos, end(), next());
|
|
|
|
UsePosition* use = uses_;
|
|
while (use != NULL && use->pos() <= pos) {
|
|
use = use->next();
|
|
}
|
|
|
|
tail->uses_ = use;
|
|
|
|
end_ = pos;
|
|
|
|
return tail;
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AssignFreeRegister(UseInterval* unallocated,
|
|
Register reg) {
|
|
TRACE_ALLOC(("assigning free register %s to %d\n",
|
|
Location::RegisterLocation(reg).Name(),
|
|
unallocated->vreg()));
|
|
|
|
UseInterval* a = cpu_regs_[reg];
|
|
if (a == NULL) {
|
|
// Register is completely free.
|
|
cpu_regs_[reg] = unallocated;
|
|
return;
|
|
}
|
|
|
|
UseInterval* u = unallocated;
|
|
ASSERT(u->start() < a->start()); // Register is free.
|
|
cpu_regs_[reg] = u;
|
|
if (u->next() == NULL || u->next()->start() >= a->start()) {
|
|
u->set_next_allocated(a);
|
|
}
|
|
|
|
while (a != NULL && u != NULL) {
|
|
const intptr_t pos = a->Intersect(u);
|
|
if (pos != kIllegalPosition) {
|
|
// TODO(vegorov): split live ranges might require control flow resolution
|
|
// which is not implemented yet.
|
|
builder_->Bailout("ssa allocator: control flow resolution required");
|
|
|
|
TRACE_ALLOC((" splitting at %d\n", pos));
|
|
// Reached intersection
|
|
UseInterval* tail = u->Split(pos);
|
|
AddToUnallocated(tail);
|
|
ASSERT(tail == u || u->next_allocated() == a);
|
|
return;
|
|
}
|
|
|
|
if (a->start() < u->start()) {
|
|
if (a->next_allocated() == NULL) {
|
|
a->set_next_allocated(u);
|
|
break;
|
|
}
|
|
|
|
UseInterval* next = a->next_allocated();
|
|
if (next->start() > u->start()) {
|
|
a->set_next_allocated(u);
|
|
u->set_next_allocated(next);
|
|
}
|
|
|
|
a = next;
|
|
} else {
|
|
UseInterval* next = u->next();
|
|
|
|
if (next == NULL || next->start() >= a->start()) {
|
|
u->set_next_allocated(a);
|
|
}
|
|
u = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void InsertMoveBefore(Instruction* instr, Location to, Location from) {
|
|
Instruction* prev = instr->previous();
|
|
ParallelMoveInstr* move = prev->AsParallelMove();
|
|
if (move == NULL) {
|
|
move = new ParallelMoveInstr();
|
|
move->set_next(prev->next());
|
|
prev->set_next(move);
|
|
move->next()->set_previous(move);
|
|
move->set_previous(prev);
|
|
}
|
|
move->AddMove(to, from);
|
|
}
|
|
|
|
|
|
void UsePosition::AssignLocation(Location loc) {
|
|
if (location_slot_ == NULL) return;
|
|
|
|
if (location_slot_->IsUnallocated()) {
|
|
if (location_slot_->policy() == Location::kSameAsFirstInput) {
|
|
Instruction* instr = this->instr();
|
|
LocationSummary* locs = instr->locs();
|
|
if (!locs->in(0).IsUnallocated()) {
|
|
InsertMoveBefore(instr, loc, locs->in(0));
|
|
}
|
|
locs->set_in(0, loc);
|
|
}
|
|
TRACE_ALLOC((" use at %d converted to %s\n", pos(), loc.Name()));
|
|
*location_slot_ = loc;
|
|
} else if (location_slot_->IsRegister()) {
|
|
InsertMoveBefore(this->instr(), *location_slot_, loc);
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::FinalizeInterval(UseInterval* interval, Location loc) {
|
|
if (interval->vreg() == kNoVirtualRegister) return;
|
|
|
|
TRACE_ALLOC(("assigning location %s to interval [%d, %d)\n", loc.Name(),
|
|
interval->start(), interval->end()));
|
|
|
|
for (UsePosition* use = interval->first_use();
|
|
use != NULL && use->pos() <= interval->end();
|
|
use = use->next()) {
|
|
use->AssignLocation(loc);
|
|
}
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AdvanceActiveIntervals(const intptr_t start) {
|
|
for (int reg = 0; reg < kNumberOfCpuRegisters; reg++) {
|
|
if (cpu_regs_[reg] == NULL) continue;
|
|
if (cpu_regs_[reg] == kPermanentlyBlocked) continue;
|
|
|
|
UseInterval* a = cpu_regs_[reg];
|
|
while (a != NULL && a->end() <= start) {
|
|
FinalizeInterval(a,
|
|
Location::RegisterLocation(static_cast<Register>(reg)));
|
|
a = a->next_allocated();
|
|
}
|
|
|
|
cpu_regs_[reg] = a;
|
|
}
|
|
}
|
|
|
|
|
|
static inline bool ShouldBeAllocatedBefore(UseInterval* a, UseInterval* b) {
|
|
return a->start() <= b->start();
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AddToUnallocated(UseInterval* chain) {
|
|
if (unallocated_.is_empty()) {
|
|
unallocated_.Add(chain);
|
|
return;
|
|
}
|
|
|
|
for (intptr_t i = unallocated_.length() - 1; i >= 0; i--) {
|
|
if (ShouldBeAllocatedBefore(chain, unallocated_[i])) {
|
|
unallocated_.InsertAt(i + 1, chain);
|
|
return;
|
|
}
|
|
}
|
|
unallocated_.InsertAt(0, chain);
|
|
}
|
|
|
|
|
|
bool FlowGraphAllocator::UnallocatedIsSorted() {
|
|
for (intptr_t i = unallocated_.length() - 1; i >= 1; i--) {
|
|
UseInterval* a = unallocated_[i];
|
|
UseInterval* b = unallocated_[i - 1];
|
|
if (!ShouldBeAllocatedBefore(a, b)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AllocateCPURegisters() {
|
|
ASSERT(UnallocatedIsSorted());
|
|
|
|
while (!unallocated_.is_empty()) {
|
|
UseInterval* range = unallocated_.Last();
|
|
unallocated_.RemoveLast();
|
|
const intptr_t start = range->start();
|
|
TRACE_ALLOC(("Processing interval chain for vreg %d starting at %d\n",
|
|
range->vreg(),
|
|
start));
|
|
|
|
// TODO(vegorov): eagerly spill liveranges without register uses.
|
|
AdvanceActiveIntervals(start);
|
|
|
|
if (!AllocateFreeRegister(range)) {
|
|
builder_->Bailout("ssa allocator: spilling required");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// All allocation decisions were done.
|
|
ASSERT(unallocated_.is_empty());
|
|
|
|
// Finish allocation.
|
|
AdvanceActiveIntervals(kMaxPosition);
|
|
TRACE_ALLOC(("Allocation completed\n"));
|
|
}
|
|
|
|
|
|
void FlowGraphAllocator::AllocateRegisters() {
|
|
GraphEntryInstr* entry = block_order_[0]->AsGraphEntry();
|
|
ASSERT(entry != NULL);
|
|
|
|
for (intptr_t i = 0; i < entry->start_env()->values().length(); i++) {
|
|
if (entry->start_env()->values()[i]->IsUse()) {
|
|
builder_->Bailout("ssa allocator: unsupported start environment");
|
|
}
|
|
}
|
|
|
|
AnalyzeLiveness();
|
|
|
|
BuildLiveRanges();
|
|
|
|
if (FLAG_print_ssa_liveness) {
|
|
DumpLiveness();
|
|
}
|
|
|
|
if (FLAG_trace_ssa_allocator) {
|
|
PrintLiveRanges();
|
|
}
|
|
|
|
AllocateCPURegisters();
|
|
|
|
if (FLAG_trace_ssa_allocator) {
|
|
OS::Print("-- ir after allocation -------------------------\n");
|
|
FlowGraphPrinter printer(Function::Handle(), block_order_, true);
|
|
printer.PrintBlocks();
|
|
}
|
|
}
|
|
|
|
|
|
} // namespace dart
|