Commit 1d37d421 authored by aseemgarg's avatar aseemgarg Committed by Commit bot

[wasm] optimized switch implementation in asm.js to wasm builder

This change implements switch as a balanced if/else tree or break table or
hybrid. A lot of asm.js modules are expected to extensively use switch
alongside function tables that can benefit from a better implementation.

BUG=v8:4203
TEST=mjsunit/asm-wasm
R=titzer@chromium.org,bradnelson@chromium.org,ahaas@chromium.org
LOG=N

Review URL: https://codereview.chromium.org/1838973002

Cr-Commit-Position: refs/heads/master@{#35455}
parent 6df04b29
......@@ -1412,6 +1412,8 @@ source_set("v8_base") {
"src/version.h",
"src/vm-state-inl.h",
"src/vm-state.h",
"src/wasm/switch-logic.h",
"src/wasm/switch-logic.cc",
"src/wasm/asm-wasm-builder.cc",
"src/wasm/asm-wasm-builder.h",
"src/wasm/ast-decoder.cc",
......
......@@ -11,6 +11,7 @@
#include <math.h>
#include "src/wasm/asm-wasm-builder.h"
#include "src/wasm/switch-logic.h"
#include "src/wasm/wasm-macro-gen.h"
#include "src/wasm/wasm-opcodes.h"
......@@ -99,6 +100,11 @@ class AsmWasmBuilderImpl : public AstVisitor {
void VisitStatements(ZoneList<Statement*>* stmts) {
for (int i = 0; i < stmts->length(); ++i) {
Statement* stmt = stmts->at(i);
ExpressionStatement* e = stmt->AsExpressionStatement();
if (e != nullptr && e->expression()->IsUndefinedLiteral()) {
block_size_--;
continue;
}
RECURSE(Visit(stmt));
if (stmt->IsJump()) break;
}
......@@ -235,53 +241,124 @@ class AsmWasmBuilderImpl : public AstVisitor {
void VisitWithStatement(WithStatement* stmt) { UNREACHABLE(); }
void SetLocalTo(uint16_t index, int value) {
current_function_builder_->Emit(kExprSetLocal);
AddLeb128(index, true);
// TODO(bradnelson): variable size
void GenerateCaseComparisonCode(int value, WasmOpcode op,
VariableProxy* tag) {
current_function_builder_->Emit(kExprIfElse);
current_function_builder_->Emit(op);
VisitVariableProxy(tag);
byte code[] = {WASM_I32V(value)};
current_function_builder_->EmitCode(code, sizeof(code));
block_size_++;
}
void CompileCase(CaseClause* clause, uint16_t fall_through,
VariableProxy* tag) {
Literal* label = clause->label()->AsLiteral();
DCHECK_NOT_NULL(label);
block_size_++;
current_function_builder_->Emit(kExprIf);
current_function_builder_->Emit(kExprI32Ior);
current_function_builder_->Emit(kExprI32Eq);
VisitVariableProxy(tag);
VisitLiteral(label);
current_function_builder_->Emit(kExprGetLocal);
AddLeb128(fall_through, true);
BlockVisitor visitor(this, nullptr, kExprBlock, false, 0);
SetLocalTo(fall_through, 1);
ZoneList<Statement*>* stmts = clause->statements();
block_size_ += stmts->length();
RECURSE(VisitStatements(stmts));
void HandleCase(CaseNode* node,
const ZoneMap<int, unsigned int>& case_to_block,
VariableProxy* tag, int default_block) {
if (node->left != nullptr) {
GenerateCaseComparisonCode(node->begin, kExprI32LtS, tag);
HandleCase(node->left, case_to_block, tag, default_block);
}
if (node->right != nullptr) {
GenerateCaseComparisonCode(node->end, kExprI32GtS, tag);
HandleCase(node->right, case_to_block, tag, default_block);
}
if (node->begin == node->end) {
current_function_builder_->Emit(kExprIf);
current_function_builder_->Emit(kExprI32Eq);
VisitVariableProxy(tag);
byte code[] = {WASM_I32V(node->begin)};
current_function_builder_->EmitCode(code, sizeof(code));
DCHECK(case_to_block.find(node->begin) != case_to_block.end());
current_function_builder_->EmitWithVarInt(kExprBr,
case_to_block.at(node->begin));
current_function_builder_->Emit(kExprNop);
} else {
current_function_builder_->Emit(kExprBrTable);
std::vector<uint8_t> count =
UnsignedLEB128From(node->end - node->begin + 1);
current_function_builder_->EmitCode(&count[0],
static_cast<uint32_t>(count.size()));
for (int v = node->begin; v <= node->end; v++) {
if (case_to_block.find(v) != case_to_block.end()) {
byte break_code[] = {BR_TARGET(case_to_block.at(v))};
current_function_builder_->EmitCode(break_code, sizeof(break_code));
} else {
byte break_code[] = {BR_TARGET(default_block)};
current_function_builder_->EmitCode(break_code, sizeof(break_code));
}
if (v == kMaxInt) {
break;
}
}
byte break_code[] = {BR_TARGET(default_block)};
current_function_builder_->EmitCode(break_code, sizeof(break_code));
// TODO(aseemgarg): remove the if once sub 0 is fixed
if (node->begin != 0) {
current_function_builder_->Emit(kExprI32Sub);
VisitVariableProxy(tag);
byte code[] = {WASM_I32V(node->begin)};
current_function_builder_->EmitCode(code, sizeof(code));
} else {
VisitVariableProxy(tag);
}
}
}
void VisitSwitchStatement(SwitchStatement* stmt) {
VariableProxy* tag = stmt->tag()->AsVariableProxy();
DCHECK_NOT_NULL(tag);
BlockVisitor visitor(this, stmt->AsBreakableStatement(), kExprBlock, false,
0);
uint16_t fall_through = current_function_builder_->AddLocal(kAstI32);
SetLocalTo(fall_through, 0);
ZoneList<CaseClause*>* clauses = stmt->cases();
for (int i = 0; i < clauses->length(); ++i) {
int case_count = clauses->length();
if (case_count == 0) {
block_size_--;
return;
}
BlockVisitor visitor(this, stmt->AsBreakableStatement(), kExprBlock, false,
1);
ZoneVector<BlockVisitor*> blocks(zone_);
ZoneVector<int32_t> cases(zone_);
ZoneMap<int, unsigned int> case_to_block(zone_);
bool has_default = false;
for (int i = case_count - 1; i >= 0; i--) {
CaseClause* clause = clauses->at(i);
blocks.push_back(new BlockVisitor(this, nullptr, kExprBlock, false,
clause->statements()->length() + 1));
if (!clause->is_default()) {
CompileCase(clause, fall_through, tag);
Literal* label = clause->label()->AsLiteral();
Handle<Object> value = label->value();
DCHECK(value->IsNumber() &&
label->bounds().upper->Is(cache_.kAsmSigned));
int32_t label_value;
if (!value->ToInt32(&label_value)) {
UNREACHABLE();
}
case_to_block[label_value] = i;
cases.push_back(label_value);
} else {
ZoneList<Statement*>* stmts = clause->statements();
block_size_ += stmts->length();
RECURSE(VisitStatements(stmts));
DCHECK_EQ(i, case_count - 1);
has_default = true;
}
}
if (!has_default || case_count > 1) {
int default_block = has_default ? case_count - 1 : case_count;
BlockVisitor switch_logic_block(this, nullptr, kExprBlock, false, 1);
CaseNode* root = OrderCases(&cases, zone_);
HandleCase(root, case_to_block, tag, default_block);
if (root->left != nullptr || root->right != nullptr ||
root->begin == root->end) {
block_size_++;
current_function_builder_->EmitWithVarInt(kExprBr, default_block);
current_function_builder_->Emit(kExprNop);
}
} else {
block_size_ = clauses->at(0)->statements()->length();
}
for (int i = 0; i < case_count; i++) {
CaseClause* clause = clauses->at(i);
RECURSE(VisitStatements(clause->statements()));
BlockVisitor* v = blocks.at(case_count - i - 1);
blocks.pop_back();
delete v;
}
}
void VisitCaseClause(CaseClause* clause) { UNREACHABLE(); }
......
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/wasm/switch-logic.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
CaseNode* CreateBst(ZoneVector<CaseNode*>* nodes, size_t begin, size_t end) {
if (end < begin) {
return nullptr;
} else if (end == begin) {
return nodes->at(begin);
} else {
size_t root_index = (begin + end) / 2;
CaseNode* root = nodes->at(root_index);
if (root_index != 0) {
root->left = CreateBst(nodes, begin, root_index - 1);
}
root->right = CreateBst(nodes, root_index + 1, end);
return root;
}
}
} // namespace
CaseNode* OrderCases(ZoneVector<int>* cases, Zone* zone) {
const int max_distance = 2;
const int min_size = 4;
if (cases->empty()) {
return nullptr;
}
std::sort(cases->begin(), cases->end());
ZoneVector<size_t> table_breaks(zone);
for (size_t i = 1; i < cases->size(); i++) {
if (cases->at(i) - cases->at(i - 1) > max_distance) {
table_breaks.push_back(i);
}
}
table_breaks.push_back(cases->size());
ZoneVector<CaseNode*> nodes(zone);
size_t curr_pos = 0;
for (size_t i = 0; i < table_breaks.size(); i++) {
size_t break_pos = table_breaks[i];
if (break_pos - curr_pos >= min_size) {
int begin = cases->at(curr_pos);
int end = cases->at(break_pos - 1);
nodes.push_back(new (zone) CaseNode(begin, end));
curr_pos = break_pos;
} else {
for (; curr_pos < break_pos; curr_pos++) {
nodes.push_back(new (zone)
CaseNode(cases->at(curr_pos), cases->at(curr_pos)));
}
}
}
return CreateBst(&nodes, 0, nodes.size() - 1);
}
} // namespace wasm
} // namespace internal
} // namespace v8
// 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_WASM_SWITCH_LOGIC_H
#define V8_WASM_SWITCH_LOGIC_H
#include "src/zone-containers.h"
#include "src/zone.h"
namespace v8 {
namespace internal {
namespace wasm {
struct CaseNode : public ZoneObject {
const int begin;
const int end;
CaseNode* left;
CaseNode* right;
CaseNode(int begin, int end) : begin(begin), end(end) {
left = nullptr;
right = nullptr;
}
};
CaseNode* OrderCases(ZoneVector<int>* cases, Zone* zone);
} // namespace wasm
} // namespace internal
} // namespace v8
#endif
......@@ -274,6 +274,7 @@
'wasm/asm-wasm-literals': [PASS, ['arch in [arm, arm64, mips, mipsel, mips64, mips64el] or ignition == True', SKIP]],
'wasm/asm-wasm-copy': [PASS, ['arch in [arm, arm64, mips, mipsel, mips64, mips64el]', SKIP]],
'wasm/asm-wasm-deopt': [PASS, ['arch in [arm, arm64, mips, mipsel, mips64, mips64el]', SKIP]],
'wasm/asm-wasm-switch': [PASS, ['arch in [arm, arm64, mips, mipsel, mips64, mips64el]', SKIP]],
# TODO(branelson): Figure out why ignition + asm->wasm fails embenchen.
'wasm/embenchen/*': [PASS, ['arch == arm64', SKIP], ['ignition == True', SKIP]],
......
This diff is collapsed.
......@@ -769,82 +769,6 @@ function TestConditional() {
assertWasm(41, TestConditional);
function TestSwitch() {
"use asm"
function caller() {
var ret = 0;
var x = 7;
switch (x) {
case 1: return 0;
case 7: {
ret = 12;
break;
}
default: return 0;
}
switch (x) {
case 1: return 0;
case 8: return 0;
default: ret = (ret + 11)|0;
}
return ret|0;
}
return {caller:caller};
}
assertWasm(23, TestSwitch);
function TestSwitchFallthrough() {
"use asm"
function caller() {
var x = 17;
var ret = 0;
switch (x) {
case 17:
case 14: ret = 39;
case 1: ret = (ret + 3)|0;
case 4: break;
default: ret = (ret + 1)|0;
}
return ret|0;
}
return {caller:caller};
}
assertWasm(42, TestSwitchFallthrough);
function TestNestedSwitch() {
"use asm"
function caller() {
var x = 3;
var y = -13;
switch (x) {
case 1: return 0;
case 3: {
switch (y) {
case 2: return 0;
case -13: return 43;
default: return 0;
}
}
default: return 0;
}
return 0;
}
return {caller:caller};
}
assertWasm(43, TestNestedSwitch);
(function () {
function TestInitFunctionWithNoGlobals() {
"use asm";
......
......@@ -120,6 +120,7 @@
'wasm/encoder-unittest.cc',
'wasm/loop-assignment-analysis-unittest.cc',
'wasm/module-decoder-unittest.cc',
'wasm/switch-logic-unittest.cc',
'wasm/wasm-macro-gen-unittest.cc',
],
'conditions': [
......
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/wasm/switch-logic.h"
#include "test/unittests/test-utils.h"
namespace v8 {
namespace internal {
namespace wasm {
class SwitchLogicTest : public TestWithZone {};
void CheckNodeValues(CaseNode* node, int begin, int end) {
CHECK_EQ(node->begin, begin);
CHECK_EQ(node->end, end);
}
TEST_F(SwitchLogicTest, Single_Table_Test) {
ZoneVector<int> values(zone());
values.push_back(14);
values.push_back(12);
values.push_back(15);
values.push_back(19);
values.push_back(18);
values.push_back(16);
CaseNode* root = OrderCases(&values, zone());
CHECK_NULL(root->left);
CHECK_NULL(root->right);
CheckNodeValues(root, 12, 19);
}
TEST_F(SwitchLogicTest, Balanced_Tree_Test) {
ZoneVector<int> values(zone());
values.push_back(5);
values.push_back(1);
values.push_back(6);
values.push_back(9);
values.push_back(-4);
CaseNode* root = OrderCases(&values, zone());
CheckNodeValues(root, 5, 5);
CheckNodeValues(root->left, -4, -4);
CHECK_NULL(root->left->left);
CheckNodeValues(root->left->right, 1, 1);
CHECK_NULL(root->left->right->left);
CHECK_NULL(root->left->right->right);
CheckNodeValues(root->right, 6, 6);
CHECK_NULL(root->right->left);
CheckNodeValues(root->right->right, 9, 9);
CHECK_NULL(root->right->right->left);
CHECK_NULL(root->right->right->right);
}
TEST_F(SwitchLogicTest, Hybrid_Test) {
ZoneVector<int> values(zone());
values.push_back(1);
values.push_back(2);
values.push_back(3);
values.push_back(4);
values.push_back(7);
values.push_back(10);
values.push_back(11);
values.push_back(12);
values.push_back(13);
values.push_back(16);
CaseNode* root = OrderCases(&values, zone());
CheckNodeValues(root, 7, 7);
CheckNodeValues(root->left, 1, 4);
CheckNodeValues(root->right, 10, 13);
CheckNodeValues(root->right->right, 16, 16);
}
TEST_F(SwitchLogicTest, Single_Case) {
ZoneVector<int> values(zone());
values.push_back(3);
CaseNode* root = OrderCases(&values, zone());
CheckNodeValues(root, 3, 3);
CHECK_NULL(root->left);
CHECK_NULL(root->right);
}
TEST_F(SwitchLogicTest, Empty_Case) {
ZoneVector<int> values(zone());
CaseNode* root = OrderCases(&values, zone());
CHECK_NULL(root);
}
} // namespace wasm
} // namespace internal
} // namespace v8
......@@ -1116,6 +1116,8 @@
'../../src/version.h',
'../../src/vm-state-inl.h',
'../../src/vm-state.h',
'../../src/wasm/switch-logic.h',
'../../src/wasm/switch-logic.cc',
'../../src/wasm/asm-wasm-builder.cc',
'../../src/wasm/asm-wasm-builder.h',
'../../src/wasm/ast-decoder.cc',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment