summaryrefslogtreecommitdiff
path: root/chromium/v8/src/wasm/wasm-debug.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/v8/src/wasm/wasm-debug.cc')
-rw-r--r--chromium/v8/src/wasm/wasm-debug.cc725
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 {