Reland "[interpeter] Move to table based peephole optimizer."

Original issue's description:
> [interpeter] Move to table based peephole optimizer.
>
> Introduces a lookup table for peephole optimizations.
>
> Fixes some tests using BytecodePeepholeOptimizer::Write() that should
> have been update to use BytecodePeepholeOptimizer::WriteJump().
>
> BUG=v8:4280
> LOG=N
>
> Committed: https://crrev.com/f4234422b93b21a286b0f31799009bcbe8b90b9e
> Cr-Commit-Position: refs/heads/master@{#37819}

BUG=v8:4280
LOG=N

Review-Url: https://codereview.chromium.org/2164583002
Cr-Commit-Position: refs/heads/master@{#37866}
This commit is contained in:
oth 2016-07-19 04:54:21 -07:00 committed by Commit bot
parent 460e0b80a9
commit a451bd1a68
12 changed files with 811 additions and 283 deletions

View File

@ -86,6 +86,7 @@ if (v8_enable_gdbjit == "") {
}
}
v8_generated_peephole_source = "$target_gen_dir/bytecode-peephole-table.cc"
v8_random_seed = "314159265"
v8_toolset_for_shell = "host"
@ -649,6 +650,25 @@ action("run_mksnapshot") {
}
}
action("run_mkpeephole") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
deps = [ ":mkpeephole($v8_snapshot_toolchain)" ]
outputs = [ v8_generated_peephole_source ]
sources = []
script = "tools/run.py"
args = [
"./" + rebase_path(get_label_info(":mkpeephole($v8_snapshot_toolchain)",
"root_out_dir") + "/mkpeephole",
root_build_dir),
rebase_path(v8_generated_peephole_source, root_build_dir),
]
}
action("v8_dump_build_config") {
script = "tools/testrunner/utils/dump_build_config.py"
outputs = [
@ -1315,6 +1335,7 @@ v8_source_set("v8_base") {
"src/interpreter/bytecode-label.h",
"src/interpreter/bytecode-peephole-optimizer.cc",
"src/interpreter/bytecode-peephole-optimizer.h",
"src/interpreter/bytecode-peephole-table.h",
"src/interpreter/bytecode-pipeline.cc",
"src/interpreter/bytecode-pipeline.h",
"src/interpreter/bytecode-register-allocator.cc",
@ -1913,6 +1934,9 @@ v8_source_set("v8_base") {
":v8_libsampler",
]
sources += [ v8_generated_peephole_source ]
deps += [ ":run_mkpeephole" ]
if (is_win) {
# TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
cflags = [ "/wd4267" ]
@ -2136,6 +2160,31 @@ if (current_toolchain == v8_snapshot_toolchain) {
}
}
v8_executable("mkpeephole") {
# mkpeephole needs to be built for the build host so the peephole lookup
# table can built during build. The table depends on the properties of
# bytecodes that are described in bytecodes.{cc,h}.
visibility = [ ":*" ] # Only targets in this file can depend on this.
sources = [
"src/interpreter/bytecode-peephole-optimizer.h",
"src/interpreter/bytecodes.cc",
"src/interpreter/bytecodes.h",
"src/interpreter/mkpeephole.cc",
]
configs = [
":external_config",
":internal_config",
]
deps = [
":v8_libbase",
"//build/config/sanitizers:deps",
"//build/win:default_exe_manifest",
]
}
###############################################################################
# Public targets
#

View File

@ -238,6 +238,7 @@
(v8_target_arch=="arm64" and host_arch!="arm64") or \
(v8_target_arch=="mipsel" and host_arch!="mipsel") or \
(v8_target_arch=="mips64el" and host_arch!="mips64el") or \
(v8_target_arch=="mips" and host_arch!="mips") or \
(v8_target_arch=="x64" and host_arch!="x64") or \
(OS=="android" or OS=="qnx")', {
'want_separate_host_toolset': 1,

View File

@ -1,4 +1,4 @@
// Copyright 2015 the V8 project authors. All rights reserved.
// 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.
@ -28,21 +28,6 @@ Handle<BytecodeArray> BytecodePeepholeOptimizer::ToBytecodeArray(
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();
@ -52,14 +37,29 @@ void BytecodePeepholeOptimizer::BindLabel(BytecodeLabel* 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.
// 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() {
// TODO(oth/rmcilroy): We could check CanElideLast() here to potentially
// eliminate last rather than writing it.
if (LastIsValid()) {
next_stage_->Write(&last_);
InvalidateLast();
@ -75,6 +75,11 @@ bool BytecodePeepholeOptimizer::LastIsValid() const {
}
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_.Clone(node);
}
@ -86,51 +91,6 @@ Handle<Object> BytecodePeepholeOptimizer::GetConstantForIndexOperand(
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) current is Nop.
// (ii) ToNumber if the last puts a number in the accumulator.
return false;
}
}
bool BytecodePeepholeOptimizer::CanElideLastBasedOnSourcePosition(
const BytecodeNode* const current) const {
//
@ -153,17 +113,13 @@ bool BytecodePeepholeOptimizer::CanElideLastBasedOnSourcePosition(
// 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
// 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(),
// 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.
// 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());
@ -189,13 +145,21 @@ void TransformLdaStarToLdrLdar(Bytecode new_bytecode, BytecodeNode* const last,
current->set_bytecode(Bytecode::kLdar, current->operand(0));
}
void TransformToBinaryOpWithSmiOnRhs(Bytecode new_bytecode,
BytecodeNode* const last,
BytecodeNode* const current) {
DCHECK(Bytecodes::IsLdaSmiOrLdaZero(last->bytecode()));
uint32_t imm_operand =
last->bytecode() == Bytecode::kLdaSmi ? last->operand(0) : 0;
current->set_bytecode(new_bytecode, imm_operand, current->operand(0));
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));
if (last->source_info().is_valid()) {
current->source_info().Clone(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));
if (last->source_info().is_valid()) {
current->source_info().Clone(last->source_info());
}
@ -203,171 +167,198 @@ void TransformToBinaryOpWithSmiOnRhs(Bytecode new_bytecode,
} // 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;
}
} else if (Bytecodes::IsLdaSmiOrLdaZero(last_.bytecode()) &&
(!last_.source_info().is_valid() ||
!current->source_info().is_valid())) {
switch (current->bytecode()) {
case Bytecode::kAdd:
TransformToBinaryOpWithSmiOnRhs(Bytecode::kAddSmi, &last_, current);
InvalidateLast();
return true;
case Bytecode::kSub:
TransformToBinaryOpWithSmiOnRhs(Bytecode::kSubSmi, &last_, current);
InvalidateLast();
return true;
case Bytecode::kBitwiseOr:
TransformToBinaryOpWithSmiOnRhs(Bytecode::kBitwiseOrSmi, &last_,
current);
InvalidateLast();
return true;
case Bytecode::kBitwiseAnd:
TransformToBinaryOpWithSmiOnRhs(Bytecode::kBitwiseAndSmi, &last_,
current);
InvalidateLast();
return true;
case Bytecode::kShiftLeft:
TransformToBinaryOpWithSmiOnRhs(Bytecode::kShiftLeftSmi, &last_,
current);
InvalidateLast();
return true;
case Bytecode::kShiftRight:
TransformToBinaryOpWithSmiOnRhs(Bytecode::kShiftRightSmi, &last_,
current);
InvalidateLast();
return true;
default:
break;
}
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);
}
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;
}
void BytecodePeepholeOptimizer::ElideCurrentAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
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;
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 {
return false;
// Nothing to do, keep last and wait for next bytecode to pair with it.
}
}
BytecodeNode* BytecodePeepholeOptimizer::Optimize(BytecodeNode* current) {
TryToRemoveLastExpressionPosition(current);
if (TransformCurrentBytecode(current) ||
TransformLastAndCurrentBytecodes(current)) {
return current;
}
void BytecodePeepholeOptimizer::ElideCurrentIfOperand0MatchesAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
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 (last()->operand(0) == node->operand(0)) {
ElideCurrentAction(node);
} else {
DefaultAction(node);
}
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();
}
void BytecodePeepholeOptimizer::ElideCurrentIfLoadingNameConstantAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK_EQ(last()->bytecode(), Bytecode::kLdaConstant);
DCHECK(!Bytecodes::IsJump(node->bytecode()));
if (GetConstantForIndexOperand(last(), 0)->IsName()) {
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->source_info().Clone(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->set_bytecode(action_data->bytecode);
DefaultAction(node);
}
void BytecodePeepholeOptimizer::TransformLdaStarToLdrLdarAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
if (!node->source_info().is_statement()) {
TransformLdaStarToLdrLdar(action_data->bytecode, last(), node);
}
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()));
DCHECK(CanElideLastBasedOnSourcePosition(node));
if (!node->source_info().is_valid()) {
node->source_info().Clone(last()->source_info());
} else {
next_stage()->Write(last());
}
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;
}
return current;
}
} // namespace interpreter

View File

@ -1,10 +1,11 @@
// Copyright 2015 the V8 project authors. All rights reserved.
// 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_PEEPHOLE_OPTIMIZER_H_
#define V8_INTERPRETER_BYTECODE_PEEPHOLE_OPTIMIZER_H_
#include "src/interpreter/bytecode-peephole-table.h"
#include "src/interpreter/bytecode-pipeline.h"
namespace v8 {
@ -12,6 +13,7 @@ namespace internal {
namespace interpreter {
class ConstantArrayBuilder;
class BytecodePeepholeActionAndData;
// An optimization stage for performing peephole optimizations on
// generated bytecode. The optimizer may buffer one bytecode
@ -32,31 +34,26 @@ class BytecodePeepholeOptimizer final : public BytecodePipelineStage,
Handle<FixedArray> handler_table) override;
private:
BytecodeNode* OptimizeAndEmitLast(BytecodeNode* current);
BytecodeNode* Optimize(BytecodeNode* current);
void Flush();
#define DECLARE_ACTION(Action) \
void Action(BytecodeNode* const node, \
const PeepholeActionAndData* const action_data = nullptr);
PEEPHOLE_ACTION_LIST(DECLARE_ACTION)
#undef DECLARE_ACTION
void TryToRemoveLastExpressionPosition(const BytecodeNode* const current);
bool TransformCurrentBytecode(BytecodeNode* const current);
bool TransformLastAndCurrentBytecodes(BytecodeNode* const current);
bool CanElideCurrent(const BytecodeNode* const current) const;
bool CanElideLast(const BytecodeNode* const current) const;
void ApplyPeepholeAction(BytecodeNode* const node);
void Flush();
bool CanElideLastBasedOnSourcePosition(
const BytecodeNode* const current) const;
// Simple substitution methods.
bool RemoveToBooleanFromJump(BytecodeNode* const current);
bool RemoveToBooleanFromLogicalNot(BytecodeNode* const current);
void InvalidateLast();
bool LastIsValid() const;
void SetLast(const BytecodeNode* const node);
bool LastBytecodePutsNameInAccumulator() const;
Handle<Object> GetConstantForIndexOperand(const BytecodeNode* const node,
int index) const;
BytecodePipelineStage* next_stage() const { return next_stage_; }
BytecodeNode* last() { return &last_; }
ConstantArrayBuilder* constant_array_builder_;
BytecodePipelineStage* next_stage_;
BytecodeNode last_;

View File

@ -0,0 +1,73 @@
// 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_PEEPHOLE_TABLE_H_
#define V8_INTERPRETER_BYTECODE_PEEPHOLE_TABLE_H_
#include "src/interpreter/bytecodes.h"
namespace v8 {
namespace internal {
namespace interpreter {
#define PEEPHOLE_NON_JUMP_ACTION_LIST(V) \
V(DefaultAction) \
V(UpdateLastAction) \
V(UpdateLastIfSourceInfoPresentAction) \
V(ElideCurrentAction) \
V(ElideCurrentIfOperand0MatchesAction) \
V(ElideCurrentIfLoadingNameConstantAction) \
V(ElideLastAction) \
V(ChangeBytecodeAction) \
V(TransformLdaStarToLdrLdarAction) \
V(TransformLdaSmiBinaryOpToBinaryOpWithSmiAction) \
V(TransformLdaZeroBinaryOpToBinaryOpWithZeroAction)
#define PEEPHOLE_JUMP_ACTION_LIST(V) \
V(DefaultJumpAction) \
V(UpdateLastJumpAction) \
V(ChangeJumpBytecodeAction) \
V(ElideLastBeforeJumpAction)
#define PEEPHOLE_ACTION_LIST(V) \
PEEPHOLE_NON_JUMP_ACTION_LIST(V) \
PEEPHOLE_JUMP_ACTION_LIST(V)
// Actions to take when a pair of bytes is encountered. A handler
// exists for each action.
enum class PeepholeAction : uint8_t {
#define DECLARE_PEEPHOLE_ACTION(Action) k##Action,
PEEPHOLE_ACTION_LIST(DECLARE_PEEPHOLE_ACTION)
#undef DECLARE_PEEPHOLE_ACTION
};
// Tuple of action to take when pair of bytecodes is encountered and
// optional data to invoke handler with.
struct PeepholeActionAndData final {
// Action to take when tuple of bytecodes encountered.
PeepholeAction action;
// Replacement bytecode (if valid).
Bytecode bytecode;
};
// Lookup table for matching pairs of bytecodes to peephole optimization
// actions. The contents of the table are generated by mkpeephole.cc.
struct PeepholeActionTable final {
public:
static const PeepholeActionAndData* Lookup(Bytecode last, Bytecode current);
private:
static const size_t kNumberOfBytecodes =
static_cast<size_t>(Bytecode::kLast) + 1;
static const PeepholeActionAndData row_data_[][kNumberOfBytecodes];
static const PeepholeActionAndData* const row_[kNumberOfBytecodes];
};
} // namespace interpreter
} // namespace internal
} // namespace v8
#endif // V8_INTERPRETER_BYTECODE_PEEPHOLE_TABLE_H_

View File

@ -476,11 +476,6 @@ bool Bytecodes::IsLdarOrStar(Bytecode bytecode) {
return bytecode == Bytecode::kLdar || bytecode == Bytecode::kStar;
}
// static
bool Bytecodes::IsLdaSmiOrLdaZero(Bytecode bytecode) {
return bytecode == Bytecode::kLdaSmi || bytecode == Bytecode::kLdaZero;
}
// static
bool Bytecodes::IsBytecodeWithScalableOperands(Bytecode bytecode) {
switch (bytecode) {
@ -508,6 +503,11 @@ bool Bytecodes::IsPrefixScalingBytecode(Bytecode bytecode) {
}
}
// static
bool Bytecodes::PutsNameInAccumulator(Bytecode bytecode) {
return bytecode == Bytecode::kToName || bytecode == Bytecode::kTypeOf;
}
// static
bool Bytecodes::IsJumpOrReturn(Bytecode bytecode) {
return bytecode == Bytecode::kReturn || IsJump(bytecode);

View File

@ -514,15 +514,15 @@ class Bytecodes final {
// Returns true if the bytecode is Ldar or Star.
static bool IsLdarOrStar(Bytecode bytecode);
// Returns true if the bytecode is LdaSmi or LdaZero.
static bool IsLdaSmiOrLdaZero(Bytecode bytecode);
// Returns true if the bytecode has wider operand forms.
static bool IsBytecodeWithScalableOperands(Bytecode bytecode);
// Returns true if the bytecode is a scaling prefix bytecode.
static bool IsPrefixScalingBytecode(Bytecode bytecode);
// Returns true if |bytecode| puts a name in the accumulator.
static bool PutsNameInAccumulator(Bytecode bytecode);
// Returns true if |operand_type| is any type of register operand.
static bool IsRegisterOperandType(OperandType operand_type);

View File

@ -0,0 +1,387 @@
// 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 <array>
#include <fstream>
#include <map>
#include <string>
#include <vector>
#include "src/globals.h"
#include "src/interpreter/bytecode-peephole-table.h"
#include "src/interpreter/bytecodes.h"
namespace v8 {
namespace internal {
namespace interpreter {
const char* ActionName(PeepholeAction action) {
switch (action) {
#define CASE(Name) \
case PeepholeAction::k##Name: \
return "PeepholeAction::k" #Name;
PEEPHOLE_ACTION_LIST(CASE)
#undef CASE
default:
UNREACHABLE();
return "";
}
}
std::string BytecodeName(Bytecode bytecode) {
return "Bytecode::k" + std::string(Bytecodes::ToString(bytecode));
}
class PeepholeActionTableWriter final {
public:
static const size_t kNumberOfBytecodes =
static_cast<size_t>(Bytecode::kLast) + 1;
typedef std::array<PeepholeActionAndData, kNumberOfBytecodes> Row;
void BuildTable();
void Write(std::ostream& os);
private:
static const char* kIndent;
static const char* kNamespaceElements[];
void WriteHeader(std::ostream& os);
void WriteIncludeFiles(std::ostream& os);
void WriteClassMethods(std::ostream& os);
void WriteUniqueRows(std::ostream& os);
void WriteRowMap(std::ostream& os);
void WriteRow(std::ostream& os, size_t row_index);
void WriteOpenNamespace(std::ostream& os);
void WriteCloseNamespace(std::ostream& os);
PeepholeActionAndData LookupActionAndData(Bytecode last, Bytecode current);
void BuildRow(Bytecode last, Row* row);
size_t HashRow(const Row* row);
void InsertRow(size_t row_index, const Row* const row, size_t row_hash,
std::map<size_t, size_t>* hash_to_row_map);
bool RowsEqual(const Row* const first, const Row* const second);
std::vector<Row>* table() { return &table_; }
// Table of unique rows.
std::vector<Row> table_;
// Mapping of row index to unique row index.
std::array<size_t, kNumberOfBytecodes> row_map_;
};
const char* PeepholeActionTableWriter::kIndent = " ";
const char* PeepholeActionTableWriter::kNamespaceElements[] = {"v8", "internal",
"interpreter"};
// static
PeepholeActionAndData PeepholeActionTableWriter::LookupActionAndData(
Bytecode last, Bytecode current) {
// Optimize various accumulator loads followed by store accumulator
// to an equivalent register load and loading the accumulator with
// the register. The latter accumulator load can often be elided as
// it is side-effect free and often followed by another accumulator
// load so can be elided.
if (current == Bytecode::kStar) {
switch (last) {
case Bytecode::kLdaNamedProperty:
return {PeepholeAction::kTransformLdaStarToLdrLdarAction,
Bytecode::kLdrNamedProperty};
case Bytecode::kLdaKeyedProperty:
return {PeepholeAction::kTransformLdaStarToLdrLdarAction,
Bytecode::kLdrKeyedProperty};
case Bytecode::kLdaGlobal:
return {PeepholeAction::kTransformLdaStarToLdrLdarAction,
Bytecode::kLdrGlobal};
case Bytecode::kLdaContextSlot:
return {PeepholeAction::kTransformLdaStarToLdrLdarAction,
Bytecode::kLdrContextSlot};
case Bytecode::kLdaUndefined:
return {PeepholeAction::kTransformLdaStarToLdrLdarAction,
Bytecode::kLdrUndefined};
default:
break;
}
}
// ToName optimizations: remove unnecessary ToName bytecodes.
if (current == Bytecode::kToName) {
if (last == Bytecode::kLdaConstant) {
return {PeepholeAction::kElideCurrentIfLoadingNameConstantAction,
Bytecode::kIllegal};
} else if (Bytecodes::PutsNameInAccumulator(last)) {
return {PeepholeAction::kElideCurrentAction, Bytecode::kIllegal};
}
}
// Nop are placeholders for holding source position information and can be
// elided if there is no source information.
if (last == Bytecode::kNop) {
if (Bytecodes::IsJump(current)) {
return {PeepholeAction::kElideLastBeforeJumpAction, Bytecode::kIllegal};
} else {
return {PeepholeAction::kElideLastAction, Bytecode::kIllegal};
}
}
// 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.
if (Bytecodes::IsAccumulatorLoadWithoutEffects(last) &&
Bytecodes::IsAccumulatorLoadWithoutEffects(current)) {
return {PeepholeAction::kElideLastAction, Bytecode::kIllegal};
}
// The current instruction clobbers the accumulator without reading
// it. The load in the last instruction can be elided as it has no
// effect.
if (Bytecodes::IsAccumulatorLoadWithoutEffects(last) &&
Bytecodes::GetAccumulatorUse(current) == AccumulatorUse::kWrite) {
return {PeepholeAction::kElideLastAction, Bytecode::kIllegal};
}
// 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.
if (Bytecodes::IsLdarOrStar(last) && Bytecodes::IsLdarOrStar(current)) {
return {PeepholeAction::kElideCurrentIfOperand0MatchesAction,
Bytecode::kIllegal};
}
// Remove ToBoolean coercion from conditional jumps where possible.
if (Bytecodes::WritesBooleanToAccumulator(last)) {
if (Bytecodes::IsJumpIfToBoolean(current)) {
return {PeepholeAction::kChangeJumpBytecodeAction,
Bytecodes::GetJumpWithoutToBoolean(current)};
} else if (current == Bytecode::kToBooleanLogicalNot) {
return {PeepholeAction::kChangeBytecodeAction, Bytecode::kLogicalNot};
}
}
// Fuse LdaSmi followed by binary op to produce binary op with a
// immediate integer argument. This savaes on dispatches and size.
if (last == Bytecode::kLdaSmi) {
switch (current) {
case Bytecode::kAdd:
return {PeepholeAction::kTransformLdaSmiBinaryOpToBinaryOpWithSmiAction,
Bytecode::kAddSmi};
case Bytecode::kSub:
return {PeepholeAction::kTransformLdaSmiBinaryOpToBinaryOpWithSmiAction,
Bytecode::kSubSmi};
case Bytecode::kBitwiseAnd:
return {PeepholeAction::kTransformLdaSmiBinaryOpToBinaryOpWithSmiAction,
Bytecode::kBitwiseAndSmi};
case Bytecode::kBitwiseOr:
return {PeepholeAction::kTransformLdaSmiBinaryOpToBinaryOpWithSmiAction,
Bytecode::kBitwiseOrSmi};
case Bytecode::kShiftLeft:
return {PeepholeAction::kTransformLdaSmiBinaryOpToBinaryOpWithSmiAction,
Bytecode::kShiftLeftSmi};
case Bytecode::kShiftRight:
return {PeepholeAction::kTransformLdaSmiBinaryOpToBinaryOpWithSmiAction,
Bytecode::kShiftRightSmi};
default:
break;
}
}
// Fuse LdaZero followed by binary op to produce binary op with a
// zero immediate argument. This saves dispatches, but not size.
if (last == Bytecode::kLdaZero) {
switch (current) {
case Bytecode::kAdd:
return {
PeepholeAction::kTransformLdaZeroBinaryOpToBinaryOpWithZeroAction,
Bytecode::kAddSmi};
case Bytecode::kSub:
return {
PeepholeAction::kTransformLdaZeroBinaryOpToBinaryOpWithZeroAction,
Bytecode::kSubSmi};
case Bytecode::kBitwiseAnd:
return {
PeepholeAction::kTransformLdaZeroBinaryOpToBinaryOpWithZeroAction,
Bytecode::kBitwiseAndSmi};
case Bytecode::kBitwiseOr:
return {
PeepholeAction::kTransformLdaZeroBinaryOpToBinaryOpWithZeroAction,
Bytecode::kBitwiseOrSmi};
case Bytecode::kShiftLeft:
return {
PeepholeAction::kTransformLdaZeroBinaryOpToBinaryOpWithZeroAction,
Bytecode::kShiftLeftSmi};
case Bytecode::kShiftRight:
return {
PeepholeAction::kTransformLdaZeroBinaryOpToBinaryOpWithZeroAction,
Bytecode::kShiftRightSmi};
default:
break;
}
}
// If there is no last bytecode to optimize against, store the incoming
// bytecode or for jumps emit incoming bytecode immediately.
if (last == Bytecode::kIllegal) {
if (Bytecodes::IsJump(current)) {
return {PeepholeAction::kUpdateLastJumpAction, Bytecode::kIllegal};
} else if (current == Bytecode::kNop) {
return {PeepholeAction::kUpdateLastIfSourceInfoPresentAction,
Bytecode::kIllegal};
} else {
return {PeepholeAction::kUpdateLastAction, Bytecode::kIllegal};
}
}
// No matches, take the default action.
if (Bytecodes::IsJump(current)) {
return {PeepholeAction::kDefaultJumpAction, Bytecode::kIllegal};
} else {
return {PeepholeAction::kDefaultAction, Bytecode::kIllegal};
}
}
void PeepholeActionTableWriter::Write(std::ostream& os) {
WriteHeader(os);
WriteIncludeFiles(os);
WriteOpenNamespace(os);
WriteUniqueRows(os);
WriteRowMap(os);
WriteClassMethods(os);
WriteCloseNamespace(os);
}
void PeepholeActionTableWriter::WriteHeader(std::ostream& os) {
os << "// Copyright 2016 the V8 project authors. All rights reserved.\n"
<< "// Use of this source code is governed by a BSD-style license that\n"
<< "// can be found in the LICENSE file.\n\n"
<< "// Autogenerated by " __FILE__ ". Do not edit.\n\n";
}
void PeepholeActionTableWriter::WriteIncludeFiles(std::ostream& os) {
os << "#include \"src/interpreter/bytecode-peephole-table.h\"\n\n";
}
void PeepholeActionTableWriter::WriteUniqueRows(std::ostream& os) {
os << "const PeepholeActionAndData PeepholeActionTable::row_data_["
<< table_.size() << "][" << kNumberOfBytecodes << "] = {\n";
for (size_t i = 0; i < table_.size(); ++i) {
os << "{\n";
WriteRow(os, i);
os << "},\n";
}
os << "};\n\n";
}
void PeepholeActionTableWriter::WriteRowMap(std::ostream& os) {
os << "const PeepholeActionAndData* const PeepholeActionTable::row_["
<< kNumberOfBytecodes << "] = {\n";
for (size_t i = 0; i < kNumberOfBytecodes; ++i) {
os << kIndent << " PeepholeActionTable::row_data_[" << row_map_[i]
<< "], \n";
}
os << "};\n\n";
}
void PeepholeActionTableWriter::WriteRow(std::ostream& os, size_t row_index) {
const Row row = table_.at(row_index);
for (PeepholeActionAndData action_data : row) {
os << kIndent << "{" << ActionName(action_data.action) << ","
<< BytecodeName(action_data.bytecode) << "},\n";
}
}
void PeepholeActionTableWriter::WriteOpenNamespace(std::ostream& os) {
for (auto element : kNamespaceElements) {
os << "namespace " << element << " {\n";
}
os << "\n";
}
void PeepholeActionTableWriter::WriteCloseNamespace(std::ostream& os) {
for (auto element : kNamespaceElements) {
os << "} // namespace " << element << "\n";
}
}
void PeepholeActionTableWriter::WriteClassMethods(std::ostream& os) {
os << "// static\n"
<< "const PeepholeActionAndData*\n"
<< "PeepholeActionTable::Lookup(Bytecode last, Bytecode current) {\n"
<< kIndent
<< "return &row_[Bytecodes::ToByte(last)][Bytecodes::ToByte(current)];\n"
<< "}\n\n";
}
void PeepholeActionTableWriter::BuildTable() {
std::map<size_t, size_t> hash_to_row_map;
Row row;
for (size_t i = 0; i < kNumberOfBytecodes; ++i) {
uint8_t byte_value = static_cast<uint8_t>(i);
Bytecode last = Bytecodes::FromByte(byte_value);
BuildRow(last, &row);
size_t row_hash = HashRow(&row);
InsertRow(i, &row, row_hash, &hash_to_row_map);
}
}
void PeepholeActionTableWriter::BuildRow(Bytecode last, Row* row) {
for (size_t i = 0; i < kNumberOfBytecodes; ++i) {
uint8_t byte_value = static_cast<uint8_t>(i);
Bytecode current = Bytecodes::FromByte(byte_value);
PeepholeActionAndData action_data = LookupActionAndData(last, current);
row->at(i) = action_data;
}
}
// static
bool PeepholeActionTableWriter::RowsEqual(const Row* const first,
const Row* const second) {
return memcmp(first, second, sizeof(*first)) == 0;
}
// static
void PeepholeActionTableWriter::InsertRow(
size_t row_index, const Row* const row, size_t row_hash,
std::map<size_t, size_t>* hash_to_row_map) {
// Insert row if no existing row matches, otherwise use existing row.
auto iter = hash_to_row_map->find(row_hash);
if (iter == hash_to_row_map->end()) {
row_map_[row_index] = table()->size();
table()->push_back(*row);
} else {
row_map_[row_index] = iter->second;
// If the following DCHECK fails, the HashRow() is not adequate.
DCHECK(RowsEqual(&table()->at(iter->second), row));
}
}
// static
size_t PeepholeActionTableWriter::HashRow(const Row* row) {
static const size_t kHashShift = 3;
std::size_t result = (1u << 31) - 1u;
const uint8_t* raw_data = reinterpret_cast<const uint8_t*>(row);
for (size_t i = 0; i < sizeof(*row); ++i) {
size_t top_bits = result >> (kBitsPerByte * sizeof(size_t) - kHashShift);
result = (result << kHashShift) ^ top_bits ^ raw_data[i];
}
return result;
}
} // namespace interpreter
} // namespace internal
} // namespace v8
int main(int argc, const char* argv[]) {
CHECK_EQ(argc, 2);
std::ofstream ofs(argv[1], std::ofstream::trunc);
v8::internal::interpreter::PeepholeActionTableWriter writer;
writer.BuildTable();
writer.Write(ofs);
ofs.flush();
ofs.close();
return 0;
}

View File

@ -35,6 +35,7 @@
'v8_extra_library_files%': [],
'v8_experimental_extra_library_files%': [],
'mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)mksnapshot<(EXECUTABLE_SUFFIX)',
'mkpeephole_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)mkpeephole<(EXECUTABLE_SUFFIX)',
},
'includes': ['../gypfiles/toolchain.gypi', '../gypfiles/features.gypi'],
'targets': [
@ -382,13 +383,31 @@
'v8_libbase',
'v8_libsampler',
],
'objs': ['foo.o'],
'variables': {
'optimize': 'max',
},
'include_dirs+': [
'..',
'<(DEPTH)',
'<(SHARED_INTERMEDIATE_DIR)'
],
'actions':[{
'action_name': 'run mkpeephole',
'inputs': ['<(mkpeephole_exec)'],
'outputs': ['<(INTERMEDIATE_DIR)/bytecode-peephole-table.cc'],
'action': ['<(mkpeephole_exec)', '<(INTERMEDIATE_DIR)/bytecode-peephole-table.cc' ],
'process_outputs_as_sources': 1,
'conditions': [
['want_separate_host_toolset==1', {
'dependencies': ['mkpeephole#host'],
'toolsets': ['host'],
}, {
'dependencies': ['mkpeephole'],
'toolsets': ['target'],
}],
],
}],
'sources': [ ### gcmole(all) ###
'../include/v8-debug.h',
'../include/v8-experimental.h',
@ -933,6 +952,7 @@
'interpreter/bytecode-label.h',
'interpreter/bytecode-peephole-optimizer.cc',
'interpreter/bytecode-peephole-optimizer.h',
'interpreter/bytecode-peephole-table.h',
'interpreter/bytecode-pipeline.cc',
'interpreter/bytecode-pipeline.h',
'interpreter/bytecode-register.cc',
@ -2319,5 +2339,26 @@
}],
],
},
{
'target_name': 'mkpeephole',
'type': 'executable',
'dependencies': [ 'v8_libbase' ],
'include_dirs+': [
'..',
],
'sources': [
'interpreter/bytecode-peephole-table.h',
'interpreter/bytecodes.h',
'interpreter/bytecodes.cc',
'interpreter/mkpeephole.cc'
],
'conditions': [
['want_separate_host_toolset==1', {
'toolsets': ['host'],
}, {
'toolsets': ['target'],
}],
],
},
],
}

View File

@ -59,7 +59,7 @@ snippet: "
"
frame size: 3
parameter count: 1
bytecode array length: 33
bytecode array length: 31
bytecodes: [
B(LdaTheHole),
B(Star), R(0),
@ -73,7 +73,6 @@ bytecodes: [
/* 48 E> */ B(CallRuntime), U16(Runtime::kThrowReferenceError), R(2), U8(1),
B(CallRuntime), U16(Runtime::kThrowConstAssignError), R(0), U8(0),
B(Mov), R(1), R(0),
B(Ldar), R(0),
B(LdaUndefined),
/* 55 S> */ B(Return),
]

View File

@ -59,7 +59,7 @@ snippet: "
"
frame size: 3
parameter count: 1
bytecode array length: 28
bytecode array length: 26
bytecodes: [
B(LdaTheHole),
B(Star), R(0),
@ -72,7 +72,6 @@ bytecodes: [
B(Star), R(2),
/* 45 E> */ B(CallRuntime), U16(Runtime::kThrowReferenceError), R(2), U8(1),
B(Mov), R(1), R(0),
B(Ldar), R(0),
B(LdaUndefined),
/* 52 S> */ B(Return),
]

View File

@ -134,12 +134,10 @@ TEST_F(BytecodePeepholeOptimizerTest, KeepStatementNop) {
TEST_F(BytecodePeepholeOptimizerTest, KeepJumpIfToBooleanTrue) {
BytecodeNode first(Bytecode::kLdaNull);
BytecodeNode second(Bytecode::kJumpIfToBooleanTrue, 3);
BytecodeLabel label;
optimizer()->Write(&first);
CHECK_EQ(write_count(), 0);
optimizer()->Write(&second);
CHECK_EQ(write_count(), 1);
CHECK_EQ(last_written(), first);
Flush();
optimizer()->WriteJump(&second, &label);
CHECK_EQ(write_count(), 2);
CHECK_EQ(last_written(), second);
}
@ -147,15 +145,12 @@ TEST_F(BytecodePeepholeOptimizerTest, KeepJumpIfToBooleanTrue) {
TEST_F(BytecodePeepholeOptimizerTest, ElideJumpIfToBooleanTrue) {
BytecodeNode first(Bytecode::kLdaTrue);
BytecodeNode second(Bytecode::kJumpIfToBooleanTrue, 3);
BytecodeLabel label;
optimizer()->Write(&first);
CHECK_EQ(write_count(), 0);
optimizer()->Write(&second);
CHECK_EQ(write_count(), 1);
CHECK_EQ(last_written(), first);
Flush();
optimizer()->WriteJump(&second, &label);
CHECK_EQ(write_count(), 2);
CHECK_EQ(last_written().bytecode(), Bytecode::kJumpIfTrue);
CHECK_EQ(last_written().operand(0), second.operand(0));
CHECK_EQ(last_written(), second);
}
TEST_F(BytecodePeepholeOptimizerTest, KeepToBooleanLogicalNot) {
@ -204,12 +199,11 @@ TEST_F(BytecodePeepholeOptimizerTest, StarRxLdarRx) {
BytecodeNode first(Bytecode::kStar, Register(0).ToOperand());
BytecodeNode second(Bytecode::kLdar, Register(0).ToOperand());
optimizer()->Write(&first);
CHECK_EQ(write_count(), 0);
optimizer()->Write(&second);
CHECK_EQ(write_count(), 1);
CHECK_EQ(last_written(), first);
CHECK_EQ(write_count(), 0);
Flush();
CHECK_EQ(write_count(), 1);
CHECK_EQ(last_written(), first);
}
TEST_F(BytecodePeepholeOptimizerTest, StarRxLdarRxStatement) {
@ -262,11 +256,10 @@ TEST_F(BytecodePeepholeOptimizerTest, ToNameToName) {
BytecodeNode first(Bytecode::kToName);
BytecodeNode second(Bytecode::kToName);
optimizer()->Write(&first);
CHECK_EQ(write_count(), 0);
optimizer()->Write(&second);
CHECK_EQ(write_count(), 1);
CHECK_EQ(last_written(), first);
CHECK_EQ(write_count(), 0);
Flush();
CHECK_EQ(last_written(), first);
CHECK_EQ(write_count(), 1);
}
@ -274,12 +267,11 @@ TEST_F(BytecodePeepholeOptimizerTest, TypeOfToName) {
BytecodeNode first(Bytecode::kTypeOf);
BytecodeNode second(Bytecode::kToName);
optimizer()->Write(&first);
CHECK_EQ(write_count(), 0);
optimizer()->Write(&second);
CHECK_EQ(write_count(), 1);
CHECK_EQ(last_written(), first);
CHECK_EQ(write_count(), 0);
Flush();
CHECK_EQ(write_count(), 1);
CHECK_EQ(last_written(), first);
}
TEST_F(BytecodePeepholeOptimizerTest, LdaConstantStringToName) {
@ -289,12 +281,11 @@ TEST_F(BytecodePeepholeOptimizerTest, LdaConstantStringToName) {
BytecodeNode first(Bytecode::kLdaConstant, static_cast<uint32_t>(index));
BytecodeNode second(Bytecode::kToName);
optimizer()->Write(&first);
CHECK_EQ(write_count(), 0);
optimizer()->Write(&second);
CHECK_EQ(write_count(), 1);
CHECK_EQ(last_written(), first);
CHECK_EQ(write_count(), 0);
Flush();
CHECK_EQ(write_count(), 1);
CHECK_EQ(last_written(), first);
}
TEST_F(BytecodePeepholeOptimizerTest, LdaConstantNumberToName) {