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:
Mihir Shah 2021-06-22 19:14:01 -05:00 committed by V8 LUCI CQ
parent b0ad4a11d2
commit 9711289d06
16 changed files with 610 additions and 60 deletions

View File

@ -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>

View File

@ -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));
}

View File

@ -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));
}

View File

@ -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));

View File

@ -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));
}

View File

@ -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));

View File

@ -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>(

View File

@ -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,

View File

@ -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")

View File

@ -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());
}
}

View File

@ -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(

View File

@ -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.

View File

@ -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);

View 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 });

View 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);
}

View File

@ -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" +