// 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(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(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(reg)), pos, pos + 1); } for (intptr_t reg = 0; reg < kNumberOfFpuRegisters; reg++) { BlockLocation( Location::FpuRegisterLocation(static_cast(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 occuring 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 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 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* 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& 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); } 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