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:
parent
460e0b80a9
commit
a451bd1a68
49
BUILD.gn
49
BUILD.gn
@ -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
|
||||
#
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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_;
|
||||
|
73
src/interpreter/bytecode-peephole-table.h
Normal file
73
src/interpreter/bytecode-peephole-table.h
Normal 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_
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
387
src/interpreter/mkpeephole.cc
Normal file
387
src/interpreter/mkpeephole.cc
Normal 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;
|
||||
}
|
41
src/v8.gyp
41
src/v8.gyp
@ -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'],
|
||||
}],
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -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),
|
||||
]
|
||||
|
@ -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),
|
||||
]
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user