diff options
author | Matheus Marchini <mmarchini@netflix.com> | 2020-03-05 10:49:19 -0800 |
---|---|---|
committer | Matheus Marchini <mmarchini@netflix.com> | 2020-03-18 16:23:22 -0700 |
commit | 2883c855e0105b51e5c8020d21458af109ffe3d4 (patch) | |
tree | 26777aad0a398e9f7755c8b65ac76827fe352a81 /deps/v8/src/wasm/wasm-debug.cc | |
parent | 5f0af2af2a67216e00fe07ccda11e889d14abfcd (diff) | |
download | node-new-2883c855e0105b51e5c8020d21458af109ffe3d4.tar.gz |
deps: update V8 to 8.1.307.20
PR-URL: https://github.com/nodejs/node/pull/32116
Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Diffstat (limited to 'deps/v8/src/wasm/wasm-debug.cc')
-rw-r--r-- | deps/v8/src/wasm/wasm-debug.cc | 852 |
1 files changed, 752 insertions, 100 deletions
diff --git a/deps/v8/src/wasm/wasm-debug.cc b/deps/v8/src/wasm/wasm-debug.cc index f3580e4427..230427bed0 100644 --- a/deps/v8/src/wasm/wasm-debug.cc +++ b/deps/v8/src/wasm/wasm-debug.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "src/wasm/wasm-debug.h" + #include <unordered_map> #include "src/base/optional.h" @@ -14,6 +16,7 @@ #include "src/execution/isolate.h" #include "src/heap/factory.h" #include "src/utils/identity-map.h" +#include "src/wasm/baseline/liftoff-compiler.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-code-manager.h" #include "src/wasm/wasm-interpreter.h" @@ -69,31 +72,17 @@ Handle<Object> WasmValueToValueObject(Isolate* isolate, WasmValue value) { } } -MaybeHandle<String> GetLocalName(Isolate* isolate, - Handle<WasmDebugInfo> debug_info, - int func_index, int local_index) { - DCHECK_LE(0, func_index); - DCHECK_LE(0, local_index); - if (!debug_info->has_locals_names()) { - Handle<WasmModuleObject> module_object( - debug_info->wasm_instance().module_object(), isolate); - Handle<FixedArray> locals_names = DecodeLocalNames(isolate, module_object); - debug_info->set_locals_names(*locals_names); - } - - Handle<FixedArray> locals_names(debug_info->locals_names(), isolate); - if (func_index >= locals_names->length() || - locals_names->get(func_index).IsUndefined(isolate)) { - return {}; - } - - Handle<FixedArray> func_locals_names( - FixedArray::cast(locals_names->get(func_index)), isolate); - if (local_index >= func_locals_names->length() || - func_locals_names->get(local_index).IsUndefined(isolate)) { - return {}; - } - return handle(String::cast(func_locals_names->get(local_index)), isolate); +MaybeHandle<String> GetLocalNameString(Isolate* isolate, + NativeModule* native_module, + int func_index, int local_index) { + WireBytesRef name_ref = + native_module->GetDebugInfo()->GetLocalName(func_index, local_index); + ModuleWireBytes wire_bytes{native_module->wire_bytes()}; + // Bounds were checked during decoding. + DCHECK(wire_bytes.BoundsCheck(name_ref)); + Vector<const char> name = wire_bytes.GetNameOrNull(name_ref); + if (name.begin() == nullptr) return {}; + return isolate->factory()->NewStringFromUtf8(name); } class InterpreterHandle { @@ -119,6 +108,10 @@ class InterpreterHandle { activations_.erase(frame_pointer); } + bool HasActivation(Address frame_pointer) { + return activations_.count(frame_pointer); + } + std::pair<uint32_t, uint32_t> GetActivationFrameRange( WasmInterpreter::Thread* thread, Address frame_pointer) { DCHECK_EQ(1, activations_.count(frame_pointer)); @@ -221,15 +214,28 @@ class InterpreterHandle { } // 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()) { - return_values[0] = thread->GetReturnValue(0); +#ifdef DEBUG + const int max_count = WasmFeatures::FromIsolate(isolate_).has_mv() + ? kV8MaxWasmFunctionMultiReturns + : kV8MaxWasmFunctionReturns; +#endif + DCHECK_GE(max_count, sig->return_count()); + for (unsigned i = 0; i < sig->return_count(); ++i) { + return_values[i] = thread->GetReturnValue(i); } FinishActivation(frame_pointer, activation_id); + // If we do stepping and it exits wasm interpreter then debugger need to + // prepare for it. + if (next_step_action_ != StepNone) { + // Enter the debugger. + DebugScope debug_scope(isolate_->debug()); + + isolate_->debug()->PrepareStep(StepOut); + } + ClearStepping(); + return true; } @@ -279,7 +285,7 @@ class InterpreterHandle { Handle<Script> script(module_object->script(), isolate_); int position = GetTopPosition(module_object); Handle<FixedArray> breakpoints; - if (WasmModuleObject::CheckBreakPoints(isolate_, script, position) + if (WasmScript::CheckBreakPoints(isolate_, script, position) .ToHandle(&breakpoints)) { // We hit one or several breakpoints. Clear stepping, notify the // listeners and return. @@ -340,6 +346,18 @@ class InterpreterHandle { return stack; } + int NumberOfActiveFrames(Address frame_pointer) { + if (!HasActivation(frame_pointer)) return 0; + + DCHECK_EQ(1, interpreter()->GetThreadCount()); + WasmInterpreter::Thread* thread = interpreter()->GetThread(0); + + std::pair<uint32_t, uint32_t> frame_range = + GetActivationFrameRange(thread, frame_pointer); + + return frame_range.second - frame_range.first; + } + WasmInterpreter::FramePtr GetInterpretedFrame(Address frame_pointer, int idx) { DCHECK_EQ(1, interpreter()->GetThreadCount()); @@ -358,52 +376,6 @@ class InterpreterHandle { return interpreter()->GetThread(0)->NumInterpretedCalls(); } - Handle<JSObject> GetGlobalScopeObject(InterpretedFrame* frame, - Handle<WasmDebugInfo> debug_info) { - Isolate* isolate = isolate_; - Handle<WasmInstanceObject> instance(debug_info->wasm_instance(), isolate); - - Handle<JSObject> global_scope_object = - isolate_->factory()->NewJSObjectWithNullProto(); - if (instance->has_memory_object()) { - Handle<String> name = - isolate_->factory()->InternalizeString(StaticCharVector("memory")); - Handle<JSArrayBuffer> memory_buffer( - instance->memory_object().array_buffer(), isolate_); - Handle<JSTypedArray> uint8_array = isolate_->factory()->NewJSTypedArray( - kExternalUint8Array, memory_buffer, 0, memory_buffer->byte_length()); - JSObject::SetOwnPropertyIgnoreAttributes(global_scope_object, name, - uint8_array, NONE) - .Assert(); - } - - DCHECK_EQ(1, interpreter()->GetThreadCount()); - WasmInterpreter::Thread* thread = interpreter()->GetThread(0); - - uint32_t global_count = thread->GetGlobalCount(); - if (global_count > 0) { - Handle<JSObject> globals_obj = - isolate_->factory()->NewJSObjectWithNullProto(); - Handle<String> globals_name = - isolate_->factory()->InternalizeString(StaticCharVector("globals")); - JSObject::SetOwnPropertyIgnoreAttributes(global_scope_object, - globals_name, globals_obj, NONE) - .Assert(); - - for (uint32_t i = 0; i < global_count; ++i) { - const char* label = "global#%d"; - Handle<String> name = PrintFToOneByteString<true>(isolate_, label, i); - WasmValue value = thread->GetGlobalValue(i); - Handle<Object> value_obj = WasmValueToValueObject(isolate_, value); - JSObject::SetOwnPropertyIgnoreAttributes(globals_obj, name, value_obj, - NONE) - .Assert(); - } - } - - return global_scope_object; - } - Handle<JSObject> GetLocalScopeObject(InterpretedFrame* frame, Handle<WasmDebugInfo> debug_info) { Isolate* isolate = isolate_; @@ -419,13 +391,15 @@ class InterpreterHandle { isolate_->factory()->NewJSObjectWithNullProto(); Handle<String> locals_name = isolate_->factory()->InternalizeString(StaticCharVector("locals")); - JSObject::SetOwnPropertyIgnoreAttributes(local_scope_object, locals_name, - locals_obj, NONE) - .Assert(); + JSObject::AddProperty(isolate, local_scope_object, locals_name, + locals_obj, NONE); + NativeModule* native_module = + debug_info->wasm_instance().module_object().native_module(); for (int i = 0; i < num_locals; ++i) { - MaybeHandle<String> name = - GetLocalName(isolate, debug_info, frame->function()->func_index, i); - if (name.is_null()) { + Handle<Name> name; + if (!GetLocalNameString(isolate, native_module, + frame->function()->func_index, i) + .ToHandle(&name)) { // Parameters should come before locals in alphabetical ordering, so // we name them "args" here. const char* label = i < num_params ? "arg#%d" : "local#%d"; @@ -433,9 +407,15 @@ class InterpreterHandle { } WasmValue value = frame->GetLocalValue(i); Handle<Object> value_obj = WasmValueToValueObject(isolate_, value); - JSObject::SetOwnPropertyIgnoreAttributes( - locals_obj, name.ToHandleChecked(), value_obj, NONE) - .Assert(); + // {name} can be a string representation of an element index. + LookupIterator::Key lookup_key{isolate, name}; + LookupIterator it(isolate, locals_obj, lookup_key, locals_obj, + LookupIterator::OWN_SKIP_INTERCEPTOR); + if (it.IsFound()) continue; + Object::AddDataProperty(&it, value_obj, NONE, + Just(ShouldThrow::kThrowOnError), + StoreOrigin::kNamed) + .Check(); } } @@ -448,15 +428,13 @@ class InterpreterHandle { isolate_->factory()->NewJSObjectWithNullProto(); Handle<String> stack_name = isolate_->factory()->InternalizeString(StaticCharVector("stack")); - JSObject::SetOwnPropertyIgnoreAttributes(local_scope_object, stack_name, - stack_obj, NONE) - .Assert(); + JSObject::AddProperty(isolate, local_scope_object, stack_name, stack_obj, + NONE); for (int i = 0; i < stack_count; ++i) { WasmValue value = frame->GetStackValue(i); Handle<Object> value_obj = WasmValueToValueObject(isolate_, value); - JSObject::SetOwnElementIgnoreAttributes( - stack_obj, static_cast<uint32_t>(i), value_obj, NONE) - .Assert(); + JSObject::AddDataElement(stack_obj, static_cast<uint32_t>(i), value_obj, + NONE); } return local_scope_object; } @@ -467,6 +445,261 @@ class InterpreterHandle { } // namespace +Handle<JSObject> GetGlobalScopeObject(Handle<WasmInstanceObject> instance) { + Isolate* isolate = instance->GetIsolate(); + + Handle<JSObject> global_scope_object = + isolate->factory()->NewJSObjectWithNullProto(); + if (instance->has_memory_object()) { + Handle<String> name = + isolate->factory()->InternalizeString(StaticCharVector("memory")); + Handle<JSArrayBuffer> memory_buffer( + instance->memory_object().array_buffer(), isolate); + Handle<JSTypedArray> uint8_array = isolate->factory()->NewJSTypedArray( + kExternalUint8Array, memory_buffer, 0, memory_buffer->byte_length()); + JSObject::AddProperty(isolate, global_scope_object, name, uint8_array, + NONE); + } + + auto& globals = instance->module()->globals; + if (globals.size() > 0) { + Handle<JSObject> globals_obj = + isolate->factory()->NewJSObjectWithNullProto(); + Handle<String> globals_name = + isolate->factory()->InternalizeString(StaticCharVector("globals")); + JSObject::AddProperty(isolate, global_scope_object, globals_name, + globals_obj, NONE); + + for (size_t i = 0; i < globals.size(); ++i) { + const char* label = "global#%d"; + Handle<String> name = PrintFToOneByteString<true>(isolate, label, i); + WasmValue value = + WasmInstanceObject::GetGlobalValue(instance, globals[i]); + Handle<Object> value_obj = WasmValueToValueObject(isolate, value); + JSObject::AddProperty(isolate, globals_obj, name, value_obj, NONE); + } + } + + return global_scope_object; +} + +class DebugInfoImpl { + public: + explicit DebugInfoImpl(NativeModule* native_module) + : native_module_(native_module) {} + + Handle<JSObject> GetLocalScopeObject(Isolate* isolate, Address pc, + Address fp) { + Handle<JSObject> local_scope_object = + isolate->factory()->NewJSObjectWithNullProto(); + + wasm::WasmCodeRefScope wasm_code_ref_scope; + wasm::WasmCode* code = + isolate->wasm_engine()->code_manager()->LookupCode(pc); + // Only Liftoff code can be inspected. + if (!code->is_liftoff()) return local_scope_object; + + const WasmModule* module = native_module_->module(); + const WasmFunction* function = &module->functions[code->index()]; + DebugSideTable* debug_side_table = + GetDebugSideTable(isolate->allocator(), function->func_index); + int pc_offset = static_cast<int>(pc - code->instruction_start()); + auto* debug_side_table_entry = debug_side_table->GetEntry(pc_offset); + DCHECK_NOT_NULL(debug_side_table_entry); + + // Fill parameters and locals. + int num_params = static_cast<int>(function->sig->parameter_count()); + int num_locals = static_cast<int>(debug_side_table->num_locals()); + DCHECK_LE(num_params, num_locals); + if (num_locals > 0) { + Handle<JSObject> locals_obj = + isolate->factory()->NewJSObjectWithNullProto(); + Handle<String> locals_name = + isolate->factory()->InternalizeString(StaticCharVector("locals")); + JSObject::AddProperty(isolate, local_scope_object, locals_name, + locals_obj, NONE); + for (int i = 0; i < num_locals; ++i) { + Handle<Name> name; + if (!GetLocalNameString(isolate, native_module_, function->func_index, + i) + .ToHandle(&name)) { + // Parameters should come before locals in alphabetical ordering, so + // we name them "args" here. + const char* label = i < num_params ? "arg#%d" : "local#%d"; + name = PrintFToOneByteString<true>(isolate, label, i); + } + WasmValue value = + GetValue(debug_side_table_entry, debug_side_table->local_type(i), i, + fp - debug_side_table->local_stack_offset(i)); + Handle<Object> value_obj = WasmValueToValueObject(isolate, value); + // {name} can be a string representation of an element index. + LookupIterator::Key lookup_key{isolate, name}; + LookupIterator it(isolate, locals_obj, lookup_key, locals_obj, + LookupIterator::OWN_SKIP_INTERCEPTOR); + if (it.IsFound()) continue; + Object::AddDataProperty(&it, value_obj, NONE, + Just(ShouldThrow::kThrowOnError), + StoreOrigin::kNamed) + .Check(); + } + } + + // Fill stack values. + int stack_count = debug_side_table_entry->stack_height(); + // Use an object without prototype instead of an Array, for nicer displaying + // in DevTools. For Arrays, the length field and prototype is displayed, + // which does not make too much sense here. + Handle<JSObject> stack_obj = isolate->factory()->NewJSObjectWithNullProto(); + Handle<String> stack_name = + isolate->factory()->InternalizeString(StaticCharVector("stack")); + JSObject::AddProperty(isolate, local_scope_object, stack_name, stack_obj, + NONE); + for (int i = 0; i < stack_count; ++i) { + ValueType type = debug_side_table_entry->stack_type(i); + WasmValue value = GetValue(debug_side_table_entry, type, num_locals + i, + fp - debug_side_table_entry->stack_offset(i)); + Handle<Object> value_obj = WasmValueToValueObject(isolate, value); + JSObject::AddDataElement(stack_obj, static_cast<uint32_t>(i), value_obj, + NONE); + } + return local_scope_object; + } + + WireBytesRef GetLocalName(int func_index, int local_index) { + base::MutexGuard guard(&mutex_); + if (!local_names_) { + local_names_ = std::make_unique<LocalNames>( + DecodeLocalNames(native_module_->wire_bytes())); + } + return local_names_->GetName(func_index, local_index); + } + + void SetBreakpoint(int func_index, int offset) { + // Hold the mutex while setting the breakpoint. This guards against multiple + // isolates setting breakpoints at the same time. We don't really support + // that scenario yet, but concurrently compiling and installing different + // Liftoff variants of a function would be problematic. + base::MutexGuard guard(&mutex_); + + std::vector<int>& breakpoints = breakpoints_per_function_[func_index]; + auto insertion_point = + std::lower_bound(breakpoints.begin(), breakpoints.end(), offset); + if (insertion_point != breakpoints.end() && *insertion_point == offset) { + // The breakpoint is already set. + return; + } + breakpoints.insert(insertion_point, offset); + + // Recompile the function with Liftoff, setting the new breakpoints. + CompilationEnv env = native_module_->CreateCompilationEnv(); + auto* function = &native_module_->module()->functions[func_index]; + Vector<const uint8_t> wire_bytes = native_module_->wire_bytes(); + FunctionBody body{function->sig, function->code.offset(), + wire_bytes.begin() + function->code.offset(), + wire_bytes.begin() + function->code.end_offset()}; + WasmCompilationResult result = ExecuteLiftoffCompilation( + native_module_->engine()->allocator(), &env, body, func_index, nullptr, + nullptr, VectorOf(breakpoints)); + DCHECK(result.succeeded()); + + WasmCodeRefScope wasm_code_ref_scope; + WasmCode* new_code = native_module_->AddCompiledCode(std::move(result)); + + // TODO(clemensb): OSR active frames on the stack (on all threads). + USE(new_code); + } + + private: + DebugSideTable* GetDebugSideTable(AccountingAllocator* allocator, + int func_index) { + base::MutexGuard guard(&mutex_); + if (debug_side_tables_.empty()) { + debug_side_tables_.resize(native_module_->module()->functions.size()); + } + if (auto& existing_table = debug_side_tables_[func_index]) { + return existing_table.get(); + } + + // Otherwise create the debug side table now. + const WasmModule* module = native_module_->module(); + const WasmFunction* function = &module->functions[func_index]; + ModuleWireBytes wire_bytes{native_module_->wire_bytes()}; + Vector<const byte> function_bytes = wire_bytes.GetFunctionBytes(function); + CompilationEnv env = native_module_->CreateCompilationEnv(); + FunctionBody func_body{function->sig, 0, function_bytes.begin(), + function_bytes.end()}; + DebugSideTable debug_side_table = + GenerateLiftoffDebugSideTable(allocator, &env, func_body); + + // Install into cache and return. + debug_side_tables_[func_index] = + std::make_unique<DebugSideTable>(std::move(debug_side_table)); + return debug_side_tables_[func_index].get(); + } + + // Get the value of a local (including parameters) or stack value. Stack + // values follow the locals in the same index space. + WasmValue GetValue(const DebugSideTable::Entry* debug_side_table_entry, + ValueType type, int index, Address stack_address) { + if (debug_side_table_entry->IsConstant(index)) { + DCHECK(type == kWasmI32 || type == kWasmI64); + return type == kWasmI32 + ? WasmValue(debug_side_table_entry->GetConstant(index)) + : WasmValue( + int64_t{debug_side_table_entry->GetConstant(index)}); + } + + // Otherwise load the value from the stack. + switch (type) { + case kWasmI32: + return WasmValue(ReadUnalignedValue<int32_t>(stack_address)); + case kWasmI64: + return WasmValue(ReadUnalignedValue<int64_t>(stack_address)); + case kWasmF32: + return WasmValue(ReadUnalignedValue<float>(stack_address)); + case kWasmF64: + return WasmValue(ReadUnalignedValue<double>(stack_address)); + default: + UNIMPLEMENTED(); + } + } + + NativeModule* const native_module_; + + // {mutex_} protects all fields below. + base::Mutex mutex_; + + // DebugSideTable per function, lazily initialized. + std::vector<std::unique_ptr<DebugSideTable>> debug_side_tables_; + + // Names of locals, lazily decoded from the wire bytes. + std::unique_ptr<LocalNames> local_names_; + + // Keeps track of the currently set breakpoints (by offset within that + // function). + std::unordered_map<int, std::vector<int>> breakpoints_per_function_; + + DISALLOW_COPY_AND_ASSIGN(DebugInfoImpl); +}; + +DebugInfo::DebugInfo(NativeModule* native_module) + : impl_(std::make_unique<DebugInfoImpl>(native_module)) {} + +DebugInfo::~DebugInfo() = default; + +Handle<JSObject> DebugInfo::GetLocalScopeObject(Isolate* isolate, Address pc, + Address fp) { + return impl_->GetLocalScopeObject(isolate, pc, fp); +} + +WireBytesRef DebugInfo::GetLocalName(int func_index, int local_index) { + return impl_->GetLocalName(func_index, local_index); +} + +void DebugInfo::SetBreakpoint(int func_index, int offset) { + impl_->SetBreakpoint(func_index, offset); +} + } // namespace wasm namespace { @@ -531,6 +764,18 @@ wasm::WasmInterpreter* WasmDebugInfo::SetupForTesting( } // static +void WasmDebugInfo::PrepareStepIn(Handle<WasmDebugInfo> debug_info, + int func_index) { + Isolate* isolate = debug_info->GetIsolate(); + auto* handle = GetOrCreateInterpreterHandle(isolate, debug_info); + RedirectToInterpreter(debug_info, Vector<int>(&func_index, 1)); + const wasm::WasmFunction* func = &handle->module()->functions[func_index]; + handle->interpreter()->PrepareStepIn(func); + // Debug break would be considered as a step-in inside wasm. + handle->PrepareStep(StepAction::StepIn); +} + +// static void WasmDebugInfo::SetBreakpoint(Handle<WasmDebugInfo> debug_info, int func_index, int offset) { Isolate* isolate = debug_info->GetIsolate(); @@ -609,6 +854,10 @@ std::vector<std::pair<uint32_t, int>> WasmDebugInfo::GetInterpretedStack( return GetInterpreterHandle(*this)->GetInterpretedStack(frame_pointer); } +int WasmDebugInfo::NumberOfActiveFrames(Address frame_pointer) { + return GetInterpreterHandle(*this)->NumberOfActiveFrames(frame_pointer); +} + wasm::WasmInterpreter::FramePtr WasmDebugInfo::GetInterpretedFrame( Address frame_pointer, int idx) { return GetInterpreterHandle(*this)->GetInterpretedFrame(frame_pointer, idx); @@ -620,14 +869,6 @@ uint64_t WasmDebugInfo::NumInterpretedCalls() { } // static -Handle<JSObject> WasmDebugInfo::GetGlobalScopeObject( - Handle<WasmDebugInfo> debug_info, Address frame_pointer, int frame_index) { - auto* interp_handle = GetInterpreterHandle(*debug_info); - auto frame = interp_handle->GetInterpretedFrame(frame_pointer, frame_index); - return interp_handle->GetGlobalScopeObject(frame.get(), debug_info); -} - -// static Handle<JSObject> WasmDebugInfo::GetLocalScopeObject( Handle<WasmDebugInfo> debug_info, Address frame_pointer, int frame_index) { auto* interp_handle = GetInterpreterHandle(*debug_info); @@ -666,5 +907,416 @@ Handle<Code> WasmDebugInfo::GetCWasmEntry(Handle<WasmDebugInfo> debug_info, return handle(Code::cast(entries->get(index)), isolate); } +namespace { + +// Return the next breakable position after {offset_in_func} in function +// {func_index}, or 0 if there is none. +// Note that 0 is never a breakable position in wasm, since the first byte +// contains the locals count for the function. +int FindNextBreakablePosition(wasm::NativeModule* native_module, int func_index, + int offset_in_func) { + AccountingAllocator alloc; + Zone tmp(&alloc, ZONE_NAME); + wasm::BodyLocalDecls locals(&tmp); + const byte* module_start = native_module->wire_bytes().begin(); + const wasm::WasmFunction& func = + native_module->module()->functions[func_index]; + wasm::BytecodeIterator iterator(module_start + func.code.offset(), + module_start + func.code.end_offset(), + &locals); + DCHECK_LT(0, locals.encoded_size); + if (offset_in_func < 0) return 0; + for (uint32_t offset : iterator.offsets()) { + if (offset >= static_cast<uint32_t>(offset_in_func)) return offset; + } + return 0; +} + +} // namespace + +// static +bool WasmScript::SetBreakPoint(Handle<Script> script, int* position, + Handle<BreakPoint> break_point) { + // Find the function for this breakpoint. + const wasm::WasmModule* module = script->wasm_native_module()->module(); + int func_index = GetContainingWasmFunction(module, *position); + if (func_index < 0) return false; + const wasm::WasmFunction& func = module->functions[func_index]; + int offset_in_func = *position - func.code.offset(); + + int breakable_offset = FindNextBreakablePosition(script->wasm_native_module(), + func_index, offset_in_func); + if (breakable_offset == 0) return false; + *position = func.code.offset() + breakable_offset; + + return WasmScript::SetBreakPointForFunction(script, func_index, + breakable_offset, break_point); +} + +// static +bool WasmScript::SetBreakPointOnFirstBreakableForFunction( + Handle<Script> script, int func_index, Handle<BreakPoint> break_point) { + if (func_index < 0) return false; + int offset_in_func = 0; + + int breakable_offset = FindNextBreakablePosition(script->wasm_native_module(), + func_index, offset_in_func); + if (breakable_offset == 0) return false; + return WasmScript::SetBreakPointForFunction(script, func_index, + breakable_offset, break_point); +} + +// static +bool WasmScript::SetBreakPointForFunction(Handle<Script> script, int func_index, + int offset, + Handle<BreakPoint> break_point) { + Isolate* isolate = script->GetIsolate(); + + DCHECK_LE(0, func_index); + DCHECK_NE(0, offset); + + // Find the function for this breakpoint. + wasm::NativeModule* native_module = script->wasm_native_module(); + const wasm::WasmModule* module = native_module->module(); + const wasm::WasmFunction& func = module->functions[func_index]; + + // Insert new break point into {wasm_breakpoint_infos} of the script. + WasmScript::AddBreakpointToInfo(script, func.code.offset() + offset, + break_point); + + if (FLAG_debug_in_liftoff) { + native_module->GetDebugInfo()->SetBreakpoint(func_index, offset); + } else { + // Iterate over all instances and tell them to set this new breakpoint. + // We do this using the weak list of all instances from the script. + Handle<WeakArrayList> weak_instance_list(script->wasm_weak_instance_list(), + isolate); + for (int i = 0; i < weak_instance_list->length(); ++i) { + MaybeObject maybe_instance = weak_instance_list->Get(i); + if (maybe_instance->IsWeak()) { + Handle<WasmInstanceObject> instance( + WasmInstanceObject::cast(maybe_instance->GetHeapObjectAssumeWeak()), + isolate); + Handle<WasmDebugInfo> debug_info = + WasmInstanceObject::GetOrCreateDebugInfo(instance); + WasmDebugInfo::SetBreakpoint(debug_info, func_index, offset); + } + } + } + + return true; +} + +// static +bool WasmScript::ClearBreakPoint(Handle<Script> script, int position, + Handle<BreakPoint> break_point) { + Isolate* isolate = script->GetIsolate(); + + // Find the function for this breakpoint. + const wasm::WasmModule* module = script->wasm_native_module()->module(); + int func_index = GetContainingWasmFunction(module, position); + if (func_index < 0) return false; + const wasm::WasmFunction& func = module->functions[func_index]; + int offset_in_func = position - func.code.offset(); + + if (!WasmScript::RemoveBreakpointFromInfo(script, position, break_point)) { + return false; + } + + // Iterate over all instances and tell them to remove this breakpoint. + // We do this using the weak list of all instances from the script. + Handle<WeakArrayList> weak_instance_list(script->wasm_weak_instance_list(), + isolate); + for (int i = 0; i < weak_instance_list->length(); ++i) { + MaybeObject maybe_instance = weak_instance_list->Get(i); + if (maybe_instance->IsWeak()) { + Handle<WasmInstanceObject> instance( + WasmInstanceObject::cast(maybe_instance->GetHeapObjectAssumeWeak()), + isolate); + Handle<WasmDebugInfo> debug_info = + WasmInstanceObject::GetOrCreateDebugInfo(instance); + WasmDebugInfo::ClearBreakpoint(debug_info, func_index, offset_in_func); + } + } + + return true; +} + +// static +bool WasmScript::ClearBreakPointById(Handle<Script> script, int breakpoint_id) { + if (!script->has_wasm_breakpoint_infos()) { + return false; + } + Isolate* isolate = script->GetIsolate(); + Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate); + // If the array exists, it should not be empty. + DCHECK_LT(0, breakpoint_infos->length()); + + for (int i = 0, e = breakpoint_infos->length(); i < e; ++i) { + Handle<Object> obj(breakpoint_infos->get(i), isolate); + if (obj->IsUndefined(isolate)) { + continue; + } + Handle<BreakPointInfo> breakpoint_info = Handle<BreakPointInfo>::cast(obj); + Handle<BreakPoint> breakpoint; + if (BreakPointInfo::GetBreakPointById(isolate, breakpoint_info, + breakpoint_id) + .ToHandle(&breakpoint)) { + DCHECK(breakpoint->id() == breakpoint_id); + return WasmScript::ClearBreakPoint( + script, breakpoint_info->source_position(), breakpoint); + } + } + return false; +} + +namespace { + +int GetBreakpointPos(Isolate* isolate, Object break_point_info_or_undef) { + if (break_point_info_or_undef.IsUndefined(isolate)) return kMaxInt; + return BreakPointInfo::cast(break_point_info_or_undef).source_position(); +} + +int FindBreakpointInfoInsertPos(Isolate* isolate, + Handle<FixedArray> breakpoint_infos, + int position) { + // Find insert location via binary search, taking care of undefined values on + // the right. Position is always greater than zero. + DCHECK_LT(0, position); + + int left = 0; // inclusive + int right = breakpoint_infos->length(); // exclusive + while (right - left > 1) { + int mid = left + (right - left) / 2; + Object mid_obj = breakpoint_infos->get(mid); + if (GetBreakpointPos(isolate, mid_obj) <= position) { + left = mid; + } else { + right = mid; + } + } + + int left_pos = GetBreakpointPos(isolate, breakpoint_infos->get(left)); + return left_pos < position ? left + 1 : left; +} + +} // namespace + +// static +void WasmScript::AddBreakpointToInfo(Handle<Script> script, int position, + Handle<BreakPoint> break_point) { + Isolate* isolate = script->GetIsolate(); + Handle<FixedArray> breakpoint_infos; + if (script->has_wasm_breakpoint_infos()) { + breakpoint_infos = handle(script->wasm_breakpoint_infos(), isolate); + } else { + breakpoint_infos = + isolate->factory()->NewFixedArray(4, AllocationType::kOld); + script->set_wasm_breakpoint_infos(*breakpoint_infos); + } + + int insert_pos = + FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position); + + // If a BreakPointInfo object already exists for this position, add the new + // breakpoint object and return. + if (insert_pos < breakpoint_infos->length() && + GetBreakpointPos(isolate, breakpoint_infos->get(insert_pos)) == + position) { + Handle<BreakPointInfo> old_info( + BreakPointInfo::cast(breakpoint_infos->get(insert_pos)), isolate); + BreakPointInfo::SetBreakPoint(isolate, old_info, break_point); + return; + } + + // Enlarge break positions array if necessary. + bool need_realloc = !breakpoint_infos->get(breakpoint_infos->length() - 1) + .IsUndefined(isolate); + Handle<FixedArray> new_breakpoint_infos = breakpoint_infos; + if (need_realloc) { + new_breakpoint_infos = isolate->factory()->NewFixedArray( + 2 * breakpoint_infos->length(), AllocationType::kOld); + script->set_wasm_breakpoint_infos(*new_breakpoint_infos); + // Copy over the entries [0, insert_pos). + for (int i = 0; i < insert_pos; ++i) + new_breakpoint_infos->set(i, breakpoint_infos->get(i)); + } + + // Move elements [insert_pos, ...] up by one. + for (int i = breakpoint_infos->length() - 1; i >= insert_pos; --i) { + Object entry = breakpoint_infos->get(i); + if (entry.IsUndefined(isolate)) continue; + new_breakpoint_infos->set(i + 1, entry); + } + + // Generate new BreakpointInfo. + Handle<BreakPointInfo> breakpoint_info = + isolate->factory()->NewBreakPointInfo(position); + BreakPointInfo::SetBreakPoint(isolate, breakpoint_info, break_point); + + // Now insert new position at insert_pos. + new_breakpoint_infos->set(insert_pos, *breakpoint_info); +} + +// static +bool WasmScript::RemoveBreakpointFromInfo(Handle<Script> script, int position, + Handle<BreakPoint> break_point) { + if (!script->has_wasm_breakpoint_infos()) return false; + + Isolate* isolate = script->GetIsolate(); + Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate); + + int pos = FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position); + + // Does a BreakPointInfo object already exist for this position? + if (pos == breakpoint_infos->length()) return false; + + Handle<BreakPointInfo> info(BreakPointInfo::cast(breakpoint_infos->get(pos)), + isolate); + BreakPointInfo::ClearBreakPoint(isolate, info, break_point); + + // Check if there are no more breakpoints at this location. + if (info->GetBreakPointCount(isolate) == 0) { + // Update array by moving breakpoints up one position. + for (int i = pos; i < breakpoint_infos->length() - 1; i++) { + Object entry = breakpoint_infos->get(i + 1); + breakpoint_infos->set(i, entry); + if (entry.IsUndefined(isolate)) break; + } + // Make sure last array element is empty as a result. + breakpoint_infos->set_undefined(breakpoint_infos->length() - 1); + } + return true; +} + +void WasmScript::SetBreakpointsOnNewInstance( + Handle<Script> script, Handle<WasmInstanceObject> instance) { + if (!script->has_wasm_breakpoint_infos()) return; + Isolate* isolate = script->GetIsolate(); + Handle<WasmDebugInfo> debug_info = + WasmInstanceObject::GetOrCreateDebugInfo(instance); + + Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate); + // If the array exists, it should not be empty. + DCHECK_LT(0, breakpoint_infos->length()); + + for (int i = 0, e = breakpoint_infos->length(); i < e; ++i) { + Handle<Object> obj(breakpoint_infos->get(i), isolate); + if (obj->IsUndefined(isolate)) { + for (; i < e; ++i) { + DCHECK(breakpoint_infos->get(i).IsUndefined(isolate)); + } + break; + } + Handle<BreakPointInfo> breakpoint_info = Handle<BreakPointInfo>::cast(obj); + int position = breakpoint_info->source_position(); + + // Find the function for this breakpoint, and set the breakpoint. + const wasm::WasmModule* module = script->wasm_native_module()->module(); + int func_index = GetContainingWasmFunction(module, position); + DCHECK_LE(0, func_index); + const wasm::WasmFunction& func = module->functions[func_index]; + int offset_in_func = position - func.code.offset(); + WasmDebugInfo::SetBreakpoint(debug_info, func_index, offset_in_func); + } +} + +// static +bool WasmScript::GetPossibleBreakpoints( + wasm::NativeModule* native_module, const v8::debug::Location& start, + const v8::debug::Location& end, + std::vector<v8::debug::BreakLocation>* locations) { + DisallowHeapAllocation no_gc; + + const wasm::WasmModule* module = native_module->module(); + const std::vector<wasm::WasmFunction>& functions = module->functions; + + if (start.GetLineNumber() != 0 || start.GetColumnNumber() < 0 || + (!end.IsEmpty() && + (end.GetLineNumber() != 0 || end.GetColumnNumber() < 0 || + end.GetColumnNumber() < start.GetColumnNumber()))) + return false; + + // start_func_index, start_offset and end_func_index is inclusive. + // end_offset is exclusive. + // start_offset and end_offset are module-relative byte offsets. + // We set strict to false because offsets may be between functions. + int start_func_index = + GetNearestWasmFunction(module, start.GetColumnNumber()); + if (start_func_index < 0) return false; + uint32_t start_offset = start.GetColumnNumber(); + int end_func_index; + uint32_t end_offset; + + if (end.IsEmpty()) { + // Default: everything till the end of the Script. + end_func_index = static_cast<uint32_t>(functions.size() - 1); + end_offset = functions[end_func_index].code.end_offset(); + } else { + // If end is specified: Use it and check for valid input. + end_offset = end.GetColumnNumber(); + end_func_index = GetNearestWasmFunction(module, end_offset); + DCHECK_GE(end_func_index, start_func_index); + } + + if (start_func_index == end_func_index && + start_offset > functions[end_func_index].code.end_offset()) + return false; + AccountingAllocator alloc; + Zone tmp(&alloc, ZONE_NAME); + const byte* module_start = native_module->wire_bytes().begin(); + + for (int func_idx = start_func_index; func_idx <= end_func_index; + ++func_idx) { + const wasm::WasmFunction& func = functions[func_idx]; + if (func.code.length() == 0) continue; + + wasm::BodyLocalDecls locals(&tmp); + wasm::BytecodeIterator iterator(module_start + func.code.offset(), + module_start + func.code.end_offset(), + &locals); + DCHECK_LT(0u, locals.encoded_size); + for (uint32_t offset : iterator.offsets()) { + uint32_t total_offset = func.code.offset() + offset; + if (total_offset >= end_offset) { + DCHECK_EQ(end_func_index, func_idx); + break; + } + if (total_offset < start_offset) continue; + locations->emplace_back(0, total_offset, debug::kCommonBreakLocation); + } + } + return true; +} + +// static +MaybeHandle<FixedArray> WasmScript::CheckBreakPoints(Isolate* isolate, + Handle<Script> script, + int position) { + if (!script->has_wasm_breakpoint_infos()) return {}; + + Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate); + int insert_pos = + FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position); + if (insert_pos >= breakpoint_infos->length()) return {}; + + Handle<Object> maybe_breakpoint_info(breakpoint_infos->get(insert_pos), + isolate); + if (maybe_breakpoint_info->IsUndefined(isolate)) return {}; + Handle<BreakPointInfo> breakpoint_info = + Handle<BreakPointInfo>::cast(maybe_breakpoint_info); + if (breakpoint_info->source_position() != position) return {}; + + // There is no support for conditional break points. Just assume that every + // break point always hits. + Handle<Object> break_points(breakpoint_info->break_points(), isolate); + if (break_points->IsFixedArray()) { + return Handle<FixedArray>::cast(break_points); + } + Handle<FixedArray> break_points_hit = isolate->factory()->NewFixedArray(1); + break_points_hit->set(0, *break_points); + return break_points_hit; +} + } // namespace internal } // namespace v8 |