[wasm] Instantiate the interpreter on demand

If a breakpoint is set on a wasm function, compile an interpreter entry
stub for it, and replace all calls to the original function by calls to
this interpreter entry.
Also, instantiate a wasm interpreter object on demand and set the
breakpoint there.

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

Review-Url: https://codereview.chromium.org/2625093004
Cr-Commit-Position: refs/heads/master@{#42309}
This commit is contained in:
clemensh 2017-01-13 01:04:00 -08:00 committed by Commit bot
parent 865b5e5795
commit eb04a25f1f
4 changed files with 279 additions and 25 deletions

View File

@ -2949,7 +2949,9 @@ void WasmGraphBuilder::BuildWasmInterpreterEntry(
}
// The return value is also passed via this buffer:
DCHECK_GE(1, sig->return_count());
DCHECK_GE(wasm::kV8MaxWasmFunctionReturns, sig->return_count());
// TODO(wasm): Handle multi-value returns.
DCHECK_EQ(1, wasm::kV8MaxWasmFunctionReturns);
int return_size_bytes =
sig->return_count() == 0 ? 0 : 1 << ElementSizeLog2Of(sig->GetReturn(0));

View File

@ -2,30 +2,246 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/assembler-inl.h"
#include "src/assert-scope.h"
#include "src/compiler/wasm-compiler.h"
#include "src/debug/debug.h"
#include "src/factory.h"
#include "src/frames-inl.h"
#include "src/isolate.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-interpreter.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects.h"
#include "src/zone/accounting-allocator.h"
using namespace v8::internal;
using namespace v8::internal::wasm;
namespace {
class InterpreterHandle {
AccountingAllocator allocator_;
WasmInstance instance_;
WasmInterpreter interpreter_;
public:
// Initialize in the right order, using helper methods to make this possible.
// WasmInterpreter has to be allocated in place, since it is not movable.
InterpreterHandle(Isolate *isolate, WasmDebugInfo *debug_info)
: instance_(debug_info->wasm_instance()->compiled_module()->module()),
interpreter_(GetBytesEnv(&instance_, debug_info), &allocator_) {
Handle<JSArrayBuffer> mem_buffer =
handle(debug_info->wasm_instance()->memory_buffer(), isolate);
if (mem_buffer->IsUndefined(isolate)) {
DCHECK_EQ(0, instance_.module->min_mem_pages);
instance_.mem_start = nullptr;
instance_.mem_size = 0;
} else {
instance_.mem_start =
reinterpret_cast<byte *>(mem_buffer->backing_store());
CHECK(mem_buffer->byte_length()->ToUint32(&instance_.mem_size));
}
}
static ModuleBytesEnv GetBytesEnv(WasmInstance *instance,
WasmDebugInfo *debug_info) {
// Return raw pointer into heap. The WasmInterpreter will make its own copy
// of this data anyway, and there is no heap allocation in-between.
SeqOneByteString *bytes_str =
debug_info->wasm_instance()->compiled_module()->module_bytes();
Vector<const byte> bytes(bytes_str->GetChars(), bytes_str->length());
return ModuleBytesEnv(instance->module, instance, bytes);
}
WasmInterpreter *interpreter() { return &interpreter_; }
const WasmModule *module() { return instance_.module; }
void Execute(uint32_t func_index, uint8_t *arg_buffer) {
DCHECK_GE(module()->functions.size(), func_index);
FunctionSig *sig = module()->functions[func_index].sig;
DCHECK_GE(kMaxInt, sig->parameter_count());
int num_params = static_cast<int>(sig->parameter_count());
ScopedVector<WasmVal> wasm_args(num_params);
uint8_t *arg_buf_ptr = arg_buffer;
for (int i = 0; i < num_params; ++i) {
uint32_t param_size = 1 << ElementSizeLog2Of(sig->GetParam(i));
#define CASE_ARG_TYPE(type, ctype) \
case type: \
DCHECK_EQ(param_size, sizeof(ctype)); \
wasm_args[i] = WasmVal(*reinterpret_cast<ctype *>(arg_buf_ptr)); \
break;
switch (sig->GetParam(i)) {
CASE_ARG_TYPE(kWasmI32, uint32_t)
CASE_ARG_TYPE(kWasmI64, uint64_t)
CASE_ARG_TYPE(kWasmF32, float)
CASE_ARG_TYPE(kWasmF64, double)
#undef CASE_ARG_TYPE
default:
UNREACHABLE();
}
arg_buf_ptr += param_size;
}
WasmInterpreter::Thread *thread = interpreter_.GetThread(0);
// We do not support reentering an already running interpreter at the moment
// (like INTERPRETER -> JS -> WASM -> INTERPRETER).
DCHECK(thread->state() == WasmInterpreter::STOPPED ||
thread->state() == WasmInterpreter::FINISHED);
thread->Reset();
thread->PushFrame(&module()->functions[func_index], wasm_args.start());
WasmInterpreter::State state;
do {
state = thread->Run();
switch (state) {
case WasmInterpreter::State::PAUSED: {
// We hit a breakpoint.
// TODO(clemensh): Handle this.
} break;
case WasmInterpreter::State::FINISHED:
// Perfect, just break the switch and exit the loop.
break;
case WasmInterpreter::State::TRAPPED:
// TODO(clemensh): Generate appropriate JS exception.
UNIMPLEMENTED();
break;
// STOPPED and RUNNING should never occur here.
case WasmInterpreter::State::STOPPED:
case WasmInterpreter::State::RUNNING:
default:
UNREACHABLE();
}
} while (state != WasmInterpreter::State::FINISHED);
// Copy back the return value
DCHECK_GE(kV8MaxWasmFunctionReturns, sig->return_count());
// TODO(wasm): Handle multi-value returns.
DCHECK_EQ(1, kV8MaxWasmFunctionReturns);
if (sig->return_count()) {
WasmVal ret_val = thread->GetReturnValue(0);
#define CASE_RET_TYPE(type, ctype) \
case type: \
DCHECK_EQ(1 << ElementSizeLog2Of(sig->GetReturn(0)), sizeof(ctype)); \
*reinterpret_cast<ctype *>(arg_buffer) = ret_val.to<ctype>(); \
break;
switch (sig->GetReturn(0)) {
CASE_RET_TYPE(kWasmI32, uint32_t)
CASE_RET_TYPE(kWasmI64, uint64_t)
CASE_RET_TYPE(kWasmF32, float)
CASE_RET_TYPE(kWasmF64, double)
#undef CASE_RET_TYPE
default:
UNREACHABLE();
}
}
}
};
InterpreterHandle *GetOrCreateInterpreterHandle(
Isolate *isolate, Handle<WasmDebugInfo> debug_info) {
Handle<Object> handle(debug_info->get(WasmDebugInfo::kInterpreterHandle),
isolate);
if (handle->IsUndefined(isolate)) {
InterpreterHandle *cpp_handle = new InterpreterHandle(isolate, *debug_info);
handle = Managed<InterpreterHandle>::New(isolate, cpp_handle);
debug_info->set(WasmDebugInfo::kInterpreterHandle, *handle);
}
return Handle<Managed<InterpreterHandle>>::cast(handle)->get();
}
int GetNumFunctions(WasmInstanceObject *instance) {
size_t num_functions =
instance->compiled_module()->module()->functions.size();
DCHECK_GE(kMaxInt, num_functions);
return static_cast<int>(num_functions);
}
Handle<FixedArray> GetOrCreateInterpretedFunctions(
Isolate *isolate, Handle<WasmDebugInfo> debug_info) {
Handle<Object> obj(debug_info->get(WasmDebugInfo::kInterpretedFunctions),
isolate);
if (!obj->IsUndefined(isolate)) return Handle<FixedArray>::cast(obj);
Handle<FixedArray> new_arr = isolate->factory()->NewFixedArray(
GetNumFunctions(debug_info->wasm_instance()));
debug_info->set(WasmDebugInfo::kInterpretedFunctions, *new_arr);
return new_arr;
}
void RedirectCallsitesInCode(Code *code, Code *old_target, Code *new_target) {
DisallowHeapAllocation no_gc;
for (RelocIterator it(code, RelocInfo::kCodeTargetMask); !it.done();
it.next()) {
DCHECK(RelocInfo::IsCodeTarget(it.rinfo()->rmode()));
Code *target = Code::GetCodeFromTargetAddress(it.rinfo()->target_address());
if (target != old_target) continue;
it.rinfo()->set_target_address(new_target->instruction_start());
}
}
void RedirectCallsitesInInstance(Isolate *isolate, WasmInstanceObject *instance,
Code *old_target, Code *new_target) {
DisallowHeapAllocation no_gc;
// Redirect all calls in wasm functions.
FixedArray *code_table = instance->compiled_module()->ptr_to_code_table();
for (int i = 0, e = GetNumFunctions(instance); i < e; ++i) {
RedirectCallsitesInCode(Code::cast(code_table->get(i)), old_target,
new_target);
}
// Redirect all calls in exported functions.
FixedArray *weak_exported_functions =
instance->compiled_module()->ptr_to_weak_exported_functions();
for (int i = 0, e = weak_exported_functions->length(); i != e; ++i) {
WeakCell *weak_function = WeakCell::cast(weak_exported_functions->get(i));
if (weak_function->cleared()) continue;
Code *code = JSFunction::cast(weak_function->value())->code();
RedirectCallsitesInCode(code, old_target, new_target);
}
}
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) {
Isolate *isolate = instance->GetIsolate();
Factory *factory = isolate->factory();
Handle<FixedArray> arr = factory->NewFixedArray(kFieldCount, TENURED);
arr->set(kInstance, *instance);
return Handle<WasmDebugInfo>::cast(arr);
}
bool WasmDebugInfo::IsDebugInfo(Object *object) {
if (!object->IsFixedArray()) return false;
FixedArray *arr = FixedArray::cast(object);
return arr->length() == kFieldCount && IsWasmInstance(arr->get(kInstance));
if (arr->length() != kFieldCount) return false;
if (!IsWasmInstance(arr->get(kInstance))) return false;
Isolate *isolate = arr->GetIsolate();
if (!arr->get(kInterpreterHandle)->IsUndefined(isolate) &&
!arr->get(kInterpreterHandle)->IsForeign())
return false;
return true;
}
WasmDebugInfo *WasmDebugInfo::cast(Object *object) {
@ -37,8 +253,22 @@ WasmInstanceObject *WasmDebugInfo::wasm_instance() {
return WasmInstanceObject::cast(get(kInstance));
}
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);
const WasmFunction *func = &handle->module()->functions[func_index];
interpreter->SetBreakpoint(func, offset, true);
EnsureRedirectToInterpreter(isolate, debug_info, func_index);
}
void WasmDebugInfo::RunInterpreter(Handle<WasmDebugInfo> debug_info,
int func_index, uint8_t *arg_buffer) {
// TODO(clemensh): Implement this.
UNIMPLEMENTED();
DCHECK_LE(0, func_index);
InterpreterHandle *interp_handle =
GetOrCreateInterpreterHandle(debug_info->GetIsolate(), debug_info);
interp_handle->Execute(static_cast<uint32_t>(func_index), arg_buffer);
}

View File

@ -1319,7 +1319,7 @@ class WasmInstanceBuilder {
//--------------------------------------------------------------------------
// Set up the exports object for the new instance.
//--------------------------------------------------------------------------
ProcessExports(code_table, instance);
ProcessExports(code_table, instance, compiled_module_);
//--------------------------------------------------------------------------
// Add instance to Memory object
@ -1781,24 +1781,23 @@ class WasmInstanceBuilder {
return mem_buffer;
}
bool NeedsWrappers() {
if (module_->num_exported_functions > 0) return true;
for (auto table_instance : table_instances_) {
if (!table_instance.js_wrappers.is_null()) return true;
}
for (auto table : module_->function_tables) {
if (table.exported) return true;
}
return false;
}
// Process the exports, creating wrappers for functions, tables, memories,
// and globals.
void ProcessExports(Handle<FixedArray> code_table,
Handle<WasmInstanceObject> instance) {
bool needs_wrappers = module_->num_exported_functions > 0;
for (auto table_instance : table_instances_) {
if (!table_instance.js_wrappers.is_null()) {
needs_wrappers = true;
break;
}
}
for (auto table : module_->function_tables) {
if (table.exported) {
needs_wrappers = true;
break;
}
}
if (needs_wrappers) {
Handle<WasmInstanceObject> instance,
Handle<WasmCompiledModule> compiled_module) {
if (NeedsWrappers()) {
// Fill the table to cache the exported JSFunction wrappers.
js_wrappers_.insert(js_wrappers_.begin(), module_->functions.size(),
Handle<JSFunction>::null());
@ -1826,6 +1825,18 @@ class WasmInstanceBuilder {
++export_index;
}
}
// Store weak references to all exported functions.
Handle<FixedArray> weak_exported_functions;
if (compiled_module->has_weak_exported_functions()) {
weak_exported_functions = compiled_module->weak_exported_functions();
} else {
weak_exported_functions =
isolate_->factory()->NewFixedArray(export_index);
compiled_module->set_weak_exported_functions(weak_exported_functions);
}
DCHECK_EQ(export_index, weak_exported_functions->length());
// Process each export in the export table (go in reverse so asm.js
// can skip duplicates).
for (auto exp : base::Reversed(module_->export_table)) {
@ -1858,6 +1869,10 @@ class WasmInstanceBuilder {
js_wrappers_[exp.index] = js_function;
}
desc.set_value(js_function);
Handle<WeakCell> weak_export =
isolate_->factory()->NewWeakCell(js_function);
DCHECK_GT(weak_exported_functions->length(), export_index);
weak_exported_functions->set(export_index, *weak_export);
break;
}
case kExternalTable: {

View File

@ -239,6 +239,7 @@ class WasmCompiledModule : public FixedArray {
MACRO(WASM_OBJECT, WasmSharedModuleData, shared) \
MACRO(OBJECT, Context, native_context) \
MACRO(OBJECT, FixedArray, code_table) \
MACRO(OBJECT, FixedArray, weak_exported_functions) \
MACRO(OBJECT, FixedArray, function_tables) \
MACRO(OBJECT, FixedArray, signature_tables) \
MACRO(OBJECT, FixedArray, empty_function_tables) \
@ -280,6 +281,7 @@ class WasmCompiledModule : public FixedArray {
ret->reset_weak_owning_instance();
ret->reset_weak_next_instance();
ret->reset_weak_prev_instance();
ret->reset_weak_exported_functions();
return ret;
}
@ -373,17 +375,22 @@ class WasmCompiledModule : public FixedArray {
DISALLOW_IMPLICIT_CONSTRUCTORS(WasmCompiledModule);
};
// TODO(clemensh): Extend this object for breakpoint support, or remove it.
// TODO(clemensh): Exclude this object from serialization.
class WasmDebugInfo : public FixedArray {
enum Fields { kInstance, kFieldCount };
public:
enum Fields {
kInstance,
kInterpreterHandle,
kInterpretedFunctions,
kFieldCount
};
static Handle<WasmDebugInfo> New(Handle<WasmInstanceObject>);
static bool IsDebugInfo(Object*);
static WasmDebugInfo* cast(Object*);
static void SetBreakpoint(Handle<WasmDebugInfo>, int func_index, int offset);
static void RunInterpreter(Handle<WasmDebugInfo>, int func_index,
uint8_t* arg_buffer);