// Copyright 2020 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/compiler/backend/spill-placer.h" #include "src/base/bits-iterator.h" #include "src/compiler/backend/register-allocator.h" namespace v8 { namespace internal { namespace compiler { SpillPlacer::SpillPlacer(LiveRangeFinder* finder, TopTierRegisterAllocationData* data, Zone* zone) : finder_(finder), data_(data), zone_(zone) {} SpillPlacer::~SpillPlacer() { if (assigned_indices_ > 0) { CommitSpills(); } } void SpillPlacer::Add(TopLevelLiveRange* range) { DCHECK(range->HasGeneralSpillRange()); InstructionOperand spill_operand = range->GetSpillRangeOperand(); range->FilterSpillMoves(data(), spill_operand); InstructionSequence* code = data_->code(); InstructionBlock* top_start_block = code->GetInstructionBlock(range->Start().ToInstructionIndex()); RpoNumber top_start_block_number = top_start_block->rpo_number(); // Check for several cases where spilling at the definition is best. // - The value is already moved on-stack somehow so the list of insertion // locations for spilling at the definition is empty. // - If the first LiveRange is spilled, then there's no sense in doing // anything other than spilling at the definition. // - If the value is defined in a deferred block, then the logic to select // the earliest deferred block as the insertion point would cause // incorrect behavior, so the value must be spilled at the definition. // - We haven't seen any indication of performance improvements from seeking // optimal spilling positions except on loop-top phi values, so spill // any value that isn't a loop-top phi at the definition to avoid // increasing the code size for no benefit. if (range->GetSpillMoveInsertionLocations(data()) == nullptr || range->spilled() || top_start_block->IsDeferred() || (!FLAG_stress_turbo_late_spilling && !range->is_loop_phi())) { range->CommitSpillMoves(data(), spill_operand); return; } // Iterate through the range and mark every block that needs the value to be // spilled. for (const LiveRange* child = range; child != nullptr; child = child->next()) { if (child->spilled()) { // Add every block that contains part of this live range. for (UseInterval* interval = child->first_interval(); interval != nullptr; interval = interval->next()) { RpoNumber start_block = code->GetInstructionBlock(interval->start().ToInstructionIndex()) ->rpo_number(); if (start_block == top_start_block_number) { // Can't do late spilling if the first spill is within the // definition block. range->CommitSpillMoves(data(), spill_operand); // Verify that we never added any data for this range to the table. DCHECK(!IsLatestVreg(range->vreg())); return; } LifetimePosition end = interval->end(); int end_instruction = end.ToInstructionIndex(); // The end position is exclusive, so an end position exactly on a block // boundary indicates that the range applies only to the prior block. if (data()->IsBlockBoundary(end)) { --end_instruction; } RpoNumber end_block = code->GetInstructionBlock(end_instruction)->rpo_number(); while (start_block <= end_block) { SetSpillRequired(code->InstructionBlockAt(start_block), range->vreg(), top_start_block_number); start_block = start_block.Next(); } } } else { // Add every block that contains a use which requires the on-stack value. for (const UsePosition* pos = child->first_pos(); pos != nullptr; pos = pos->next()) { if (pos->type() != UsePositionType::kRequiresSlot) continue; InstructionBlock* block = code->GetInstructionBlock(pos->pos().ToInstructionIndex()); RpoNumber block_number = block->rpo_number(); if (block_number == top_start_block_number) { // Can't do late spilling if the first spill is within the // definition block. range->CommitSpillMoves(data(), spill_operand); // Verify that we never added any data for this range to the table. DCHECK(!IsLatestVreg(range->vreg())); return; } SetSpillRequired(block, range->vreg(), top_start_block_number); } } } // If we haven't yet marked anything for this range, then it never needs to // spill at all. if (!IsLatestVreg(range->vreg())) { range->SetLateSpillingSelected(true); return; } SetDefinition(top_start_block_number, range->vreg()); } class SpillPlacer::Entry { public: // Functions operating on single values (during setup): void SetSpillRequiredSingleValue(int value_index) { DCHECK_LT(value_index, kValueIndicesPerEntry); uint64_t bit = uint64_t{1} << value_index; SetSpillRequired(bit); } void SetDefinitionSingleValue(int value_index) { DCHECK_LT(value_index, kValueIndicesPerEntry); uint64_t bit = uint64_t{1} << value_index; SetDefinition(bit); } // Functions operating on all values simultaneously, as bitfields: uint64_t SpillRequired() const { return GetValuesInState<kSpillRequired>(); } void SetSpillRequired(uint64_t mask) { UpdateValuesToState<kSpillRequired>(mask); } uint64_t SpillRequiredInNonDeferredSuccessor() const { return GetValuesInState<kSpillRequiredInNonDeferredSuccessor>(); } void SetSpillRequiredInNonDeferredSuccessor(uint64_t mask) { UpdateValuesToState<kSpillRequiredInNonDeferredSuccessor>(mask); } uint64_t SpillRequiredInDeferredSuccessor() const { return GetValuesInState<kSpillRequiredInDeferredSuccessor>(); } void SetSpillRequiredInDeferredSuccessor(uint64_t mask) { UpdateValuesToState<kSpillRequiredInDeferredSuccessor>(mask); } uint64_t Definition() const { return GetValuesInState<kDefinition>(); } void SetDefinition(uint64_t mask) { UpdateValuesToState<kDefinition>(mask); } private: // Possible states for every value, at every block. enum State { // This block is not (yet) known to require the on-stack value. kUnmarked, // The value must be on the stack in this block. kSpillRequired, // The value doesn't need to be on-stack in this block, but some // non-deferred successor needs it. kSpillRequiredInNonDeferredSuccessor, // The value doesn't need to be on-stack in this block, but some // deferred successor needs it. kSpillRequiredInDeferredSuccessor, // The value is defined in this block. kDefinition, }; template <State state> uint64_t GetValuesInState() const { STATIC_ASSERT(state < 8); return ((state & 1) ? first_bit_ : ~first_bit_) & ((state & 2) ? second_bit_ : ~second_bit_) & ((state & 4) ? third_bit_ : ~third_bit_); } template <State state> void UpdateValuesToState(uint64_t mask) { STATIC_ASSERT(state < 8); first_bit_ = Entry::UpdateBitDataWithMask<(state & 1) != 0>(first_bit_, mask); second_bit_ = Entry::UpdateBitDataWithMask<(state & 2) != 0>(second_bit_, mask); third_bit_ = Entry::UpdateBitDataWithMask<(state & 4) != 0>(third_bit_, mask); } template <bool set_ones> static uint64_t UpdateBitDataWithMask(uint64_t data, uint64_t mask) { return set_ones ? data | mask : data & ~mask; } // Storage for the states of up to 64 live ranges. uint64_t first_bit_ = 0; uint64_t second_bit_ = 0; uint64_t third_bit_ = 0; }; int SpillPlacer::GetOrCreateIndexForLatestVreg(int vreg) { DCHECK_LE(assigned_indices_, kValueIndicesPerEntry); // If this vreg isn't yet the last one in the list, then add it. if (!IsLatestVreg(vreg)) { if (vreg_numbers_ == nullptr) { DCHECK_EQ(assigned_indices_, 0); DCHECK_EQ(entries_, nullptr); // We lazily allocate these arrays because many functions don't have any // values that use SpillPlacer. entries_ = zone_->NewArray<Entry>(data()->code()->instruction_blocks().size()); for (size_t i = 0; i < data()->code()->instruction_blocks().size(); ++i) { new (&entries_[i]) Entry(); } vreg_numbers_ = zone_->NewArray<int>(kValueIndicesPerEntry); } if (assigned_indices_ == kValueIndicesPerEntry) { // The table is full; commit the current set of values and clear it. CommitSpills(); ClearData(); } vreg_numbers_[assigned_indices_] = vreg; ++assigned_indices_; } return assigned_indices_ - 1; } void SpillPlacer::CommitSpills() { FirstBackwardPass(); ForwardPass(); SecondBackwardPass(); } void SpillPlacer::ClearData() { assigned_indices_ = 0; for (int i = 0; i < data()->code()->InstructionBlockCount(); ++i) { new (&entries_[i]) Entry(); } first_block_ = RpoNumber::Invalid(); last_block_ = RpoNumber::Invalid(); } void SpillPlacer::ExpandBoundsToInclude(RpoNumber block) { if (!first_block_.IsValid()) { DCHECK(!last_block_.IsValid()); first_block_ = block; last_block_ = block; } else { if (first_block_ > block) { first_block_ = block; } if (last_block_ < block) { last_block_ = block; } } } void SpillPlacer::SetSpillRequired(InstructionBlock* block, int vreg, RpoNumber top_start_block) { // Spilling in loops is bad, so if the block is non-deferred and nested // within a loop, and the definition is before that loop, then mark the loop // top instead. Of course we must find the outermost such loop. if (!block->IsDeferred()) { while (block->loop_header().IsValid() && block->loop_header() > top_start_block) { block = data()->code()->InstructionBlockAt(block->loop_header()); } } int value_index = GetOrCreateIndexForLatestVreg(vreg); entries_[block->rpo_number().ToSize()].SetSpillRequiredSingleValue( value_index); ExpandBoundsToInclude(block->rpo_number()); } void SpillPlacer::SetDefinition(RpoNumber block, int vreg) { int value_index = GetOrCreateIndexForLatestVreg(vreg); entries_[block.ToSize()].SetDefinitionSingleValue(value_index); ExpandBoundsToInclude(block); } void SpillPlacer::FirstBackwardPass() { InstructionSequence* code = data()->code(); for (int i = last_block_.ToInt(); i >= first_block_.ToInt(); --i) { RpoNumber block_id = RpoNumber::FromInt(i); InstructionBlock* block = code->instruction_blocks()[i]; Entry& entry = entries_[i]; // State that will be accumulated from successors. uint64_t spill_required_in_non_deferred_successor = 0; uint64_t spill_required_in_deferred_successor = 0; for (RpoNumber successor_id : block->successors()) { // Ignore loop back-edges. if (successor_id <= block_id) continue; InstructionBlock* successor = code->InstructionBlockAt(successor_id); const Entry& successor_entry = entries_[successor_id.ToSize()]; if (successor->IsDeferred()) { spill_required_in_deferred_successor |= successor_entry.SpillRequired(); } else { spill_required_in_non_deferred_successor |= successor_entry.SpillRequired(); } spill_required_in_deferred_successor |= successor_entry.SpillRequiredInDeferredSuccessor(); spill_required_in_non_deferred_successor |= successor_entry.SpillRequiredInNonDeferredSuccessor(); } // Starting state of the current block. uint64_t defs = entry.Definition(); uint64_t needs_spill = entry.SpillRequired(); // Info about successors doesn't get to override existing info about // definitions and spills required by this block itself. spill_required_in_deferred_successor &= ~(defs | needs_spill); spill_required_in_non_deferred_successor &= ~(defs | needs_spill); entry.SetSpillRequiredInDeferredSuccessor( spill_required_in_deferred_successor); entry.SetSpillRequiredInNonDeferredSuccessor( spill_required_in_non_deferred_successor); } } void SpillPlacer::ForwardPass() { InstructionSequence* code = data()->code(); for (int i = first_block_.ToInt(); i <= last_block_.ToInt(); ++i) { RpoNumber block_id = RpoNumber::FromInt(i); InstructionBlock* block = code->instruction_blocks()[i]; // Deferred blocks don't need to participate in the forward pass, because // their spills all get pulled forward to the earliest possible deferred // block (where a non-deferred block jumps to a deferred block), and // decisions about spill requirements for non-deferred blocks don't take // deferred blocks into account. if (block->IsDeferred()) continue; Entry& entry = entries_[i]; // State that will be accumulated from predecessors. uint64_t spill_required_in_non_deferred_predecessor = 0; uint64_t spill_required_in_all_non_deferred_predecessors = static_cast<uint64_t>(int64_t{-1}); for (RpoNumber predecessor_id : block->predecessors()) { // Ignore loop back-edges. if (predecessor_id >= block_id) continue; InstructionBlock* predecessor = code->InstructionBlockAt(predecessor_id); if (predecessor->IsDeferred()) continue; const Entry& predecessor_entry = entries_[predecessor_id.ToSize()]; spill_required_in_non_deferred_predecessor |= predecessor_entry.SpillRequired(); spill_required_in_all_non_deferred_predecessors &= predecessor_entry.SpillRequired(); } // Starting state of the current block. uint64_t spill_required_in_non_deferred_successor = entry.SpillRequiredInNonDeferredSuccessor(); uint64_t spill_required_in_any_successor = spill_required_in_non_deferred_successor | entry.SpillRequiredInDeferredSuccessor(); // If all of the predecessors agree that a spill is required, then a // spill is required. Note that we don't set anything for values that // currently have no markings in this block, to avoid pushing data too // far down the graph and confusing the next backward pass. entry.SetSpillRequired(spill_required_in_any_successor & spill_required_in_non_deferred_predecessor & spill_required_in_all_non_deferred_predecessors); // If only some of the predecessors require a spill, but some successor // of this block also requires a spill, then this merge point requires a // spill. This ensures that no control-flow path through non-deferred // blocks ever has to spill twice. entry.SetSpillRequired(spill_required_in_non_deferred_successor & spill_required_in_non_deferred_predecessor); } } void SpillPlacer::SecondBackwardPass() { InstructionSequence* code = data()->code(); for (int i = last_block_.ToInt(); i >= first_block_.ToInt(); --i) { RpoNumber block_id = RpoNumber::FromInt(i); InstructionBlock* block = code->instruction_blocks()[i]; Entry& entry = entries_[i]; // State that will be accumulated from successors. uint64_t spill_required_in_non_deferred_successor = 0; uint64_t spill_required_in_deferred_successor = 0; uint64_t spill_required_in_all_non_deferred_successors = static_cast<uint64_t>(int64_t{-1}); for (RpoNumber successor_id : block->successors()) { // Ignore loop back-edges. if (successor_id <= block_id) continue; InstructionBlock* successor = code->InstructionBlockAt(successor_id); const Entry& successor_entry = entries_[successor_id.ToSize()]; if (successor->IsDeferred()) { spill_required_in_deferred_successor |= successor_entry.SpillRequired(); } else { spill_required_in_non_deferred_successor |= successor_entry.SpillRequired(); spill_required_in_all_non_deferred_successors &= successor_entry.SpillRequired(); } } // Starting state of the current block. uint64_t defs = entry.Definition(); // If all of the successors of a definition need the value to be // spilled, then the value should be spilled at the definition. uint64_t spill_at_def = defs & spill_required_in_non_deferred_successor & spill_required_in_all_non_deferred_successors; for (int index_to_spill : base::bits::IterateBits(spill_at_def)) { int vreg_to_spill = vreg_numbers_[index_to_spill]; TopLevelLiveRange* top = data()->live_ranges()[vreg_to_spill]; top->CommitSpillMoves(data(), top->GetSpillRangeOperand()); } if (block->IsDeferred()) { DCHECK_EQ(defs, 0); // Any deferred successor needing a spill is sufficient to make the // current block need a spill. entry.SetSpillRequired(spill_required_in_deferred_successor); } // Propagate data upward if there are non-deferred successors and they // all need a spill, regardless of whether the current block is // deferred. entry.SetSpillRequired(~defs & spill_required_in_non_deferred_successor & spill_required_in_all_non_deferred_successors); // Iterate the successors again to find out which ones require spills at // their beginnings, and insert those spills. for (RpoNumber successor_id : block->successors()) { // Ignore loop back-edges. if (successor_id <= block_id) continue; InstructionBlock* successor = code->InstructionBlockAt(successor_id); const Entry& successor_entry = entries_[successor_id.ToSize()]; for (int index_to_spill : base::bits::IterateBits(successor_entry.SpillRequired() & ~entry.SpillRequired() & ~spill_at_def)) { CommitSpill(vreg_numbers_[index_to_spill], block, successor); } } } } void SpillPlacer::CommitSpill(int vreg, InstructionBlock* predecessor, InstructionBlock* successor) { TopLevelLiveRange* top = data()->live_ranges()[vreg]; LiveRangeBoundArray* array = finder_->ArrayFor(vreg); LifetimePosition pred_end = LifetimePosition::InstructionFromInstructionIndex( predecessor->last_instruction_index()); LiveRangeBound* bound = array->Find(pred_end); InstructionOperand pred_op = bound->range_->GetAssignedOperand(); DCHECK(pred_op.IsAnyRegister()); DCHECK_EQ(successor->PredecessorCount(), 1); data()->AddGapMove(successor->first_instruction_index(), Instruction::GapPosition::START, pred_op, top->GetSpillRangeOperand()); successor->mark_needs_frame(); top->SetLateSpillingSelected(true); } } // namespace compiler } // namespace internal } // namespace v8