| // 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) {} | |
| 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_; | |
| DISALLOW_COPY_AND_ASSIGN(RegisterInfo); | |
| }; | |
| 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); | |
| register_info_table_offset_ = | |
| -Register::FromParameterIndex(0, 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] = new (zone) 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] = | |
| new (zone()) 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 |