| // Copyright 2016 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/interpreter/bytecode-register-optimizer.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace interpreter { |
| |
| const uint32_t BytecodeRegisterOptimizer::kInvalidEquivalenceId = kMaxUInt32; |
| |
| // A class for tracking the state of a register. This class tracks |
| // which equivalence set a register is a member of and also whether a |
| // register is materialized in the bytecode stream. |
| class BytecodeRegisterOptimizer::RegisterInfo final : public ZoneObject { |
| public: |
| RegisterInfo(Register reg, uint32_t equivalence_id, bool materialized, |
| bool allocated) |
| : register_(reg), |
| equivalence_id_(equivalence_id), |
| materialized_(materialized), |
| allocated_(allocated), |
| needs_flush_(false), |
| next_(this), |
| prev_(this) {} |
| RegisterInfo(const RegisterInfo&) = delete; |
| RegisterInfo& operator=(const RegisterInfo&) = delete; |
| |
| void AddToEquivalenceSetOf(RegisterInfo* info); |
| void MoveToNewEquivalenceSet(uint32_t equivalence_id, bool materialized); |
| bool IsOnlyMemberOfEquivalenceSet() const; |
| bool IsOnlyMaterializedMemberOfEquivalenceSet() const; |
| bool IsInSameEquivalenceSet(RegisterInfo* info) const; |
| |
| // Get a member of the register's equivalence set that is allocated. |
| // Returns itself if allocated, and nullptr if there is no unallocated |
| // equivalent register. |
| RegisterInfo* GetAllocatedEquivalent(); |
| |
| // Get a member of this register's equivalence set that is |
| // materialized. The materialized equivalent will be this register |
| // if it is materialized. Returns nullptr if no materialized |
| // equivalent exists. |
| RegisterInfo* GetMaterializedEquivalent(); |
| |
| // Get a member of this register's equivalence set that is |
| // materialized and not register |reg|. The materialized equivalent |
| // will be this register if it is materialized. Returns nullptr if |
| // no materialized equivalent exists. |
| RegisterInfo* GetMaterializedEquivalentOtherThan(Register reg); |
| |
| // Get a member of this register's equivalence set that is intended |
| // to be materialized in place of this register (which is currently |
| // materialized). The best candidate is deemed to be the register |
| // with the lowest index as this permits temporary registers to be |
| // removed from the bytecode stream. Returns nullptr if no candidate |
| // exists. |
| RegisterInfo* GetEquivalentToMaterialize(); |
| |
| // Marks all temporary registers of the equivalence set as unmaterialized. |
| void MarkTemporariesAsUnmaterialized(Register temporary_base); |
| |
| // Get an equivalent register. Returns this if none exists. |
| RegisterInfo* GetEquivalent(); |
| |
| Register register_value() const { return register_; } |
| bool materialized() const { return materialized_; } |
| void set_materialized(bool materialized) { materialized_ = materialized; } |
| bool allocated() const { return allocated_; } |
| void set_allocated(bool allocated) { allocated_ = allocated; } |
| void set_equivalence_id(uint32_t equivalence_id) { |
| equivalence_id_ = equivalence_id; |
| } |
| uint32_t equivalence_id() const { return equivalence_id_; } |
| // Indicates if a register should be processed when calling Flush(). |
| bool needs_flush() const { return needs_flush_; } |
| void set_needs_flush(bool needs_flush) { needs_flush_ = needs_flush; } |
| |
| private: |
| Register register_; |
| uint32_t equivalence_id_; |
| bool materialized_; |
| bool allocated_; |
| bool needs_flush_; |
| |
| // Equivalence set pointers. |
| RegisterInfo* next_; |
| RegisterInfo* prev_; |
| }; |
| |
| void BytecodeRegisterOptimizer::RegisterInfo::AddToEquivalenceSetOf( |
| RegisterInfo* info) { |
| DCHECK_NE(kInvalidEquivalenceId, info->equivalence_id()); |
| // Fix old list |
| next_->prev_ = prev_; |
| prev_->next_ = next_; |
| // Add to new list. |
| next_ = info->next_; |
| prev_ = info; |
| prev_->next_ = this; |
| next_->prev_ = this; |
| set_equivalence_id(info->equivalence_id()); |
| set_materialized(false); |
| } |
| |
| void BytecodeRegisterOptimizer::RegisterInfo::MoveToNewEquivalenceSet( |
| uint32_t equivalence_id, bool materialized) { |
| next_->prev_ = prev_; |
| prev_->next_ = next_; |
| next_ = prev_ = this; |
| equivalence_id_ = equivalence_id; |
| materialized_ = materialized; |
| } |
| |
| bool BytecodeRegisterOptimizer::RegisterInfo::IsOnlyMemberOfEquivalenceSet() |
| const { |
| return this->next_ == this; |
| } |
| |
| bool BytecodeRegisterOptimizer::RegisterInfo:: |
| IsOnlyMaterializedMemberOfEquivalenceSet() const { |
| DCHECK(materialized()); |
| |
| const RegisterInfo* visitor = this->next_; |
| while (visitor != this) { |
| if (visitor->materialized()) { |
| return false; |
| } |
| visitor = visitor->next_; |
| } |
| return true; |
| } |
| |
| bool BytecodeRegisterOptimizer::RegisterInfo::IsInSameEquivalenceSet( |
| RegisterInfo* info) const { |
| return equivalence_id() == info->equivalence_id(); |
| } |
| |
| BytecodeRegisterOptimizer::RegisterInfo* |
| BytecodeRegisterOptimizer::RegisterInfo::GetAllocatedEquivalent() { |
| RegisterInfo* visitor = this; |
| do { |
| if (visitor->allocated()) { |
| return visitor; |
| } |
| visitor = visitor->next_; |
| } while (visitor != this); |
| |
| return nullptr; |
| } |
| |
| BytecodeRegisterOptimizer::RegisterInfo* |
| BytecodeRegisterOptimizer::RegisterInfo::GetMaterializedEquivalent() { |
| RegisterInfo* visitor = this; |
| do { |
| if (visitor->materialized()) { |
| return visitor; |
| } |
| visitor = visitor->next_; |
| } while (visitor != this); |
| |
| return nullptr; |
| } |
| |
| BytecodeRegisterOptimizer::RegisterInfo* |
| BytecodeRegisterOptimizer::RegisterInfo::GetMaterializedEquivalentOtherThan( |
| Register reg) { |
| RegisterInfo* visitor = this; |
| do { |
| if (visitor->materialized() && visitor->register_value() != reg) { |
| return visitor; |
| } |
| visitor = visitor->next_; |
| } while (visitor != this); |
| |
| return nullptr; |
| } |
| |
| BytecodeRegisterOptimizer::RegisterInfo* |
| BytecodeRegisterOptimizer::RegisterInfo::GetEquivalentToMaterialize() { |
| DCHECK(this->materialized()); |
| RegisterInfo* visitor = this->next_; |
| RegisterInfo* best_info = nullptr; |
| while (visitor != this) { |
| if (visitor->materialized()) { |
| return nullptr; |
| } |
| if (visitor->allocated() && |
| (best_info == nullptr || |
| visitor->register_value() < best_info->register_value())) { |
| best_info = visitor; |
| } |
| visitor = visitor->next_; |
| } |
| return best_info; |
| } |
| |
| void BytecodeRegisterOptimizer::RegisterInfo::MarkTemporariesAsUnmaterialized( |
| Register temporary_base) { |
| DCHECK(this->register_value() < temporary_base); |
| DCHECK(this->materialized()); |
| RegisterInfo* visitor = this->next_; |
| while (visitor != this) { |
| if (visitor->register_value() >= temporary_base) { |
| visitor->set_materialized(false); |
| } |
| visitor = visitor->next_; |
| } |
| } |
| |
| BytecodeRegisterOptimizer::RegisterInfo* |
| BytecodeRegisterOptimizer::RegisterInfo::GetEquivalent() { |
| return next_; |
| } |
| |
| BytecodeRegisterOptimizer::BytecodeRegisterOptimizer( |
| Zone* zone, BytecodeRegisterAllocator* register_allocator, |
| int fixed_registers_count, int parameter_count, |
| BytecodeWriter* bytecode_writer) |
| : accumulator_(Register::virtual_accumulator()), |
| temporary_base_(fixed_registers_count), |
| max_register_index_(fixed_registers_count - 1), |
| register_info_table_(zone), |
| registers_needing_flushed_(zone), |
| equivalence_id_(0), |
| bytecode_writer_(bytecode_writer), |
| flush_required_(false), |
| zone_(zone) { |
| register_allocator->set_observer(this); |
| |
| // Calculate offset so register index values can be mapped into |
| // a vector of register metadata. |
| // There is at least one parameter, which is the JS receiver. |
| DCHECK_NE(parameter_count, 0); |
| int first_slot_index = parameter_count - 1; |
| register_info_table_offset_ = |
| -Register::FromParameterIndex(first_slot_index, parameter_count).index(); |
| |
| // Initialize register map for parameters, locals, and the |
| // accumulator. |
| register_info_table_.resize(register_info_table_offset_ + |
| static_cast<size_t>(temporary_base_.index())); |
| for (size_t i = 0; i < register_info_table_.size(); ++i) { |
| register_info_table_[i] = zone->New<RegisterInfo>( |
| RegisterFromRegisterInfoTableIndex(i), NextEquivalenceId(), true, true); |
| DCHECK_EQ(register_info_table_[i]->register_value().index(), |
| RegisterFromRegisterInfoTableIndex(i).index()); |
| } |
| accumulator_info_ = GetRegisterInfo(accumulator_); |
| DCHECK(accumulator_info_->register_value() == accumulator_); |
| } |
| |
| void BytecodeRegisterOptimizer::PushToRegistersNeedingFlush(RegisterInfo* reg) { |
| if (!reg->needs_flush()) { |
| reg->set_needs_flush(true); |
| registers_needing_flushed_.push_back(reg); |
| } |
| } |
| |
| bool BytecodeRegisterOptimizer::EnsureAllRegistersAreFlushed() const { |
| for (RegisterInfo* reg_info : register_info_table_) { |
| if (reg_info->needs_flush()) { |
| return false; |
| } else if (!reg_info->IsOnlyMemberOfEquivalenceSet()) { |
| return false; |
| } else if (reg_info->allocated() && !reg_info->materialized()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void BytecodeRegisterOptimizer::Flush() { |
| if (!flush_required_) { |
| return; |
| } |
| |
| // Materialize all live registers and break equivalences. |
| for (RegisterInfo* reg_info : registers_needing_flushed_) { |
| if (!reg_info->needs_flush()) continue; |
| reg_info->set_needs_flush(false); |
| |
| RegisterInfo* materialized = reg_info->materialized() |
| ? reg_info |
| : reg_info->GetMaterializedEquivalent(); |
| |
| if (materialized != nullptr) { |
| // Walk equivalents of materialized registers, materializing |
| // each equivalent register as necessary and placing in their |
| // own equivalence set. |
| RegisterInfo* equivalent; |
| while ((equivalent = materialized->GetEquivalent()) != materialized) { |
| if (equivalent->allocated() && !equivalent->materialized()) { |
| OutputRegisterTransfer(materialized, equivalent); |
| } |
| equivalent->MoveToNewEquivalenceSet(NextEquivalenceId(), true); |
| equivalent->set_needs_flush(false); |
| } |
| } else { |
| // Equivalernce class containing only unallocated registers. |
| DCHECK_NULL(reg_info->GetAllocatedEquivalent()); |
| reg_info->MoveToNewEquivalenceSet(NextEquivalenceId(), false); |
| } |
| } |
| |
| registers_needing_flushed_.clear(); |
| DCHECK(EnsureAllRegistersAreFlushed()); |
| |
| flush_required_ = false; |
| } |
| |
| void BytecodeRegisterOptimizer::OutputRegisterTransfer( |
| RegisterInfo* input_info, RegisterInfo* output_info) { |
| Register input = input_info->register_value(); |
| Register output = output_info->register_value(); |
| DCHECK_NE(input.index(), output.index()); |
| |
| if (input == accumulator_) { |
| bytecode_writer_->EmitStar(output); |
| } else if (output == accumulator_) { |
| bytecode_writer_->EmitLdar(input); |
| } else { |
| bytecode_writer_->EmitMov(input, output); |
| } |
| if (output != accumulator_) { |
| max_register_index_ = std::max(max_register_index_, output.index()); |
| } |
| output_info->set_materialized(true); |
| } |
| |
| void BytecodeRegisterOptimizer::CreateMaterializedEquivalent( |
| RegisterInfo* info) { |
| DCHECK(info->materialized()); |
| RegisterInfo* unmaterialized = info->GetEquivalentToMaterialize(); |
| if (unmaterialized) { |
| OutputRegisterTransfer(info, unmaterialized); |
| } |
| } |
| |
| BytecodeRegisterOptimizer::RegisterInfo* |
| BytecodeRegisterOptimizer::GetMaterializedEquivalent(RegisterInfo* info) { |
| return info->materialized() ? info : info->GetMaterializedEquivalent(); |
| } |
| |
| BytecodeRegisterOptimizer::RegisterInfo* |
| BytecodeRegisterOptimizer::GetMaterializedEquivalentNotAccumulator( |
| RegisterInfo* info) { |
| if (info->materialized()) { |
| return info; |
| } |
| |
| RegisterInfo* result = info->GetMaterializedEquivalentOtherThan(accumulator_); |
| if (result == nullptr) { |
| Materialize(info); |
| result = info; |
| } |
| DCHECK(result->register_value() != accumulator_); |
| return result; |
| } |
| |
| void BytecodeRegisterOptimizer::Materialize(RegisterInfo* info) { |
| if (!info->materialized()) { |
| RegisterInfo* materialized = info->GetMaterializedEquivalent(); |
| DCHECK_NOT_NULL(materialized); |
| OutputRegisterTransfer(materialized, info); |
| } |
| } |
| |
| void BytecodeRegisterOptimizer::AddToEquivalenceSet( |
| RegisterInfo* set_member, RegisterInfo* non_set_member) { |
| // Equivalence class is now of size >= 2, so we make sure it will be flushed. |
| PushToRegistersNeedingFlush(non_set_member); |
| non_set_member->AddToEquivalenceSetOf(set_member); |
| // Flushing is only required when two or more registers are placed |
| // in the same equivalence set. |
| flush_required_ = true; |
| } |
| |
| void BytecodeRegisterOptimizer::RegisterTransfer(RegisterInfo* input_info, |
| RegisterInfo* output_info) { |
| bool output_is_observable = |
| RegisterIsObservable(output_info->register_value()); |
| bool in_same_equivalence_set = |
| output_info->IsInSameEquivalenceSet(input_info); |
| if (in_same_equivalence_set && |
| (!output_is_observable || output_info->materialized())) { |
| return; // Nothing more to do. |
| } |
| |
| // Materialize an alternate in the equivalence set that |
| // |output_info| is leaving. |
| if (output_info->materialized()) { |
| CreateMaterializedEquivalent(output_info); |
| } |
| |
| // Add |output_info| to new equivalence set. |
| if (!in_same_equivalence_set) { |
| AddToEquivalenceSet(input_info, output_info); |
| } |
| |
| if (output_is_observable) { |
| // Force store to be emitted when register is observable. |
| output_info->set_materialized(false); |
| RegisterInfo* materialized_info = input_info->GetMaterializedEquivalent(); |
| OutputRegisterTransfer(materialized_info, output_info); |
| } |
| |
| bool input_is_observable = RegisterIsObservable(input_info->register_value()); |
| if (input_is_observable) { |
| // If input is observable by the debugger, mark all other temporaries |
| // registers as unmaterialized so that this register is used in preference. |
| input_info->MarkTemporariesAsUnmaterialized(temporary_base_); |
| } |
| } |
| |
| void BytecodeRegisterOptimizer::PrepareOutputRegister(Register reg) { |
| RegisterInfo* reg_info = GetRegisterInfo(reg); |
| if (reg_info->materialized()) { |
| CreateMaterializedEquivalent(reg_info); |
| } |
| reg_info->MoveToNewEquivalenceSet(NextEquivalenceId(), true); |
| max_register_index_ = |
| std::max(max_register_index_, reg_info->register_value().index()); |
| } |
| |
| void BytecodeRegisterOptimizer::PrepareOutputRegisterList( |
| RegisterList reg_list) { |
| int start_index = reg_list.first_register().index(); |
| for (int i = 0; i < reg_list.register_count(); ++i) { |
| Register current(start_index + i); |
| PrepareOutputRegister(current); |
| } |
| } |
| |
| Register BytecodeRegisterOptimizer::GetInputRegister(Register reg) { |
| RegisterInfo* reg_info = GetRegisterInfo(reg); |
| if (reg_info->materialized()) { |
| return reg; |
| } else { |
| RegisterInfo* equivalent_info = |
| GetMaterializedEquivalentNotAccumulator(reg_info); |
| return equivalent_info->register_value(); |
| } |
| } |
| |
| RegisterList BytecodeRegisterOptimizer::GetInputRegisterList( |
| RegisterList reg_list) { |
| if (reg_list.register_count() == 1) { |
| // If there is only a single register, treat it as a normal input register. |
| Register reg(GetInputRegister(reg_list.first_register())); |
| return RegisterList(reg); |
| } else { |
| int start_index = reg_list.first_register().index(); |
| for (int i = 0; i < reg_list.register_count(); ++i) { |
| Register current(start_index + i); |
| RegisterInfo* input_info = GetRegisterInfo(current); |
| Materialize(input_info); |
| } |
| return reg_list; |
| } |
| } |
| |
| void BytecodeRegisterOptimizer::GrowRegisterMap(Register reg) { |
| DCHECK(RegisterIsTemporary(reg)); |
| size_t index = GetRegisterInfoTableIndex(reg); |
| if (index >= register_info_table_.size()) { |
| size_t new_size = index + 1; |
| size_t old_size = register_info_table_.size(); |
| register_info_table_.resize(new_size); |
| for (size_t i = old_size; i < new_size; ++i) { |
| register_info_table_[i] = |
| zone()->New<RegisterInfo>(RegisterFromRegisterInfoTableIndex(i), |
| NextEquivalenceId(), true, false); |
| } |
| } |
| } |
| |
| void BytecodeRegisterOptimizer::AllocateRegister(RegisterInfo* info) { |
| info->set_allocated(true); |
| if (!info->materialized()) { |
| info->MoveToNewEquivalenceSet(NextEquivalenceId(), true); |
| } |
| } |
| |
| void BytecodeRegisterOptimizer::RegisterAllocateEvent(Register reg) { |
| AllocateRegister(GetOrCreateRegisterInfo(reg)); |
| } |
| |
| void BytecodeRegisterOptimizer::RegisterListAllocateEvent( |
| RegisterList reg_list) { |
| if (reg_list.register_count() != 0) { |
| int first_index = reg_list.first_register().index(); |
| GrowRegisterMap(Register(first_index + reg_list.register_count() - 1)); |
| for (int i = 0; i < reg_list.register_count(); i++) { |
| AllocateRegister(GetRegisterInfo(Register(first_index + i))); |
| } |
| } |
| } |
| |
| void BytecodeRegisterOptimizer::RegisterListFreeEvent(RegisterList reg_list) { |
| int first_index = reg_list.first_register().index(); |
| for (int i = 0; i < reg_list.register_count(); i++) { |
| GetRegisterInfo(Register(first_index + i))->set_allocated(false); |
| } |
| } |
| |
| } // namespace interpreter |
| } // namespace internal |
| } // namespace v8 |