A jump-table implementation for constant case switch statements
The change is made since for switch statements with lots of cases, where each case is a constant integer, the emitted bytecode is still a series of jumps, when we can instead use a jump table. If there are 6 or more cases (similar to GCC) of Smi literals, and if the max Smi case minus the min Smi case is not more than 3 times the number of cases, we use a jump table up front to handle Smi's, and then use traditional if-else logic for the rest of the cases. We then use the jump table in interpreter/bytecode-jump-table to do the optimization. This tries to go off issue 9738 in v8's issue tracker. It is not exactly the same, since that recommends doing the work at JIT-time, but has similar ideas. It also partially goes off issue 10764. Bug: v8:9738 Change-Id: Ic805682ee3abf9ce464bb733b427fa0c83a6e10c Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2904926 Reviewed-by: Leszek Swirski <leszeks@chromium.org> Commit-Queue: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/master@{#75323}
This commit is contained in:
parent
b0ad4a11d2
commit
9711289d06
1
AUTHORS
1
AUTHORS
@ -163,6 +163,7 @@ Michael Lutz <michi@icosahedron.de>
|
||||
Michael Mclaughlin <m8ch88l@gmail.com>
|
||||
Michael Smith <mike@w3.org>
|
||||
Michaël Zasso <mic.besace@gmail.com>
|
||||
Mihir Shah <mihirshah.11204@gmail.com>
|
||||
Mike Gilbert <floppymaster@gmail.com>
|
||||
Mike Pennisi <mike@mikepennisi.com>
|
||||
Mikhail Gusarov <dottedmag@dottedmag.net>
|
||||
|
@ -426,7 +426,7 @@ void BaselineAssembler::AddSmi(Register lhs, Smi rhs) {
|
||||
void BaselineAssembler::Switch(Register reg, int case_value_base,
|
||||
Label** labels, int num_labels) {
|
||||
Label fallthrough;
|
||||
if (case_value_base > 0) {
|
||||
if (case_value_base != 0) {
|
||||
__ sub(reg, reg, Operand(case_value_base));
|
||||
}
|
||||
|
||||
|
@ -503,7 +503,7 @@ void BaselineAssembler::AddSmi(Register lhs, Smi rhs) {
|
||||
void BaselineAssembler::Switch(Register reg, int case_value_base,
|
||||
Label** labels, int num_labels) {
|
||||
Label fallthrough;
|
||||
if (case_value_base > 0) {
|
||||
if (case_value_base != 0) {
|
||||
__ Sub(reg, reg, Immediate(case_value_base));
|
||||
}
|
||||
|
||||
|
@ -390,7 +390,7 @@ void BaselineAssembler::Switch(Register reg, int case_value_base,
|
||||
Register table = scope.AcquireScratch();
|
||||
DCHECK(!AreAliased(reg, table));
|
||||
Label fallthrough, jump_table;
|
||||
if (case_value_base > 0) {
|
||||
if (case_value_base != 0) {
|
||||
__ sub(reg, Immediate(case_value_base));
|
||||
}
|
||||
__ cmp(reg, Immediate(num_labels));
|
||||
|
@ -541,7 +541,7 @@ void BaselineAssembler::AddSmi(Register lhs, Smi rhs) {
|
||||
void BaselineAssembler::Switch(Register reg, int case_value_base,
|
||||
Label** labels, int num_labels) {
|
||||
Label fallthrough;
|
||||
if (case_value_base > 0) {
|
||||
if (case_value_base != 0) {
|
||||
__ Sub64(reg, reg, Operand(case_value_base));
|
||||
}
|
||||
|
||||
|
@ -399,7 +399,7 @@ void BaselineAssembler::Switch(Register reg, int case_value_base,
|
||||
ScratchRegisterScope scope(this);
|
||||
Register table = scope.AcquireScratch();
|
||||
Label fallthrough, jump_table;
|
||||
if (case_value_base > 0) {
|
||||
if (case_value_base != 0) {
|
||||
__ subq(reg, Immediate(case_value_base));
|
||||
}
|
||||
__ cmpq(reg, Immediate(num_labels));
|
||||
|
@ -5395,6 +5395,28 @@ void CodeStubAssembler::InitializeAllocationMemento(
|
||||
Comment("]");
|
||||
}
|
||||
|
||||
TNode<IntPtrT> CodeStubAssembler::TryTaggedToInt32AsIntPtr(
|
||||
TNode<Object> acc, Label* if_not_possible) {
|
||||
TVARIABLE(IntPtrT, acc_intptr);
|
||||
Label is_not_smi(this), have_int32(this);
|
||||
|
||||
GotoIfNot(TaggedIsSmi(acc), &is_not_smi);
|
||||
acc_intptr = SmiUntag(CAST(acc));
|
||||
Goto(&have_int32);
|
||||
|
||||
BIND(&is_not_smi);
|
||||
GotoIfNot(IsHeapNumber(CAST(acc)), if_not_possible);
|
||||
TNode<Float64T> value = LoadHeapNumberValue(CAST(acc));
|
||||
TNode<Int32T> value32 = RoundFloat64ToInt32(value);
|
||||
TNode<Float64T> value64 = ChangeInt32ToFloat64(value32);
|
||||
GotoIfNot(Float64Equal(value, value64), if_not_possible);
|
||||
acc_intptr = ChangeInt32ToIntPtr(value32);
|
||||
Goto(&have_int32);
|
||||
|
||||
BIND(&have_int32);
|
||||
return acc_intptr.value();
|
||||
}
|
||||
|
||||
TNode<Float64T> CodeStubAssembler::TryTaggedToFloat64(
|
||||
TNode<Object> value, Label* if_valueisnotnumber) {
|
||||
return Select<Float64T>(
|
||||
|
@ -2297,6 +2297,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
|
||||
TNode<IntPtrT> base_allocation_size,
|
||||
TNode<AllocationSite> allocation_site);
|
||||
|
||||
TNode<IntPtrT> TryTaggedToInt32AsIntPtr(TNode<Object> value,
|
||||
Label* if_not_possible);
|
||||
TNode<Float64T> TryTaggedToFloat64(TNode<Object> value,
|
||||
Label* if_valueisnotnumber);
|
||||
TNode<Float64T> TruncateTaggedToFloat64(TNode<Context> context,
|
||||
|
@ -1433,6 +1433,15 @@ DEFINE_BOOL(test_small_max_function_context_stub_size, false,
|
||||
DEFINE_BOOL(inline_new, true, "use fast inline allocation")
|
||||
DEFINE_NEG_NEG_IMPLICATION(inline_new, turbo_allocation_folding)
|
||||
|
||||
// bytecode-generator.cc
|
||||
DEFINE_INT(switch_table_spread_threshold, 3,
|
||||
"allow the jump table used for switch statements to span a range "
|
||||
"of integers roughly equal to this number times the number of "
|
||||
"clauses in the switch")
|
||||
DEFINE_INT(switch_table_min_cases, 6,
|
||||
"the number of Smi integer cases present in the switch statement "
|
||||
"before using the jump table optimization")
|
||||
|
||||
// codegen-ia32.cc / codegen-arm.cc
|
||||
DEFINE_BOOL(trace, false, "trace javascript function calls")
|
||||
|
||||
|
@ -4,6 +4,10 @@
|
||||
|
||||
#include "src/interpreter/bytecode-generator.h"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "src/api/api-inl.h"
|
||||
#include "src/ast/ast-source-ranges.h"
|
||||
#include "src/ast/ast.h"
|
||||
@ -20,6 +24,7 @@
|
||||
#include "src/interpreter/control-flow-builders.h"
|
||||
#include "src/logging/local-logger.h"
|
||||
#include "src/logging/log.h"
|
||||
#include "src/numbers/conversions.h"
|
||||
#include "src/objects/debug-objects.h"
|
||||
#include "src/objects/literal-objects-inl.h"
|
||||
#include "src/objects/objects-inl.h"
|
||||
@ -1792,53 +1797,320 @@ void BytecodeGenerator::VisitWithStatement(WithStatement* stmt) {
|
||||
VisitInScope(stmt->statement(), stmt->scope());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsSmiLiteralSwitchCaseValue(Expression* expr) {
|
||||
if (expr->IsSmiLiteral() ||
|
||||
(expr->IsLiteral() && expr->AsLiteral()->IsNumber() &&
|
||||
expr->AsLiteral()->AsNumber() == 0.0)) {
|
||||
return true;
|
||||
#ifdef DEBUG
|
||||
} else if (expr->IsLiteral() && expr->AsLiteral()->IsNumber()) {
|
||||
DCHECK(!IsSmiDouble(expr->AsLiteral()->AsNumber()));
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Precondition: we called IsSmiLiteral to check this.
|
||||
inline int ReduceToSmiSwitchCaseValue(Expression* expr) {
|
||||
if (V8_LIKELY(expr->IsSmiLiteral())) {
|
||||
return expr->AsLiteral()->AsSmiLiteral().value();
|
||||
} else {
|
||||
// Only the zero case is possible otherwise.
|
||||
DCHECK(expr->IsLiteral() && expr->AsLiteral()->IsNumber() &&
|
||||
expr->AsLiteral()->AsNumber() == -0.0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Is the range of Smi's small enough relative to number of cases?
|
||||
inline bool IsSpreadAcceptable(int spread, int ncases) {
|
||||
return spread < FLAG_switch_table_spread_threshold * ncases;
|
||||
}
|
||||
|
||||
struct SwitchInfo {
|
||||
static const int kDefaultNotFound = -1;
|
||||
|
||||
std::map<int, CaseClause*> covered_cases;
|
||||
int default_case;
|
||||
|
||||
SwitchInfo() { default_case = kDefaultNotFound; }
|
||||
|
||||
bool DefaultExists() { return default_case != kDefaultNotFound; }
|
||||
bool CaseExists(int j) {
|
||||
return covered_cases.find(j) != covered_cases.end();
|
||||
}
|
||||
bool CaseExists(Expression* expr) {
|
||||
return IsSmiLiteralSwitchCaseValue(expr)
|
||||
? CaseExists(ReduceToSmiSwitchCaseValue(expr))
|
||||
: false;
|
||||
}
|
||||
CaseClause* GetClause(int j) { return covered_cases[j]; }
|
||||
|
||||
bool IsDuplicate(CaseClause* clause) {
|
||||
return IsSmiLiteralSwitchCaseValue(clause->label()) &&
|
||||
CaseExists(clause->label()) &&
|
||||
clause != GetClause(ReduceToSmiSwitchCaseValue(clause->label()));
|
||||
}
|
||||
int MinCase() {
|
||||
return covered_cases.size() == 0 ? INT_MAX : covered_cases.begin()->first;
|
||||
}
|
||||
int MaxCase() {
|
||||
return covered_cases.size() == 0 ? INT_MIN : covered_cases.rbegin()->first;
|
||||
}
|
||||
void Print() {
|
||||
std::cout << "Covered_cases: " << '\n';
|
||||
for (auto iter = covered_cases.begin(); iter != covered_cases.end();
|
||||
++iter) {
|
||||
std::cout << iter->first << "->" << iter->second << '\n';
|
||||
}
|
||||
std::cout << "Default_case: " << default_case << '\n';
|
||||
}
|
||||
};
|
||||
|
||||
// Checks whether we should use a jump table to implement a switch operation.
|
||||
bool IsSwitchOptimizable(SwitchStatement* stmt, SwitchInfo* info) {
|
||||
ZonePtrList<CaseClause>* cases = stmt->cases();
|
||||
|
||||
for (int i = 0; i < cases->length(); ++i) {
|
||||
CaseClause* clause = cases->at(i);
|
||||
if (clause->is_default()) {
|
||||
continue;
|
||||
} else if (!(clause->label()->IsLiteral())) {
|
||||
// Don't consider Smi cases after a non-literal, because we
|
||||
// need to evaluate the non-literal.
|
||||
break;
|
||||
} else if (IsSmiLiteralSwitchCaseValue(clause->label())) {
|
||||
int value = ReduceToSmiSwitchCaseValue(clause->label());
|
||||
info->covered_cases.insert({value, clause});
|
||||
}
|
||||
}
|
||||
|
||||
// GCC also jump-table optimizes switch statements with 6 cases or more.
|
||||
if (!(static_cast<int>(info->covered_cases.size()) >=
|
||||
FLAG_switch_table_min_cases &&
|
||||
IsSpreadAcceptable(info->MaxCase() - info->MinCase(),
|
||||
cases->length()))) {
|
||||
// Invariant- covered_cases has all cases and only cases that will go in the
|
||||
// jump table.
|
||||
info->covered_cases.clear();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// This adds a jump table optimization for switch statements with Smi cases.
|
||||
// If there are 5+ non-duplicate Smi clauses, and they are sufficiently compact,
|
||||
// we generate a jump table. In the fall-through path, we put the compare-jumps
|
||||
// for the non-Smi cases.
|
||||
|
||||
// e.g.
|
||||
//
|
||||
// switch(x){
|
||||
// case -0: out = 10;
|
||||
// case 1: out = 11; break;
|
||||
// case 0: out = 12; break;
|
||||
// case 2: out = 13;
|
||||
// case 3: out = 14; break;
|
||||
// case 0.5: out = 15; break;
|
||||
// case 4: out = 16;
|
||||
// case y: out = 17;
|
||||
// case 5: out = 18;
|
||||
// default: out = 19; break;
|
||||
// }
|
||||
|
||||
// becomes this pseudo-bytecode:
|
||||
|
||||
// lda x
|
||||
// star r1
|
||||
// test_type number
|
||||
// jump_if_false @fallthrough
|
||||
// ldar r1
|
||||
// test_greater_than_or_equal_to smi_min
|
||||
// jump_if_false @fallthrough
|
||||
// ldar r1
|
||||
// test_less_than_or_equal_to smi_max
|
||||
// jump_if_false @fallthrough
|
||||
// ldar r1
|
||||
// bitwise_or 0
|
||||
// star r2
|
||||
// test_strict_equal r1
|
||||
// jump_if_false @fallthrough
|
||||
// ldar r2
|
||||
// switch_on_smi {1: @case_1, 2: @case_2, 3: @case_3, 4: @case_4}
|
||||
// @fallthrough:
|
||||
// jump_if_strict_equal -0.0 @case_minus_0.0
|
||||
// jump_if_strict_equal 0.5 @case_0.5
|
||||
// jump_if_strict_equal y @case_y
|
||||
// jump_if_strict_equal 5 @case_5
|
||||
// jump @default
|
||||
// @case_minus_0.0:
|
||||
// <out = 10>
|
||||
// @case_1
|
||||
// <out = 11, break>
|
||||
// @case_0:
|
||||
// <out = 12, break>
|
||||
// @case_2:
|
||||
// <out = 13>
|
||||
// @case_3:
|
||||
// <out = 14, break>
|
||||
// @case_0.5:
|
||||
// <out = 15, break>
|
||||
// @case_4:
|
||||
// <out = 16>
|
||||
// @case_y:
|
||||
// <out = 17>
|
||||
// @case_5:
|
||||
// <out = 18>
|
||||
// @default:
|
||||
// <out = 19, break>
|
||||
|
||||
void BytecodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
|
||||
// We need this scope because we visit for register values. We have to
|
||||
// maintain a execution result scope where registers can be allocated.
|
||||
ZonePtrList<CaseClause>* clauses = stmt->cases();
|
||||
SwitchBuilder switch_builder(builder(), block_coverage_builder_, stmt,
|
||||
clauses->length());
|
||||
ControlScopeForBreakable scope(this, stmt, &switch_builder);
|
||||
int default_index = -1;
|
||||
|
||||
builder()->SetStatementPosition(stmt);
|
||||
SwitchInfo info;
|
||||
BytecodeJumpTable* jump_table = nullptr;
|
||||
bool use_jump_table = IsSwitchOptimizable(stmt, &info);
|
||||
|
||||
// Keep the switch value in a register until a case matches.
|
||||
Register tag = VisitForRegisterValue(stmt->tag());
|
||||
FeedbackSlot slot = clauses->length() > 0
|
||||
? feedback_spec()->AddCompareICSlot()
|
||||
: FeedbackSlot::Invalid();
|
||||
// N_comp_cases is number of cases we will generate comparison jumps for.
|
||||
// Note we ignore duplicate cases, since they are very unlikely.
|
||||
|
||||
// Iterate over all cases and create nodes for label comparison.
|
||||
for (int i = 0; i < clauses->length(); i++) {
|
||||
CaseClause* clause = clauses->at(i);
|
||||
|
||||
// The default is not a test, remember index.
|
||||
if (clause->is_default()) {
|
||||
default_index = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Perform label comparison as if via '===' with tag.
|
||||
VisitForAccumulatorValue(clause->label());
|
||||
builder()->CompareOperation(Token::Value::EQ_STRICT, tag,
|
||||
feedback_index(slot));
|
||||
switch_builder.Case(ToBooleanMode::kAlreadyBoolean, i);
|
||||
int n_comp_cases = clauses->length();
|
||||
if (use_jump_table) {
|
||||
n_comp_cases -= static_cast<int>(info.covered_cases.size());
|
||||
jump_table = builder()->AllocateJumpTable(
|
||||
info.MaxCase() - info.MinCase() + 1, info.MinCase());
|
||||
}
|
||||
|
||||
if (default_index >= 0) {
|
||||
// Emit default jump if there is a default case.
|
||||
switch_builder.DefaultAt(default_index);
|
||||
// Are we still using any if-else bytecodes to evaluate the switch?
|
||||
bool use_jumps = n_comp_cases != 0;
|
||||
|
||||
SwitchBuilder switch_builder(builder(), block_coverage_builder_, stmt,
|
||||
n_comp_cases, jump_table);
|
||||
ControlScopeForBreakable scope(this, stmt, &switch_builder);
|
||||
builder()->SetStatementPosition(stmt);
|
||||
|
||||
VisitForAccumulatorValue(stmt->tag());
|
||||
|
||||
if (use_jump_table) {
|
||||
// This also fills empty slots in jump table.
|
||||
Register r2 = register_allocator()->NewRegister();
|
||||
|
||||
Register r1 = register_allocator()->NewRegister();
|
||||
builder()->StoreAccumulatorInRegister(r1);
|
||||
|
||||
builder()->CompareTypeOf(TestTypeOfFlags::LiteralFlag::kNumber);
|
||||
switch_builder.JumpToFallThroughIfFalse();
|
||||
builder()->LoadAccumulatorWithRegister(r1);
|
||||
|
||||
// TODO(leszeks): Note these are duplicated range checks with the
|
||||
// SwitchOnSmi handler for the most part.
|
||||
|
||||
builder()->LoadLiteral(Smi::kMinValue);
|
||||
builder()->StoreAccumulatorInRegister(r2);
|
||||
builder()->CompareOperation(
|
||||
Token::Value::GTE, r1,
|
||||
feedback_index(feedback_spec()->AddCompareICSlot()));
|
||||
|
||||
switch_builder.JumpToFallThroughIfFalse();
|
||||
builder()->LoadAccumulatorWithRegister(r1);
|
||||
|
||||
builder()->LoadLiteral(Smi::kMaxValue);
|
||||
builder()->StoreAccumulatorInRegister(r2);
|
||||
builder()->CompareOperation(
|
||||
Token::Value::LTE, r1,
|
||||
feedback_index(feedback_spec()->AddCompareICSlot()));
|
||||
|
||||
switch_builder.JumpToFallThroughIfFalse();
|
||||
builder()->LoadAccumulatorWithRegister(r1);
|
||||
|
||||
builder()->BinaryOperationSmiLiteral(
|
||||
Token::Value::BIT_OR, Smi::FromInt(0),
|
||||
feedback_index(feedback_spec()->AddBinaryOpICSlot()));
|
||||
|
||||
builder()->StoreAccumulatorInRegister(r2);
|
||||
builder()->CompareOperation(
|
||||
Token::Value::EQ_STRICT, r1,
|
||||
feedback_index(feedback_spec()->AddCompareICSlot()));
|
||||
|
||||
switch_builder.JumpToFallThroughIfFalse();
|
||||
builder()->LoadAccumulatorWithRegister(r2);
|
||||
|
||||
switch_builder.EmitJumpTableIfExists(info.MinCase(), info.MaxCase(),
|
||||
info.covered_cases);
|
||||
|
||||
if (use_jumps) {
|
||||
builder()->LoadAccumulatorWithRegister(r1);
|
||||
}
|
||||
}
|
||||
|
||||
int case_compare_ctr = 0;
|
||||
#ifdef DEBUG
|
||||
std::unordered_map<int, int> case_ctr_checker;
|
||||
#endif
|
||||
|
||||
if (use_jumps) {
|
||||
Register tag_holder = register_allocator()->NewRegister();
|
||||
FeedbackSlot slot = clauses->length() > 0
|
||||
? feedback_spec()->AddCompareICSlot()
|
||||
: FeedbackSlot::Invalid();
|
||||
builder()->StoreAccumulatorInRegister(tag_holder);
|
||||
|
||||
for (int i = 0; i < clauses->length(); ++i) {
|
||||
CaseClause* clause = clauses->at(i);
|
||||
if (clause->is_default()) {
|
||||
info.default_case = i;
|
||||
} else if (!info.CaseExists(clause->label())) {
|
||||
// Perform label comparison as if via '===' with tag.
|
||||
VisitForAccumulatorValue(clause->label());
|
||||
builder()->CompareOperation(Token::Value::EQ_STRICT, tag_holder,
|
||||
feedback_index(slot));
|
||||
#ifdef DEBUG
|
||||
case_ctr_checker[i] = case_compare_ctr;
|
||||
#endif
|
||||
switch_builder.JumpToCaseIfTrue(ToBooleanMode::kAlreadyBoolean,
|
||||
case_compare_ctr++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For fall-throughs after comparisons (or out-of-range/non-Smi's for jump
|
||||
// tables).
|
||||
if (info.DefaultExists()) {
|
||||
switch_builder.JumpToDefault();
|
||||
} else {
|
||||
// Otherwise if we have reached here none of the cases matched, so jump to
|
||||
// the end.
|
||||
switch_builder.Break();
|
||||
}
|
||||
|
||||
// Iterate over all cases and create the case bodies.
|
||||
for (int i = 0; i < clauses->length(); i++) {
|
||||
case_compare_ctr = 0;
|
||||
for (int i = 0; i < clauses->length(); ++i) {
|
||||
CaseClause* clause = clauses->at(i);
|
||||
switch_builder.SetCaseTarget(i, clause);
|
||||
if (i != info.default_case) {
|
||||
if (!info.IsDuplicate(clause)) {
|
||||
bool use_table = use_jump_table && info.CaseExists(clause->label());
|
||||
if (!use_table) {
|
||||
// Guarantee that we should generate compare/jump if no table.
|
||||
#ifdef DEBUG
|
||||
DCHECK(case_ctr_checker[i] == case_compare_ctr);
|
||||
#endif
|
||||
switch_builder.BindCaseTargetForCompareJump(case_compare_ctr++,
|
||||
clause);
|
||||
} else {
|
||||
// Use jump table if this is not a duplicate label.
|
||||
switch_builder.BindCaseTargetForJumpTable(
|
||||
ReduceToSmiSwitchCaseValue(clause->label()), clause);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch_builder.BindDefault(clause);
|
||||
}
|
||||
// Regardless, generate code (in case of fall throughs).
|
||||
VisitStatements(clause->statements());
|
||||
}
|
||||
}
|
||||
|
@ -98,15 +98,47 @@ SwitchBuilder::~SwitchBuilder() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void SwitchBuilder::SetCaseTarget(int index, CaseClause* clause) {
|
||||
BytecodeLabel& site = case_sites_.at(index);
|
||||
builder()->Bind(&site);
|
||||
if (block_coverage_builder_) {
|
||||
block_coverage_builder_->IncrementBlockCounter(clause,
|
||||
SourceRangeKind::kBody);
|
||||
void SwitchBuilder::BindCaseTargetForJumpTable(int case_value,
|
||||
CaseClause* clause) {
|
||||
builder()->Bind(jump_table_, case_value);
|
||||
BuildBlockCoverage(clause);
|
||||
}
|
||||
|
||||
void SwitchBuilder::BindCaseTargetForCompareJump(int index,
|
||||
CaseClause* clause) {
|
||||
builder()->Bind(&case_sites_.at(index));
|
||||
BuildBlockCoverage(clause);
|
||||
}
|
||||
|
||||
void SwitchBuilder::JumpToCaseIfTrue(BytecodeArrayBuilder::ToBooleanMode mode,
|
||||
int index) {
|
||||
builder()->JumpIfTrue(mode, &case_sites_.at(index));
|
||||
}
|
||||
|
||||
// Precondition: tag is in the accumulator
|
||||
void SwitchBuilder::EmitJumpTableIfExists(
|
||||
int min_case, int max_case, std::map<int, CaseClause*>& covered_cases) {
|
||||
builder()->SwitchOnSmiNoFeedback(jump_table_);
|
||||
fall_through_.Bind(builder());
|
||||
for (int j = min_case; j <= max_case; ++j) {
|
||||
if (covered_cases.find(j) == covered_cases.end()) {
|
||||
this->BindCaseTargetForJumpTable(j, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SwitchBuilder::BindDefault(CaseClause* clause) {
|
||||
default_.Bind(builder());
|
||||
BuildBlockCoverage(clause);
|
||||
}
|
||||
|
||||
void SwitchBuilder::JumpToDefault() { this->EmitJump(&default_); }
|
||||
|
||||
void SwitchBuilder::JumpToFallThroughIfFalse() {
|
||||
this->EmitJumpIfFalse(BytecodeArrayBuilder::ToBooleanMode::kAlreadyBoolean,
|
||||
&fall_through_);
|
||||
}
|
||||
|
||||
TryCatchBuilder::~TryCatchBuilder() {
|
||||
if (block_coverage_builder_ != nullptr) {
|
||||
block_coverage_builder_->IncrementBlockCounter(
|
||||
|
@ -5,11 +5,13 @@
|
||||
#ifndef V8_INTERPRETER_CONTROL_FLOW_BUILDERS_H_
|
||||
#define V8_INTERPRETER_CONTROL_FLOW_BUILDERS_H_
|
||||
|
||||
#include "src/interpreter/bytecode-array-builder.h"
|
||||
#include <map>
|
||||
|
||||
#include "src/ast/ast-source-ranges.h"
|
||||
#include "src/interpreter/block-coverage-builder.h"
|
||||
#include "src/interpreter/bytecode-array-builder.h"
|
||||
#include "src/interpreter/bytecode-generator.h"
|
||||
#include "src/interpreter/bytecode-jump-table.h"
|
||||
#include "src/interpreter/bytecode-label.h"
|
||||
#include "src/zone/zone-containers.h"
|
||||
|
||||
@ -151,32 +153,49 @@ class V8_EXPORT_PRIVATE SwitchBuilder final
|
||||
public:
|
||||
SwitchBuilder(BytecodeArrayBuilder* builder,
|
||||
BlockCoverageBuilder* block_coverage_builder,
|
||||
SwitchStatement* statement, int number_of_cases)
|
||||
SwitchStatement* statement, int number_of_cases,
|
||||
BytecodeJumpTable* jump_table)
|
||||
: BreakableControlFlowBuilder(builder, block_coverage_builder, statement),
|
||||
case_sites_(builder->zone()) {
|
||||
case_sites_(builder->zone()),
|
||||
default_(builder->zone()),
|
||||
fall_through_(builder->zone()),
|
||||
jump_table_(jump_table) {
|
||||
case_sites_.resize(number_of_cases);
|
||||
}
|
||||
|
||||
~SwitchBuilder() override;
|
||||
|
||||
// This method should be called by the SwitchBuilder owner when the case
|
||||
// statement with |index| is emitted to update the case jump site.
|
||||
void SetCaseTarget(int index, CaseClause* clause);
|
||||
void BindCaseTargetForJumpTable(int case_value, CaseClause* clause);
|
||||
|
||||
void BindCaseTargetForCompareJump(int index, CaseClause* clause);
|
||||
|
||||
// This method is called when visiting case comparison operation for |index|.
|
||||
// Inserts a JumpIfTrue with ToBooleanMode |mode| to a unbound label that is
|
||||
// patched when the corresponding SetCaseTarget is called.
|
||||
void Case(BytecodeArrayBuilder::ToBooleanMode mode, int index) {
|
||||
builder()->JumpIfTrue(mode, &case_sites_.at(index));
|
||||
}
|
||||
void JumpToCaseIfTrue(BytecodeArrayBuilder::ToBooleanMode mode, int index);
|
||||
|
||||
// This method is called when all cases comparisons have been emitted if there
|
||||
// is a default case statement. Inserts a Jump to a unbound label that is
|
||||
// patched when the corresponding SetCaseTarget is called.
|
||||
void DefaultAt(int index) { builder()->Jump(&case_sites_.at(index)); }
|
||||
void EmitJumpTableIfExists(int min_case, int max_case,
|
||||
std::map<int, CaseClause*>& covered_cases);
|
||||
|
||||
void BindDefault(CaseClause* clause);
|
||||
|
||||
void JumpToDefault();
|
||||
|
||||
void JumpToFallThroughIfFalse();
|
||||
|
||||
private:
|
||||
// Unbound labels that identify jumps for case statements in the code.
|
||||
ZoneVector<BytecodeLabel> case_sites_;
|
||||
BytecodeLabels default_;
|
||||
BytecodeLabels fall_through_;
|
||||
BytecodeJumpTable* jump_table_;
|
||||
|
||||
void BuildBlockCoverage(CaseClause* clause) {
|
||||
if (block_coverage_builder_ && clause != nullptr) {
|
||||
block_coverage_builder_->IncrementBlockCounter(clause,
|
||||
SourceRangeKind::kBody);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// A class to help with co-ordinating control flow in try-catch statements.
|
||||
|
@ -2181,6 +2181,7 @@ IGNITION_HANDLER(JumpLoop, InterpreterAssembler) {
|
||||
// case_value falls outside of the table |table_length|, fall-through to the
|
||||
// next bytecode.
|
||||
IGNITION_HANDLER(SwitchOnSmiNoFeedback, InterpreterAssembler) {
|
||||
// The accumulator must be a Smi.
|
||||
TNode<Object> acc = GetAccumulator();
|
||||
TNode<UintPtrT> table_start = BytecodeOperandIdx(0);
|
||||
TNode<UintPtrT> table_length = BytecodeOperandUImmWord(1);
|
||||
@ -2188,14 +2189,19 @@ IGNITION_HANDLER(SwitchOnSmiNoFeedback, InterpreterAssembler) {
|
||||
|
||||
Label fall_through(this);
|
||||
|
||||
// The accumulator must be a Smi.
|
||||
// TODO(leszeks): Add a bytecode with type feedback that allows other
|
||||
// accumulator values.
|
||||
// TODO(leszeks): Use this as an alternative to adding extra bytecodes ahead
|
||||
// of a jump-table optimized switch statement, using this code, in lieu of the
|
||||
// current case_value line.
|
||||
// TNode<IntPtrT> acc_intptr = TryTaggedToInt32AsIntPtr(acc, &fall_through);
|
||||
// TNode<IntPtrT> case_value = IntPtrSub(acc_intptr, case_value_base);
|
||||
|
||||
CSA_ASSERT(this, TaggedIsSmi(acc));
|
||||
|
||||
TNode<IntPtrT> case_value = IntPtrSub(SmiUntag(CAST(acc)), case_value_base);
|
||||
|
||||
GotoIf(IntPtrLessThan(case_value, IntPtrConstant(0)), &fall_through);
|
||||
GotoIf(IntPtrGreaterThanOrEqual(case_value, table_length), &fall_through);
|
||||
|
||||
TNode<WordT> entry = IntPtrAdd(table_start, case_value);
|
||||
TNode<IntPtrT> relative_jump = LoadAndUntagConstantPoolEntry(entry);
|
||||
Jump(relative_jump);
|
||||
|
25
test/js-perf-test/SwitchStatements/run.js
Normal file
25
test/js-perf-test/SwitchStatements/run.js
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
load('../base.js');
|
||||
load('switch_statement.js');
|
||||
|
||||
var success = true;
|
||||
|
||||
function PrintResult(name, result) {
|
||||
print(name + '-SwitchStatement(Score): ' + result);
|
||||
}
|
||||
|
||||
|
||||
function PrintError(name, error) {
|
||||
PrintResult(name, error);
|
||||
success = false;
|
||||
}
|
||||
|
||||
|
||||
BenchmarkSuite.config.doWarmup = undefined;
|
||||
BenchmarkSuite.config.doDeterministic = undefined;
|
||||
|
||||
BenchmarkSuite.RunSuites({ NotifyResult: PrintResult,
|
||||
NotifyError: PrintError });
|
29
test/js-perf-test/SwitchStatements/switch_statement.js
Normal file
29
test/js-perf-test/SwitchStatements/switch_statement.js
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// running on old version takes approximately 50 seconds, so 50,000 milliseconds
|
||||
new BenchmarkSuite('Big-Switch', [50000], [
|
||||
new Benchmark('Big-Switch', false, false, 0, BigSwitch),
|
||||
]);
|
||||
|
||||
function BigSwitch() {
|
||||
"use strict";
|
||||
const n = 100000;
|
||||
const c = (a, b) => Array(a).fill().map((a, c) => b(c));
|
||||
|
||||
Function('n, c',
|
||||
`
|
||||
const a = c(n, a => a);
|
||||
let ctr = 0;
|
||||
|
||||
for(let i = 0; i !== (1+n); i++){
|
||||
switch(i){
|
||||
${c(n, a => `case ${a}: ctr += i; break;`).join('\n')}
|
||||
default: ctr += i; break;
|
||||
}
|
||||
}
|
||||
|
||||
return ctr;
|
||||
`)(n,c);
|
||||
}
|
@ -262,6 +262,25 @@ assertEquals(2016, f6(64), "largeSwitch.64");
|
||||
assertEquals(4032, f6(128), "largeSwitch.128");
|
||||
assertEquals(4222, f6(148), "largeSwitch.148");
|
||||
|
||||
function fhole(x) {
|
||||
switch(x){
|
||||
case 0:
|
||||
x = 2;
|
||||
case 2:
|
||||
x = 3;
|
||||
case 3:
|
||||
x = 4;
|
||||
case 4:
|
||||
x = 5;
|
||||
break;
|
||||
case 5:
|
||||
x = 6;
|
||||
break;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
assertEquals(1, fhole(1), "fhole.jumptablehole");
|
||||
|
||||
function f7(value) {
|
||||
switch (value) {
|
||||
@ -305,6 +324,120 @@ assertEquals("default", f7(1<<30), "0-1-switch.maxsmi++");
|
||||
assertEquals("default", f7(-(1<<30)-1), "0-1-switch.minsmi--");
|
||||
assertEquals("A", f7((170/16)-(170%16/16)), "0-1-switch.heapnum");
|
||||
|
||||
function zeroCheck1(value){
|
||||
switch(value){
|
||||
case -0:
|
||||
return 313;
|
||||
case -5:
|
||||
return 5;
|
||||
case -4:
|
||||
return 4;
|
||||
case -3:
|
||||
return 3;
|
||||
case -2:
|
||||
return 1;
|
||||
case -1:
|
||||
case 0:
|
||||
return 291949;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
assertEquals(313, zeroCheck1(0), "zero-check-1.1");
|
||||
assertEquals(313, zeroCheck1(0.0), "zero-check-1.2");
|
||||
assertEquals(313, zeroCheck1(-0), "zero-check-1.3");
|
||||
assertEquals(291949, zeroCheck1(-1), "zero-check-1.4");
|
||||
|
||||
function zeroCheck2(value){
|
||||
switch(value){
|
||||
case 0:
|
||||
return 291;
|
||||
case -5:
|
||||
return 5;
|
||||
case -4:
|
||||
case -0:
|
||||
return 313;
|
||||
case -3:
|
||||
return 3;
|
||||
case -2:
|
||||
return 1;
|
||||
case -1:
|
||||
return 10;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
assertEquals(291, zeroCheck2(0), "zero-check-2.1");
|
||||
assertEquals(291, zeroCheck2(0.0), "zero-check-2.2");
|
||||
assertEquals(291, zeroCheck2(-0), "zero-check-2.3");
|
||||
assertEquals(313, zeroCheck2(-4), "zero-check-2.4");
|
||||
|
||||
function duplicateCaseCheck(value){
|
||||
switch(value){
|
||||
case 1:
|
||||
return 291;
|
||||
case 2.0:
|
||||
return 5;
|
||||
case 3:
|
||||
case 1.0:
|
||||
return 324;
|
||||
case 4:
|
||||
case 2:
|
||||
return 15;
|
||||
case 5:
|
||||
case 6:
|
||||
return 18;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
assertEquals(291, duplicateCaseCheck(1.0), "duplicate-check.1");
|
||||
assertEquals(324, duplicateCaseCheck(3), "duplicate-check.2");
|
||||
assertEquals(15, duplicateCaseCheck(4), "duplicate-check.3");
|
||||
assertEquals(5, duplicateCaseCheck(2), "duplicate-check.4");
|
||||
|
||||
function jumpTableHoleAliasCheck(value){
|
||||
let y = 4;
|
||||
switch(value){
|
||||
case 0: return 10;
|
||||
case 1: return 20;
|
||||
case 2: return 30;
|
||||
case 3: return 40;
|
||||
case 5: return 60;
|
||||
case 6: return 70;
|
||||
case y: return 50;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
assertEquals(50, jumpTableHoleAliasCheck(4), "jump-table-hole-alias.1");
|
||||
|
||||
function caseSideEffects(value){
|
||||
let y = 2;
|
||||
switch(value){
|
||||
case y--:
|
||||
return 'first!';
|
||||
case 3:
|
||||
return 'const1';
|
||||
case 4:
|
||||
return 'const2';
|
||||
case 5:
|
||||
return 'const3';
|
||||
case 6:
|
||||
return 'const4';
|
||||
case 7:
|
||||
return 'const5';
|
||||
case 8:
|
||||
return 'const6';
|
||||
case y:
|
||||
return 'wow';
|
||||
case 1:
|
||||
return 'ouch';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
assertEquals('wow', caseSideEffects(1), "case-side-effects.1");
|
||||
|
||||
function makeVeryLong(length) {
|
||||
var res = "(function () {\n" +
|
||||
|
Loading…
Reference in New Issue
Block a user