Commit e6819ee2 authored by clemensh's avatar clemensh Committed by Commit bot

[wasm] Test argument passing in the interpreter entry

Test the wasm interpreter entry stub by creating two wasm functions A
and B, make A pass arguments to B, then redirect B to be executed in the
interpreter.
Test different number and types or arguments.

BUG=v8:5822
R=titzer@chromium.org

Review-Url: https://codereview.chromium.org/2651793003
Cr-Commit-Position: refs/heads/master@{#43353}
parent cc805e42
......@@ -273,6 +273,11 @@ class InterpreterHandle {
return std::unique_ptr<wasm::InterpretedFrame>(
new wasm::InterpretedFrame(thread->GetMutableFrame(idx)));
}
uint64_t NumInterpretedCalls() {
DCHECK_EQ(1, interpreter()->GetThreadCount());
return interpreter()->GetThread(0)->NumInterpretedCalls();
}
};
InterpreterHandle* GetOrCreateInterpreterHandle(
......@@ -294,6 +299,12 @@ InterpreterHandle* GetInterpreterHandle(WasmDebugInfo* debug_info) {
return Managed<InterpreterHandle>::cast(handle_obj)->get();
}
InterpreterHandle* GetInterpreterHandleOrNull(WasmDebugInfo* debug_info) {
Object* handle_obj = debug_info->get(WasmDebugInfo::kInterpreterHandle);
if (handle_obj->IsUndefined(debug_info->GetIsolate())) return nullptr;
return Managed<InterpreterHandle>::cast(handle_obj)->get();
}
int GetNumFunctions(WasmInstanceObject* instance) {
size_t num_functions =
instance->compiled_module()->module()->functions.size();
......@@ -345,26 +356,6 @@ void RedirectCallsitesInInstance(Isolate* isolate, WasmInstanceObject* instance,
}
}
void EnsureRedirectToInterpreter(Isolate* isolate,
Handle<WasmDebugInfo> debug_info,
int func_index) {
Handle<FixedArray> interpreted_functions =
GetOrCreateInterpretedFunctions(isolate, debug_info);
if (!interpreted_functions->get(func_index)->IsUndefined(isolate)) return;
Handle<WasmInstanceObject> instance(debug_info->wasm_instance(), isolate);
Handle<Code> new_code = compiler::CompileWasmInterpreterEntry(
isolate, func_index,
instance->compiled_module()->module()->functions[func_index].sig,
instance);
Handle<FixedArray> code_table = instance->compiled_module()->code_table();
Handle<Code> old_code(Code::cast(code_table->get(func_index)), isolate);
interpreted_functions->set(func_index, *new_code);
RedirectCallsitesInInstance(isolate, *instance, *old_code, *new_code);
}
} // namespace
Handle<WasmDebugInfo> WasmDebugInfo::New(Handle<WasmInstanceObject> instance) {
......@@ -400,12 +391,34 @@ void WasmDebugInfo::SetBreakpoint(Handle<WasmDebugInfo> debug_info,
int func_index, int offset) {
Isolate* isolate = debug_info->GetIsolate();
InterpreterHandle* handle = GetOrCreateInterpreterHandle(isolate, debug_info);
WasmInterpreter* interpreter = handle->interpreter();
DCHECK_LE(0, func_index);
DCHECK_GT(handle->module()->functions.size(), func_index);
RedirectToInterpreter(debug_info, func_index);
const WasmFunction* func = &handle->module()->functions[func_index];
interpreter->SetBreakpoint(func, offset, true);
EnsureRedirectToInterpreter(isolate, debug_info, func_index);
handle->interpreter()->SetBreakpoint(func, offset, true);
}
void WasmDebugInfo::RedirectToInterpreter(Handle<WasmDebugInfo> debug_info,
int func_index) {
Isolate* isolate = debug_info->GetIsolate();
DCHECK_LE(0, func_index);
DCHECK_GT(debug_info->wasm_instance()->module()->functions.size(),
func_index);
Handle<FixedArray> interpreted_functions =
GetOrCreateInterpretedFunctions(isolate, debug_info);
if (!interpreted_functions->get(func_index)->IsUndefined(isolate)) return;
// Ensure that the interpreter is instantiated.
GetOrCreateInterpreterHandle(isolate, debug_info);
Handle<WasmInstanceObject> instance(debug_info->wasm_instance(), isolate);
Handle<Code> new_code = compiler::CompileWasmInterpreterEntry(
isolate, func_index,
instance->compiled_module()->module()->functions[func_index].sig,
instance);
Handle<FixedArray> code_table = instance->compiled_module()->code_table();
Handle<Code> old_code(Code::cast(code_table->get(func_index)), isolate);
interpreted_functions->set(func_index, *new_code);
RedirectCallsitesInInstance(isolate, *instance, *old_code, *new_code);
}
void WasmDebugInfo::PrepareStep(StepAction step_action) {
......@@ -427,3 +440,8 @@ std::unique_ptr<wasm::InterpretedFrame> WasmDebugInfo::GetInterpretedFrame(
Address frame_pointer, int idx) {
return GetInterpreterHandle(this)->GetInterpretedFrame(frame_pointer, idx);
}
uint64_t WasmDebugInfo::NumInterpretedCalls() {
auto handle = GetInterpreterHandleOrNull(this);
return handle ? handle->NumInterpretedCalls() : 0;
}
......@@ -935,6 +935,7 @@ class ThreadImpl {
void PushFrame(const WasmFunction* function, WasmVal* args) {
InterpreterCode* code = codemap()->FindCode(function);
CHECK_NOT_NULL(code);
++num_interpreted_calls_;
frames_.push_back({code, 0, 0, stack_.size()});
for (size_t i = 0; i < function->sig->parameter_count(); ++i) {
stack_.push_back(args[i]);
......@@ -1009,6 +1010,8 @@ class ThreadImpl {
bool PossibleNondeterminism() { return possible_nondeterminism_; }
uint64_t NumInterpretedCalls() { return num_interpreted_calls_; }
void AddBreakFlags(uint8_t flags) { break_flags_ |= flags; }
void ClearBreakFlags() { break_flags_ = WasmInterpreter::BreakFlag::None; }
......@@ -1044,6 +1047,7 @@ class ThreadImpl {
TrapReason trap_reason_ = kTrapCount;
bool possible_nondeterminism_ = false;
uint8_t break_flags_ = 0; // a combination of WasmInterpreter::BreakFlag
uint64_t num_interpreted_calls_ = 0;
CodeMap* codemap() { return codemap_; }
WasmInstance* instance() { return instance_; }
......@@ -1059,6 +1063,7 @@ class ThreadImpl {
void PushFrame(InterpreterCode* code, pc_t call_pc, pc_t ret_pc) {
CHECK_NOT_NULL(code);
DCHECK(!frames_.empty());
++num_interpreted_calls_;
frames_.back().call_pc = call_pc;
frames_.back().ret_pc = ret_pc;
size_t arity = code->function->sig->parameter_count();
......@@ -1797,6 +1802,9 @@ WasmVal WasmInterpreter::Thread::GetReturnValue(int index) {
bool WasmInterpreter::Thread::PossibleNondeterminism() {
return ToImpl(this)->PossibleNondeterminism();
}
uint64_t WasmInterpreter::Thread::NumInterpretedCalls() {
return ToImpl(this)->NumInterpretedCalls();
}
void WasmInterpreter::Thread::AddBreakFlags(uint8_t flags) {
ToImpl(this)->AddBreakFlags(flags);
}
......
......@@ -148,11 +148,15 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
const InterpretedFrame GetFrame(int index);
InterpretedFrame GetMutableFrame(int index);
WasmVal GetReturnValue(int index = 0);
// Returns true if the thread executed an instruction which may produce
// nondeterministic results, e.g. float div, float sqrt, and float mul,
// where the sign bit of a NaN is nondeterministic.
bool PossibleNondeterminism();
// Returns the number of calls / function frames executed on this thread.
uint64_t NumInterpretedCalls();
// Thread-specific breakpoints.
// TODO(wasm): Implement this once we support multiple threads.
// bool SetBreakpoint(const WasmFunction* function, int pc, bool enabled);
......
......@@ -372,15 +372,16 @@ class LocalDeclEncoder {
static_cast<byte>(bit_cast<uint32_t>(static_cast<float>(val)) >> 8), \
static_cast<byte>(bit_cast<uint32_t>(static_cast<float>(val)) >> 16), \
static_cast<byte>(bit_cast<uint32_t>(static_cast<float>(val)) >> 24)
#define WASM_F64(val) \
kExprF64Const, static_cast<byte>(bit_cast<uint64_t>(val)), \
static_cast<byte>(bit_cast<uint64_t>(val) >> 8), \
static_cast<byte>(bit_cast<uint64_t>(val) >> 16), \
static_cast<byte>(bit_cast<uint64_t>(val) >> 24), \
static_cast<byte>(bit_cast<uint64_t>(val) >> 32), \
static_cast<byte>(bit_cast<uint64_t>(val) >> 40), \
static_cast<byte>(bit_cast<uint64_t>(val) >> 48), \
static_cast<byte>(bit_cast<uint64_t>(val) >> 56)
#define WASM_F64(val) \
kExprF64Const, \
static_cast<byte>(bit_cast<uint64_t>(static_cast<double>(val))), \
static_cast<byte>(bit_cast<uint64_t>(static_cast<double>(val)) >> 8), \
static_cast<byte>(bit_cast<uint64_t>(static_cast<double>(val)) >> 16), \
static_cast<byte>(bit_cast<uint64_t>(static_cast<double>(val)) >> 24), \
static_cast<byte>(bit_cast<uint64_t>(static_cast<double>(val)) >> 32), \
static_cast<byte>(bit_cast<uint64_t>(static_cast<double>(val)) >> 40), \
static_cast<byte>(bit_cast<uint64_t>(static_cast<double>(val)) >> 48), \
static_cast<byte>(bit_cast<uint64_t>(static_cast<double>(val)) >> 56)
#define WASM_GET_LOCAL(index) kExprGetLocal, static_cast<byte>(index)
#define WASM_SET_LOCAL(index, val) val, kExprSetLocal, static_cast<byte>(index)
#define WASM_TEE_LOCAL(index, val) val, kExprTeeLocal, static_cast<byte>(index)
......
......@@ -420,8 +420,15 @@ class WasmDebugInfo : public FixedArray {
static bool IsDebugInfo(Object*);
static WasmDebugInfo* cast(Object*);
// Set a breakpoint in the given function at the given byte offset within that
// function. This will redirect all future calls to this function to the
// interpreter and will always pause at the given offset.
static void SetBreakpoint(Handle<WasmDebugInfo>, int func_index, int offset);
// Make a function always execute in the interpreter without setting a
// breakpoints.
static void RedirectToInterpreter(Handle<WasmDebugInfo>, int func_index);
void PrepareStep(StepAction);
void RunInterpreter(int func_index, uint8_t* arg_buffer);
......@@ -434,6 +441,9 @@ class WasmDebugInfo : public FixedArray {
std::unique_ptr<wasm::InterpretedFrame> GetInterpretedFrame(
Address frame_pointer, int idx);
// Returns the number of calls / function frames executed in the interpreter.
uint64_t NumInterpretedCalls();
DECLARE_GETTER(wasm_instance, WasmInstanceObject);
};
......
......@@ -195,6 +195,7 @@ v8_executable("cctest") {
"wasm/test-run-wasm-relocation.cc",
"wasm/test-run-wasm.cc",
"wasm/test-wasm-breakpoints.cc",
"wasm/test-wasm-interpreter-entry.cc",
"wasm/test-wasm-stack.cc",
"wasm/test-wasm-trap-position.cc",
"wasm/wasm-run-utils.h",
......
......@@ -216,6 +216,7 @@
'wasm/test-run-wasm-module.cc',
'wasm/test-run-wasm-relocation.cc',
'wasm/test-wasm-breakpoints.cc',
'wasm/test-wasm-interpreter-entry.cc',
'wasm/test-wasm-stack.cc',
'wasm/test-wasm-trap-position.cc',
'wasm/wasm-run-utils.h',
......
// 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.
#include <cstdint>
#include "src/wasm/wasm-macro-gen.h"
#include "src/wasm/wasm-objects.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/value-helper.h"
#include "test/cctest/wasm/wasm-run-utils.h"
using namespace v8::internal;
using namespace v8::internal::wasm;
namespace debug = v8::debug;
/**
* We test the interface from Wasm compiled code to the Wasm interpreter by
* building a module with two functions. The external function is called from
* this test, and will be compiled code. It takes its arguments and passes them
* on to the internal function, which will be redirected to the interpreter.
* If the internal function has an i64 parameter, is has to be replaced by two
* i32 parameters on the external function.
* The internal function just converts all its arguments to f64, sums them up
* and returns the sum.
*/
namespace {
template <typename T>
class ArgPassingHelper {
public:
ArgPassingHelper(WasmRunnerBase& runner, WasmFunctionCompiler& inner_compiler,
std::initializer_list<uint8_t> bytes_inner_function,
std::initializer_list<uint8_t> bytes_outer_function,
const T& expected_lambda)
: isolate_(runner.main_isolate()),
expected_lambda_(expected_lambda),
debug_info_(WasmInstanceObject::GetOrCreateDebugInfo(
runner.module().instance_object())) {
std::vector<uint8_t> inner_code{bytes_inner_function};
inner_compiler.Build(inner_code.data(),
inner_code.data() + inner_code.size());
std::vector<uint8_t> outer_code{bytes_outer_function};
runner.Build(outer_code.data(), outer_code.data() + outer_code.size());
WasmDebugInfo::RedirectToInterpreter(debug_info_,
inner_compiler.function_index());
main_fun_wrapper_ = runner.module().WrapCode(runner.function_index());
}
template <typename... Args>
void CheckCall(Args... args) {
Handle<Object> arg_objs[] = {isolate_->factory()->NewNumber(args)...};
uint64_t num_interpreted_before = debug_info_->NumInterpretedCalls();
Handle<Object> global(isolate_->context()->global_object(), isolate_);
MaybeHandle<Object> retval = Execution::Call(
isolate_, main_fun_wrapper_, global, arraysize(arg_objs), arg_objs);
uint64_t num_interpreted_after = debug_info_->NumInterpretedCalls();
// Check that we really went through the interpreter.
CHECK_EQ(num_interpreted_before + 1, num_interpreted_after);
// Check the result.
double result = retval.ToHandleChecked()->Number();
double expected = expected_lambda_(args...);
CHECK_DOUBLE_EQ(expected, result);
}
private:
Isolate* isolate_;
T expected_lambda_;
Handle<WasmDebugInfo> debug_info_;
Handle<JSFunction> main_fun_wrapper_;
};
template <typename T>
static ArgPassingHelper<T> GetHelper(
WasmRunnerBase& runner, WasmFunctionCompiler& inner_compiler,
std::initializer_list<uint8_t> bytes_inner_function,
std::initializer_list<uint8_t> bytes_outer_function,
const T& expected_lambda) {
return ArgPassingHelper<T>(runner, inner_compiler, bytes_inner_function,
bytes_outer_function, expected_lambda);
}
} // namespace
TEST(TestArgumentPassing_int32) {
WasmRunner<int32_t, int32_t> runner(kExecuteCompiled);
WasmFunctionCompiler& f2 = runner.NewFunction<int32_t, int32_t>();
auto helper = GetHelper(
runner, f2,
{// Return 2*<0> + 1.
WASM_I32_ADD(WASM_I32_MUL(WASM_I32V_1(2), WASM_GET_LOCAL(0)), WASM_ONE)},
{// Call f2 with param <0>.
WASM_GET_LOCAL(0), WASM_CALL_FUNCTION0(f2.function_index())},
[](int32_t a) { return 2 * a + 1; });
std::vector<int32_t> test_values = compiler::ValueHelper::int32_vector();
for (int32_t v : test_values) helper.CheckCall(v);
}
TEST(TestArgumentPassing_int64) {
WasmRunner<double, int32_t, int32_t> runner(kExecuteCompiled);
WasmFunctionCompiler& f2 = runner.NewFunction<double, int64_t>();
auto helper = GetHelper(
runner, f2,
{// Return (double)<0>.
WASM_F64_SCONVERT_I64(WASM_GET_LOCAL(0))},
{// Call f2 with param (<0> | (<1> << 32)).
WASM_I64_IOR(WASM_I64_UCONVERT_I32(WASM_GET_LOCAL(0)),
WASM_I64_SHL(WASM_I64_UCONVERT_I32(WASM_GET_LOCAL(1)),
WASM_I64V_1(32))),
WASM_CALL_FUNCTION0(f2.function_index())},
[](int32_t a, int32_t b) {
int64_t a64 = static_cast<int64_t>(a) & 0xffffffff;
int64_t b64 = static_cast<int64_t>(b) << 32;
return static_cast<double>(a64 | b64);
});
std::vector<int32_t> test_values_i32 = compiler::ValueHelper::int32_vector();
for (int32_t v1 : test_values_i32) {
for (int32_t v2 : test_values_i32) {
helper.CheckCall(v1, v2);
}
}
std::vector<int64_t> test_values_i64 = compiler::ValueHelper::int64_vector();
for (int64_t v : test_values_i64) {
int32_t v1 = static_cast<int32_t>(v);
int32_t v2 = static_cast<int32_t>(v >> 32);
helper.CheckCall(v1, v2);
helper.CheckCall(v2, v1);
}
}
TEST(TestArgumentPassing_float_double) {
WasmRunner<double, float> runner(kExecuteCompiled);
WasmFunctionCompiler& f2 = runner.NewFunction<double, float>();
auto helper = GetHelper(
runner, f2,
{// Return 2*(double)<0> + 1.
WASM_F64_ADD(
WASM_F64_MUL(WASM_F64(2), WASM_F64_CONVERT_F32(WASM_GET_LOCAL(0))),
WASM_F64(1))},
{// Call f2 with param <0>.
WASM_GET_LOCAL(0), WASM_CALL_FUNCTION0(f2.function_index())},
[](float f) { return 2. * static_cast<double>(f) + 1.; });
std::vector<float> test_values = compiler::ValueHelper::float32_vector();
for (float f : test_values) helper.CheckCall(f);
}
TEST(TestArgumentPassing_double_double) {
WasmRunner<double, double, double> runner(kExecuteCompiled);
WasmFunctionCompiler& f2 = runner.NewFunction<double, double, double>();
auto helper = GetHelper(runner, f2,
{// Return <0> + <1>.
WASM_F64_ADD(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))},
{// Call f2 with params <0>, <1>.
WASM_GET_LOCAL(0), WASM_GET_LOCAL(1),
WASM_CALL_FUNCTION0(f2.function_index())},
[](double a, double b) { return a + b; });
std::vector<double> test_values = compiler::ValueHelper::float64_vector();
for (double d1 : test_values) {
for (double d2 : test_values) {
helper.CheckCall(d1, d2);
}
}
}
TEST(TestArgumentPassing_AllTypes) {
// The second and third argument will be combined to an i64.
WasmRunner<double, int, int, int, float, double> runner(kExecuteCompiled);
WasmFunctionCompiler& f2 =
runner.NewFunction<double, int, int64_t, float, double>();
auto helper = GetHelper(
runner, f2,
{
// Convert all arguments to double, add them and return the sum.
WASM_F64_ADD( // <0+1+2> + <3>
WASM_F64_ADD( // <0+1> + <2>
WASM_F64_ADD( // <0> + <1>
WASM_F64_SCONVERT_I32(
WASM_GET_LOCAL(0)), // <0> to double
WASM_F64_SCONVERT_I64(
WASM_GET_LOCAL(1))), // <1> to double
WASM_F64_CONVERT_F32(WASM_GET_LOCAL(2))), // <2> to double
WASM_GET_LOCAL(3)) // <3>
},
{WASM_GET_LOCAL(0), // first arg
WASM_I64_IOR(WASM_I64_UCONVERT_I32(WASM_GET_LOCAL(1)), // second arg
WASM_I64_SHL(WASM_I64_UCONVERT_I32(WASM_GET_LOCAL(2)),
WASM_I64V_1(32))),
WASM_GET_LOCAL(3), // third arg
WASM_GET_LOCAL(4), // fourth arg
WASM_CALL_FUNCTION0(f2.function_index())},
[](int32_t a, int32_t b, int32_t c, float d, double e) {
return 0. + a + (static_cast<int64_t>(b) & 0xffffffff) +
((static_cast<int64_t>(c) & 0xffffffff) << 32) + d + e;
});
auto CheckCall = [&](int32_t a, int64_t b, float c, double d) {
int32_t b0 = static_cast<int32_t>(b);
int32_t b1 = static_cast<int32_t>(b >> 32);
helper.CheckCall(a, b0, b1, c, d);
helper.CheckCall(a, b1, b0, c, d);
};
std::vector<int32_t> test_values_i32 = compiler::ValueHelper::int32_vector();
std::vector<int64_t> test_values_i64 = compiler::ValueHelper::int64_vector();
std::vector<float> test_values_f32 = compiler::ValueHelper::float32_vector();
std::vector<double> test_values_f64 = compiler::ValueHelper::float64_vector();
size_t max_len =
std::max(std::max(test_values_i32.size(), test_values_i64.size()),
std::max(test_values_f32.size(), test_values_f64.size()));
for (size_t i = 0; i < max_len; ++i) {
int32_t i32 = test_values_i32[i % test_values_i32.size()];
int64_t i64 = test_values_i64[i % test_values_i64.size()];
float f32 = test_values_f32[i % test_values_f32.size()];
double f64 = test_values_f64[i % test_values_f64.size()];
CheckCall(i32, i64, f32, f64);
}
}
......@@ -564,8 +564,16 @@ class WasmFunctionCompiler : private GraphAndBuilders {
Handle<WasmCompiledModule> compiled_module(
testing_module_->instance_object()->compiled_module(), isolate());
Handle<FixedArray> code_table = compiled_module->code_table();
code_table = FixedArray::SetAndGrow(code_table, function_index(), code);
compiled_module->set_code_table(code_table);
if (static_cast<int>(function_index()) >= code_table->length()) {
Handle<FixedArray> new_arr = isolate()->factory()->NewFixedArray(
static_cast<int>(function_index()) + 1);
code_table->CopyTo(0, *new_arr, 0, code_table->length());
code_table = new_arr;
compiled_module->set_code_table(code_table);
}
DCHECK(code_table->get(static_cast<int>(function_index()))
->IsUndefined(isolate()));
code_table->set(static_cast<int>(function_index()), *code);
}
byte AllocateLocal(ValueType type) {
......
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