// 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(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