Commit e4bb7ff9 authored by titzer's avatar titzer Committed by Commit bot

[wasm] Implement an interpreter for WASM.

This interpreter directly decodes and executes WASM binary code for
the purpose of supporting low-level debugging. It is not currently
integrated into the main WASM implementation.

R=ahaas@chromium.org,clemensh@chromium.org,rossberg@chromium.org,binji@chromium.org
BUG=

Review-Url: https://codereview.chromium.org/1972153002
Cr-Commit-Position: refs/heads/master@{#36497}
parent 39d08198
......@@ -1492,6 +1492,8 @@ v8_source_set("v8_base") {
"src/wasm/wasm-external-refs.h",
"src/wasm/wasm-function-name-table.cc",
"src/wasm/wasm-function-name-table.h",
"src/wasm/wasm-interpreter.cc",
"src/wasm/wasm-interpreter.h",
"src/wasm/wasm-js.cc",
"src/wasm/wasm-js.h",
"src/wasm/wasm-macro-gen.h",
......
......@@ -480,6 +480,7 @@ DEFINE_BOOL(trace_wasm_encoder, false, "trace encoding of wasm code")
DEFINE_BOOL(trace_wasm_decoder, false, "trace decoding of wasm code")
DEFINE_BOOL(trace_wasm_decode_time, false, "trace decoding time of wasm code")
DEFINE_BOOL(trace_wasm_compiler, false, "trace compiling of wasm code")
DEFINE_BOOL(trace_wasm_interpreter, false, "trace interpretation of wasm code")
DEFINE_INT(trace_wasm_ast_start, 0,
"start function for WASM AST trace (inclusive)")
DEFINE_INT(trace_wasm_ast_end, 0, "end function for WASM AST trace (exclusive)")
......
......@@ -1170,6 +1170,8 @@
'wasm/wasm-macro-gen.h',
'wasm/wasm-module.cc',
'wasm/wasm-module.h',
'wasm/wasm-interpreter.cc',
'wasm/wasm-interpreter.h',
'wasm/wasm-opcodes.cc',
'wasm/wasm-opcodes.h',
'wasm/wasm-result.cc',
......
This diff is collapsed.
// 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_INTERPRETER_H_
#define V8_WASM_INTERPRETER_H_
#include "src/wasm/wasm-opcodes.h"
#include "src/zone-containers.h"
namespace v8 {
namespace base {
class AccountingAllocator;
}
namespace internal {
namespace wasm {
// forward declarations.
struct WasmFunction;
struct WasmModuleInstance;
class WasmInterpreterInternals;
typedef size_t pc_t;
typedef size_t sp_t;
typedef int32_t pcdiff_t;
typedef uint32_t spdiff_t;
// Visible for testing. A {ControlTransfer} helps the interpreter figure out
// the target program counter and stack manipulations for a branch.
struct ControlTransfer {
enum StackAction { kNoAction, kPopAndRepush, kPushVoid };
pcdiff_t pcdiff; // adjustment to the program counter (positive or negative).
spdiff_t spdiff; // number of elements to pop off the stack.
StackAction action; // action to perform on the stack.
};
typedef ZoneMap<pc_t, ControlTransfer> ControlTransferMap;
// Macro for defining union members.
#define FOREACH_UNION_MEMBER(V) \
V(i32, kAstI32, int32_t) \
V(u32, kAstI32, uint32_t) \
V(i64, kAstI64, int64_t) \
V(u64, kAstI64, uint64_t) \
V(f32, kAstF32, float) \
V(f64, kAstF64, double)
// Representation of values within the interpreter.
struct WasmVal {
LocalType type;
union {
#define DECLARE_FIELD(field, localtype, ctype) ctype field;
FOREACH_UNION_MEMBER(DECLARE_FIELD)
#undef DECLARE_FIELD
} val;
WasmVal() : type(kAstStmt) {}
#define DECLARE_CONSTRUCTOR(field, localtype, ctype) \
explicit WasmVal(ctype v) : type(localtype) { val.field = v; }
FOREACH_UNION_MEMBER(DECLARE_CONSTRUCTOR)
#undef DECLARE_CONSTRUCTOR
template <typename T>
T to() {
UNREACHABLE();
}
};
#define DECLARE_CAST(field, localtype, ctype) \
template <> \
inline ctype WasmVal::to() { \
CHECK_EQ(localtype, type); \
return val.field; \
}
FOREACH_UNION_MEMBER(DECLARE_CAST)
#undef DECLARE_CAST
template <>
inline void WasmVal::to() {
CHECK_EQ(kAstStmt, type);
}
// Representation of frames within the interpreter.
class WasmFrame {
public:
const WasmFunction* function() const { return function_; }
int pc() const { return pc_; }
private:
friend class WasmInterpreter;
WasmFrame(const WasmFunction* function, int pc, int fp, int sp)
: function_(function), pc_(pc), fp_(fp), sp_(sp) {}
const WasmFunction* function_;
int pc_;
int fp_;
int sp_;
};
// An interpreter capable of executing WASM.
class WasmInterpreter {
public:
// State machine for a Thread:
// +---------------Run()-----------+
// V |
// STOPPED ---Run()--> RUNNING ------Pause()-----+-> PAUSED <------+
// | | | / | |
// | | +---- Breakpoint ---+ +-- Step() --+
// | |
// | +------------ Trap --------------> TRAPPED
// +------------- Finish -------------> FINISHED
enum State { STOPPED, RUNNING, PAUSED, FINISHED, TRAPPED };
// Representation of a thread in the interpreter.
class Thread {
public:
// Execution control.
virtual State state() = 0;
virtual void PushFrame(const WasmFunction* function, WasmVal* args) = 0;
virtual State Run() = 0;
virtual State Step() = 0;
virtual void Pause() = 0;
virtual void Reset() = 0;
virtual ~Thread() {}
// Stack inspection and modification.
virtual int GetFrameCount() = 0;
virtual const WasmFrame* GetFrame(int index) = 0;
virtual WasmFrame* GetMutableFrame(int index) = 0;
virtual WasmVal GetReturnValue() = 0;
// Thread-specific breakpoints.
bool SetBreakpoint(const WasmFunction* function, int pc, bool enabled);
bool GetBreakpoint(const WasmFunction* function, int pc);
};
WasmInterpreter(WasmModuleInstance* instance,
base::AccountingAllocator* allocator);
~WasmInterpreter();
//==========================================================================
// Execution controls.
//==========================================================================
void Run();
void Pause();
// Set a breakpoint at {pc} in {function} to be {enabled}. Returns the
// previous state of the breakpoint at {pc}.
bool SetBreakpoint(const WasmFunction* function, int pc, bool enabled);
// Gets the current state of the breakpoint at {function}.
bool GetBreakpoint(const WasmFunction* function, int pc);
// Enable or disable tracing for {function}. Return the previous state.
bool SetTracing(const WasmFunction* function, bool enabled);
//==========================================================================
// Thread iteration and inspection.
//==========================================================================
int GetThreadCount();
Thread& GetThread(int id);
//==========================================================================
// Stack frame inspection.
//==========================================================================
WasmVal GetLocalVal(const WasmFrame* frame, int index);
WasmVal GetExprVal(const WasmFrame* frame, int pc);
void SetLocalVal(WasmFrame* frame, int index, WasmVal val);
void SetExprVal(WasmFrame* frame, int pc, WasmVal val);
//==========================================================================
// Memory access.
//==========================================================================
size_t GetMemorySize();
WasmVal ReadMemory(size_t offset);
void WriteMemory(size_t offset, WasmVal val);
//==========================================================================
// Testing functionality.
//==========================================================================
// Manually adds a function to this interpreter, returning the index of the
// function.
int AddFunctionForTesting(const WasmFunction* function);
// Manually adds code to the interpreter for the given function.
bool SetFunctionCodeForTesting(const WasmFunction* function,
const byte* start, const byte* end);
// Computes the control targets for the given bytecode as {pc offset, sp
// offset}
// pairs. Used internally in the interpreter, but exposed for testing.
static ControlTransferMap ComputeControlTransfersForTesting(Zone* zone,
const byte* start,
const byte* end);
private:
Zone zone_;
WasmInterpreterInternals* internals_;
};
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_INTERPRETER_H_
......@@ -140,9 +140,9 @@ class LocalDeclEncoder {
// Prepend local declarations by creating a new buffer and copying data
// over. The new buffer must be delete[]'d by the caller.
void Prepend(const byte** start, const byte** end) const {
void Prepend(Zone* zone, const byte** start, const byte** end) const {
size_t size = (*end - *start);
byte* buffer = new byte[Size() + size];
byte* buffer = reinterpret_cast<byte*>(zone->New(Size() + size));
size_t pos = Emit(buffer);
memcpy(buffer + pos, *start, size);
pos += size;
......
......@@ -190,6 +190,7 @@
'wasm/test-run-wasm.cc',
'wasm/test-run-wasm-64.cc',
'wasm/test-run-wasm-asmjs.cc',
'wasm/test-run-wasm-interpreter.cc',
'wasm/test-run-wasm-js.cc',
'wasm/test-run-wasm-module.cc',
'wasm/test-signatures.h',
......
This diff is collapsed.
......@@ -27,7 +27,8 @@ using namespace v8::internal::wasm;
#define RET_I8(x) kExprI8Const, x, kExprReturn, 1
WASM_EXEC_TEST(Int32AsmjsDivS) {
WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32());
WasmRunner<int32_t> r(execution_mode, MachineType::Int32(),
MachineType::Int32());
BUILD(r, WASM_BINOP(kExprI32AsmjsDivS, WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(0, r.Call(0, 100));
......@@ -38,7 +39,8 @@ WASM_EXEC_TEST(Int32AsmjsDivS) {
}
WASM_EXEC_TEST(Int32AsmjsRemS) {
WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32());
WasmRunner<int32_t> r(execution_mode, MachineType::Int32(),
MachineType::Int32());
BUILD(r, WASM_BINOP(kExprI32AsmjsRemS, WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(33, r.Call(133, 100));
......@@ -49,7 +51,8 @@ WASM_EXEC_TEST(Int32AsmjsRemS) {
}
WASM_EXEC_TEST(Int32AsmjsDivU) {
WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32());
WasmRunner<int32_t> r(execution_mode, MachineType::Int32(),
MachineType::Int32());
BUILD(r, WASM_BINOP(kExprI32AsmjsDivU, WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(0, r.Call(0, 100));
......@@ -60,7 +63,8 @@ WASM_EXEC_TEST(Int32AsmjsDivU) {
}
WASM_EXEC_TEST(Int32AsmjsRemU) {
WasmRunner<int32_t> r(MachineType::Int32(), MachineType::Int32());
WasmRunner<int32_t> r(execution_mode, MachineType::Int32(),
MachineType::Int32());
BUILD(r, WASM_BINOP(kExprI32AsmjsRemU, WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
const int32_t kMin = std::numeric_limits<int32_t>::min();
CHECK_EQ(17, r.Call(217, 100));
......@@ -71,7 +75,7 @@ WASM_EXEC_TEST(Int32AsmjsRemU) {
}
WASM_EXEC_TEST(I32AsmjsSConvertF32) {
WasmRunner<int32_t> r(MachineType::Float32());
WasmRunner<int32_t> r(execution_mode, MachineType::Float32());
BUILD(r, WASM_UNOP(kExprI32AsmjsSConvertF32, WASM_GET_LOCAL(0)));
FOR_FLOAT32_INPUTS(i) {
......@@ -81,7 +85,7 @@ WASM_EXEC_TEST(I32AsmjsSConvertF32) {
}
WASM_EXEC_TEST(I32AsmjsSConvertF64) {
WasmRunner<int32_t> r(MachineType::Float64());
WasmRunner<int32_t> r(execution_mode, MachineType::Float64());
BUILD(r, WASM_UNOP(kExprI32AsmjsSConvertF64, WASM_GET_LOCAL(0)));
FOR_FLOAT64_INPUTS(i) {
......@@ -91,7 +95,7 @@ WASM_EXEC_TEST(I32AsmjsSConvertF64) {
}
WASM_EXEC_TEST(I32AsmjsUConvertF32) {
WasmRunner<uint32_t> r(MachineType::Float32());
WasmRunner<uint32_t> r(execution_mode, MachineType::Float32());
BUILD(r, WASM_UNOP(kExprI32AsmjsUConvertF32, WASM_GET_LOCAL(0)));
FOR_FLOAT32_INPUTS(i) {
......@@ -101,7 +105,7 @@ WASM_EXEC_TEST(I32AsmjsUConvertF32) {
}
WASM_EXEC_TEST(I32AsmjsUConvertF64) {
WasmRunner<uint32_t> r(MachineType::Float64());
WasmRunner<uint32_t> r(execution_mode, MachineType::Float64());
BUILD(r, WASM_UNOP(kExprI32AsmjsUConvertF64, WASM_GET_LOCAL(0)));
FOR_FLOAT64_INPUTS(i) {
......@@ -111,7 +115,7 @@ WASM_EXEC_TEST(I32AsmjsUConvertF64) {
}
WASM_EXEC_TEST(LoadMemI32_oob_asm) {
TestingModule module;
TestingModule module(execution_mode);
int32_t* memory = module.AddMemoryElems<int32_t>(8);
WasmRunner<int32_t> r(&module, MachineType::Uint32());
module.RandomizeMemory(1112);
......@@ -131,7 +135,7 @@ WASM_EXEC_TEST(LoadMemI32_oob_asm) {
}
WASM_EXEC_TEST(LoadMemF32_oob_asm) {
TestingModule module;
TestingModule module(execution_mode);
float* memory = module.AddMemoryElems<float>(8);
WasmRunner<float> r(&module, MachineType::Uint32());
module.RandomizeMemory(1112);
......@@ -151,7 +155,7 @@ WASM_EXEC_TEST(LoadMemF32_oob_asm) {
}
WASM_EXEC_TEST(LoadMemF64_oob_asm) {
TestingModule module;
TestingModule module(execution_mode);
double* memory = module.AddMemoryElems<double>(8);
WasmRunner<double> r(&module, MachineType::Uint32());
module.RandomizeMemory(1112);
......@@ -173,7 +177,7 @@ WASM_EXEC_TEST(LoadMemF64_oob_asm) {
}
WASM_EXEC_TEST(StoreMemI32_oob_asm) {
TestingModule module;
TestingModule module(execution_mode);
int32_t* memory = module.AddMemoryElems<int32_t>(8);
WasmRunner<int32_t> r(&module, MachineType::Uint32(), MachineType::Uint32());
module.RandomizeMemory(1112);
......
// 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "src/wasm/wasm-macro-gen.h"
#include "src/wasm/wasm-interpreter.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/value-helper.h"
#include "test/cctest/wasm/test-signatures.h"
#include "test/cctest/wasm/wasm-run-utils.h"
using namespace v8::base;
using namespace v8::internal;
using namespace v8::internal::compiler;
using namespace v8::internal::wasm;
namespace v8 {
namespace internal {
namespace wasm {
TEST(Run_WasmInt8Const_i) {
WasmRunner<int32_t> r(kExecuteInterpreted);
const byte kExpectedValue = 109;
// return(kExpectedValue)
BUILD(r, WASM_I8(kExpectedValue));
CHECK_EQ(kExpectedValue, r.Call());
}
TEST(Run_WasmIfElse) {
WasmRunner<int32_t> r(kExecuteInterpreted, MachineType::Int32());
BUILD(r, WASM_IF_ELSE(WASM_GET_LOCAL(0), WASM_I8(9), WASM_I8(10)));
CHECK_EQ(10, r.Call(0));
CHECK_EQ(9, r.Call(1));
}
TEST(Run_WasmIfReturn) {
WasmRunner<int32_t> r(kExecuteInterpreted, MachineType::Int32());
BUILD(r, WASM_IF(WASM_GET_LOCAL(0), WASM_RETURN1(WASM_I8(77))), WASM_I8(65));
CHECK_EQ(65, r.Call(0));
CHECK_EQ(77, r.Call(1));
}
TEST(Run_WasmNopsN) {
const int kMaxNops = 10;
byte code[kMaxNops + 2];
for (int nops = 0; nops < kMaxNops; nops++) {
byte expected = static_cast<byte>(20 + nops);
memset(code, kExprNop, sizeof(code));
code[nops] = kExprI8Const;
code[nops + 1] = expected;
WasmRunner<int32_t> r(kExecuteInterpreted);
r.Build(code, code + nops + 2);
CHECK_EQ(expected, r.Call());
}
}
TEST(Run_WasmConstsN) {
const int kMaxConsts = 10;
byte code[kMaxConsts * 2];
for (int count = 1; count < kMaxConsts; count++) {
for (int i = 0; i < count; i++) {
code[i * 2] = kExprI8Const;
code[i * 2 + 1] = static_cast<byte>(count * 10 + i);
}
byte expected = static_cast<byte>(count * 11 - 1);
WasmRunner<int32_t> r(kExecuteInterpreted);
r.Build(code, code + (count * 2));
CHECK_EQ(expected, r.Call());
}
}
TEST(Run_WasmBlocksN) {
const int kMaxNops = 10;
const int kExtra = 4;
byte code[kMaxNops + kExtra];
for (int nops = 0; nops < kMaxNops; nops++) {
byte expected = static_cast<byte>(30 + nops);
memset(code, kExprNop, sizeof(code));
code[0] = kExprBlock;
code[1 + nops] = kExprI8Const;
code[1 + nops + 1] = expected;
code[1 + nops + 2] = kExprEnd;
WasmRunner<int32_t> r(kExecuteInterpreted);
r.Build(code, code + nops + kExtra);
CHECK_EQ(expected, r.Call());
}
}
TEST(Run_WasmBlockBreakN) {
const int kMaxNops = 10;
const int kExtra = 6;
byte code[kMaxNops + kExtra];
for (int nops = 0; nops < kMaxNops; nops++) {
// Place the break anywhere within the block.
for (int index = 0; index < nops; index++) {
memset(code, kExprNop, sizeof(code));
code[0] = kExprBlock;
code[sizeof(code) - 1] = kExprEnd;
int expected = nops * 11 + index;
code[1 + index + 0] = kExprI8Const;
code[1 + index + 1] = static_cast<byte>(expected);
code[1 + index + 2] = kExprBr;
code[1 + index + 3] = ARITY_1;
code[1 + index + 4] = 0;
WasmRunner<int32_t> r(kExecuteInterpreted);
r.Build(code, code + kMaxNops + kExtra);
CHECK_EQ(expected, r.Call());
}
}
}
TEST(Run_Wasm_nested_ifs_i) {
WasmRunner<int32_t> r(kExecuteInterpreted, MachineType::Int32(),
MachineType::Int32());
BUILD(r, WASM_IF_ELSE(
WASM_GET_LOCAL(0),
WASM_IF_ELSE(WASM_GET_LOCAL(1), WASM_I8(11), WASM_I8(12)),
WASM_IF_ELSE(WASM_GET_LOCAL(1), WASM_I8(13), WASM_I8(14))));
CHECK_EQ(11, r.Call(1, 1));
CHECK_EQ(12, r.Call(1, 0));
CHECK_EQ(13, r.Call(0, 1));
CHECK_EQ(14, r.Call(0, 0));
}
TEST(Step_I32Add) {
WasmRunner<int32_t> r(kExecuteInterpreted, MachineType::Int32(),
MachineType::Int32());
BUILD(r, WASM_I32_ADD(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)));
WasmInterpreter* interpreter = r.interpreter();
interpreter->SetBreakpoint(r.function(), 0, true);
r.Call(1, 1);
interpreter->Run();
CHECK_EQ(2, interpreter->GetThread(0).GetReturnValue().to<int32_t>());
}
} // namespace wasm
} // namespace internal
} // namespace v8
This diff is collapsed.
This diff is collapsed.
......@@ -119,6 +119,7 @@
'test-utils.h',
'test-utils.cc',
'wasm/ast-decoder-unittest.cc',
'wasm/control-transfer-unittest.cc',
'wasm/decoder-unittest.cc',
'wasm/encoder-unittest.cc',
'wasm/leb-helper-unittest.cc',
......
......@@ -82,7 +82,7 @@ class AstDecoderTest : public TestWithZone {
// verification failures.
void Verify(ErrorCode expected, FunctionSig* sig, const byte* start,
const byte* end) {
local_decls.Prepend(&start, &end);
local_decls.Prepend(zone(), &start, &end);
// Verify the code.
TreeResult result =
VerifyWasmCode(zone()->allocator(), module, sig, start, end);
......@@ -105,8 +105,6 @@ class AstDecoderTest : public TestWithZone {
}
FATAL(str.str().c_str());
}
delete[] start; // local_decls.Prepend() allocated a new buffer.
}
void TestBinop(WasmOpcode opcode, FunctionSig* success) {
......@@ -2427,7 +2425,7 @@ TEST_F(LocalDeclDecoderTest, UseEncoder) {
local_decls.AddLocals(5, kAstF32);
local_decls.AddLocals(1337, kAstI32);
local_decls.AddLocals(212, kAstI64);
local_decls.Prepend(&data, &end);
local_decls.Prepend(zone(), &data, &end);
AstLocalDecls decls(zone());
bool result = DecodeLocalDecls(decls, data, end);
......@@ -2439,7 +2437,6 @@ TEST_F(LocalDeclDecoderTest, UseEncoder) {
pos = ExpectRun(map, pos, kAstF32, 5);
pos = ExpectRun(map, pos, kAstI32, 1337);
pos = ExpectRun(map, pos, kAstI64, 212);
delete[] data;
}
} // namespace wasm
......
This diff is collapsed.
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