| // 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. |
| |
| #ifndef V8_INTERPRETER_BYTECODE_REGISTER_OPTIMIZER_H_ |
| #define V8_INTERPRETER_BYTECODE_REGISTER_OPTIMIZER_H_ |
| |
| #include "src/base/compiler-specific.h" |
| #include "src/common/globals.h" |
| #include "src/interpreter/bytecode-register-allocator.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace interpreter { |
| |
| // An optimization stage for eliminating unnecessary transfers between |
| // registers. The bytecode generator uses temporary registers |
| // liberally for correctness and convenience and this stage removes |
| // transfers that are not required and preserves correctness. |
| class V8_EXPORT_PRIVATE BytecodeRegisterOptimizer final |
| : public NON_EXPORTED_BASE(BytecodeRegisterAllocator::Observer), |
| public NON_EXPORTED_BASE(ZoneObject) { |
| public: |
| class BytecodeWriter { |
| public: |
| BytecodeWriter() = default; |
| virtual ~BytecodeWriter() = default; |
| BytecodeWriter(const BytecodeWriter&) = delete; |
| BytecodeWriter& operator=(const BytecodeWriter&) = delete; |
| |
| // Called to emit a register transfer bytecode. |
| virtual void EmitLdar(Register input) = 0; |
| virtual void EmitStar(Register output) = 0; |
| virtual void EmitMov(Register input, Register output) = 0; |
| }; |
| |
| BytecodeRegisterOptimizer(Zone* zone, |
| BytecodeRegisterAllocator* register_allocator, |
| int fixed_registers_count, int parameter_count, |
| BytecodeWriter* bytecode_writer); |
| ~BytecodeRegisterOptimizer() override = default; |
| BytecodeRegisterOptimizer(const BytecodeRegisterOptimizer&) = delete; |
| BytecodeRegisterOptimizer& operator=(const BytecodeRegisterOptimizer&) = |
| delete; |
| |
| // Perform explicit register transfer operations. |
| void DoLdar(Register input) { |
| // TODO(rmcilroy): Avoid treating accumulator loads as clobbering the |
| // accumulator until the value is actually materialized in the accumulator. |
| RegisterInfo* input_info = GetRegisterInfo(input); |
| RegisterTransfer(input_info, accumulator_info_); |
| } |
| void DoStar(Register output) { |
| RegisterInfo* output_info = GetRegisterInfo(output); |
| RegisterTransfer(accumulator_info_, output_info); |
| } |
| void DoMov(Register input, Register output) { |
| RegisterInfo* input_info = GetRegisterInfo(input); |
| RegisterInfo* output_info = GetRegisterInfo(output); |
| RegisterTransfer(input_info, output_info); |
| } |
| |
| // Materialize all live registers and flush equivalence sets. |
| void Flush(); |
| bool EnsureAllRegistersAreFlushed() const; |
| |
| // Prepares for |bytecode|. |
| template <Bytecode bytecode, AccumulatorUse accumulator_use> |
| V8_INLINE void PrepareForBytecode() { |
| if (Bytecodes::IsJump(bytecode) || Bytecodes::IsSwitch(bytecode) || |
| bytecode == Bytecode::kDebugger || |
| bytecode == Bytecode::kSuspendGenerator || |
| bytecode == Bytecode::kResumeGenerator) { |
| // All state must be flushed before emitting |
| // - a jump bytecode (as the register equivalents at the jump target |
| // aren't known) |
| // - a switch bytecode (as the register equivalents at the switch targets |
| // aren't known) |
| // - a call to the debugger (as it can manipulate locals and parameters), |
| // - a generator suspend (as this involves saving all registers). |
| // - a generator register restore. |
| Flush(); |
| } |
| |
| // Materialize the accumulator if it is read by the bytecode. The |
| // accumulator is special and no other register can be materialized |
| // in it's place. |
| if (BytecodeOperands::ReadsAccumulator(accumulator_use)) { |
| Materialize(accumulator_info_); |
| } |
| |
| // Materialize an equivalent to the accumulator if it will be |
| // clobbered when the bytecode is dispatched. |
| if (BytecodeOperands::WritesAccumulator(accumulator_use)) { |
| PrepareOutputRegister(accumulator_); |
| } |
| } |
| |
| // Prepares |reg| for being used as an output operand. |
| void PrepareOutputRegister(Register reg); |
| |
| // Prepares registers in |reg_list| for being used as an output operand. |
| void PrepareOutputRegisterList(RegisterList reg_list); |
| |
| // Returns an equivalent register to |reg| to be used as an input operand. |
| Register GetInputRegister(Register reg); |
| |
| // Returns an equivalent register list to |reg_list| to be used as an input |
| // operand. |
| RegisterList GetInputRegisterList(RegisterList reg_list); |
| |
| int maxiumum_register_index() const { return max_register_index_; } |
| |
| private: |
| static const uint32_t kInvalidEquivalenceId; |
| |
| class RegisterInfo; |
| |
| // BytecodeRegisterAllocator::Observer interface. |
| void RegisterAllocateEvent(Register reg) override; |
| void RegisterListAllocateEvent(RegisterList reg_list) override; |
| void RegisterListFreeEvent(RegisterList reg) override; |
| |
| // Update internal state for register transfer from |input| to |output| |
| void RegisterTransfer(RegisterInfo* input, RegisterInfo* output); |
| |
| // Emit a register transfer bytecode from |input| to |output|. |
| void OutputRegisterTransfer(RegisterInfo* input, RegisterInfo* output); |
| |
| void CreateMaterializedEquivalent(RegisterInfo* info); |
| RegisterInfo* GetMaterializedEquivalent(RegisterInfo* info); |
| RegisterInfo* GetMaterializedEquivalentNotAccumulator(RegisterInfo* info); |
| void Materialize(RegisterInfo* info); |
| void AddToEquivalenceSet(RegisterInfo* set_member, |
| RegisterInfo* non_set_member); |
| |
| void PushToRegistersNeedingFlush(RegisterInfo* reg); |
| // Methods for finding and creating metadata for each register. |
| RegisterInfo* GetRegisterInfo(Register reg) { |
| size_t index = GetRegisterInfoTableIndex(reg); |
| DCHECK_LT(index, register_info_table_.size()); |
| return register_info_table_[index]; |
| } |
| RegisterInfo* GetOrCreateRegisterInfo(Register reg) { |
| size_t index = GetRegisterInfoTableIndex(reg); |
| return index < register_info_table_.size() ? register_info_table_[index] |
| : NewRegisterInfo(reg); |
| } |
| RegisterInfo* NewRegisterInfo(Register reg) { |
| size_t index = GetRegisterInfoTableIndex(reg); |
| DCHECK_GE(index, register_info_table_.size()); |
| GrowRegisterMap(reg); |
| return register_info_table_[index]; |
| } |
| |
| void GrowRegisterMap(Register reg); |
| |
| bool RegisterIsTemporary(Register reg) const { |
| return reg >= temporary_base_; |
| } |
| |
| bool RegisterIsObservable(Register reg) const { |
| return reg != accumulator_ && !RegisterIsTemporary(reg); |
| } |
| |
| static Register OperandToRegister(uint32_t operand) { |
| return Register::FromOperand(static_cast<int32_t>(operand)); |
| } |
| |
| size_t GetRegisterInfoTableIndex(Register reg) const { |
| return static_cast<size_t>(reg.index() + register_info_table_offset_); |
| } |
| |
| Register RegisterFromRegisterInfoTableIndex(size_t index) const { |
| return Register(static_cast<int>(index) - register_info_table_offset_); |
| } |
| |
| uint32_t NextEquivalenceId() { |
| equivalence_id_++; |
| // TODO(rmcilroy): use the same type for these and remove static_cast. |
| CHECK_NE(static_cast<size_t>(equivalence_id_), kInvalidEquivalenceId); |
| return equivalence_id_; |
| } |
| |
| void AllocateRegister(RegisterInfo* info); |
| |
| Zone* zone() { return zone_; } |
| |
| const Register accumulator_; |
| RegisterInfo* accumulator_info_; |
| const Register temporary_base_; |
| int max_register_index_; |
| |
| // Direct mapping to register info. |
| ZoneVector<RegisterInfo*> register_info_table_; |
| int register_info_table_offset_; |
| |
| ZoneDeque<RegisterInfo*> registers_needing_flushed_; |
| |
| // Counter for equivalence sets identifiers. |
| int equivalence_id_; |
| |
| BytecodeWriter* bytecode_writer_; |
| bool flush_required_; |
| Zone* zone_; |
| }; |
| |
| } // namespace interpreter |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_INTERPRETER_BYTECODE_REGISTER_OPTIMIZER_H_ |