Commit 0ab1e562 authored by titzer's avatar titzer Committed by Commit bot

[wasm] Add loop assignment analysis.

This CL implements loop assignment analysis, a pass over a loop's body
to record local variables that are assigned. This pre-pass is similar
to that done on the JavaScript AST for the same reason: avoid introducing
too many phis at loop headers when building a graph.

R=bradnelson@chromium.org,ahaas@chromium.org
BUG=

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

Cr-Commit-Position: refs/heads/master@{#33486}
parent 28d3cba2
......@@ -5,6 +5,7 @@
#include "src/base/platform/elapsed-timer.h"
#include "src/signature.h"
#include "src/bit-vector.h"
#include "src/flags.h"
#include "src/handles.h"
#include "src/zone-containers.h"
......@@ -97,13 +98,111 @@ struct IfEnv {
#define BUILD0(func) (build() ? builder_->func() : nullptr)
// Generic Wasm bytecode decoder with utilities for decoding operands,
// lengths, etc.
class WasmDecoder : public Decoder {
public:
WasmDecoder() : Decoder(nullptr, nullptr), function_env_(nullptr) {}
protected:
FunctionEnv* function_env_;
void Reset(FunctionEnv* function_env, const byte* start, const byte* end) {
Decoder::Reset(start, end);
function_env_ = function_env;
}
// Load an operand at [pc + 1].
template <typename V>
V Operand(const byte* pc) {
if ((limit_ - pc) < static_cast<int>(1 + sizeof(V))) {
const char* msg = "Expected operand following opcode";
switch (sizeof(V)) {
case 1:
msg = "Expected 1-byte operand following opcode";
break;
case 2:
msg = "Expected 2-byte operand following opcode";
break;
case 4:
msg = "Expected 4-byte operand following opcode";
break;
default:
break;
}
error(pc, msg);
return -1;
}
return *reinterpret_cast<const V*>(pc + 1);
}
LocalType LocalOperand(const byte* pc, uint32_t* index, int* length) {
*index = UnsignedLEB128Operand(pc, length);
if (function_env_->IsValidLocal(*index)) {
return function_env_->GetLocalType(*index);
}
error(pc, "invalid local variable index");
return kAstStmt;
}
LocalType GlobalOperand(const byte* pc, uint32_t* index, int* length) {
*index = UnsignedLEB128Operand(pc, length);
if (function_env_->module->IsValidGlobal(*index)) {
return WasmOpcodes::LocalTypeFor(
function_env_->module->GetGlobalType(*index));
}
error(pc, "invalid global variable index");
return kAstStmt;
}
FunctionSig* FunctionSigOperand(const byte* pc, uint32_t* index,
int* length) {
*index = UnsignedLEB128Operand(pc, length);
if (function_env_->module->IsValidFunction(*index)) {
return function_env_->module->GetFunctionSignature(*index);
}
error(pc, "invalid function index");
return nullptr;
}
FunctionSig* SigOperand(const byte* pc, uint32_t* index, int* length) {
*index = UnsignedLEB128Operand(pc, length);
if (function_env_->module->IsValidSignature(*index)) {
return function_env_->module->GetSignature(*index);
}
error(pc, "invalid signature index");
return nullptr;
}
uint32_t UnsignedLEB128Operand(const byte* pc, int* length) {
uint32_t result = 0;
ReadUnsignedLEB128ErrorCode error_code =
ReadUnsignedLEB128Operand(pc + 1, limit_, length, &result);
if (error_code == kInvalidLEB128) error(pc, "invalid LEB128 varint");
if (error_code == kMissingLEB128) error(pc, "expected LEB128 varint");
(*length)++;
return result;
}
void MemoryAccessOperand(const byte* pc, int* length, uint32_t* offset) {
byte bitfield = Operand<uint8_t>(pc);
if (MemoryAccess::OffsetField::decode(bitfield)) {
*offset = UnsignedLEB128Operand(pc + 1, length);
(*length)++; // to account for the memory access byte
} else {
*offset = 0;
*length = 2;
}
}
};
// A shift-reduce-parser strategy for decoding Wasm code that uses an explicit
// shift-reduce strategy with multiple internal stacks.
class LR_WasmDecoder : public Decoder {
class LR_WasmDecoder : public WasmDecoder {
public:
LR_WasmDecoder(Zone* zone, TFBuilder* builder)
: Decoder(nullptr, nullptr),
zone_(zone),
: zone_(zone),
builder_(builder),
trees_(zone),
stack_(zone),
......@@ -127,8 +226,7 @@ class LR_WasmDecoder : public Decoder {
}
base_ = base;
Reset(pc, end);
function_env_ = function_env;
Reset(function_env, pc, end);
InitSsaEnv();
DecodeFunctionBody();
......@@ -177,7 +275,6 @@ class LR_WasmDecoder : public Decoder {
TreeResult result_;
SsaEnv* ssa_env_;
FunctionEnv* function_env_;
ZoneVector<Tree*> trees_;
ZoneVector<Production> stack_;
......@@ -1291,94 +1388,11 @@ class LR_WasmDecoder : public Decoder {
return result;
}
// Load an operand at [pc + 1].
template <typename V>
V Operand(const byte* pc) {
if ((limit_ - pc) < static_cast<int>(1 + sizeof(V))) {
const char* msg = "Expected operand following opcode";
switch (sizeof(V)) {
case 1:
msg = "Expected 1-byte operand following opcode";
break;
case 2:
msg = "Expected 2-byte operand following opcode";
break;
case 4:
msg = "Expected 4-byte operand following opcode";
break;
default:
break;
}
error(pc, msg);
return -1;
}
return *reinterpret_cast<const V*>(pc + 1);
}
int EnvironmentCount() {
if (builder_) return static_cast<int>(function_env_->GetLocalCount());
return 0; // if we aren't building a graph, don't bother with SSA renaming.
}
LocalType LocalOperand(const byte* pc, uint32_t* index, int* length) {
*index = UnsignedLEB128Operand(pc, length);
if (function_env_->IsValidLocal(*index)) {
return function_env_->GetLocalType(*index);
}
error(pc, "invalid local variable index");
return kAstStmt;
}
LocalType GlobalOperand(const byte* pc, uint32_t* index, int* length) {
*index = UnsignedLEB128Operand(pc, length);
if (function_env_->module->IsValidGlobal(*index)) {
return WasmOpcodes::LocalTypeFor(
function_env_->module->GetGlobalType(*index));
}
error(pc, "invalid global variable index");
return kAstStmt;
}
FunctionSig* FunctionSigOperand(const byte* pc, uint32_t* index,
int* length) {
*index = UnsignedLEB128Operand(pc, length);
if (function_env_->module->IsValidFunction(*index)) {
return function_env_->module->GetFunctionSignature(*index);
}
error(pc, "invalid function index");
return nullptr;
}
FunctionSig* SigOperand(const byte* pc, uint32_t* index, int* length) {
*index = UnsignedLEB128Operand(pc, length);
if (function_env_->module->IsValidSignature(*index)) {
return function_env_->module->GetSignature(*index);
}
error(pc, "invalid signature index");
return nullptr;
}
uint32_t UnsignedLEB128Operand(const byte* pc, int* length) {
uint32_t result = 0;
ReadUnsignedLEB128ErrorCode error_code =
ReadUnsignedLEB128Operand(pc + 1, limit_, length, &result);
if (error_code == kInvalidLEB128) error(pc, "invalid LEB128 varint");
if (error_code == kMissingLEB128) error(pc, "expected LEB128 varint");
(*length)++;
return result;
}
void MemoryAccessOperand(const byte* pc, int* length, uint32_t* offset) {
byte bitfield = Operand<uint8_t>(pc);
if (MemoryAccess::OffsetField::decode(bitfield)) {
*offset = UnsignedLEB128Operand(pc + 1, length);
(*length)++; // to account for the memory access byte
} else {
*offset = 0;
*length = 2;
}
}
virtual void onFirstError() {
limit_ = start_; // Terminate decoding loop.
builder_ = nullptr; // Don't build any more nodes.
......@@ -1476,6 +1490,7 @@ ReadUnsignedLEB128ErrorCode ReadUnsignedLEB128Operand(const byte* pc,
}
// TODO(titzer): move this into WasmDecoder and bounds check accesses.
int OpcodeLength(const byte* pc) {
switch (static_cast<WasmOpcode>(*pc)) {
#define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name:
......@@ -1527,6 +1542,7 @@ int OpcodeLength(const byte* pc) {
}
// TODO(titzer): move this into WasmDecoder and bounds check accesses.
int OpcodeArity(FunctionEnv* env, const byte* pc) {
#define DECLARE_ARITY(name, ...) \
static const LocalType kTypes_##name[] = {__VA_ARGS__}; \
......@@ -1595,6 +1611,7 @@ int OpcodeArity(FunctionEnv* env, const byte* pc) {
}
void PrintAst(FunctionEnv* env, const byte* start, const byte* end) {
const byte* pc = start;
std::vector<int> arity_stack;
......@@ -1624,6 +1641,69 @@ void PrintAst(FunctionEnv* env, const byte* start, const byte* end) {
}
}
}
// Analyzes loop bodies for static assignments to locals, which helps in
// reducing the number of phis introduced at loop headers.
class LoopAssignmentAnalyzer : public WasmDecoder {
public:
LoopAssignmentAnalyzer(Zone* zone, FunctionEnv* function_env) : zone_(zone) {
function_env_ = function_env;
}
BitVector* Analyze(const byte* pc, const byte* limit) {
Decoder::Reset(pc, limit);
if (pc_ >= limit_) return nullptr;
if (*pc_ != kExprLoop) return nullptr;
BitVector* assigned =
new (zone_) BitVector(function_env_->total_locals, zone_);
// Keep a stack to model the nesting of expressions.
std::vector<int> arity_stack;
arity_stack.push_back(OpcodeArity(function_env_, pc_));
pc_ += OpcodeLength(pc_);
// Iteratively process all AST nodes nested inside the loop.
while (pc_ < limit_) {
WasmOpcode opcode = static_cast<WasmOpcode>(*pc_);
int arity = 0;
int length = 1;
if (opcode == kExprSetLocal) {
uint32_t index;
LocalOperand(pc_, &index, &length);
if (assigned->length() > 0 &&
static_cast<int>(index) < assigned->length()) {
// Unverified code might have an out-of-bounds index.
assigned->Add(index);
}
arity = 1;
} else {
arity = OpcodeArity(function_env_, pc_);
length = OpcodeLength(pc_);
}
pc_ += length;
arity_stack.push_back(arity);
while (arity_stack.back() == 0) {
arity_stack.pop_back();
if (arity_stack.empty()) return assigned; // reached end of loop
arity_stack.back()--;
}
}
return assigned;
}
private:
Zone* zone_;
};
BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone, FunctionEnv* env,
const byte* start, const byte* end) {
LoopAssignmentAnalyzer analyzer(zone, env);
return analyzer.Analyze(start, end);
}
} // namespace wasm
} // namespace internal
} // namespace v8
......@@ -12,6 +12,8 @@
namespace v8 {
namespace internal {
class BitVector; // forward declaration
namespace compiler { // external declarations from compiler.
class WasmGraphBuilder;
}
......@@ -106,6 +108,9 @@ enum ReadUnsignedLEB128ErrorCode { kNoError, kInvalidLEB128, kMissingLEB128 };
ReadUnsignedLEB128ErrorCode ReadUnsignedLEB128Operand(const byte*, const byte*,
int*, uint32_t*);
BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone, FunctionEnv* env,
const byte* start, const byte* end);
// Computes the length of the opcode at the given address.
int OpcodeLength(const byte* pc);
......
......@@ -114,6 +114,7 @@
'test-utils.cc',
'wasm/ast-decoder-unittest.cc',
'wasm/encoder-unittest.cc',
'wasm/loop-assignment-analysis-unittest.cc',
'wasm/module-decoder-unittest.cc',
'wasm/wasm-macro-gen-unittest.cc',
],
......
// 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 "test/unittests/test-utils.h"
#include "src/v8.h"
#include "test/cctest/wasm/test-signatures.h"
#include "src/bit-vector.h"
#include "src/objects.h"
#include "src/wasm/ast-decoder.h"
#include "src/wasm/wasm-macro-gen.h"
#include "src/wasm/wasm-module.h"
#define WASM_SET_ZERO(i) WASM_SET_LOCAL(i, WASM_ZERO)
namespace v8 {
namespace internal {
namespace wasm {
class WasmLoopAssignmentAnalyzerTest : public TestWithZone {
public:
WasmLoopAssignmentAnalyzerTest() : TestWithZone(), sigs() {
init_env(&env, sigs.v_v());
}
TestSignatures sigs;
FunctionEnv env;
static void init_env(FunctionEnv* env, FunctionSig* sig) {
env->module = nullptr;
env->sig = sig;
env->local_int32_count = 0;
env->local_int64_count = 0;
env->local_float32_count = 0;
env->local_float64_count = 0;
env->SumLocals();
}
BitVector* Analyze(const byte* start, const byte* end) {
return AnalyzeLoopAssignmentForTesting(zone(), &env, start, end);
}
};
TEST_F(WasmLoopAssignmentAnalyzerTest, Empty0) {
byte code[] = { 0 };
BitVector* assigned = Analyze(code, code);
CHECK_NULL(assigned);
}
TEST_F(WasmLoopAssignmentAnalyzerTest, Empty1) {
byte code[] = {kExprLoop, 0};
for (int i = 0; i < 5; i++) {
BitVector* assigned = Analyze(code, code + arraysize(code));
for (int j = 0; j < assigned->length(); j++) {
CHECK_EQ(false, assigned->Contains(j));
}
env.AddLocals(kAstI32, 1);
}
}
TEST_F(WasmLoopAssignmentAnalyzerTest, One) {
env.AddLocals(kAstI32, 5);
for (int i = 0; i < 5; i++) {
byte code[] = {WASM_LOOP(1, WASM_SET_ZERO(i))};
BitVector* assigned = Analyze(code, code + arraysize(code));
for (int j = 0; j < assigned->length(); j++) {
CHECK_EQ(j == i, assigned->Contains(j));
}
}
}
TEST_F(WasmLoopAssignmentAnalyzerTest, OneBeyond) {
env.AddLocals(kAstI32, 5);
for (int i = 0; i < 5; i++) {
byte code[] = {WASM_LOOP(1, WASM_SET_ZERO(i)), WASM_SET_ZERO(1)};
BitVector* assigned = Analyze(code, code + arraysize(code));
for (int j = 0; j < assigned->length(); j++) {
CHECK_EQ(j == i, assigned->Contains(j));
}
}
}
TEST_F(WasmLoopAssignmentAnalyzerTest, Two) {
env.AddLocals(kAstI32, 5);
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
byte code[] = {WASM_LOOP(2, WASM_SET_ZERO(i), WASM_SET_ZERO(j))};
BitVector* assigned = Analyze(code, code + arraysize(code));
for (int k = 0; k < assigned->length(); k++) {
bool expected = k == i || k == j;
CHECK_EQ(expected, assigned->Contains(k));
}
}
}
}
TEST_F(WasmLoopAssignmentAnalyzerTest, NestedIf) {
env.AddLocals(kAstI32, 5);
for (int i = 0; i < 5; i++) {
byte code[] = {WASM_LOOP(
1, WASM_IF_ELSE(WASM_SET_ZERO(0), WASM_SET_ZERO(i), WASM_SET_ZERO(1)))};
BitVector* assigned = Analyze(code, code + arraysize(code));
for (int j = 0; j < assigned->length(); j++) {
bool expected = i == j || j == 0 || j == 1;
CHECK_EQ(expected, assigned->Contains(j));
}
}
}
static byte LEBByte(uint32_t val, byte which) {
byte b = (val >> (which * 7)) & 0x7F;
if (val >> ((which + 1) * 7)) b |= 0x80;
return b;
}
TEST_F(WasmLoopAssignmentAnalyzerTest, BigLocal) {
env.AddLocals(kAstI32, 65000);
for (int i = 13; i < 65000; i = static_cast<int>(i * 1.5)) {
byte code[] = {kExprLoop,
1,
kExprSetLocal,
LEBByte(i, 0),
LEBByte(i, 1),
LEBByte(i, 2),
11,
12,
13};
BitVector* assigned = Analyze(code, code + arraysize(code));
for (int j = 0; j < assigned->length(); j++) {
bool expected = i == j;
CHECK_EQ(expected, assigned->Contains(j));
}
}
}
TEST_F(WasmLoopAssignmentAnalyzerTest, Break) {
env.AddLocals(kAstI32, 3);
byte code[] = {
WASM_LOOP(1, WASM_IF(WASM_GET_LOCAL(0), WASM_BRV(1, WASM_SET_ZERO(1)))),
WASM_SET_ZERO(0)};
BitVector* assigned = Analyze(code, code + arraysize(code));
for (int j = 0; j < assigned->length(); j++) {
bool expected = j == 1;
CHECK_EQ(expected, assigned->Contains(j));
}
}
TEST_F(WasmLoopAssignmentAnalyzerTest, Loop1) {
env.AddLocals(kAstI32, 5);
byte code[] = {
WASM_LOOP(1, WASM_IF(WASM_GET_LOCAL(0),
WASM_BRV(0, WASM_SET_LOCAL(
3, WASM_I32_SUB(WASM_GET_LOCAL(0),
WASM_I8(1)))))),
WASM_GET_LOCAL(0)};
BitVector* assigned = Analyze(code, code + arraysize(code));
for (int j = 0; j < assigned->length(); j++) {
bool expected = j == 3;
CHECK_EQ(expected, assigned->Contains(j));
}
}
TEST_F(WasmLoopAssignmentAnalyzerTest, Loop2) {
env.AddLocals(kAstI32, 3);
const byte kIter = 0;
env.AddLocals(kAstF32, 3);
const byte kSum = 3;
byte code[] = {WASM_BLOCK(
3,
WASM_WHILE(
WASM_GET_LOCAL(kIter),
WASM_BLOCK(2, WASM_SET_LOCAL(
kSum, WASM_F32_ADD(
WASM_GET_LOCAL(kSum),
WASM_LOAD_MEM(MachineType::Float32(),
WASM_GET_LOCAL(kIter)))),
WASM_SET_LOCAL(kIter, WASM_I32_SUB(WASM_GET_LOCAL(kIter),
WASM_I8(4))))),
WASM_STORE_MEM(MachineType::Float32(), WASM_ZERO, WASM_GET_LOCAL(kSum)),
WASM_GET_LOCAL(kIter))};
BitVector* assigned = Analyze(code + 2, code + arraysize(code));
for (int j = 0; j < assigned->length(); j++) {
bool expected = j == kIter || j == kSum;
CHECK_EQ(expected, assigned->Contains(j));
}
}
} // namespace wasm
} // namespace internal
} // namespace v8
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