[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:
parent
f424837386
commit
f3aeb762ae
@ -936,5 +936,17 @@ RUNTIME_FUNCTION(Runtime_Verify) {
|
|||||||
return isolate->heap()->ToBoolean(true);
|
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 internal
|
||||||
} // namespace v8
|
} // namespace v8
|
||||||
|
@ -605,7 +605,8 @@ namespace internal {
|
|||||||
F(ValidateWasmOrphanedInstance, 1, 1) \
|
F(ValidateWasmOrphanedInstance, 1, 1) \
|
||||||
F(SetWasmCompileControls, 2, 1) \
|
F(SetWasmCompileControls, 2, 1) \
|
||||||
F(SetWasmInstantiateControls, 0, 1) \
|
F(SetWasmInstantiateControls, 0, 1) \
|
||||||
F(Verify, 1, 1)
|
F(Verify, 1, 1) \
|
||||||
|
F(WasmNumInterpretedCalls, 1, 1)
|
||||||
|
|
||||||
#define FOR_EACH_INTRINSIC_TYPEDARRAY(F) \
|
#define FOR_EACH_INTRINSIC_TYPEDARRAY(F) \
|
||||||
F(ArrayBufferGetByteLength, 1, 1) \
|
F(ArrayBufferGetByteLength, 1, 1) \
|
||||||
|
@ -200,10 +200,7 @@ struct CallFunctionOperand {
|
|||||||
FunctionSig* sig;
|
FunctionSig* sig;
|
||||||
unsigned length;
|
unsigned length;
|
||||||
inline CallFunctionOperand(Decoder* decoder, const byte* pc) {
|
inline CallFunctionOperand(Decoder* decoder, const byte* pc) {
|
||||||
unsigned len1 = 0;
|
index = decoder->checked_read_u32v(pc, 1, &length, "function index");
|
||||||
unsigned len2 = 0;
|
|
||||||
index = decoder->checked_read_u32v(pc, 1 + len1, &len2, "function index");
|
|
||||||
length = len1 + len2;
|
|
||||||
sig = nullptr;
|
sig = nullptr;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -21,6 +21,27 @@ using namespace v8::internal::wasm;
|
|||||||
|
|
||||||
namespace {
|
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.
|
// Forward declaration.
|
||||||
class InterpreterHandle;
|
class InterpreterHandle;
|
||||||
InterpreterHandle* GetInterpreterHandle(WasmDebugInfo* debug_info);
|
InterpreterHandle* GetInterpreterHandle(WasmDebugInfo* debug_info);
|
||||||
@ -32,6 +53,7 @@ class InterpreterHandle {
|
|||||||
Isolate* isolate_;
|
Isolate* isolate_;
|
||||||
StepAction next_step_action_ = StepNone;
|
StepAction next_step_action_ = StepNone;
|
||||||
int last_step_stack_depth_ = 0;
|
int last_step_stack_depth_ = 0;
|
||||||
|
DeferredHandles* import_handles_ = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Initialize in the right order, using helper methods to make this possible.
|
// Initialize in the right order, using helper methods to make this possible.
|
||||||
@ -50,6 +72,32 @@ class InterpreterHandle {
|
|||||||
instance_.mem_start = nullptr;
|
instance_.mem_start = nullptr;
|
||||||
instance_.mem_size = 0;
|
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,
|
static ModuleBytesEnv GetBytesEnv(WasmInstance* instance,
|
||||||
|
@ -34,6 +34,9 @@ namespace wasm {
|
|||||||
|
|
||||||
#define FOREACH_INTERNAL_OPCODE(V) V(Breakpoint, 0xFF)
|
#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) \
|
#define FOREACH_SIMPLE_BINOP(V) \
|
||||||
V(I32Add, uint32_t, +) \
|
V(I32Add, uint32_t, +) \
|
||||||
V(I32Sub, uint32_t, -) \
|
V(I32Sub, uint32_t, -) \
|
||||||
@ -849,9 +852,13 @@ class CodeMap {
|
|||||||
Zone* zone_;
|
Zone* zone_;
|
||||||
const WasmModule* module_;
|
const WasmModule* module_;
|
||||||
ZoneVector<InterpreterCode> interpreter_code_;
|
ZoneVector<InterpreterCode> interpreter_code_;
|
||||||
|
ZoneVector<Handle<HeapObject>> imported_functions_; // callable objects
|
||||||
|
|
||||||
CodeMap(const WasmModule* module, const uint8_t* module_start, Zone* zone)
|
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;
|
if (module == nullptr) return;
|
||||||
interpreter_code_.reserve(module->functions.size());
|
interpreter_code_.reserve(module->functions.size());
|
||||||
for (const WasmFunction& function : module->functions) {
|
for (const WasmFunction& function : module->functions) {
|
||||||
@ -864,6 +871,18 @@ class CodeMap {
|
|||||||
AddFunction(&function, code_start, code_end);
|
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) {
|
InterpreterCode* GetCode(const WasmFunction* function) {
|
||||||
@ -924,6 +943,42 @@ class CodeMap {
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
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.
|
// Responsible for executing code directly.
|
||||||
class ThreadImpl {
|
class ThreadImpl {
|
||||||
public:
|
public:
|
||||||
@ -1076,18 +1131,12 @@ class ThreadImpl {
|
|||||||
for (auto p : code->locals.type_list) {
|
for (auto p : code->locals.type_list) {
|
||||||
WasmVal val;
|
WasmVal val;
|
||||||
switch (p) {
|
switch (p) {
|
||||||
case kWasmI32:
|
#define CASE_TYPE(wasm, ctype) \
|
||||||
val = WasmVal(static_cast<int32_t>(0));
|
case kWasm##wasm: \
|
||||||
break;
|
val = WasmVal(static_cast<ctype>(0)); \
|
||||||
case kWasmI64:
|
break;
|
||||||
val = WasmVal(static_cast<int64_t>(0));
|
WASM_CTYPES(CASE_TYPE)
|
||||||
break;
|
#undef CASE_TYPE
|
||||||
case kWasmF32:
|
|
||||||
val = WasmVal(static_cast<float>(0));
|
|
||||||
break;
|
|
||||||
case kWasmF64:
|
|
||||||
val = WasmVal(static_cast<double>(0));
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
break;
|
break;
|
||||||
@ -1246,6 +1295,7 @@ class ThreadImpl {
|
|||||||
if (V8_UNLIKELY(break_flags_ & WasmInterpreter::BreakFlag::flag)) max = 0;
|
if (V8_UNLIKELY(break_flags_ & WasmInterpreter::BreakFlag::flag)) max = 0;
|
||||||
|
|
||||||
DCHECK_GT(limit, pc);
|
DCHECK_GT(limit, pc);
|
||||||
|
DCHECK_NOT_NULL(code->start);
|
||||||
|
|
||||||
const char* skip = " ";
|
const char* skip = " ";
|
||||||
int len = 1;
|
int len = 1;
|
||||||
@ -1411,14 +1461,18 @@ class ThreadImpl {
|
|||||||
}
|
}
|
||||||
case kExprCallFunction: {
|
case kExprCallFunction: {
|
||||||
CallFunctionOperand operand(&decoder, code->at(pc));
|
CallFunctionOperand operand(&decoder, code->at(pc));
|
||||||
code = codemap()->GetCode(operand.index);
|
InterpreterCode* target = codemap()->GetCode(operand.index);
|
||||||
if (code->function->imported) {
|
if (target->function->imported) {
|
||||||
// TODO(clemensh): Call imported function.
|
CommitPc(pc);
|
||||||
UNREACHABLE();
|
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);
|
PAUSE_IF_BREAK_FLAG(AfterCall);
|
||||||
continue;
|
continue; // don't bump pc
|
||||||
}
|
}
|
||||||
case kExprCallIndirect: {
|
case kExprCallIndirect: {
|
||||||
CallIndirectOperand operand(&decoder, code->at(pc));
|
CallIndirectOperand operand(&decoder, code->at(pc));
|
||||||
@ -1452,18 +1506,16 @@ class ThreadImpl {
|
|||||||
GlobalIndexOperand operand(&decoder, code->at(pc));
|
GlobalIndexOperand operand(&decoder, code->at(pc));
|
||||||
const WasmGlobal* global = &module()->globals[operand.index];
|
const WasmGlobal* global = &module()->globals[operand.index];
|
||||||
byte* ptr = instance()->globals_start + global->offset;
|
byte* ptr = instance()->globals_start + global->offset;
|
||||||
ValueType type = global->type;
|
|
||||||
WasmVal val;
|
WasmVal val;
|
||||||
if (type == kWasmI32) {
|
switch (global->type) {
|
||||||
val = WasmVal(*reinterpret_cast<int32_t*>(ptr));
|
#define CASE_TYPE(wasm, ctype) \
|
||||||
} else if (type == kWasmI64) {
|
case kWasm##wasm: \
|
||||||
val = WasmVal(*reinterpret_cast<int64_t*>(ptr));
|
val = WasmVal(*reinterpret_cast<ctype*>(ptr)); \
|
||||||
} else if (type == kWasmF32) {
|
break;
|
||||||
val = WasmVal(*reinterpret_cast<float*>(ptr));
|
WASM_CTYPES(CASE_TYPE)
|
||||||
} else if (type == kWasmF64) {
|
#undef CASE_TYPE
|
||||||
val = WasmVal(*reinterpret_cast<double*>(ptr));
|
default:
|
||||||
} else {
|
UNREACHABLE();
|
||||||
UNREACHABLE();
|
|
||||||
}
|
}
|
||||||
Push(pc, val);
|
Push(pc, val);
|
||||||
len = 1 + operand.length;
|
len = 1 + operand.length;
|
||||||
@ -1473,18 +1525,16 @@ class ThreadImpl {
|
|||||||
GlobalIndexOperand operand(&decoder, code->at(pc));
|
GlobalIndexOperand operand(&decoder, code->at(pc));
|
||||||
const WasmGlobal* global = &module()->globals[operand.index];
|
const WasmGlobal* global = &module()->globals[operand.index];
|
||||||
byte* ptr = instance()->globals_start + global->offset;
|
byte* ptr = instance()->globals_start + global->offset;
|
||||||
ValueType type = global->type;
|
|
||||||
WasmVal val = Pop();
|
WasmVal val = Pop();
|
||||||
if (type == kWasmI32) {
|
switch (global->type) {
|
||||||
*reinterpret_cast<int32_t*>(ptr) = val.to<int32_t>();
|
#define CASE_TYPE(wasm, ctype) \
|
||||||
} else if (type == kWasmI64) {
|
case kWasm##wasm: \
|
||||||
*reinterpret_cast<int64_t*>(ptr) = val.to<int64_t>();
|
*reinterpret_cast<ctype*>(ptr) = val.to<ctype>(); \
|
||||||
} else if (type == kWasmF32) {
|
break;
|
||||||
*reinterpret_cast<float*>(ptr) = val.to<float>();
|
WASM_CTYPES(CASE_TYPE)
|
||||||
} else if (type == kWasmF64) {
|
#undef CASE_TYPE
|
||||||
*reinterpret_cast<double*>(ptr) = val.to<double>();
|
default:
|
||||||
} else {
|
UNREACHABLE();
|
||||||
UNREACHABLE();
|
|
||||||
}
|
}
|
||||||
len = 1 + operand.length;
|
len = 1 + operand.length;
|
||||||
break;
|
break;
|
||||||
@ -1761,6 +1811,53 @@ class ThreadImpl {
|
|||||||
}
|
}
|
||||||
#endif // DEBUG
|
#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.
|
// Converters between WasmInterpreter::Thread and WasmInterpreter::ThreadImpl.
|
||||||
@ -1903,6 +2000,10 @@ bool WasmInterpreter::SetTracing(const WasmFunction* function, bool enabled) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WasmInterpreter::AddImportedFunction(Handle<HeapObject> code) {
|
||||||
|
internals_->codemap_.AddImportedFunction(code);
|
||||||
|
}
|
||||||
|
|
||||||
int WasmInterpreter::GetThreadCount() {
|
int WasmInterpreter::GetThreadCount() {
|
||||||
return 1; // only one thread for now.
|
return 1; // only one thread for now.
|
||||||
}
|
}
|
||||||
|
@ -185,6 +185,11 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
|
|||||||
// Enable or disable tracing for {function}. Return the previous state.
|
// Enable or disable tracing for {function}. Return the previous state.
|
||||||
bool SetTracing(const WasmFunction* function, bool enabled);
|
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.
|
// Thread iteration and inspection.
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
80
test/mjsunit/wasm/interpreter.js
Normal file
80
test/mjsunit/wasm/interpreter.js
Normal 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);
|
||||||
|
})();
|
Loading…
Reference in New Issue
Block a user