diff options
Diffstat (limited to 'chromium/v8/src/wasm/wasm-debug.cc')
-rw-r--r-- | chromium/v8/src/wasm/wasm-debug.cc | 725 |
1 files changed, 360 insertions, 365 deletions
diff --git a/chromium/v8/src/wasm/wasm-debug.cc b/chromium/v8/src/wasm/wasm-debug.cc index a8fd6505f0e..61f3492af96 100644 --- a/chromium/v8/src/wasm/wasm-debug.cc +++ b/chromium/v8/src/wasm/wasm-debug.cc @@ -4,6 +4,7 @@ #include "src/wasm/wasm-debug.h" +#include <iomanip> #include <unordered_map> #include "src/base/optional.h" @@ -15,15 +16,14 @@ #include "src/execution/frames-inl.h" #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/baseline/liftoff-register.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-code-manager.h" -#include "src/wasm/wasm-interpreter.h" #include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects-inl.h" +#include "src/wasm/wasm-opcodes-inl.h" #include "src/wasm/wasm-value.h" #include "src/zone/accounting-allocator.h" @@ -49,29 +49,102 @@ Handle<String> PrintFToOneByteString(Isolate* isolate, const char* format, : isolate->factory()->NewStringFromOneByte(name).ToHandleChecked(); } +MaybeHandle<JSObject> CreateFunctionTablesObject( + Handle<WasmInstanceObject> instance) { + Isolate* isolate = instance->GetIsolate(); + auto tables = handle(instance->tables(), isolate); + if (tables->length() == 0) return MaybeHandle<JSObject>(); + + const char* table_label = "table%d"; + Handle<JSObject> tables_obj = isolate->factory()->NewJSObjectWithNullProto(); + for (int table_index = 0; table_index < tables->length(); ++table_index) { + auto func_table = + handle(WasmTableObject::cast(tables->get(table_index)), isolate); + if (func_table->type().heap_type() != kHeapFunc) continue; + + Handle<String> table_name; + if (!WasmInstanceObject::GetTableNameOrNull(isolate, instance, table_index) + .ToHandle(&table_name)) { + table_name = + PrintFToOneByteString<true>(isolate, table_label, table_index); + } + + Handle<JSObject> func_table_obj = + isolate->factory()->NewJSObjectWithNullProto(); + JSObject::AddProperty(isolate, tables_obj, table_name, func_table_obj, + NONE); + for (int i = 0; i < func_table->current_length(); ++i) { + Handle<Object> func = WasmTableObject::Get(isolate, func_table, i); + DCHECK(!WasmCapiFunction::IsWasmCapiFunction(*func)); + if (func->IsNull(isolate)) continue; + + Handle<String> func_name; + Handle<JSObject> func_obj = + isolate->factory()->NewJSObjectWithNullProto(); + + if (WasmExportedFunction::IsWasmExportedFunction(*func)) { + auto target_func = Handle<WasmExportedFunction>::cast(func); + auto target_instance = handle(target_func->instance(), isolate); + auto module = handle(target_instance->module_object(), isolate); + func_name = WasmModuleObject::GetFunctionName( + isolate, module, target_func->function_index()); + } else if (WasmJSFunction::IsWasmJSFunction(*func)) { + auto target_func = Handle<JSFunction>::cast(func); + func_name = JSFunction::GetName(target_func); + if (func_name->length() == 0) { + func_name = isolate->factory()->InternalizeUtf8String("anonymous"); + } + } + JSObject::AddProperty(isolate, func_obj, func_name, func, NONE); + JSObject::AddDataElement(func_table_obj, i, func_obj, NONE); + } + } + return tables_obj; +} + Handle<Object> WasmValueToValueObject(Isolate* isolate, WasmValue value) { + Handle<ByteArray> bytes; switch (value.type().kind()) { - case ValueType::kI32: - if (Smi::IsValid(value.to<int32_t>())) - return handle(Smi::FromInt(value.to<int32_t>()), isolate); - return PrintFToOneByteString<false>(isolate, "%d", value.to<int32_t>()); + case ValueType::kI32: { + int32_t val = value.to_i32(); + bytes = isolate->factory()->NewByteArray(sizeof(val)); + memcpy(bytes->GetDataStartAddress(), &val, sizeof(val)); + break; + } case ValueType::kI64: { - int64_t i64 = value.to<int64_t>(); - int32_t i32 = static_cast<int32_t>(i64); - if (i32 == i64 && Smi::IsValid(i32)) - return handle(Smi::FromIntptr(i32), isolate); - return PrintFToOneByteString<false>(isolate, "%" PRId64, i64); + int64_t val = value.to_i64(); + bytes = isolate->factory()->NewByteArray(sizeof(val)); + memcpy(bytes->GetDataStartAddress(), &val, sizeof(val)); + break; + } + case ValueType::kF32: { + float val = value.to_f32(); + bytes = isolate->factory()->NewByteArray(sizeof(val)); + memcpy(bytes->GetDataStartAddress(), &val, sizeof(val)); + break; + } + case ValueType::kF64: { + double val = value.to_f64(); + bytes = isolate->factory()->NewByteArray(sizeof(val)); + memcpy(bytes->GetDataStartAddress(), &val, sizeof(val)); + break; } - case ValueType::kF32: - return isolate->factory()->NewNumber(value.to<float>()); - case ValueType::kF64: - return isolate->factory()->NewNumber(value.to<double>()); - case ValueType::kAnyRef: - return value.to_anyref(); - default: + case ValueType::kOptRef: { + if (value.type().heap_type() == kHeapExtern) { + return isolate->factory()->NewWasmValue( + static_cast<int32_t>(kHeapExtern), value.to_externref()); + } else { + // TODO(7748): Implement. + UNIMPLEMENTED(); + } + } + default: { + // TODO(7748): Implement. UNIMPLEMENTED(); - return isolate->factory()->undefined_value(); + } } + return isolate->factory()->NewWasmValue( + static_cast<int32_t>(value.type().kind()), bytes); } MaybeHandle<String> GetLocalNameString(Isolate* isolate, @@ -87,176 +160,10 @@ MaybeHandle<String> GetLocalNameString(Isolate* isolate, return isolate->factory()->NewStringFromUtf8(name); } -class InterpreterHandle { - Isolate* isolate_; - const WasmModule* module_; - WasmInterpreter interpreter_; - std::unordered_map<Address, uint32_t> activations_; - - uint32_t StartActivation(Address frame_pointer) { - WasmInterpreter::Thread* thread = interpreter_.GetThread(0); - uint32_t activation_id = thread->StartActivation(); - DCHECK_EQ(0, activations_.count(frame_pointer)); - activations_.insert(std::make_pair(frame_pointer, activation_id)); - return activation_id; - } - - void FinishActivation(Address frame_pointer, uint32_t activation_id) { - WasmInterpreter::Thread* thread = interpreter_.GetThread(0); - thread->FinishActivation(activation_id); - DCHECK_EQ(1, activations_.count(frame_pointer)); - 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)); - uint32_t activation_id = activations_.find(frame_pointer)->second; - uint32_t num_activations = static_cast<uint32_t>(activations_.size() - 1); - uint32_t frame_base = thread->ActivationFrameBase(activation_id); - uint32_t frame_limit = activation_id == num_activations - ? thread->GetFrameCount() - : thread->ActivationFrameBase(activation_id + 1); - DCHECK_LE(frame_base, frame_limit); - DCHECK_LE(frame_limit, thread->GetFrameCount()); - return {frame_base, frame_limit}; - } - - static ModuleWireBytes GetBytes(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. - NativeModule* native_module = - debug_info.wasm_instance().module_object().native_module(); - return ModuleWireBytes{native_module->wire_bytes()}; - } - - public: - InterpreterHandle(Isolate* isolate, Handle<WasmDebugInfo> debug_info) - : isolate_(isolate), - module_(debug_info->wasm_instance().module_object().module()), - interpreter_(isolate, module_, GetBytes(*debug_info), - handle(debug_info->wasm_instance(), isolate)) {} - - WasmInterpreter* interpreter() { return &interpreter_; } - const WasmModule* module() const { return module_; } - - // Returns true if exited regularly, false if a trap/exception occurred and - // was not handled inside this activation. In the latter case, a pending - // exception will have been set on the isolate. - bool Execute(Handle<WasmInstanceObject> instance_object, - Address frame_pointer, uint32_t func_index, - Vector<WasmValue> argument_values, - Vector<WasmValue> return_values) { - DCHECK_GE(module()->functions.size(), func_index); - const FunctionSig* sig = module()->functions[func_index].sig; - DCHECK_EQ(sig->parameter_count(), argument_values.size()); - DCHECK_EQ(sig->return_count(), return_values.size()); - - uint32_t activation_id = StartActivation(frame_pointer); - - WasmCodeRefScope code_ref_scope; - WasmInterpreter::Thread* thread = interpreter_.GetThread(0); - thread->InitFrame(&module()->functions[func_index], - argument_values.begin()); - bool finished = false; - while (!finished) { - // TODO(clemensb): Add occasional StackChecks. - WasmInterpreter::State state = thread->Run(); - switch (state) { - case WasmInterpreter::State::PAUSED: - UNREACHABLE(); - case WasmInterpreter::State::FINISHED: - // Perfect, just break the switch and exit the loop. - finished = true; - break; - case WasmInterpreter::State::TRAPPED: { - MessageTemplate message_id = - WasmOpcodes::TrapReasonToMessageId(thread->GetTrapReason()); - Handle<JSObject> exception = - isolate_->factory()->NewWasmRuntimeError(message_id); - JSObject::AddProperty(isolate_, exception, - isolate_->factory()->wasm_uncatchable_symbol(), - isolate_->factory()->true_value(), NONE); - auto result = thread->RaiseException(isolate_, exception); - if (result == WasmInterpreter::Thread::HANDLED) break; - // If no local handler was found, we fall-thru to {STOPPED}. - DCHECK_EQ(WasmInterpreter::State::STOPPED, thread->state()); - V8_FALLTHROUGH; - } - case WasmInterpreter::State::STOPPED: - // An exception happened, and the current activation was unwound - // without hitting a local exception handler. All that remains to be - // done is finish the activation and let the exception propagate. - DCHECK_EQ(thread->ActivationFrameBase(activation_id), - thread->GetFrameCount()); - DCHECK(isolate_->has_pending_exception()); - FinishActivation(frame_pointer, activation_id); - return false; - // RUNNING should never occur here. - case WasmInterpreter::State::RUNNING: - default: - UNREACHABLE(); - } - } - - // Copy back the return value. -#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); - - return true; - } - - std::vector<std::pair<uint32_t, int>> GetInterpretedStack( - Address frame_pointer) { - DCHECK_EQ(1, interpreter()->GetThreadCount()); - WasmInterpreter::Thread* thread = interpreter()->GetThread(0); - - std::pair<uint32_t, uint32_t> frame_range = - GetActivationFrameRange(thread, frame_pointer); - - std::vector<std::pair<uint32_t, int>> stack; - stack.reserve(frame_range.second - frame_range.first); - for (uint32_t fp = frame_range.first; fp < frame_range.second; ++fp) { - auto frame = thread->GetFrame(fp); - stack.emplace_back(frame->function()->func_index, frame->pc()); - } - 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; - } - - private: - DISALLOW_COPY_AND_ASSIGN(InterpreterHandle); -}; - // Generate a sorted and deduplicated list of byte offsets for this function's // current positions on the stack. std::vector<int> StackFramePositions(int func_index, Isolate* isolate) { std::vector<int> byte_offsets; - WasmCodeRefScope code_ref_scope; for (StackTraceFrameIterator it(isolate); !it.done(); it.Advance()) { if (!it.is_wasm()) continue; WasmFrame* frame = WasmFrame::cast(it.frame()); @@ -304,11 +211,43 @@ Address FindNewPC(WasmCode* wasm_code, int byte_offset, } // namespace +void DebugSideTable::Print(std::ostream& os) const { + os << "Debug side table (" << num_locals_ << " locals, " << entries_.size() + << " entries):\n"; + for (auto& entry : entries_) entry.Print(os); + os << "\n"; +} + +void DebugSideTable::Entry::Print(std::ostream& os) const { + os << std::setw(6) << std::hex << pc_offset_ << std::dec << " ["; + for (auto& value : values_) { + os << " " << value.type.type_name() << ":"; + switch (value.kind) { + case kConstant: + os << "const#" << value.i32_const; + break; + case kRegister: + os << "reg#" << value.reg_code; + break; + case kStack: + os << "stack#" << value.stack_offset; + break; + } + } + os << " ]\n"; +} + Handle<JSObject> GetModuleScopeObject(Handle<WasmInstanceObject> instance) { Isolate* isolate = instance->GetIsolate(); Handle<JSObject> module_scope_object = isolate->factory()->NewJSObjectWithNullProto(); + + Handle<String> instance_name = + isolate->factory()->InternalizeString(StaticCharVector("instance")); + JSObject::AddProperty(isolate, module_scope_object, instance_name, instance, + NONE); + if (instance->has_memory_object()) { Handle<String> name; // TODO(duongn): extend the logic when multiple memories are supported. @@ -327,6 +266,14 @@ Handle<JSObject> GetModuleScopeObject(Handle<WasmInstanceObject> instance) { NONE); } + Handle<JSObject> function_tables_obj; + if (CreateFunctionTablesObject(instance).ToHandle(&function_tables_obj)) { + Handle<String> tables_name = isolate->factory()->InternalizeString( + StaticCharVector("function tables")); + JSObject::AddProperty(isolate, module_scope_object, tables_name, + function_tables_obj, NONE); + } + auto& globals = instance->module()->globals; if (globals.size() > 0) { Handle<JSObject> globals_obj = @@ -357,29 +304,29 @@ class DebugInfoImpl { explicit DebugInfoImpl(NativeModule* native_module) : native_module_(native_module) {} - int GetNumLocals(Isolate* isolate, Address pc) { - FrameInspectionScope scope(this, isolate, pc); + int GetNumLocals(Address pc) { + FrameInspectionScope scope(this, pc); if (!scope.is_inspectable()) return 0; return scope.debug_side_table->num_locals(); } - WasmValue GetLocalValue(int local, Isolate* isolate, Address pc, Address fp, + WasmValue GetLocalValue(int local, Address pc, Address fp, Address debug_break_fp) { - FrameInspectionScope scope(this, isolate, pc); + FrameInspectionScope scope(this, pc); return GetValue(scope.debug_side_table_entry, local, fp, debug_break_fp); } - int GetStackDepth(Isolate* isolate, Address pc) { - FrameInspectionScope scope(this, isolate, pc); + int GetStackDepth(Address pc) { + FrameInspectionScope scope(this, pc); if (!scope.is_inspectable()) return 0; int num_locals = static_cast<int>(scope.debug_side_table->num_locals()); int value_count = scope.debug_side_table_entry->num_values(); return value_count - num_locals; } - WasmValue GetStackValue(int index, Isolate* isolate, Address pc, Address fp, + WasmValue GetStackValue(int index, Address pc, Address fp, Address debug_break_fp) { - FrameInspectionScope scope(this, isolate, pc); + FrameInspectionScope scope(this, pc); int num_locals = static_cast<int>(scope.debug_side_table->num_locals()); int value_count = scope.debug_side_table_entry->num_values(); if (num_locals + index >= value_count) return {}; @@ -389,7 +336,7 @@ class DebugInfoImpl { Handle<JSObject> GetLocalScopeObject(Isolate* isolate, Address pc, Address fp, Address debug_break_fp) { - FrameInspectionScope scope(this, isolate, pc); + FrameInspectionScope scope(this, pc); Handle<JSObject> local_scope_object = isolate->factory()->NewJSObjectWithNullProto(); @@ -401,40 +348,32 @@ class DebugInfoImpl { // Fill parameters and locals. int num_locals = static_cast<int>(scope.debug_side_table->num_locals()); DCHECK_LE(static_cast<int>(function->sig->parameter_count()), 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)) { - name = PrintFToOneByteString<true>(isolate, "var%d", i); - } - WasmValue value = - GetValue(scope.debug_side_table_entry, i, fp, debug_break_fp); - 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(); + for (int i = 0; i < num_locals; ++i) { + Handle<Name> name; + if (!GetLocalNameString(isolate, native_module_, function->func_index, i) + .ToHandle(&name)) { + name = PrintFToOneByteString<true>(isolate, "var%d", i); } + WasmValue value = + GetValue(scope.debug_side_table_entry, i, fp, debug_break_fp); + 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, local_scope_object, lookup_key, + local_scope_object, + LookupIterator::OWN_SKIP_INTERCEPTOR); + if (it.IsFound()) continue; + Object::AddDataProperty(&it, value_obj, NONE, + Just(ShouldThrow::kThrowOnError), + StoreOrigin::kNamed) + .Check(); } return local_scope_object; } Handle<JSObject> GetStackScopeObject(Isolate* isolate, Address pc, Address fp, Address debug_break_fp) { - FrameInspectionScope scope(this, isolate, pc); + FrameInspectionScope scope(this, pc); Handle<JSObject> stack_scope_obj = isolate->factory()->NewJSObjectWithNullProto(); @@ -468,10 +407,7 @@ class DebugInfoImpl { WasmCode* RecompileLiftoffWithBreakpoints( int func_index, Vector<int> offsets, Vector<int> extra_source_positions) { - // During compilation, we cannot hold the lock, since compilation takes the - // {NativeModule} lock, which could lead to deadlocks. - mutex_.AssertUnheld(); - + DCHECK(!mutex_.TryLock()); // Mutex is held externally. // Recompile the function with Liftoff, setting the new breakpoints. // Not thread-safe. The caller is responsible for locking {mutex_}. CompilationEnv env = native_module_->CreateCompilationEnv(); @@ -484,9 +420,11 @@ class DebugInfoImpl { ForDebugging for_debugging = offsets.size() == 1 && offsets[0] == 0 ? kForStepping : kForDebugging; + Counters* counters = nullptr; + WasmFeatures unused_detected; WasmCompilationResult result = ExecuteLiftoffCompilation( native_module_->engine()->allocator(), &env, body, func_index, - for_debugging, nullptr, nullptr, offsets, &debug_sidetable, + for_debugging, counters, &unused_detected, offsets, &debug_sidetable, extra_source_positions); // Liftoff compilation failure is a FATAL error. We rely on complete Liftoff // support for debugging. @@ -497,62 +435,99 @@ class DebugInfoImpl { native_module_->AddCompiledCode(std::move(result))); DCHECK(new_code->is_inspectable()); - bool added = - debug_side_tables_.emplace(new_code, std::move(debug_sidetable)).second; - DCHECK(added); - USE(added); + DCHECK_EQ(0, debug_side_tables_.count(new_code)); + debug_side_tables_.emplace(new_code, std::move(debug_sidetable)); return new_code; } - void SetBreakpoint(int func_index, int offset, Isolate* current_isolate) { - std::vector<int> breakpoints_copy; - { - // Hold the mutex while modifying the set of breakpoints, but release it - // before compiling the new code (see comment in - // {RecompileLiftoffWithBreakpoints}). This needs to be revisited once we - // support setting different breakpoints in different isolates - // (https://crbug.com/v8/10351). - base::MutexGuard guard(&mutex_); + void SetBreakpoint(int func_index, int offset, Isolate* isolate) { + // Put the code ref scope outside of the mutex, so we don't unnecessarily + // hold the mutex while freeing code. + WasmCodeRefScope wasm_code_ref_scope; - // offset == 0 indicates flooding and should not happen here. - DCHECK_NE(0, offset); + // Generate additional source positions for current stack frame positions. + // These source positions are used to find return addresses in the new code. + std::vector<int> stack_frame_positions = + StackFramePositions(func_index, isolate); - 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); - breakpoints_copy = breakpoints; + // Hold the mutex while modifying breakpoints, to ensure consistency when + // multiple isolates set/remove breakpoints at the same time. + base::MutexGuard guard(&mutex_); + + // offset == 0 indicates flooding and should not happen here. + DCHECK_NE(0, offset); + + // Get the set of previously set breakpoints, to check later whether a new + // breakpoint was actually added. + std::vector<int> all_breakpoints = FindAllBreakpoints(func_index); + + auto& isolate_data = per_isolate_data_[isolate]; + std::vector<int>& breakpoints = + isolate_data.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 for this isolate. + return; } + breakpoints.insert(insertion_point, offset); + + DCHECK(std::is_sorted(all_breakpoints.begin(), all_breakpoints.end())); + // Find the insertion position within {all_breakpoints}. + insertion_point = std::lower_bound(all_breakpoints.begin(), + all_breakpoints.end(), offset); + bool breakpoint_exists = + insertion_point != all_breakpoints.end() && *insertion_point == offset; + // If the breakpoint was already set before *and* we don't need any special + // positions for OSR, then we can just reuse the old code. Otherwise, + // recompile it. In any case, rewrite this isolate's stack to make sure that + // it uses up-to-date code containing the breakpoint. + WasmCode* new_code; + if (breakpoint_exists && stack_frame_positions.empty()) { + new_code = native_module_->GetCode(func_index); + } else { + // Add the new offset to the set of all breakpoints, then recompile. + if (!breakpoint_exists) all_breakpoints.insert(insertion_point, offset); + new_code = + RecompileLiftoffWithBreakpoints(func_index, VectorOf(all_breakpoints), + VectorOf(stack_frame_positions)); + } + UpdateReturnAddresses(isolate, new_code, isolate_data.stepping_frame); + } - UpdateBreakpoints(func_index, VectorOf(breakpoints_copy), current_isolate); + std::vector<int> FindAllBreakpoints(int func_index) { + DCHECK(!mutex_.TryLock()); // Mutex must be held externally. + std::set<int> breakpoints; + for (auto& data : per_isolate_data_) { + auto it = data.second.breakpoints_per_function.find(func_index); + if (it == data.second.breakpoints_per_function.end()) continue; + for (int offset : it->second) breakpoints.insert(offset); + } + return {breakpoints.begin(), breakpoints.end()}; } void UpdateBreakpoints(int func_index, Vector<int> breakpoints, - Isolate* current_isolate) { + Isolate* isolate, StackFrameId stepping_frame) { + DCHECK(!mutex_.TryLock()); // Mutex is held externally. // Generate additional source positions for current stack frame positions. // These source positions are used to find return addresses in the new code. std::vector<int> stack_frame_positions = - StackFramePositions(func_index, current_isolate); + StackFramePositions(func_index, isolate); - WasmCodeRefScope wasm_code_ref_scope; WasmCode* new_code = RecompileLiftoffWithBreakpoints( func_index, breakpoints, VectorOf(stack_frame_positions)); - UpdateReturnAddresses(current_isolate, new_code); + UpdateReturnAddresses(isolate, new_code, stepping_frame); } - void FloodWithBreakpoints(WasmFrame* frame, Isolate* current_isolate, - ReturnLocation return_location) { + void FloodWithBreakpoints(WasmFrame* frame, ReturnLocation return_location) { // 0 is an invalid offset used to indicate flooding. int offset = 0; WasmCodeRefScope wasm_code_ref_scope; DCHECK(frame->wasm_code()->is_liftoff()); // Generate an additional source position for the current byte offset. int byte_offset = frame->byte_offset(); + base::MutexGuard guard(&mutex_); WasmCode* new_code = RecompileLiftoffWithBreakpoints( frame->function_index(), VectorOf(&offset, 1), VectorOf(&byte_offset, 1)); @@ -579,37 +554,55 @@ class DebugInfoImpl { return_location = kAfterWasmCall; } - FloodWithBreakpoints(frame, isolate, return_location); - stepping_frame_ = frame->id(); + FloodWithBreakpoints(frame, return_location); + + base::MutexGuard guard(&mutex_); + per_isolate_data_[isolate].stepping_frame = frame->id(); } - void ClearStepping() { stepping_frame_ = NO_ID; } + void ClearStepping(Isolate* isolate) { + base::MutexGuard guard(&mutex_); + auto it = per_isolate_data_.find(isolate); + if (it != per_isolate_data_.end()) it->second.stepping_frame = NO_ID; + } bool IsStepping(WasmFrame* frame) { Isolate* isolate = frame->wasm_instance().GetIsolate(); - StepAction last_step_action = isolate->debug()->last_step_action(); - return stepping_frame_ == frame->id() || last_step_action == StepIn; + if (isolate->debug()->last_step_action() == StepIn) return true; + base::MutexGuard guard(&mutex_); + auto it = per_isolate_data_.find(isolate); + return it != per_isolate_data_.end() && + it->second.stepping_frame == frame->id(); } - void RemoveBreakpoint(int func_index, int position, - Isolate* current_isolate) { - std::vector<int> breakpoints_copy; - { - base::MutexGuard guard(&mutex_); - const auto& function = native_module_->module()->functions[func_index]; - int offset = position - function.code.offset(); - - std::vector<int>& breakpoints = breakpoints_per_function_[func_index]; - DCHECK_LT(0, offset); - auto insertion_point = - std::lower_bound(breakpoints.begin(), breakpoints.end(), offset); - if (insertion_point == breakpoints.end()) return; - if (*insertion_point != offset) return; - breakpoints.erase(insertion_point); - breakpoints_copy = breakpoints; - } + void RemoveBreakpoint(int func_index, int position, Isolate* isolate) { + // Put the code ref scope outside of the mutex, so we don't unnecessarily + // hold the mutex while freeing code. + WasmCodeRefScope wasm_code_ref_scope; - UpdateBreakpoints(func_index, VectorOf(breakpoints_copy), current_isolate); + // Hold the mutex while modifying breakpoints, to ensure consistency when + // multiple isolates set/remove breakpoints at the same time. + base::MutexGuard guard(&mutex_); + + const auto& function = native_module_->module()->functions[func_index]; + int offset = position - function.code.offset(); + + auto& isolate_data = per_isolate_data_[isolate]; + std::vector<int>& breakpoints = + isolate_data.breakpoints_per_function[func_index]; + DCHECK_LT(0, offset); + auto insertion_point = + std::lower_bound(breakpoints.begin(), breakpoints.end(), offset); + if (insertion_point == breakpoints.end()) return; + if (*insertion_point != offset) return; + breakpoints.erase(insertion_point); + + std::vector<int> remaining = FindAllBreakpoints(func_index); + // If the breakpoint is still set in another isolate, don't remove it. + DCHECK(std::is_sorted(remaining.begin(), remaining.end())); + if (std::binary_search(remaining.begin(), remaining.end(), offset)) return; + UpdateBreakpoints(func_index, VectorOf(remaining), isolate, + isolate_data.stepping_frame); } void RemoveDebugSideTables(Vector<WasmCode* const> codes) { @@ -619,15 +612,55 @@ class DebugInfoImpl { } } + DebugSideTable* GetDebugSideTableIfExists(const WasmCode* code) const { + base::MutexGuard guard(&mutex_); + auto it = debug_side_tables_.find(code); + return it == debug_side_tables_.end() ? nullptr : it->second.get(); + } + + static bool HasRemovedBreakpoints(const std::vector<int>& removed, + const std::vector<int>& remaining) { + DCHECK(std::is_sorted(remaining.begin(), remaining.end())); + for (int offset : removed) { + // Return true if we removed a breakpoint which is not part of remaining. + if (!std::binary_search(remaining.begin(), remaining.end(), offset)) { + return true; + } + } + return false; + } + + void RemoveIsolate(Isolate* isolate) { + // Put the code ref scope outside of the mutex, so we don't unnecessarily + // hold the mutex while freeing code. + WasmCodeRefScope wasm_code_ref_scope; + + base::MutexGuard guard(&mutex_); + auto per_isolate_data_it = per_isolate_data_.find(isolate); + if (per_isolate_data_it == per_isolate_data_.end()) return; + std::unordered_map<int, std::vector<int>> removed_per_function = + std::move(per_isolate_data_it->second.breakpoints_per_function); + per_isolate_data_.erase(per_isolate_data_it); + for (auto& entry : removed_per_function) { + int func_index = entry.first; + std::vector<int>& removed = entry.second; + std::vector<int> remaining = FindAllBreakpoints(func_index); + if (HasRemovedBreakpoints(removed, remaining)) { + RecompileLiftoffWithBreakpoints(func_index, VectorOf(remaining), {}); + } + } + } + private: struct FrameInspectionScope { - FrameInspectionScope(DebugInfoImpl* debug_info, Isolate* isolate, - Address pc) - : code(isolate->wasm_engine()->code_manager()->LookupCode(pc)), + FrameInspectionScope(DebugInfoImpl* debug_info, Address pc) + : code(debug_info->native_module_->engine()->code_manager()->LookupCode( + pc)), pc_offset(static_cast<int>(pc - code->instruction_start())), debug_side_table( code->is_inspectable() - ? debug_info->GetDebugSideTable(code, isolate->allocator()) + ? debug_info->GetDebugSideTable( + code, debug_info->native_module_->engine()->allocator()) : nullptr), debug_side_table_entry(debug_side_table ? debug_side_table->GetEntry(pc_offset) @@ -667,11 +700,17 @@ class DebugInfoImpl { GenerateLiftoffDebugSideTable(allocator, &env, func_body); DebugSideTable* ret = debug_side_table.get(); - // Install into cache and return. + // Check cache again, maybe another thread concurrently generated a debug + // side table already. { base::MutexGuard guard(&mutex_); - debug_side_tables_[code] = std::move(debug_side_table); + auto& slot = debug_side_tables_[code]; + if (slot != nullptr) return slot.get(); + slot = std::move(debug_side_table); } + + // Print the code together with the debug table, if requested. + code->MaybePrint(); return ret; } @@ -741,15 +780,15 @@ class DebugInfoImpl { // After installing a Liftoff code object with a different set of breakpoints, // update return addresses on the stack so that execution resumes in the new // code. The frame layout itself should be independent of breakpoints. - // TODO(thibaudm): update other threads as well. - void UpdateReturnAddresses(Isolate* isolate, WasmCode* new_code) { + void UpdateReturnAddresses(Isolate* isolate, WasmCode* new_code, + StackFrameId stepping_frame) { // The first return location is after the breakpoint, others are after wasm // calls. ReturnLocation return_location = kAfterBreakpoint; for (StackTraceFrameIterator it(isolate); !it.done(); it.Advance(), return_location = kAfterWasmCall) { // We still need the flooded function for stepping. - if (it.frame()->id() == stepping_frame_) continue; + if (it.frame()->id() == stepping_frame) continue; if (!it.is_wasm()) continue; WasmFrame* frame = WasmFrame::cast(it.frame()); if (frame->native_module() != new_code->native_module()) continue; @@ -788,25 +827,32 @@ class DebugInfoImpl { return static_cast<size_t>(position) == code.end_offset() - 1; } + // Isolate-specific data, for debugging modules that are shared by multiple + // isolates. + struct PerIsolateDebugData { + // Keeps track of the currently set breakpoints (by offset within that + // function). + std::unordered_map<int, std::vector<int>> breakpoints_per_function; + + // Store the frame ID when stepping, to avoid overwriting that frame when + // setting or removing a breakpoint. + StackFrameId stepping_frame = NO_ID; + }; + NativeModule* const native_module_; // {mutex_} protects all fields below. mutable base::Mutex mutex_; // DebugSideTable per code object, lazily initialized. - std::unordered_map<WasmCode*, std::unique_ptr<DebugSideTable>> + std::unordered_map<const WasmCode*, 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_; - - // Store the frame ID when stepping, to avoid overwriting that frame when - // setting or removing a breakpoint. - StackFrameId stepping_frame_ = NO_ID; + // Isolate-specific data. + std::unordered_map<Isolate*, PerIsolateDebugData> per_isolate_data_; DISALLOW_COPY_AND_ASSIGN(DebugInfoImpl); }; @@ -816,22 +862,18 @@ DebugInfo::DebugInfo(NativeModule* native_module) DebugInfo::~DebugInfo() = default; -int DebugInfo::GetNumLocals(Isolate* isolate, Address pc) { - return impl_->GetNumLocals(isolate, pc); -} +int DebugInfo::GetNumLocals(Address pc) { return impl_->GetNumLocals(pc); } -WasmValue DebugInfo::GetLocalValue(int local, Isolate* isolate, Address pc, - Address fp, Address debug_break_fp) { - return impl_->GetLocalValue(local, isolate, pc, fp, debug_break_fp); +WasmValue DebugInfo::GetLocalValue(int local, Address pc, Address fp, + Address debug_break_fp) { + return impl_->GetLocalValue(local, pc, fp, debug_break_fp); } -int DebugInfo::GetStackDepth(Isolate* isolate, Address pc) { - return impl_->GetStackDepth(isolate, pc); -} +int DebugInfo::GetStackDepth(Address pc) { return impl_->GetStackDepth(pc); } -WasmValue DebugInfo::GetStackValue(int index, Isolate* isolate, Address pc, - Address fp, Address debug_break_fp) { - return impl_->GetStackValue(index, isolate, pc, fp, debug_break_fp); +WasmValue DebugInfo::GetStackValue(int index, Address pc, Address fp, + Address debug_break_fp) { + return impl_->GetStackValue(index, pc, fp, debug_break_fp); } Handle<JSObject> DebugInfo::GetLocalScopeObject(Isolate* isolate, Address pc, @@ -859,7 +901,9 @@ void DebugInfo::PrepareStep(Isolate* isolate, StackFrameId break_frame_id) { impl_->PrepareStep(isolate, break_frame_id); } -void DebugInfo::ClearStepping() { impl_->ClearStepping(); } +void DebugInfo::ClearStepping(Isolate* isolate) { + impl_->ClearStepping(isolate); +} bool DebugInfo::IsStepping(WasmFrame* frame) { return impl_->IsStepping(frame); @@ -874,65 +918,16 @@ void DebugInfo::RemoveDebugSideTables(Vector<WasmCode* const> code) { impl_->RemoveDebugSideTables(code); } -} // namespace wasm - -Handle<WasmDebugInfo> WasmDebugInfo::New(Handle<WasmInstanceObject> instance) { - DCHECK(!instance->has_debug_info()); - Factory* factory = instance->GetIsolate()->factory(); - Handle<Cell> stack_cell = factory->NewCell(factory->empty_fixed_array()); - Handle<WasmDebugInfo> debug_info = Handle<WasmDebugInfo>::cast( - factory->NewStruct(WASM_DEBUG_INFO_TYPE, AllocationType::kOld)); - debug_info->set_wasm_instance(*instance); - debug_info->set_interpreter_reference_stack(*stack_cell); - instance->set_debug_info(*debug_info); - return debug_info; +DebugSideTable* DebugInfo::GetDebugSideTableIfExists( + const WasmCode* code) const { + return impl_->GetDebugSideTableIfExists(code); } -wasm::WasmInterpreter* WasmDebugInfo::SetupForTesting( - Handle<WasmInstanceObject> instance_obj) { - Handle<WasmDebugInfo> debug_info = WasmDebugInfo::New(instance_obj); - Isolate* isolate = instance_obj->GetIsolate(); - // Use the maximum stack size to estimate the maximum size of the interpreter. - // The interpreter keeps its own stack internally, and the size of the stack - // should dominate the overall size of the interpreter. We multiply by '2' to - // account for the growing strategy for the backing store of the stack. - size_t interpreter_size = FLAG_stack_size * KB * 2; - auto interp_handle = Managed<wasm::InterpreterHandle>::Allocate( - isolate, interpreter_size, isolate, debug_info); - debug_info->set_interpreter_handle(*interp_handle); - return interp_handle->raw()->interpreter(); +void DebugInfo::RemoveIsolate(Isolate* isolate) { + return impl_->RemoveIsolate(isolate); } -// static -Handle<Code> WasmDebugInfo::GetCWasmEntry(Handle<WasmDebugInfo> debug_info, - const wasm::FunctionSig* sig) { - Isolate* isolate = debug_info->GetIsolate(); - DCHECK_EQ(debug_info->has_c_wasm_entries(), - debug_info->has_c_wasm_entry_map()); - if (!debug_info->has_c_wasm_entries()) { - auto entries = isolate->factory()->NewFixedArray(4, AllocationType::kOld); - debug_info->set_c_wasm_entries(*entries); - size_t map_size = 0; // size estimate not so important here. - auto managed_map = Managed<wasm::SignatureMap>::Allocate(isolate, map_size); - debug_info->set_c_wasm_entry_map(*managed_map); - } - Handle<FixedArray> entries(debug_info->c_wasm_entries(), isolate); - wasm::SignatureMap* map = debug_info->c_wasm_entry_map().raw(); - int32_t index = map->Find(*sig); - if (index == -1) { - index = static_cast<int32_t>(map->FindOrInsert(*sig)); - if (index == entries->length()) { - entries = - isolate->factory()->CopyFixedArrayAndGrow(entries, entries->length()); - debug_info->set_c_wasm_entries(*entries); - } - DCHECK(entries->get(index).IsUndefined(isolate)); - Handle<Code> new_entry_code = - compiler::CompileCWasmEntry(isolate, sig).ToHandleChecked(); - entries->set(index, *new_entry_code); - } - return handle(Code::cast(entries->get(index)), isolate); -} +} // namespace wasm namespace { |