[wasm] [interpreter] Implement calling imported functions

When instantiating the wasm interpreter for debugging, we unwrap all
wasm-to-js wrappers and store the callable objects. The handles are
stored in a DeferredHandleScope and deleted when the InterpreterHandle
(store in WasmDebugInfo) is freed.
A call to an imported function reads the arguments from the stack,
converts them to JS objects, calls the callable, converts back the
return value and pushes it onto the stack.
Reentering the interpreter from the calles JS code is not permitted
yet, but will be in a follow-up CL.
Also, indirect calls to imported functions will have to follow.

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

Change-Id: I66c35053bccb6cf8d416606e4f840d888ccb3b65
Reviewed-on: https://chromium-review.googlesource.com/453838
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: Ben Titzer <titzer@chromium.org>
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#43855}
This commit is contained in:
Clemens Hammacher 2017-03-15 16:57:02 +01:00 committed by Commit Bot
parent f424837386
commit f3aeb762ae
7 changed files with 290 additions and 46 deletions

View File

@ -936,5 +936,17 @@ RUNTIME_FUNCTION(Runtime_Verify) {
return isolate->heap()->ToBoolean(true);
}
RUNTIME_FUNCTION(Runtime_WasmNumInterpretedCalls) {
DCHECK_EQ(1, args.length());
HandleScope scope(isolate);
CONVERT_ARG_HANDLE_CHECKED(JSObject, instance_obj, 0);
CHECK(WasmInstanceObject::IsWasmInstanceObject(*instance_obj));
Handle<WasmInstanceObject> instance =
Handle<WasmInstanceObject>::cast(instance_obj);
if (!instance->has_debug_info()) return 0;
uint64_t num = instance->debug_info()->NumInterpretedCalls();
return *isolate->factory()->NewNumberFromSize(static_cast<size_t>(num));
}
} // namespace internal
} // namespace v8

View File

@ -605,7 +605,8 @@ namespace internal {
F(ValidateWasmOrphanedInstance, 1, 1) \
F(SetWasmCompileControls, 2, 1) \
F(SetWasmInstantiateControls, 0, 1) \
F(Verify, 1, 1)
F(Verify, 1, 1) \
F(WasmNumInterpretedCalls, 1, 1)
#define FOR_EACH_INTRINSIC_TYPEDARRAY(F) \
F(ArrayBufferGetByteLength, 1, 1) \

View File

@ -200,10 +200,7 @@ struct CallFunctionOperand {
FunctionSig* sig;
unsigned length;
inline CallFunctionOperand(Decoder* decoder, const byte* pc) {
unsigned len1 = 0;
unsigned len2 = 0;
index = decoder->checked_read_u32v(pc, 1 + len1, &len2, "function index");
length = len1 + len2;
index = decoder->checked_read_u32v(pc, 1, &length, "function index");
sig = nullptr;
}
};

View File

@ -21,6 +21,27 @@ using namespace v8::internal::wasm;
namespace {
// Unwrap a wasm to js wrapper, return the callable heap object.
// Only call this method for proper wrappers that do not throw a type error.
HeapObject* UnwrapJSWrapper(Code* js_wrapper) {
int mask = RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT);
for (RelocIterator it(js_wrapper, mask);; it.next()) {
DCHECK(!it.done());
HeapObject* obj = it.rinfo()->target_object();
if (!obj->IsCallable()) continue;
#ifdef DEBUG
// There should only be this reference to a callable object.
for (it.next(); !it.done(); it.next()) {
HeapObject* other = it.rinfo()->target_object();
DCHECK(!other->IsCallable());
}
#endif
return obj;
}
UNREACHABLE();
return nullptr;
}
// Forward declaration.
class InterpreterHandle;
InterpreterHandle* GetInterpreterHandle(WasmDebugInfo* debug_info);
@ -32,6 +53,7 @@ class InterpreterHandle {
Isolate* isolate_;
StepAction next_step_action_ = StepNone;
int last_step_stack_depth_ = 0;
DeferredHandles* import_handles_ = nullptr;
public:
// Initialize in the right order, using helper methods to make this possible.
@ -50,6 +72,32 @@ class InterpreterHandle {
instance_.mem_start = nullptr;
instance_.mem_size = 0;
}
if (instance_.module->num_imported_functions > 0) {
int num_imported_functions =
static_cast<int>(instance_.module->num_imported_functions);
Handle<WasmCompiledModule> compiled_module(
debug_info->wasm_instance()->compiled_module(), isolate);
Handle<FixedArray> code_table = compiled_module->code_table();
DeferredHandleScope deferred_scope(isolate_);
instance_.context =
handle(compiled_module->ptr_to_native_context(), isolate_);
for (int i = 0; i < num_imported_functions; ++i) {
Code* code = Code::cast(code_table->get(i));
Handle<HeapObject> called_obj;
if (code->kind() == Code::WASM_FUNCTION) {
called_obj = handle(code, isolate_);
} else if (IsJSCompatibleSignature(
instance_.module->functions[i].sig)) {
called_obj = handle(UnwrapJSWrapper(code), isolate_);
}
interpreter_.AddImportedFunction(called_obj);
}
import_handles_ = deferred_scope.Detach();
}
}
~InterpreterHandle() {
delete import_handles_; // might be nullptr, which is ok.
}
static ModuleBytesEnv GetBytesEnv(WasmInstance* instance,

View File

@ -34,6 +34,9 @@ namespace wasm {
#define FOREACH_INTERNAL_OPCODE(V) V(Breakpoint, 0xFF)
#define WASM_CTYPES(V) \
V(I32, uint32_t) V(I64, uint64_t) V(F32, float) V(F64, double)
#define FOREACH_SIMPLE_BINOP(V) \
V(I32Add, uint32_t, +) \
V(I32Sub, uint32_t, -) \
@ -849,9 +852,13 @@ class CodeMap {
Zone* zone_;
const WasmModule* module_;
ZoneVector<InterpreterCode> interpreter_code_;
ZoneVector<Handle<HeapObject>> imported_functions_; // callable objects
CodeMap(const WasmModule* module, const uint8_t* module_start, Zone* zone)
: zone_(zone), module_(module), interpreter_code_(zone) {
: zone_(zone),
module_(module),
interpreter_code_(zone),
imported_functions_(zone) {
if (module == nullptr) return;
interpreter_code_.reserve(module->functions.size());
for (const WasmFunction& function : module->functions) {
@ -864,6 +871,18 @@ class CodeMap {
AddFunction(&function, code_start, code_end);
}
}
imported_functions_.reserve(module_->num_imported_functions);
}
void AddImportedFunction(Handle<HeapObject> function) {
DCHECK_GT(module_->num_imported_functions, imported_functions_.size());
imported_functions_.emplace_back(function);
}
Handle<HeapObject> GetImportedFunction(uint32_t function_index) {
DCHECK_GT(module_->num_imported_functions, function_index);
DCHECK_EQ(module_->num_imported_functions, imported_functions_.size());
return imported_functions_[function_index];
}
InterpreterCode* GetCode(const WasmFunction* function) {
@ -924,6 +943,42 @@ class CodeMap {
};
namespace {
Handle<Object> WasmValToNumber(Factory* factory, WasmVal val,
wasm::ValueType type) {
switch (type) {
case kWasmI32:
return factory->NewNumberFromInt(val.to<int32_t>());
case kWasmI64:
// wasm->js and js->wasm is illegal for i64 type.
UNREACHABLE();
return Handle<Object>::null();
case kWasmF32:
return factory->NewNumber(val.to<float>());
case kWasmF64:
return factory->NewNumber(val.to<double>());
default:
// TODO(wasm): Implement simd.
UNIMPLEMENTED();
return Handle<Object>::null();
}
}
WasmVal NumberToWasmVal(Handle<Object> number, wasm::ValueType type) {
double val = number->Number();
switch (type) {
#define CASE_TYPE(wasm, ctype) \
case kWasm##wasm: \
return WasmVal(static_cast<ctype>(val));
WASM_CTYPES(CASE_TYPE)
#undef CASE_TYPE
default:
// TODO(wasm): Handle simd.
UNIMPLEMENTED();
return WasmVal();
}
}
// Responsible for executing code directly.
class ThreadImpl {
public:
@ -1076,18 +1131,12 @@ class ThreadImpl {
for (auto p : code->locals.type_list) {
WasmVal val;
switch (p) {
case kWasmI32:
val = WasmVal(static_cast<int32_t>(0));
break;
case kWasmI64:
val = WasmVal(static_cast<int64_t>(0));
break;
case kWasmF32:
val = WasmVal(static_cast<float>(0));
break;
case kWasmF64:
val = WasmVal(static_cast<double>(0));
break;
#define CASE_TYPE(wasm, ctype) \
case kWasm##wasm: \
val = WasmVal(static_cast<ctype>(0)); \
break;
WASM_CTYPES(CASE_TYPE)
#undef CASE_TYPE
default:
UNREACHABLE();
break;
@ -1246,6 +1295,7 @@ class ThreadImpl {
if (V8_UNLIKELY(break_flags_ & WasmInterpreter::BreakFlag::flag)) max = 0;
DCHECK_GT(limit, pc);
DCHECK_NOT_NULL(code->start);
const char* skip = " ";
int len = 1;
@ -1411,14 +1461,18 @@ class ThreadImpl {
}
case kExprCallFunction: {
CallFunctionOperand operand(&decoder, code->at(pc));
code = codemap()->GetCode(operand.index);
if (code->function->imported) {
// TODO(clemensh): Call imported function.
UNREACHABLE();
InterpreterCode* target = codemap()->GetCode(operand.index);
if (target->function->imported) {
CommitPc(pc);
CallImportedFunction(operand.index);
PAUSE_IF_BREAK_FLAG(AfterCall);
len = 1 + operand.length;
break; // bump pc
}
DoCall(&decoder, code, &pc, &limit);
DoCall(&decoder, target, &pc, &limit);
code = target;
PAUSE_IF_BREAK_FLAG(AfterCall);
continue;
continue; // don't bump pc
}
case kExprCallIndirect: {
CallIndirectOperand operand(&decoder, code->at(pc));
@ -1452,18 +1506,16 @@ class ThreadImpl {
GlobalIndexOperand operand(&decoder, code->at(pc));
const WasmGlobal* global = &module()->globals[operand.index];
byte* ptr = instance()->globals_start + global->offset;
ValueType type = global->type;
WasmVal val;
if (type == kWasmI32) {
val = WasmVal(*reinterpret_cast<int32_t*>(ptr));
} else if (type == kWasmI64) {
val = WasmVal(*reinterpret_cast<int64_t*>(ptr));
} else if (type == kWasmF32) {
val = WasmVal(*reinterpret_cast<float*>(ptr));
} else if (type == kWasmF64) {
val = WasmVal(*reinterpret_cast<double*>(ptr));
} else {
UNREACHABLE();
switch (global->type) {
#define CASE_TYPE(wasm, ctype) \
case kWasm##wasm: \
val = WasmVal(*reinterpret_cast<ctype*>(ptr)); \
break;
WASM_CTYPES(CASE_TYPE)
#undef CASE_TYPE
default:
UNREACHABLE();
}
Push(pc, val);
len = 1 + operand.length;
@ -1473,18 +1525,16 @@ class ThreadImpl {
GlobalIndexOperand operand(&decoder, code->at(pc));
const WasmGlobal* global = &module()->globals[operand.index];
byte* ptr = instance()->globals_start + global->offset;
ValueType type = global->type;
WasmVal val = Pop();
if (type == kWasmI32) {
*reinterpret_cast<int32_t*>(ptr) = val.to<int32_t>();
} else if (type == kWasmI64) {
*reinterpret_cast<int64_t*>(ptr) = val.to<int64_t>();
} else if (type == kWasmF32) {
*reinterpret_cast<float*>(ptr) = val.to<float>();
} else if (type == kWasmF64) {
*reinterpret_cast<double*>(ptr) = val.to<double>();
} else {
UNREACHABLE();
switch (global->type) {
#define CASE_TYPE(wasm, ctype) \
case kWasm##wasm: \
*reinterpret_cast<ctype*>(ptr) = val.to<ctype>(); \
break;
WASM_CTYPES(CASE_TYPE)
#undef CASE_TYPE
default:
UNREACHABLE();
}
len = 1 + operand.length;
break;
@ -1761,6 +1811,53 @@ class ThreadImpl {
}
#endif // DEBUG
}
void CallImportedFunction(uint32_t function_index) {
Handle<HeapObject> target = codemap()->GetImportedFunction(function_index);
if (target.is_null()) {
// The function does not have a js-compatible signature.
// TODO(clemensh): Throw type error.
UNIMPLEMENTED();
}
if (target->IsCode()) {
DCHECK_EQ(Code::WASM_FUNCTION, Code::cast(*target)->kind());
// TODO(clemensh): Call wasm code directly.
UNIMPLEMENTED();
}
std::ostringstream oss;
TRACE(" => CallImportedFunction #%u: %s\n", function_index,
(target->HeapObjectShortPrint(oss), oss.str().c_str()));
const WasmFunction* called_fun =
&codemap()->module_->functions[function_index];
int num_args = static_cast<int>(called_fun->sig->parameter_count());
DCHECK(!instance()->context.is_null());
Isolate* isolate = instance()->context->GetIsolate();
// Get all arguments as JS values.
std::vector<Handle<Object>> args;
args.reserve(num_args);
WasmVal* wasm_args = stack_.data() + (stack_.size() - num_args);
for (int i = 0; i < num_args; ++i) {
args.push_back(WasmValToNumber(isolate->factory(), wasm_args[i],
called_fun->sig->GetParam(i)));
}
MaybeHandle<Object> maybe_retval = Execution::Call(
isolate, target, isolate->global_proxy(), num_args, args.data());
if (maybe_retval.is_null()) {
// TODO(clemensh): Exception handling here.
UNIMPLEMENTED();
}
Handle<Object> retval = maybe_retval.ToHandleChecked();
// TODO(clemensh): Call ToNumber on retval.
// Pop arguments of the stack.
stack_.resize(stack_.size() - num_args);
if (called_fun->sig->return_count() > 0) {
// TODO(wasm): Handle multiple returns.
DCHECK_EQ(1, called_fun->sig->return_count());
stack_.push_back(NumberToWasmVal(retval, called_fun->sig->GetReturn()));
}
}
};
// Converters between WasmInterpreter::Thread and WasmInterpreter::ThreadImpl.
@ -1903,6 +2000,10 @@ bool WasmInterpreter::SetTracing(const WasmFunction* function, bool enabled) {
return false;
}
void WasmInterpreter::AddImportedFunction(Handle<HeapObject> code) {
internals_->codemap_.AddImportedFunction(code);
}
int WasmInterpreter::GetThreadCount() {
return 1; // only one thread for now.
}

View File

@ -185,6 +185,11 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
// Enable or disable tracing for {function}. Return the previous state.
bool SetTracing(const WasmFunction* function, bool enabled);
// Add an imported function.
// We store the passed Handle internally, so the caller must ensure that it
// stays valid at least as long as the WasmInterpreter.
void AddImportedFunction(Handle<HeapObject>);
//==========================================================================
// Thread iteration and inspection.
//==========================================================================

View File

@ -0,0 +1,80 @@
// 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.
// Flags: --wasm-interpret-all --allow-natives-syntax
load('test/mjsunit/wasm/wasm-constants.js');
load('test/mjsunit/wasm/wasm-module-builder.js');
// The stack trace contains file path, only keep "interpreter.js".
let stripPath = s => s.replace(/[^ (]*interpreter\.js/g, 'interpreter.js');
function checkStack(stack, expected_lines) {
print('stack: ' + stack);
var lines = stack.split('\n');
assertEquals(expected_lines.length, lines.length);
for (var i = 0; i < lines.length; ++i) {
let test =
typeof expected_lines[i] == 'string' ? assertEquals : assertMatches;
test(expected_lines[i], lines[i], 'line ' + i);
}
}
(function testCallImported() {
var stack;
let func = () => stack = new Error('test imported stack').stack;
var builder = new WasmModuleBuilder();
builder.addImport('mod', 'func', kSig_v_v);
builder.addFunction('main', kSig_v_v)
.addBody([kExprCallFunction, 0])
.exportFunc();
var instance = builder.instantiate({mod: {func: func}});
// Test that this does not mess up internal state by executing it three times.
for (var i = 0; i < 3; ++i) {
var interpreted_before = % WasmNumInterpretedCalls(instance);
instance.exports.main();
assertEquals(interpreted_before + 1, % WasmNumInterpretedCalls(instance));
checkStack(stripPath(stack), [
'Error: test imported stack', // -
/^ at func \(interpreter.js:\d+:28\)$/, // -
' at main (<WASM>[1]+1)', // -
/^ at testCallImported \(interpreter.js:\d+:22\)$/, // -
/^ at interpreter.js:\d+:3$/
]);
}
})();
(function testCallImportedWithParameters() {
var stack;
var passed_args = [];
let func1 = (i, j) => (passed_args.push(i, j), 2 * i + j);
let func2 = (f) => (passed_args.push(f), 8 * f);
var builder = new WasmModuleBuilder();
builder.addImport('mod', 'func1', makeSig([kWasmI32, kWasmI32], [kWasmF32]));
builder.addImport('mod', 'func2', makeSig([kWasmF64], [kWasmI32]));
builder.addFunction('main', makeSig([kWasmI32, kWasmF64], [kWasmF32]))
.addBody([
// call #0 with arg 0 and arg 0 + 1
kExprGetLocal, 0, kExprGetLocal, 0, kExprI32Const, 1, kExprI32Add,
kExprCallFunction, 0,
// call #1 with arg 1
kExprGetLocal, 1, kExprCallFunction, 1,
// convert returned value to f32
kExprF32UConvertI32,
// add the two values
kExprF32Add
])
.exportFunc();
var instance = builder.instantiate({mod: {func1: func1, func2: func2}});
var interpreted_before = % WasmNumInterpretedCalls(instance);
var args = [11, 0.3];
var ret = instance.exports.main(...args);
assertEquals(interpreted_before + 1, % WasmNumInterpretedCalls(instance));
var passed_test_args = [...passed_args];
var expected = func1(args[0], args[0] + 1) + func2(args[1]) | 0;
assertEquals(expected, ret);
assertArrayEquals([args[0], args[0] + 1, args[1]], passed_test_args);
})();