// 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-peephole-optimizer.h"
#include "src/objects-inl.h"
#include "src/objects.h"
namespace v8 {
namespace internal {
namespace interpreter {
BytecodePeepholeOptimizer::BytecodePeepholeOptimizer(
BytecodePipelineStage* next_stage)
: next_stage_(next_stage), last_(Bytecode::kIllegal, BytecodeSourceInfo()) {
InvalidateLast();
}
// override
Handle<BytecodeArray> BytecodePeepholeOptimizer::ToBytecodeArray(
Isolate* isolate, int register_count, int parameter_count,
Handle<FixedArray> handler_table) {
Flush();
return next_stage_->ToBytecodeArray(isolate, register_count, parameter_count,
handler_table);
}
// 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);
}
// override
void BytecodePeepholeOptimizer::WriteJump(BytecodeNode* node,
BytecodeLabel* label) {
// Handlers for jump bytecodes do not emit |node| as WriteJump()
// requires the |label| and having a label argument in all action
// handlers results in dead work in the non-jump case.
ApplyPeepholeAction(node);
next_stage()->WriteJump(node, label);
}
// override
void BytecodePeepholeOptimizer::Write(BytecodeNode* node) {
// Handlers for non-jump bytecodes run to completion emitting
// bytecode to next stage as appropriate.
ApplyPeepholeAction(node);
}
void BytecodePeepholeOptimizer::Flush() {
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) {
// An action shouldn't leave a NOP as last bytecode unless it has
// source position information. NOP without source information can
// always be elided.
DCHECK(node->bytecode() != Bytecode::kNop || node->source_info().is_valid());
last_ = *node;
}
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 could 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(). By
// default, the upstream bytecode generator filters out unneeded
// expression position information so there is neglible benefit to
// handling MAYBE specially. Hence MAYBE is treated the same as NO.
//
return (!last_.source_info().is_valid() ||
!current->source_info().is_valid());
}
namespace {
void TransformLdaSmiBinaryOpToBinaryOpWithSmi(Bytecode new_bytecode,
BytecodeNode* const last,
BytecodeNode* const current) {
DCHECK_EQ(last->bytecode(), Bytecode::kLdaSmi);
current->set_bytecode(new_bytecode, last->operand(0), current->operand(0),
current->operand(1));
if (last->source_info().is_valid()) {
current->set_source_info(last->source_info());
}
}
void TransformLdaZeroBinaryOpToBinaryOpWithZero(Bytecode new_bytecode,
BytecodeNode* const last,
BytecodeNode* const current) {
DCHECK_EQ(last->bytecode(), Bytecode::kLdaZero);
current->set_bytecode(new_bytecode, 0, current->operand(0),
current->operand(1));
if (last->source_info().is_valid()) {
current->set_source_info(last->source_info());
}
}
} // namespace
void BytecodePeepholeOptimizer::DefaultAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
next_stage()->Write(last());
SetLast(node);
}
void BytecodePeepholeOptimizer::UpdateLastAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(!LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
SetLast(node);
}
void BytecodePeepholeOptimizer::UpdateLastIfSourceInfoPresentAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(!LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
if (node->source_info().is_valid()) {
SetLast(node);
}
}
void BytecodePeepholeOptimizer::ElideCurrentAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
if (node->source_info().is_valid()) {
// Preserve the source information by replacing the node bytecode
// with a no op bytecode.
node->set_bytecode(Bytecode::kNop);
DefaultAction(node);
} else {
// Nothing to do, keep last and wait for next bytecode to pair with it.
}
}
void BytecodePeepholeOptimizer::ElideCurrentIfOperand0MatchesAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
if (last()->operand(0) == node->operand(0)) {
ElideCurrentAction(node);
} else {
DefaultAction(node);
}
}
void BytecodePeepholeOptimizer::ElideLastAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
if (CanElideLastBasedOnSourcePosition(node)) {
if (last()->source_info().is_valid()) {
// |node| can not have a valid source position if the source
// position of last() is valid (per rules in
// CanElideLastBasedOnSourcePosition()).
node->set_source_info(last()->source_info());
}
SetLast(node);
} else {
DefaultAction(node);
}
}
void BytecodePeepholeOptimizer::ChangeBytecodeAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
node->replace_bytecode(action_data->bytecode);
DefaultAction(node);
}
void BytecodePeepholeOptimizer::TransformLdaSmiBinaryOpToBinaryOpWithSmiAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
if (!node->source_info().is_valid() || !last()->source_info().is_valid()) {
// Fused last and current into current.
TransformLdaSmiBinaryOpToBinaryOpWithSmi(action_data->bytecode, last(),
node);
SetLast(node);
} else {
DefaultAction(node);
}
}
void BytecodePeepholeOptimizer::
TransformLdaZeroBinaryOpToBinaryOpWithZeroAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
if (!node->source_info().is_valid() || !last()->source_info().is_valid()) {
// Fused last and current into current.
TransformLdaZeroBinaryOpToBinaryOpWithZero(action_data->bytecode, last(),
node);
SetLast(node);
} else {
DefaultAction(node);
}
}
void BytecodePeepholeOptimizer::DefaultJumpAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(Bytecodes::IsJump(node->bytecode()));
next_stage()->Write(last());
InvalidateLast();
}
void BytecodePeepholeOptimizer::UpdateLastJumpAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(!LastIsValid());
DCHECK(Bytecodes::IsJump(node->bytecode()));
}
void BytecodePeepholeOptimizer::ChangeJumpBytecodeAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(Bytecodes::IsJump(node->bytecode()));
next_stage()->Write(last());
InvalidateLast();
node->set_bytecode(action_data->bytecode, node->operand(0));
}
void BytecodePeepholeOptimizer::ElideLastBeforeJumpAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(Bytecodes::IsJump(node->bytecode()));
if (!CanElideLastBasedOnSourcePosition(node)) {
next_stage()->Write(last());
} else if (!node->source_info().is_valid()) {
node->set_source_info(last()->source_info());
}
InvalidateLast();
}
void BytecodePeepholeOptimizer::ApplyPeepholeAction(BytecodeNode* const node) {
// A single table is used for looking up peephole optimization
// matches as it is observed to have better performance. This is
// inspite of the fact that jump bytecodes and non-jump bytecodes
// have different processing logic, in particular a jump bytecode
// always needs to emit the jump via WriteJump().
const PeepholeActionAndData* const action_data =
PeepholeActionTable::Lookup(last()->bytecode(), node->bytecode());
switch (action_data->action) {
#define CASE(Action) \
case PeepholeAction::k##Action: \
Action(node, action_data); \
break;
PEEPHOLE_ACTION_LIST(CASE)
#undef CASE
default:
UNREACHABLE();
break;
}
}
} // namespace interpreter
} // namespace internal
} // namespace v8