| // Copyright 2015 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-peephole-optimizer.h" |
| |
| #include "src/interpreter/constant-array-builder.h" |
| #include "src/objects-inl.h" |
| #include "src/objects.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace interpreter { |
| |
| BytecodePeepholeOptimizer::BytecodePeepholeOptimizer( |
| ConstantArrayBuilder* constant_array_builder, |
| BytecodePipelineStage* next_stage) |
| : constant_array_builder_(constant_array_builder), next_stage_(next_stage) { |
| InvalidateLast(); |
| } |
| |
| // override |
| Handle<BytecodeArray> BytecodePeepholeOptimizer::ToBytecodeArray( |
| int fixed_register_count, int parameter_count, |
| Handle<FixedArray> handler_table) { |
| Flush(); |
| return next_stage_->ToBytecodeArray(fixed_register_count, parameter_count, |
| handler_table); |
| } |
| |
| // override |
| void BytecodePeepholeOptimizer::Write(BytecodeNode* node) { |
| node = OptimizeAndEmitLast(node); |
| if (node != nullptr) { |
| SetLast(node); |
| } |
| } |
| |
| // override |
| void BytecodePeepholeOptimizer::WriteJump(BytecodeNode* node, |
| BytecodeLabel* label) { |
| node = OptimizeAndEmitLast(node); |
| next_stage_->WriteJump(node, label); |
| } |
| |
| // override |
| void BytecodePeepholeOptimizer::BindLabel(BytecodeLabel* label) { |
| Flush(); |
| next_stage_->BindLabel(label); |
| } |
| |
| // override |
| void BytecodePeepholeOptimizer::BindLabel(const BytecodeLabel& target, |
| BytecodeLabel* label) { |
| // There is no need to flush here, it will have been flushed when |target| |
| // was bound. |
| next_stage_->BindLabel(target, label); |
| } |
| |
| void BytecodePeepholeOptimizer::Flush() { |
| // TODO(oth/rmcilroy): We could check CanElideLast() here to potentially |
| // eliminate last rather than writing it. |
| if (LastIsValid()) { |
| next_stage_->Write(&last_); |
| InvalidateLast(); |
| } |
| } |
| |
| void BytecodePeepholeOptimizer::InvalidateLast() { |
| last_.set_bytecode(Bytecode::kIllegal); |
| } |
| |
| bool BytecodePeepholeOptimizer::LastIsValid() const { |
| return last_.bytecode() != Bytecode::kIllegal; |
| } |
| |
| void BytecodePeepholeOptimizer::SetLast(const BytecodeNode* const node) { |
| last_.Clone(node); |
| } |
| |
| Handle<Object> BytecodePeepholeOptimizer::GetConstantForIndexOperand( |
| const BytecodeNode* const node, int index) const { |
| DCHECK_LE(index, node->operand_count()); |
| DCHECK_EQ(Bytecodes::GetOperandType(node->bytecode(), 0), OperandType::kIdx); |
| uint32_t index_operand = node->operand(0); |
| return constant_array_builder_->At(index_operand); |
| } |
| |
| bool BytecodePeepholeOptimizer::LastBytecodePutsNameInAccumulator() const { |
| DCHECK(LastIsValid()); |
| return (last_.bytecode() == Bytecode::kTypeOf || |
| last_.bytecode() == Bytecode::kToName || |
| (last_.bytecode() == Bytecode::kLdaConstant && |
| GetConstantForIndexOperand(&last_, 0)->IsName())); |
| } |
| |
| void BytecodePeepholeOptimizer::TryToRemoveLastExpressionPosition( |
| const BytecodeNode* const current) { |
| if (current->source_info().is_valid() && |
| last_.source_info().is_expression() && |
| Bytecodes::IsWithoutExternalSideEffects(last_.bytecode())) { |
| // The last bytecode has been marked as expression. It has no |
| // external effects so can't throw and the current bytecode is a |
| // source position. Remove the expression position on the last |
| // bytecode to open up potential peephole optimizations and to |
| // save the memory and perf cost of storing the unneeded |
| // expression position. |
| last_.source_info().set_invalid(); |
| } |
| } |
| |
| bool BytecodePeepholeOptimizer::CanElideCurrent( |
| const BytecodeNode* const current) const { |
| if (Bytecodes::IsLdarOrStar(last_.bytecode()) && |
| Bytecodes::IsLdarOrStar(current->bytecode()) && |
| current->operand(0) == last_.operand(0)) { |
| // Ldar and Star make the accumulator and register hold equivalent |
| // values. Only the first bytecode is needed if there's a sequence |
| // of back-to-back Ldar and Star bytecodes with the same operand. |
| return true; |
| } else if (current->bytecode() == Bytecode::kToName && |
| LastBytecodePutsNameInAccumulator()) { |
| // If the previous bytecode ensured a name was in the accumulator, |
| // the type coercion ToName() can be elided. |
| return true; |
| } else { |
| // Additional candidates for eliding current: |
| // (i) ToNumber if the last puts a number in the accumulator. |
| return false; |
| } |
| } |
| |
| bool BytecodePeepholeOptimizer::CanElideLastBasedOnSourcePosition( |
| const BytecodeNode* const current) const { |
| // |
| // The rules for allowing the elision of the last bytecode based |
| // on source position are: |
| // |
| // C U R R E N T |
| // +--------+--------+--------+ |
| // | None | Expr | Stmt | |
| // L +--------+--------+--------+--------+ |
| // | None | YES | YES | YES | |
| // A +--------+--------+--------+--------+ |
| // | Expr | YES | MAYBE | MAYBE | |
| // S +--------+--------+--------+--------+ |
| // | Stmt | YES | NO | NO | |
| // T +--------+--------+--------+--------+ |
| // |
| // The goal is not lose any statement positions and not lose useful |
| // expression positions. Whenever the last bytecode is elided it's |
| // source position information is applied to the current node |
| // updating it if necessary. |
| // |
| // The last bytecode can be elided for the MAYBE cases if the last |
| // bytecode is known not to throw. If it throws, the system would |
| // not have correct stack trace information. The appropriate check |
| // for this would be Bytecodes::IsWithoutExternalSideEffects(), |
| // which is checked in |
| // BytecodePeepholeOptimizer::TransformLastAndCurrentBytecodes() to |
| // keep the check here simple. |
| // |
| // In rare cases, bytecode generation produces consecutive bytecodes |
| // with the same expression positions. In principle, the latter of |
| // these can be elided, but would make this function more expensive. |
| // |
| return (!last_.source_info().is_valid() || |
| !current->source_info().is_valid()); |
| } |
| |
| namespace { |
| |
| void TransformLdaStarToLdrLdar(Bytecode new_bytecode, BytecodeNode* const last, |
| BytecodeNode* const current) { |
| DCHECK_EQ(current->bytecode(), Bytecode::kStar); |
| |
| // |
| // An example transformation here would be: |
| // |
| // LdaGlobal i0, i1 ____\ LdrGlobal i0, i1, R |
| // Star R ====/ Ldar R |
| // |
| // which loads a global value into both a register and the |
| // accumulator. However, in the second form the Ldar can often be |
| // peephole optimized away unlike the Star in the first form. |
| // |
| last->Transform(new_bytecode, current->operand(0)); |
| current->set_bytecode(Bytecode::kLdar, current->operand(0)); |
| } |
| |
| } // namespace |
| |
| bool BytecodePeepholeOptimizer::TransformLastAndCurrentBytecodes( |
| BytecodeNode* const current) { |
| if (current->bytecode() == Bytecode::kStar && |
| !current->source_info().is_statement()) { |
| // Note: If the Star is tagged with a statement position, we can't |
| // perform this transform as the store to the register will |
| // have the wrong ordering for stepping in the debugger. |
| switch (last_.bytecode()) { |
| case Bytecode::kLdaNamedProperty: |
| TransformLdaStarToLdrLdar(Bytecode::kLdrNamedProperty, &last_, current); |
| return true; |
| case Bytecode::kLdaKeyedProperty: |
| TransformLdaStarToLdrLdar(Bytecode::kLdrKeyedProperty, &last_, current); |
| return true; |
| case Bytecode::kLdaGlobal: |
| TransformLdaStarToLdrLdar(Bytecode::kLdrGlobal, &last_, current); |
| return true; |
| case Bytecode::kLdaContextSlot: |
| TransformLdaStarToLdrLdar(Bytecode::kLdrContextSlot, &last_, current); |
| return true; |
| case Bytecode::kLdaUndefined: |
| TransformLdaStarToLdrLdar(Bytecode::kLdrUndefined, &last_, current); |
| return true; |
| default: |
| break; |
| } |
| } |
| return false; |
| } |
| |
| bool BytecodePeepholeOptimizer::RemoveToBooleanFromJump( |
| BytecodeNode* const current) { |
| bool can_remove = Bytecodes::IsJumpIfToBoolean(current->bytecode()) && |
| Bytecodes::WritesBooleanToAccumulator(last_.bytecode()); |
| if (can_remove) { |
| // Conditional jumps with boolean conditions are emiitted in |
| // ToBoolean form by the bytecode array builder, |
| // i.e. JumpIfToBooleanTrue rather JumpIfTrue. The ToBoolean |
| // element can be removed if the previous bytecode put a boolean |
| // value in the accumulator. |
| Bytecode jump = Bytecodes::GetJumpWithoutToBoolean(current->bytecode()); |
| current->set_bytecode(jump, current->operand(0)); |
| } |
| return can_remove; |
| } |
| |
| bool BytecodePeepholeOptimizer::RemoveToBooleanFromLogicalNot( |
| BytecodeNode* const current) { |
| bool can_remove = current->bytecode() == Bytecode::kToBooleanLogicalNot && |
| Bytecodes::WritesBooleanToAccumulator(last_.bytecode()); |
| if (can_remove) { |
| // Logical-nots are emitted in ToBoolean form by the bytecode array |
| // builder, The ToBoolean element can be removed if the previous bytecode |
| // put a boolean value in the accumulator. |
| current->set_bytecode(Bytecode::kLogicalNot); |
| } |
| return can_remove; |
| } |
| |
| bool BytecodePeepholeOptimizer::TransformCurrentBytecode( |
| BytecodeNode* const current) { |
| return RemoveToBooleanFromJump(current) || |
| RemoveToBooleanFromLogicalNot(current); |
| } |
| |
| bool BytecodePeepholeOptimizer::CanElideLast( |
| const BytecodeNode* const current) const { |
| if (last_.bytecode() == Bytecode::kNop) { |
| // Nop are placeholders for holding source position information. |
| return true; |
| } else if (Bytecodes::IsAccumulatorLoadWithoutEffects(current->bytecode()) && |
| Bytecodes::IsAccumulatorLoadWithoutEffects(last_.bytecode())) { |
| // The accumulator is invisible to the debugger. If there is a sequence of |
| // consecutive accumulator loads (that don't have side effects) then only |
| // the final load is potentially visible. |
| return true; |
| } else if (Bytecodes::GetAccumulatorUse(current->bytecode()) == |
| AccumulatorUse::kWrite && |
| Bytecodes::IsAccumulatorLoadWithoutEffects(last_.bytecode())) { |
| // The current instruction clobbers the accumulator without reading it. The |
| // load in the last instruction can be elided as it has no effect. |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| BytecodeNode* BytecodePeepholeOptimizer::Optimize(BytecodeNode* current) { |
| TryToRemoveLastExpressionPosition(current); |
| |
| if (TransformCurrentBytecode(current) || |
| TransformLastAndCurrentBytecodes(current)) { |
| return current; |
| } |
| |
| if (CanElideCurrent(current)) { |
| if (current->source_info().is_valid()) { |
| // Preserve the source information by replacing the current bytecode |
| // with a no op bytecode. |
| current->set_bytecode(Bytecode::kNop); |
| } else { |
| current = nullptr; |
| } |
| return current; |
| } |
| |
| if (CanElideLast(current) && CanElideLastBasedOnSourcePosition(current)) { |
| if (last_.source_info().is_valid()) { |
| // Current can not be valid per CanElideLastBasedOnSourcePosition(). |
| current->source_info().Clone(last_.source_info()); |
| } |
| InvalidateLast(); |
| return current; |
| } |
| |
| return current; |
| } |
| |
| BytecodeNode* BytecodePeepholeOptimizer::OptimizeAndEmitLast( |
| BytecodeNode* current) { |
| // Attempt optimization if there is an earlier node to optimize with. |
| if (LastIsValid()) { |
| current = Optimize(current); |
| // Only output the last node if it wasn't invalidated by the optimization. |
| if (LastIsValid()) { |
| next_stage_->Write(&last_); |
| InvalidateLast(); |
| } |
| } |
| return current; |
| } |
| |
| } // namespace interpreter |
| } // namespace internal |
| } // namespace v8 |