Index: src/wasm/wasm-debug.cc |
diff --git a/src/wasm/wasm-debug.cc b/src/wasm/wasm-debug.cc |
index f1b01593cf437415f2d4143af5e03734149a2851..1ee45d5ccfa29de433ddedef17ec3b39a0e889a5 100644 |
--- a/src/wasm/wasm-debug.cc |
+++ b/src/wasm/wasm-debug.cc |
@@ -5,10 +5,15 @@ |
#include "src/wasm/wasm-debug.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" |
+// We need src/assembler-$arch-inl.h, and macro-assembler.h includes this. |
+#include "src/macro-assembler.h" |
#include "src/wasm/module-decoder.h" |
+#include "src/wasm/wasm-interpreter.h" |
#include "src/wasm/wasm-module.h" |
using namespace v8::internal; |
@@ -21,6 +26,9 @@ enum { |
kWasmDebugInfoWasmBytesHash, |
kWasmDebugInfoFunctionByteOffsets, |
kWasmDebugInfoFunctionScripts, |
+ kWasmDebugInfoInterpreterAddr, |
+ kWasmDebugInfoInterpretedFunctions, |
+ kWasmDebugInfoDebugInfos, |
kWasmDebugInfoNumEntries |
}; |
@@ -68,17 +76,312 @@ std::pair<int, int> GetFunctionOffsetAndLength(Handle<WasmDebugInfo> debug_info, |
Vector<const uint8_t> GetFunctionBytes(Handle<WasmDebugInfo> debug_info, |
int func_index) { |
- SeqOneByteString *module_bytes = |
- wasm::GetWasmBytes(debug_info->wasm_object()); |
std::pair<int, int> offset_and_length = |
GetFunctionOffsetAndLength(debug_info, func_index); |
+ SeqOneByteString *module_bytes = |
+ wasm::GetWasmBytes(debug_info->wasm_object()); |
return Vector<const uint8_t>( |
module_bytes->GetChars() + offset_and_length.first, |
offset_and_length.second); |
} |
+FixedArray *GetOrCreateInterpretedFunctions(Handle<WasmDebugInfo> debug_info) { |
+ Object *maybe_arr = debug_info->get(kWasmDebugInfoInterpretedFunctions); |
+ Isolate *isolate = debug_info->GetIsolate(); |
+ if (!maybe_arr->IsUndefined(isolate)) return FixedArray::cast(maybe_arr); |
+ |
+ FixedArray *new_arr = *isolate->factory()->NewFixedArray( |
+ wasm::GetNumberOfFunctions(debug_info->wasm_object())); |
+ debug_info->set(kWasmDebugInfoInterpretedFunctions, new_arr); |
+ return new_arr; |
+} |
+ |
+void RedirectFunction(Code *code, Code *old_target, Code *new_target) { |
+ 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(), |
+ SKIP_WRITE_BARRIER, SKIP_ICACHE_FLUSH); |
+ } |
+} |
+ |
+void RedirectFunctionEverywhere(Handle<JSObject> wasm, Handle<Code> old_target, |
+ Handle<Code> new_target) { |
+ // Redirect in all wasm functions. |
+ for (int i = 0, e = wasm::GetNumberOfFunctions(*wasm); i < e; ++i) |
+ RedirectFunction(wasm::GetWasmFunctionCode(*wasm, i), *old_target, |
+ *new_target); |
+ |
+ // Redirect in the code of all exported functions. |
+ Isolate *isolate = wasm->GetIsolate(); |
+ // For AsmJs modules, the exports are stored on the wasm object itself. |
+ MaybeHandle<Object> maybe_exports = |
+ JSReceiver::GetProperty(isolate, wasm, "exports"); |
+ Handle<JSObject> exports_object = |
+ maybe_exports.is_null() |
+ ? wasm |
+ : Handle<JSObject>::cast(maybe_exports.ToHandleChecked()); |
+ |
+ Handle<FixedArray> values; |
+ if (JSReceiver::GetOwnValues(Handle<JSReceiver>::cast(exports_object), |
+ ALL_PROPERTIES) |
+ .ToHandle(&values)) { |
+ for (int i = 0, e = values->length(); i != e; ++i) { |
+ if (!values->get(i)->IsJSFunction()) continue; |
+ Code *code = JSFunction::cast(values->get(i))->code(); |
+ RedirectFunction(code, *old_target, *new_target); |
+ } |
+ } |
+} |
+ |
+class InterpreterHandle { |
+ v8::base::AccountingAllocator allocator; |
+ Zone zone; |
+ Handle<Object> global_ref; |
+ std::unique_ptr<const WasmModule> module; |
+ std::unique_ptr<WasmModuleInstance> module_instance; |
+ Isolate *isolate; |
+ |
+ uint8_t *parameter_buffer; |
+ uint32_t parameter_buffer_size = 0; |
+#ifdef DEBUG |
+ // Track the function index for which the last buffer was allocated. |
+ // This is updated on AllocateParameterBuffer, and checked at Execute. |
+ uint32_t parameter_buffer_taken = static_cast<uint32_t>(-1); |
+#endif |
+ |
+ public: |
+ WasmInterpreter interpreter; |
+ |
+ // Initialize in the right order. WasmInterpreter has to be allocate in place, |
+ // since it's not std::move'able. |
+ explicit InterpreterHandle(WasmDebugInfo *debug_info) |
+ : allocator(), |
+ zone(&allocator), |
+ module(CreateModule(&zone, debug_info)), |
+ isolate(debug_info->GetIsolate()), |
+ interpreter(CreateModuleInstance(module.get(), debug_info), &zone) { |
+ global_ref = debug_info->GetIsolate()->global_handles()->Create(debug_info); |
+ GlobalHandles::MakeWeak(global_ref.location(), this, |
+ InterpreterHandle::Finalize, |
+ v8::WeakCallbackType::kParameter); |
+ } |
+ |
+ static void Finalize(const v8::WeakCallbackInfo<void> &data) { |
+ void *param = data.GetParameter(); |
+ InterpreterHandle *handle = reinterpret_cast<InterpreterHandle *>(param); |
+ GlobalHandles::ClearWeakness(handle->global_ref.location()); |
+ GlobalHandles::Destroy(handle->global_ref.location()); |
+ delete handle; |
+ } |
+ |
+ static const WasmModule *CreateModule(Zone *zone, WasmDebugInfo *debug_info) { |
+ SeqOneByteString *wasm_bytes = |
+ wasm::GetWasmBytes(debug_info->wasm_object()); |
+ const byte *bytes_start = wasm_bytes->GetChars(); |
+ const byte *bytes_end = bytes_start + wasm_bytes->length(); |
+ ModuleResult module_result = |
+ DecodeWasmModule(debug_info->GetIsolate(), zone, bytes_start, bytes_end, |
+ false, ModuleOrigin::kWasmOrigin); |
+ DCHECK(module_result.ok()); |
+ return module_result.val; |
+ } |
+ |
+ static WasmModuleInstance *CreateModuleInstance(const WasmModule *module, |
+ WasmDebugInfo *debug_info) { |
+ JSArrayBuffer *memory = |
+ wasm::GetWasmMemoryBuffer(debug_info->wasm_object()); |
+ |
+ WasmModuleInstance *instance = new WasmModuleInstance(module); |
+ instance->mem_start = reinterpret_cast<byte *>(memory->backing_store()); |
+ instance->mem_size = memory->byte_length()->Number(); |
+ instance->mem_buffer = handle(memory, debug_info->GetIsolate()); |
+ |
+ return instance; |
+ } |
+ |
+ uint8_t *GetParameterBuffer(uint32_t function_index) { |
+ DCHECK_LE(function_index, module->functions.size()); |
+ DCHECK_EQ(static_cast<uint32_t>(-1), parameter_buffer_taken); |
+#ifdef DEBUG |
+ parameter_buffer_taken = function_index; |
+#endif |
+ uint32_t size_needed = 0; |
+ FunctionSig *sig = module->functions[function_index].sig; |
+ for (size_t i = 0, e = sig->parameter_count(); i != e; ++i) { |
+ size_needed += 1 << ElementSizeLog2Of(sig->GetParam(i)); |
+ } |
+ if (size_needed > parameter_buffer_size) { |
+ uint32_t alloc_size = 2 * parameter_buffer_size; |
+ if (size_needed > alloc_size) alloc_size = size_needed; |
+ parameter_buffer = zone.NewArray<uint8_t>(alloc_size); |
+ parameter_buffer_size = alloc_size; |
+ } |
+ return parameter_buffer; |
+ } |
+ |
+ void Execute(uint32_t func_index) { |
+ DCHECK_EQ(parameter_buffer_taken, func_index); |
+#ifdef DEBUG |
+ parameter_buffer_taken = static_cast<uint32_t>(-1); |
+#endif |
+ FunctionSig *sig = module->functions[func_index].sig; |
+ DCHECK_LE(sig->parameter_count(), static_cast<size_t>(kMaxInt)); |
+ int num_params = static_cast<int>(sig->parameter_count()); |
+ ScopedVector<WasmVal> wasm_args(num_params); |
+ uint8_t *buf_ptr = parameter_buffer; |
+ for (int i = 0; i < num_params; ++i) { |
+ uint32_t param_size = 1 << ElementSizeLog2Of(sig->GetParam(i)); |
+ switch (sig->GetParam(i)) { |
+ case kAstI32: |
+ DCHECK_EQ(param_size, sizeof(uint32_t)); |
+ wasm_args[i] = WasmVal(*reinterpret_cast<uint32_t *>(buf_ptr)); |
+ break; |
+ case kAstI64: |
+ DCHECK_EQ(param_size, sizeof(uint64_t)); |
+ wasm_args[i] = WasmVal(*reinterpret_cast<uint64_t *>(buf_ptr)); |
+ break; |
+ case kAstF32: |
+ DCHECK_EQ(param_size, sizeof(float)); |
+ wasm_args[i] = WasmVal(*reinterpret_cast<float *>(buf_ptr)); |
+ break; |
+ case kAstF64: |
+ DCHECK_EQ(param_size, sizeof(double)); |
+ wasm_args[i] = WasmVal(*reinterpret_cast<double *>(buf_ptr)); |
+ break; |
+ default: |
+ UNREACHABLE(); |
+ } |
+ 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. |
+ StackTraceFrameIterator frame_it(isolate); |
+ WasmInterpretedFrame *top_frame = |
+ WasmInterpretedFrame::cast(frame_it.frame()); |
+ isolate->debug()->Break(top_frame); |
+ } 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); |
+ } |
+ |
+ void EnsureInterpreterRedirect(Handle<WasmDebugInfo> debug_info, |
+ int func_index) { |
+ Isolate *isolate = debug_info->GetIsolate(); |
+ Handle<FixedArray> interpreted_functions( |
+ GetOrCreateInterpretedFunctions(debug_info), isolate); |
+ DCHECK_LT(func_index, interpreted_functions->length()); |
+ if (!interpreted_functions->get(func_index)->IsUndefined(isolate)) return; |
+ |
+ Handle<JSObject> wasm(debug_info->wasm_object(), isolate); |
+ Handle<Code> new_code = compiler::CompileWasmToInterpreter( |
+ isolate, func_index, module->functions[func_index].sig, wasm); |
+ Handle<Code> old_code(wasm::GetWasmFunctionCode(*wasm, func_index), |
+ isolate); |
+ interpreted_functions->set(func_index, *new_code); |
+ |
+ RedirectFunctionEverywhere(wasm, old_code, new_code); |
+ } |
+}; |
+ |
+InterpreterHandle *GetOrCreateInterpreterHandle(WasmDebugInfo *debug_info) { |
+ DisallowHeapAllocation no_gc; |
+ // We store the raw pointer to the InterpreterHandle in a slot in the debug |
+ // info. |
+ // Since the pointer is aligned, it will look like a Smi, and the gc will |
+ // ignore it. |
+ Object *handle_obj = debug_info->get(kWasmDebugInfoInterpreterAddr); |
+ if (!handle_obj->IsUndefined(debug_info->GetIsolate())) { |
+ DCHECK(!handle_obj->IsHeapObject()); |
+ return reinterpret_cast<InterpreterHandle *>(handle_obj); |
+ } |
+ |
+ InterpreterHandle *handle = new InterpreterHandle(debug_info); |
+ handle_obj = reinterpret_cast<Object *>(handle); |
+ DCHECK(!handle_obj->IsHeapObject()); |
+ debug_info->set(kWasmDebugInfoInterpreterAddr, handle_obj); |
+ return handle; |
+} |
+ |
+InterpreterHandle *GetInterpreterHandleIfExists(WasmDebugInfo *debug_info) { |
+ DisallowHeapAllocation no_gc; |
+ Object *handle_obj = debug_info->get(kWasmDebugInfoInterpreterAddr); |
+ return handle_obj->IsUndefined(debug_info->GetIsolate()) |
+ ? nullptr |
+ : reinterpret_cast<InterpreterHandle *>(handle_obj); |
+} |
+ |
} // namespace |
+WasmInstructionIterator::WasmInstructionIterator(const byte *start, |
+ const byte *end) |
+ : start_(start), end_(end) { |
+ base::AccountingAllocator allocator; |
+ Zone tmp_zone(&allocator); |
+ AstLocalDecls locals(&tmp_zone); |
+ bool valid = DecodeLocalDecls(locals, start, end); |
+ DCHECK(valid); |
+ USE(valid); |
+ pc_ = start + locals.decls_encoded_size; |
+} |
+ |
+void WasmInstructionIterator::Next() { |
+ DCHECK(!Done()); |
+ int len = OpcodeLength(pc_, end_); |
+ DCHECK_LE(0, len); // 0 means validation error / read past the end. |
+ pc_ += len; |
+} |
+ |
+Script *InterpreterFrameInfo::GetScript() const { |
+ return WasmDebugInfo::GetFunctionScript(debug_info_, func_index_); |
+} |
+ |
+InterpreterFrameIterator::InterpreterFrameIterator( |
+ Handle<WasmDebugInfo> debug_info) |
+ : frame_nr_(0), |
+ frame_count_(GetOrCreateInterpreterHandle(*debug_info) |
+ ->interpreter.GetThread(0) |
+ ->GetFrameCount()), |
+ debug_info_(debug_info) {} |
+ |
+InterpreterFrameInfo InterpreterFrameIterator::GetFrameInfo() const { |
+ DCHECK(!Done()); |
+ WasmInterpreter::Thread *thread = |
+ GetOrCreateInterpreterHandle(*debug_info_)->interpreter.GetThread(0); |
+ DCHECK_EQ(frame_count_, thread->GetFrameCount()); |
+ DCHECK_LE(0, frame_nr_); |
+ std::unique_ptr<const WasmInterpreterFrame> frame( |
+ thread->GetFrame(frame_nr_)); |
+ return InterpreterFrameInfo(debug_info_, frame->function()->func_index, |
+ frame->pc()); |
+} |
+ |
Handle<WasmDebugInfo> WasmDebugInfo::New(Handle<JSObject> wasm) { |
Isolate *isolate = wasm->GetIsolate(); |
Factory *factory = isolate->factory(); |
@@ -98,7 +401,7 @@ Handle<WasmDebugInfo> WasmDebugInfo::New(Handle<JSObject> wasm) { |
return Handle<WasmDebugInfo>::cast(arr); |
} |
-bool WasmDebugInfo::IsDebugInfo(Object *object) { |
+bool WasmDebugInfo::IsWasmDebugInfo(Object *object) { |
if (!object->IsFixedArray()) return false; |
FixedArray *arr = FixedArray::cast(object); |
Isolate *isolate = arr->GetIsolate(); |
@@ -108,11 +411,15 @@ bool WasmDebugInfo::IsDebugInfo(Object *object) { |
(arr->get(kWasmDebugInfoFunctionByteOffsets)->IsUndefined(isolate) || |
arr->get(kWasmDebugInfoFunctionByteOffsets)->IsByteArray()) && |
(arr->get(kWasmDebugInfoFunctionScripts)->IsUndefined(isolate) || |
- arr->get(kWasmDebugInfoFunctionScripts)->IsFixedArray()); |
+ arr->get(kWasmDebugInfoFunctionScripts)->IsFixedArray()) && |
+ (arr->get(kWasmDebugInfoInterpretedFunctions)->IsUndefined(isolate) || |
+ arr->get(kWasmDebugInfoInterpretedFunctions)->IsFixedArray()) && |
+ (arr->get(kWasmDebugInfoDebugInfos)->IsUndefined(isolate) || |
+ arr->get(kWasmDebugInfoDebugInfos)->IsFixedArray()); |
} |
WasmDebugInfo *WasmDebugInfo::cast(Object *object) { |
- DCHECK(IsDebugInfo(object)); |
+ DCHECK(IsWasmDebugInfo(object)); |
return reinterpret_cast<WasmDebugInfo *>(object); |
} |
@@ -120,6 +427,25 @@ JSObject *WasmDebugInfo::wasm_object() { |
return JSObject::cast(get(kWasmDebugInfoWasmObj)); |
} |
+void WasmDebugInfo::SetBreakpoint(Handle<WasmDebugInfo> debug_info, |
+ int func_index, int byte_offset) { |
+ DCHECK_LE(0, func_index); |
+ DCHECK_LE(0, byte_offset); |
+ InterpreterHandle *handle = GetOrCreateInterpreterHandle(*debug_info); |
+ handle->EnsureInterpreterRedirect(debug_info, func_index); |
+ handle->interpreter.SetBreakpoint(static_cast<uint32_t>(func_index), |
+ byte_offset, true); |
+} |
+ |
+bool WasmDebugInfo::HasBreakpoint(int func_index, int byte_offset) { |
+ DCHECK_LE(0, func_index); |
+ DCHECK_LE(0, byte_offset); |
+ InterpreterHandle *handle = GetInterpreterHandleIfExists(this); |
+ return handle && |
+ handle->interpreter.GetBreakpoint(static_cast<uint32_t>(func_index), |
+ byte_offset); |
+} |
+ |
Script *WasmDebugInfo::GetFunctionScript(Handle<WasmDebugInfo> debug_info, |
int func_index) { |
Isolate *isolate = debug_info->GetIsolate(); |
@@ -228,3 +554,73 @@ Handle<FixedArray> WasmDebugInfo::GetFunctionOffsetTable( |
return offset_table; |
} |
+ |
+DebugInfo *WasmDebugInfo::GetDebugInfo(Handle<WasmDebugInfo> debug_info, |
+ int func_index) { |
+ Object *maybe_arr = debug_info->get(kWasmDebugInfoDebugInfos); |
+ Isolate *isolate = debug_info->GetIsolate(); |
+ Handle<FixedArray> arr; |
+ if (maybe_arr->IsUndefined(isolate)) { |
+ arr = isolate->factory()->NewFixedArray( |
+ wasm::GetNumberOfFunctions(debug_info->wasm_object())); |
+ debug_info->set(kWasmDebugInfoDebugInfos, *arr); |
+ } else { |
+ arr = handle(FixedArray::cast(maybe_arr)); |
+ } |
+ |
+ DCHECK_LE(func_index, static_cast<uint32_t>(arr->length())); |
+ Object *maybe_info = arr->get(static_cast<int>(func_index)); |
+ if (!maybe_info->IsUndefined(isolate)) return DebugInfo::cast(maybe_info); |
+ |
+ Handle<AbstractCode> code(AbstractCode::cast( |
+ wasm::GetWasmFunctionCode(debug_info->wasm_object(), func_index))); |
+ Handle<DebugInfo> new_debug_info = isolate->factory()->NewDebugInfo(code); |
+ arr->set(static_cast<int>(func_index), *new_debug_info); |
+ return *new_debug_info; |
+} |
+ |
+WasmDebugInfo::InstructionType WasmDebugInfo::GetInstructionType( |
+ int func_index, int byte_offset) { |
+ SeqOneByteString *bytes = wasm::GetWasmBytes(wasm_object()); |
+ DCHECK(byte_offset >= 0 && byte_offset < bytes->length()); |
+ switch (static_cast<WasmOpcode>(bytes->Get(byte_offset))) { |
+ case WasmOpcode::kExprReturn: |
+ return RETURN; |
+ case WasmOpcode::kExprCallImport: |
+ case WasmOpcode::kExprCallFunction: |
+ case WasmOpcode::kExprCallIndirect: |
+ return CALL; |
+ default: |
+ return OTHER; |
+ } |
+} |
+ |
+WasmInstructionIterator WasmDebugInfo::GetInstructionIterator( |
+ Handle<WasmDebugInfo> debug_info, int func_index) { |
+ Vector<const uint8_t> function_bytes = |
+ GetFunctionBytes(debug_info, func_index); |
+ |
+ return WasmInstructionIterator(function_bytes.start(), function_bytes.end()); |
+} |
+ |
+InterpreterFrameIterator WasmDebugInfo::GetInterpreterFrameIterator() { |
+ return InterpreterFrameIterator(handle(this)); |
+} |
+ |
+Object *WasmDebugInfo::GetInterpreterArgBuffer(int func_index) { |
+ printf("GetInterpreterArgBuffer %d\n", func_index); |
+ DCHECK_LE(0, func_index); |
+ InterpreterHandle *interp_handle = GetOrCreateInterpreterHandle(this); |
+ uint8_t *buf = |
+ interp_handle->GetParameterBuffer(static_cast<uint32_t>(func_index)); |
+ Object *obj = reinterpret_cast<Object *>(buf); |
+ DCHECK(!obj->IsHeapObject()); |
+ return obj; |
+} |
+ |
+void WasmDebugInfo::RunInterpreter(int func_index) { |
+ printf("RunInterpreter %d\n", func_index); |
+ DCHECK_LE(0, func_index); |
+ InterpreterHandle *interp_handle = GetOrCreateInterpreterHandle(this); |
+ interp_handle->Execute(static_cast<uint32_t>(func_index)); |
+} |