diff --git a/BUILD.gn b/BUILD.gn index 5b0f8fa75a..98ebd46146 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -2540,6 +2540,8 @@ v8_source_set("v8_base_without_compiler") { "src/debug/debug-stack-trace-iterator.h", "src/debug/debug-type-profile.cc", "src/debug/debug-type-profile.h", + "src/debug/debug-wasm-support.cc", + "src/debug/debug-wasm-support.h", "src/debug/debug.cc", "src/debug/debug.h", "src/debug/interface-types.h", diff --git a/src/debug/debug-evaluate.cc b/src/debug/debug-evaluate.cc index 454203a0ba..11cfa37d5c 100644 --- a/src/debug/debug-evaluate.cc +++ b/src/debug/debug-evaluate.cc @@ -10,6 +10,7 @@ #include "src/common/globals.h" #include "src/debug/debug-frames.h" #include "src/debug/debug-scopes.h" +#include "src/debug/debug-wasm-support.h" #include "src/debug/debug.h" #include "src/execution/frames-inl.h" #include "src/execution/isolate-inl.h" @@ -17,7 +18,6 @@ #include "src/interpreter/bytecodes.h" #include "src/objects/contexts.h" #include "src/snapshot/snapshot.h" -#include "src/wasm/wasm-debug.h" namespace v8 { namespace internal { @@ -100,7 +100,7 @@ MaybeHandle DebugEvaluate::Local(Isolate* isolate, WasmFrame* frame = WasmFrame::cast(it.frame()); Handle outer_info( isolate->native_context()->empty_function().shared(), isolate); - Handle context_extension = wasm::GetJSDebugProxy(frame); + Handle context_extension = GetJSDebugProxy(frame); Handle scope_info = ScopeInfo::CreateForWithScope(isolate, Handle::null()); Handle context = isolate->factory()->NewWithContext( diff --git a/src/debug/debug-scope-iterator.cc b/src/debug/debug-scope-iterator.cc index 044211a318..05b1230bac 100644 --- a/src/debug/debug-scope-iterator.cc +++ b/src/debug/debug-scope-iterator.cc @@ -5,6 +5,7 @@ #include "src/debug/debug-scope-iterator.h" #include "src/api/api-inl.h" +#include "src/debug/debug-wasm-support.h" #include "src/debug/debug.h" #include "src/debug/liveedit.h" #include "src/execution/frames-inl.h" @@ -173,19 +174,15 @@ v8::Local DebugWasmScopeIterator::GetObject() { case debug::ScopeIterator::ScopeTypeModule: { Handle instance = FrameSummary::GetTop(frame_).AsWasm().wasm_instance(); - return Utils::ToLocal(wasm::GetModuleScopeObject(instance)); + return Utils::ToLocal(GetModuleScopeObject(instance)); } case debug::ScopeIterator::ScopeTypeLocal: { DCHECK(frame_->is_inspectable()); - wasm::DebugInfo* debug_info = frame_->native_module()->GetDebugInfo(); - return Utils::ToLocal(debug_info->GetLocalScopeObject( - isolate_, frame_->pc(), frame_->fp(), frame_->callee_fp())); + return Utils::ToLocal(GetLocalScopeObject(frame_)); } case debug::ScopeIterator::ScopeTypeWasmExpressionStack: { DCHECK(frame_->is_inspectable()); - wasm::DebugInfo* debug_info = frame_->native_module()->GetDebugInfo(); - return Utils::ToLocal(debug_info->GetStackScopeObject( - isolate_, frame_->pc(), frame_->fp(), frame_->callee_fp())); + return Utils::ToLocal(GetStackScopeObject(frame_)); } default: return {}; diff --git a/src/debug/debug-wasm-support.cc b/src/debug/debug-wasm-support.cc new file mode 100644 index 0000000000..b436c46f07 --- /dev/null +++ b/src/debug/debug-wasm-support.cc @@ -0,0 +1,810 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/debug/debug-wasm-support.h" + +#include "src/api/api-inl.h" +#include "src/api/api-natives.h" +#include "src/execution/frames-inl.h" +#include "src/objects/property-descriptor.h" +#include "src/wasm/wasm-debug.h" +#include "src/wasm/wasm-objects-inl.h" +#include "src/wasm/wasm-value.h" + +namespace v8 { +namespace internal { +namespace { + +template +Handle PrintFToOneByteString(Isolate* isolate, const char* format, + Args... args) { + // Maximum length of a formatted value name ("arg#%d", "local#%d", + // "global#%d", i32 constants, i64 constants), including null character. + static constexpr int kMaxStrLen = 21; + EmbeddedVector value; + int len = SNPrintF(value, format, args...); + CHECK(len > 0 && len < value.length()); + Vector name = + Vector::cast(value.SubVector(0, len)); + return internal + ? isolate->factory()->InternalizeString(name) + : isolate->factory()->NewStringFromOneByte(name).ToHandleChecked(); +} + +// Convert a WasmValue to an appropriate JS representation. +Handle WasmValueToObject(Isolate* isolate, wasm::WasmValue value) { + auto* factory = isolate->factory(); + switch (value.type().kind()) { + case wasm::ValueType::kI32: + return factory->NewNumberFromInt(value.to_i32()); + case wasm::ValueType::kI64: + return BigInt::FromInt64(isolate, value.to_i64()); + case wasm::ValueType::kF32: + return factory->NewNumber(value.to_f32()); + case wasm::ValueType::kF64: + return factory->NewNumber(value.to_f64()); + case wasm::ValueType::kS128: { + wasm::Simd128 s128 = value.to_s128(); + Handle buffer; + if (!factory + ->NewJSArrayBufferAndBackingStore( + kSimd128Size, InitializedFlag::kUninitialized) + .ToHandle(&buffer)) { + isolate->FatalProcessOutOfHeapMemory( + "failed to allocate backing store"); + } + + base::Memcpy(buffer->allocation_base(), s128.bytes(), + buffer->byte_length()); + auto array = factory->NewJSTypedArray(kExternalUint8Array, buffer, 0, + kSimd128Size); + JSObject::SetPrototype(array, factory->null_value(), false, kDontThrow) + .Check(); + return array; + } + case wasm::ValueType::kRef: + return value.to_externref(); + default: + break; + } + return factory->undefined_value(); +} + +MaybeHandle GetLocalNameString(Isolate* isolate, + wasm::NativeModule* native_module, + int func_index, int local_index) { + wasm::WireBytesRef name_ref = + native_module->GetDebugInfo()->GetLocalName(func_index, local_index); + wasm::ModuleWireBytes wire_bytes{native_module->wire_bytes()}; + // Bounds were checked during decoding. + DCHECK(wire_bytes.BoundsCheck(name_ref)); + wasm::WasmName name = wire_bytes.GetNameOrNull(name_ref); + if (name.size() == 0) return {}; + return isolate->factory()->NewStringFromUtf8(name); +} + +} // namespace + +Handle GetModuleScopeObject(Handle instance) { + Isolate* isolate = instance->GetIsolate(); + Handle module_scope_object = + isolate->factory()->NewJSObjectWithNullProto(); + + Handle instance_name = + isolate->factory()->InternalizeString(StaticCharVector("instance")); + JSObject::AddProperty(isolate, module_scope_object, instance_name, instance, + NONE); + + Handle module_object(instance->module_object(), isolate); + Handle module_name = + isolate->factory()->InternalizeString(StaticCharVector("module")); + JSObject::AddProperty(isolate, module_scope_object, module_name, + module_object, NONE); + + if (instance->has_memory_object()) { + Handle name; + // TODO(duongn): extend the logic when multiple memories are supported. + const uint32_t memory_index = 0; + if (!WasmInstanceObject::GetMemoryNameOrNull(isolate, instance, + memory_index) + .ToHandle(&name)) { + const char* label = "memory%d"; + name = PrintFToOneByteString(isolate, label, memory_index); + } + Handle memory_object(instance->memory_object(), isolate); + JSObject::AddProperty(isolate, module_scope_object, name, memory_object, + NONE); + } + + auto& globals = instance->module()->globals; + if (globals.size() > 0) { + Handle globals_obj = + isolate->factory()->NewJSObjectWithNullProto(); + Handle globals_name = + isolate->factory()->InternalizeString(StaticCharVector("globals")); + JSObject::AddProperty(isolate, module_scope_object, globals_name, + globals_obj, NONE); + + for (uint32_t i = 0; i < globals.size(); ++i) { + Handle name; + if (!WasmInstanceObject::GetGlobalNameOrNull(isolate, instance, i) + .ToHandle(&name)) { + const char* label = "global%d"; + name = PrintFToOneByteString(isolate, label, i); + } + wasm::WasmValue value = + WasmInstanceObject::GetGlobalValue(instance, globals[i]); + Handle value_obj = WasmValueToObject(isolate, value); + LookupIterator it(isolate, globals_obj, name, globals_obj, + LookupIterator::OWN_SKIP_INTERCEPTOR); + JSObject::CreateDataProperty(&it, value_obj).Check(); + } + } + return module_scope_object; +} + +Handle GetLocalScopeObject(WasmFrame* frame) { + Isolate* isolate = frame->isolate(); + auto native_module = frame->native_module(); + auto debug_info = native_module->GetDebugInfo(); + auto function = debug_info->GetFunctionAtAddress(frame->pc()); + Handle local_scope_object = + isolate->factory()->NewJSObjectWithNullProto(); + + // Fill parameters and locals. + int num_locals = debug_info->GetNumLocals(frame->pc()); + DCHECK_LE(static_cast(function.sig->parameter_count()), num_locals); + for (int i = 0; i < num_locals; ++i) { + Handle name; + if (!GetLocalNameString(isolate, native_module, function.func_index, i) + .ToHandle(&name)) { + name = PrintFToOneByteString(isolate, "var%d", i); + } + wasm::WasmValue value = debug_info->GetLocalValue( + i, frame->pc(), frame->fp(), frame->callee_fp()); + Handle value_obj = WasmValueToObject(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 GetStackScopeObject(WasmFrame* frame) { + Isolate* isolate = frame->isolate(); + auto native_module = frame->native_module(); + auto debug_info = native_module->GetDebugInfo(); + Handle stack_scope_obj = + isolate->factory()->NewJSObjectWithNullProto(); + + // Fill stack values. + // 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. + int value_count = debug_info->GetStackDepth(frame->pc()); + for (int i = 0; i < value_count; ++i) { + wasm::WasmValue value = debug_info->GetStackValue( + i, frame->pc(), frame->fp(), frame->callee_fp()); + Handle value_obj = WasmValueToObject(isolate, value); + JSObject::AddDataElement(stack_scope_obj, i, value_obj, NONE); + } + return stack_scope_obj; +} + +namespace { + +// Helper for unpacking a maybe name that makes a default with an index if +// the name is empty. If the name is not empty, it's prefixed with a $. +Handle GetNameOrDefault(Isolate* isolate, + MaybeHandle maybe_name, + const char* default_name_prefix, + uint32_t index) { + Handle name; + if (maybe_name.ToHandle(&name)) { + name = isolate->factory() + ->NewConsString( + isolate->factory()->NewStringFromAsciiChecked("$"), name) + .ToHandleChecked(); + return isolate->factory()->InternalizeString(name); + } + EmbeddedVector value; + int len = SNPrintF(value, "%s%u", default_name_prefix, index); + return isolate->factory()->InternalizeString(value.SubVector(0, len)); +} + +enum DebugProxyId { + kFunctionsProxy, + kGlobalsProxy, + kMemoriesProxy, + kTablesProxy, + kLastInstanceProxyId = kTablesProxy, + + kContextProxy, + kLocalsProxy, + kStackProxy, + kLastProxyId = kStackProxy, + + kNumProxies = kLastProxyId + 1, + kNumInstanceProxies = kLastInstanceProxyId + 1 +}; + +// Creates a FixedArray with the given |length| as cache on-demand on +// the |object|, stored under the |wasm_debug_proxy_cache_symbol|. +// This is currently used to cache the debug proxy object maps on the +// JSGlobalObject (per native context), and various debug proxy objects +// (functions, globals, tables, and memories) on the WasmInstanceObject. +Handle GetOrCreateDebugProxyCache(Isolate* isolate, + Handle object, + int length) { + Handle cache; + Handle symbol = isolate->factory()->wasm_debug_proxy_cache_symbol(); + if (!Object::GetProperty(isolate, object, symbol).ToHandle(&cache) || + cache->IsUndefined(isolate)) { + cache = isolate->factory()->NewFixedArrayWithHoles(length); + Object::SetProperty(isolate, object, symbol, cache).Check(); + } else { + DCHECK_EQ(length, Handle::cast(cache)->length()); + } + return Handle::cast(cache); +} + +// Creates a Map for the given debug proxy |id| using the |create_template_fn| +// on-demand and caches this map in the global object. The map is derived from +// the FunctionTemplate returned by |create_template_fn| and has it's prototype +// set to |null| and is marked non-extensible. +Handle GetOrCreateDebugProxyMap( + Isolate* isolate, DebugProxyId id, + v8::Local (*create_template_fn)(v8::Isolate*)) { + Handle maps = GetOrCreateDebugProxyCache( + isolate, isolate->global_object(), kNumProxies); + if (!maps->is_the_hole(isolate, id)) { + return handle(Map::cast(maps->get(id)), isolate); + } + auto tmp = (*create_template_fn)(reinterpret_cast(isolate)); + auto fun = ApiNatives::InstantiateFunction(Utils::OpenHandle(*tmp)) + .ToHandleChecked(); + auto map = JSFunction::GetDerivedMap(isolate, fun, fun).ToHandleChecked(); + Map::SetPrototype(isolate, map, isolate->factory()->null_value()); + map->set_is_extensible(false); + maps->set(id, *map); + return map; +} + +// Base class for debug proxies, offers indexed access. The subclasses +// need to implement |Count| and |Get| methods appropriately. +template +struct IndexedDebugProxy { + static constexpr DebugProxyId kId = id; + + static Handle Create(Isolate* isolate, Handle provider) { + auto object_map = + GetOrCreateDebugProxyMap(isolate, kId, &T::CreateTemplate); + auto object = isolate->factory()->NewJSObjectFromMap(object_map); + object->SetEmbedderField(kProviderField, *provider); + return object; + } + + enum { + kProviderField, + kFieldCount, + }; + + static v8::Local CreateTemplate(v8::Isolate* isolate) { + Local templ = v8::FunctionTemplate::New(isolate); + templ->SetClassName( + v8::String::NewFromUtf8(isolate, T::kClassName).ToLocalChecked()); + templ->InstanceTemplate()->SetInternalFieldCount(T::kFieldCount); + templ->InstanceTemplate()->SetHandler( + v8::IndexedPropertyHandlerConfiguration( + &T::IndexedGetter, {}, &T::IndexedQuery, {}, &T::IndexedEnumerator, + {}, &T::IndexedDescriptor, {}, + v8::PropertyHandlerFlags::kHasNoSideEffect)); + return templ; + } + + template + static Isolate* GetIsolate(const PropertyCallbackInfo& info) { + return reinterpret_cast(info.GetIsolate()); + } + + template + static Handle GetHolder(const PropertyCallbackInfo& info) { + return Handle::cast(Utils::OpenHandle(*info.Holder())); + } + + static Handle GetProvider(Handle holder, + Isolate* isolate) { + return handle(Provider::cast(holder->GetEmbedderField(kProviderField)), + isolate); + } + + template + static Handle GetProvider(const PropertyCallbackInfo& info) { + return GetProvider(GetHolder(info), GetIsolate(info)); + } + + static void IndexedGetter(uint32_t index, + const PropertyCallbackInfo& info) { + auto isolate = GetIsolate(info); + auto provider = GetProvider(info); + if (index < T::Count(isolate, provider)) { + auto value = T::Get(isolate, provider, index); + info.GetReturnValue().Set(Utils::ToLocal(value)); + } + } + + static void IndexedDescriptor(uint32_t index, + const PropertyCallbackInfo& info) { + auto isolate = GetIsolate(info); + auto provider = GetProvider(info); + if (index < T::Count(isolate, provider)) { + PropertyDescriptor descriptor; + descriptor.set_configurable(false); + descriptor.set_enumerable(true); + descriptor.set_writable(false); + descriptor.set_value(T::Get(isolate, provider, index)); + info.GetReturnValue().Set(Utils::ToLocal(descriptor.ToObject(isolate))); + } + } + + static void IndexedQuery(uint32_t index, + const PropertyCallbackInfo& info) { + if (index < T::Count(GetIsolate(info), GetProvider(info))) { + info.GetReturnValue().Set(Integer::New( + info.GetIsolate(), + PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly)); + } + } + + static void IndexedEnumerator(const PropertyCallbackInfo& info) { + auto isolate = GetIsolate(info); + auto count = T::Count(isolate, GetProvider(info)); + auto indices = isolate->factory()->NewFixedArray(count); + for (uint32_t index = 0; index < count; ++index) { + indices->set(index, Smi::FromInt(index)); + } + info.GetReturnValue().Set( + Utils::ToLocal(isolate->factory()->NewJSArrayWithElements( + indices, PACKED_SMI_ELEMENTS))); + } +}; + +// Extends |IndexedDebugProxy| with named access, where the names are computed +// on-demand, and all names are assumed to start with a dollar char ($). This +// is important in order to scale to Wasm modules with hundreds of thousands +// of functions in them. +template +struct NamedDebugProxy : IndexedDebugProxy { + enum { + kProviderField, + kNameTableField, + kFieldCount, + }; + + static v8::Local CreateTemplate(v8::Isolate* isolate) { + auto templ = IndexedDebugProxy::CreateTemplate(isolate); + templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( + &T::NamedGetter, {}, &T::NamedQuery, {}, &T::NamedEnumerator, {}, + &T::NamedDescriptor, {}, v8::PropertyHandlerFlags::kHasNoSideEffect)); + return templ; + } + + static void IndexedEnumerator(const PropertyCallbackInfo& info) { + info.GetReturnValue().Set(v8::Array::New(info.GetIsolate())); + } + + static Handle GetNameTable(Handle holder, + Isolate* isolate) { + Handle table_or_undefined(holder->GetEmbedderField(kNameTableField), + isolate); + if (!table_or_undefined->IsUndefined(isolate)) { + return Handle::cast(table_or_undefined); + } + auto provider = T::GetProvider(holder, isolate); + auto count = T::Count(isolate, provider); + auto table = NameDictionary::New(isolate, count); + for (uint32_t index = 0; index < count; ++index) { + HandleScope scope(isolate); + auto key = T::GetName(isolate, provider, index); + if (table->FindEntry(isolate, key).is_found()) continue; + Handle value(Smi::FromInt(index), isolate); + table = NameDictionary::Add(isolate, table, key, value, + PropertyDetails::Empty()); + } + holder->SetEmbedderField(kNameTableField, *table); + return table; + } + + template + static base::Optional FindName( + Local name, const PropertyCallbackInfo& info) { + if (!name->IsString()) return {}; + auto name_str = Utils::OpenHandle(*name.As()); + if (name_str->length() == 0 || name_str->Get(0) != '$') return {}; + auto isolate = T::GetIsolate(info); + auto table = GetNameTable(T::GetHolder(info), isolate); + auto entry = table->FindEntry(isolate, name_str); + if (entry.is_found()) return Smi::ToInt(table->ValueAt(entry)); + return {}; + } + + static void NamedGetter(Local name, + const PropertyCallbackInfo& info) { + if (auto index = FindName(name, info)) T::IndexedGetter(*index, info); + } + + static void NamedQuery(Local name, + const PropertyCallbackInfo& info) { + if (auto index = FindName(name, info)) T::IndexedQuery(*index, info); + } + + static void NamedDescriptor(Local name, + const PropertyCallbackInfo& info) { + if (auto index = FindName(name, info)) T::IndexedDescriptor(*index, info); + } + + static void NamedEnumerator(const PropertyCallbackInfo& info) { + auto isolate = T::GetIsolate(info); + auto table = GetNameTable(T::GetHolder(info), isolate); + auto names = NameDictionary::IterationIndices(isolate, table); + for (int i = 0; i < names->length(); ++i) { + InternalIndex entry(Smi::ToInt(names->get(i))); + names->set(i, table->NameAt(entry)); + } + info.GetReturnValue().Set(Utils::ToLocal( + isolate->factory()->NewJSArrayWithElements(names, PACKED_ELEMENTS))); + } +}; + +// This class implements the "functions" proxy. +struct FunctionsProxy : NamedDebugProxy { + static constexpr char const* kClassName = "Functions"; + + static uint32_t Count(Isolate* isolate, Handle instance) { + return static_cast(instance->module()->functions.size()); + } + + static Handle Get(Isolate* isolate, + Handle instance, + uint32_t index) { + return WasmInstanceObject::GetOrCreateWasmExternalFunction(isolate, + instance, index); + } + + static Handle GetName(Isolate* isolate, + Handle instance, + uint32_t index) { + wasm::ModuleWireBytes wire_bytes( + instance->module_object().native_module()->wire_bytes()); + auto* module = instance->module(); + wasm::WireBytesRef name_ref = + module->lazily_generated_names.LookupFunctionName( + wire_bytes, index, VectorOf(module->export_table)); + Vector name_vec = wire_bytes.GetNameOrNull(name_ref); + return GetNameOrDefault( + isolate, + name_vec.empty() ? MaybeHandle() + : isolate->factory()->NewStringFromUtf8(name_vec), + "$func", index); + } +}; + +// This class implements the "globals" proxy. +struct GlobalsProxy : NamedDebugProxy { + static constexpr char const* kClassName = "Globals"; + + static uint32_t Count(Isolate* isolate, Handle instance) { + return static_cast(instance->module()->globals.size()); + } + + static Handle Get(Isolate* isolate, + Handle instance, + uint32_t index) { + return WasmValueToObject(isolate, + WasmInstanceObject::GetGlobalValue( + instance, instance->module()->globals[index])); + } + + static Handle GetName(Isolate* isolate, + Handle instance, + uint32_t index) { + return GetNameOrDefault( + isolate, + WasmInstanceObject::GetGlobalNameOrNull(isolate, instance, index), + "$global", index); + } +}; + +// This class implements the "memories" proxy. +struct MemoriesProxy : NamedDebugProxy { + static constexpr char const* kClassName = "Memories"; + + static uint32_t Count(Isolate* isolate, Handle instance) { + return instance->has_memory_object() ? 1 : 0; + } + + static Handle Get(Isolate* isolate, + Handle instance, + uint32_t index) { + return handle(instance->memory_object(), isolate); + } + + static Handle GetName(Isolate* isolate, + Handle instance, + uint32_t index) { + return GetNameOrDefault( + isolate, + WasmInstanceObject::GetMemoryNameOrNull(isolate, instance, index), + "$memory", index); + } +}; + +// This class implements the "tables" proxy. +struct TablesProxy : NamedDebugProxy { + static constexpr char const* kClassName = "Tables"; + + static uint32_t Count(Isolate* isolate, Handle instance) { + return instance->tables().length(); + } + + static Handle Get(Isolate* isolate, + Handle instance, + uint32_t index) { + return handle(instance->tables().get(index), isolate); + } + + static Handle GetName(Isolate* isolate, + Handle instance, + uint32_t index) { + return GetNameOrDefault( + isolate, + WasmInstanceObject::GetTableNameOrNull(isolate, instance, index), + "$table", index); + } +}; + +// This class implements the "locals" proxy. +struct LocalsProxy : NamedDebugProxy { + static constexpr char const* kClassName = "Locals"; + + static Handle Create(Isolate* isolate, WasmFrame* frame) { + auto debug_info = frame->native_module()->GetDebugInfo(); + // TODO(bmeurer): Check if pc is inspectable. + int count = debug_info->GetNumLocals(frame->pc()); + auto function = debug_info->GetFunctionAtAddress(frame->pc()); + auto values = isolate->factory()->NewFixedArray(count + 2); + for (int i = 0; i < count; ++i) { + auto value = WasmValueToObject( + isolate, debug_info->GetLocalValue(i, frame->pc(), frame->fp(), + frame->callee_fp())); + values->set(i, *value); + } + values->set(count + 0, frame->wasm_instance().module_object()); + values->set(count + 1, Smi::FromInt(function.func_index)); + return NamedDebugProxy::Create(isolate, values); + } + + static uint32_t Count(Isolate* isolate, Handle values) { + return values->length() - 2; + } + + static Handle Get(Isolate* isolate, Handle values, + uint32_t index) { + return handle(values->get(index), isolate); + } + + static Handle GetName(Isolate* isolate, Handle values, + uint32_t index) { + uint32_t count = Count(isolate, values); + auto native_module = + WasmModuleObject::cast(values->get(count + 0)).native_module(); + auto function_index = Smi::ToInt(Smi::cast(values->get(count + 1))); + wasm::ModuleWireBytes module_wire_bytes(native_module->wire_bytes()); + auto name_vec = module_wire_bytes.GetNameOrNull( + native_module->GetDebugInfo()->GetLocalName(function_index, index)); + return GetNameOrDefault( + isolate, + name_vec.empty() ? MaybeHandle() + : isolate->factory()->NewStringFromUtf8(name_vec), + "$var", index); + } +}; + +// This class implements the "stack" proxy (which offers only indexed access). +struct StackProxy : IndexedDebugProxy { + static constexpr char const* kClassName = "Stack"; + + static Handle Create(Isolate* isolate, WasmFrame* frame) { + auto debug_info = + frame->wasm_instance().module_object().native_module()->GetDebugInfo(); + int count = debug_info->GetStackDepth(frame->pc()); + auto values = isolate->factory()->NewFixedArray(count); + for (int i = 0; i < count; ++i) { + auto value = WasmValueToObject( + isolate, debug_info->GetStackValue(i, frame->pc(), frame->fp(), + frame->callee_fp())); + values->set(i, *value); + } + return IndexedDebugProxy::Create(isolate, values); + } + + static uint32_t Count(Isolate* isolate, Handle values) { + return values->length(); + } + + static Handle Get(Isolate* isolate, Handle values, + uint32_t index) { + return handle(values->get(index), isolate); + } +}; + +// Creates an instance of the |Proxy| on-demand and caches that on the +// |instance|. +template +Handle GetOrCreateInstanceProxy(Isolate* isolate, + Handle instance) { + STATIC_ASSERT(Proxy::kId < kNumInstanceProxies); + Handle proxies = + GetOrCreateDebugProxyCache(isolate, instance, kNumInstanceProxies); + if (!proxies->is_the_hole(isolate, Proxy::kId)) { + return handle(JSObject::cast(proxies->get(Proxy::kId)), isolate); + } + Handle proxy = Proxy::Create(isolate, instance); + proxies->set(Proxy::kId, *proxy); + return proxy; +} + +// This class implements the debug proxy for a given Wasm frame. The debug +// proxy is used when evaluating JavaScript expressions on a wasm frame via +// the inspector |Runtime.evaluateOnCallFrame()| API and enables developers +// and extensions to inspect the WebAssembly engine state from JavaScript. +// The proxy provides the following interface: +// +// type WasmSimdValue = Uint8Array; +// type WasmValue = number | bigint | object | WasmSimdValue; +// type WasmFunction = (... args : WasmValue[]) = > WasmValue; +// interface WasmInterface { +// $globalX: WasmValue; +// $varX: WasmValue; +// $funcX(a : WasmValue /*, ...*/) : WasmValue; +// readonly $memoryX : WebAssembly.Memory; +// readonly $tableX : WebAssembly.Table; +// +// readonly instance : WebAssembly.Instance; +// readonly module : WebAssembly.Module; +// +// readonly memories : {[nameOrIndex:string | number] : WebAssembly.Memory}; +// readonly tables : {[nameOrIndex:string | number] : WebAssembly.Table}; +// readonly stack : WasmValue[]; +// readonly globals : {[nameOrIndex:string | number] : WasmValue}; +// readonly locals : {[nameOrIndex:string | number] : WasmValue}; +// readonly functions : {[nameOrIndex:string | number] : WasmFunction}; +// } +// +// The wasm index spaces memories, tables, stack, globals, locals, and +// functions are JSObjects with interceptors that lazily produce values +// either by index or by name (except for stack). +// Only the names are reported by APIs such as Object.keys() and +// Object.getOwnPropertyNames(), since the indices are not meant to be +// used interactively by developers (in Chrome DevTools), but are provided +// for WebAssembly language extensions. Also note that these JSObjects +// all have null prototypes, to not confuse context lookup and to make +// their purpose as dictionaries clear. +// +// See http://doc/1VZOJrU2VsqOZe3IUzbwQWQQSZwgGySsm5119Ust1gUA and +// http://bit.ly/devtools-wasm-entities for more details. +class ContextProxy { + public: + static Handle Create(WasmFrame* frame) { + Isolate* isolate = frame->isolate(); + auto object_map = + GetOrCreateDebugProxyMap(isolate, kContextProxy, &CreateTemplate); + auto object = isolate->factory()->NewJSObjectFromMap(object_map); + Handle instance(frame->wasm_instance(), isolate); + object->SetEmbedderField(kInstanceField, *instance); + Handle locals = LocalsProxy::Create(isolate, frame); + object->SetEmbedderField(kLocalsField, *locals); + Handle stack = StackProxy::Create(isolate, frame); + object->SetEmbedderField(kStackField, *stack); + return object; + } + + private: + enum { + kInstanceField, + kLocalsField, + kStackField, + kFieldCount, + }; + + static v8::Local CreateTemplate(v8::Isolate* isolate) { + Local templ = v8::FunctionTemplate::New(isolate); + templ->InstanceTemplate()->SetInternalFieldCount(kFieldCount); + templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( + &NamedGetter, {}, {}, {}, {}, {}, {}, {}, + static_cast( + static_cast( + v8::PropertyHandlerFlags::kOnlyInterceptStrings) | + static_cast( + v8::PropertyHandlerFlags::kHasNoSideEffect)))); + return templ; + } + + static MaybeHandle GetNamedProperty(Isolate* isolate, + Handle holder, + Handle name) { + if (name->length() == 0) return {}; + Handle instance( + WasmInstanceObject::cast(holder->GetEmbedderField(kInstanceField)), + isolate); + if (name->IsOneByteEqualTo(StaticCharVector("instance"))) { + return instance; + } + if (name->IsOneByteEqualTo(StaticCharVector("module"))) { + return handle(instance->module_object(), isolate); + } + if (name->IsOneByteEqualTo(StaticCharVector("locals"))) { + return handle(holder->GetEmbedderField(kLocalsField), isolate); + } + if (name->IsOneByteEqualTo(StaticCharVector("stack"))) { + return handle(holder->GetEmbedderField(kStackField), isolate); + } + if (name->IsOneByteEqualTo(StaticCharVector("memories"))) { + return GetOrCreateInstanceProxy(isolate, instance); + } + if (name->IsOneByteEqualTo(StaticCharVector("tables"))) { + return GetOrCreateInstanceProxy(isolate, instance); + } + if (name->IsOneByteEqualTo(StaticCharVector("globals"))) { + return GetOrCreateInstanceProxy(isolate, instance); + } + if (name->IsOneByteEqualTo(StaticCharVector("functions"))) { + return GetOrCreateInstanceProxy(isolate, instance); + } + if (name->Get(0) == '$') { + const char* kDelegateNames[] = {"memories", "locals", "tables", + "functions", "globals"}; + for (auto delegate_name : kDelegateNames) { + Handle delegate; + ASSIGN_RETURN_ON_EXCEPTION( + isolate, delegate, + JSObject::GetProperty(isolate, holder, delegate_name), Object); + if (!delegate->IsUndefined(isolate)) { + Handle value; + ASSIGN_RETURN_ON_EXCEPTION( + isolate, value, Object::GetProperty(isolate, delegate, name), + Object); + if (!value->IsUndefined(isolate)) return value; + } + } + } + return {}; + } + + static void NamedGetter(Local name, + const PropertyCallbackInfo& info) { + auto name_string = Handle::cast(Utils::OpenHandle(*name)); + auto isolate = reinterpret_cast(info.GetIsolate()); + auto holder = Handle::cast(Utils::OpenHandle(*info.Holder())); + Handle value; + if (GetNamedProperty(isolate, holder, name_string).ToHandle(&value)) { + info.GetReturnValue().Set(Utils::ToLocal(value)); + } + } +}; + +} // namespace + +Handle GetJSDebugProxy(WasmFrame* frame) { + return ContextProxy::Create(frame); +} + +} // namespace internal +} // namespace v8 diff --git a/src/debug/debug-wasm-support.h b/src/debug/debug-wasm-support.h new file mode 100644 index 0000000000..124bd8cb5b --- /dev/null +++ b/src/debug/debug-wasm-support.h @@ -0,0 +1,25 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_DEBUG_DEBUG_WASM_SUPPORT_H_ +#define V8_DEBUG_DEBUG_WASM_SUPPORT_H_ + +namespace v8 { +namespace internal { + +template +class Handle; +class JSObject; +class WasmFrame; +class WasmInstanceObject; + +Handle GetModuleScopeObject(Handle instance); +Handle GetLocalScopeObject(WasmFrame* frame); +Handle GetStackScopeObject(WasmFrame* frame); +Handle GetJSDebugProxy(WasmFrame* frame); + +} // namespace internal +} // namespace v8 + +#endif // V8_DEBUG_DEBUG_WASM_SUPPORT_H_ diff --git a/src/wasm/wasm-debug.cc b/src/wasm/wasm-debug.cc index 7644bab94d..0851bdcfed 100644 --- a/src/wasm/wasm-debug.cc +++ b/src/wasm/wasm-debug.cc @@ -7,19 +7,13 @@ #include #include -#include "src/api/api-inl.h" -#include "src/api/api-natives.h" #include "src/base/optional.h" #include "src/base/platform/wrappers.h" #include "src/codegen/assembler-inl.h" #include "src/common/assert-scope.h" #include "src/compiler/wasm-compiler.h" -#include "src/debug/debug-scopes.h" -#include "src/debug/debug.h" #include "src/execution/frames-inl.h" -#include "src/execution/isolate.h" #include "src/heap/factory.h" -#include "src/objects/property-descriptor.h" #include "src/wasm/baseline/liftoff-compiler.h" #include "src/wasm/baseline/liftoff-register.h" #include "src/wasm/module-decoder.h" @@ -39,74 +33,6 @@ namespace wasm { namespace { -template -Handle PrintFToOneByteString(Isolate* isolate, const char* format, - Args... args) { - // Maximum length of a formatted value name ("arg#%d", "local#%d", - // "global#%d", i32 constants, i64 constants), including null character. - static constexpr int kMaxStrLen = 21; - EmbeddedVector value; - int len = SNPrintF(value, format, args...); - CHECK(len > 0 && len < value.length()); - Vector name = - Vector::cast(value.SubVector(0, len)); - return internal - ? isolate->factory()->InternalizeString(name) - : isolate->factory()->NewStringFromOneByte(name).ToHandleChecked(); -} - -// Convert a WasmValue to an appropriate JS representation. -Handle WasmValueToObject(Isolate* isolate, wasm::WasmValue value) { - auto* factory = isolate->factory(); - switch (value.type().kind()) { - case wasm::ValueType::kI32: - return factory->NewNumberFromInt(value.to_i32()); - case wasm::ValueType::kI64: - return BigInt::FromInt64(isolate, value.to_i64()); - case wasm::ValueType::kF32: - return factory->NewNumber(value.to_f32()); - case wasm::ValueType::kF64: - return factory->NewNumber(value.to_f64()); - case wasm::ValueType::kS128: { - wasm::Simd128 s128 = value.to_s128(); - Handle buffer; - if (!factory - ->NewJSArrayBufferAndBackingStore( - kSimd128Size, InitializedFlag::kUninitialized) - .ToHandle(&buffer)) { - isolate->FatalProcessOutOfHeapMemory( - "failed to allocate backing store"); - } - - base::Memcpy(buffer->allocation_base(), s128.bytes(), - buffer->byte_length()); - auto array = factory->NewJSTypedArray(kExternalUint8Array, buffer, 0, - kSimd128Size); - JSObject::SetPrototype(array, factory->null_value(), false, kDontThrow) - .Check(); - return array; - } - case wasm::ValueType::kRef: - return value.to_externref(); - default: - break; - } - return factory->undefined_value(); -} - -MaybeHandle 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)); - WasmName name = wire_bytes.GetNameOrNull(name_ref); - if (name.size() == 0) return {}; - return isolate->factory()->NewStringFromUtf8(name); -} - enum ReturnLocation { kAfterBreakpoint, kAfterWasmCall }; Address FindNewPC(WasmFrame* frame, WasmCode* wasm_code, int byte_offset, @@ -181,65 +107,6 @@ void DebugSideTable::Entry::Print(std::ostream& os) const { os << " ]\n"; } -Handle GetModuleScopeObject(Handle instance) { - Isolate* isolate = instance->GetIsolate(); - - Handle module_scope_object = - isolate->factory()->NewJSObjectWithNullProto(); - - Handle instance_name = - isolate->factory()->InternalizeString(StaticCharVector("instance")); - JSObject::AddProperty(isolate, module_scope_object, instance_name, instance, - NONE); - - Handle module_object(instance->module_object(), isolate); - Handle module_name = - isolate->factory()->InternalizeString(StaticCharVector("module")); - JSObject::AddProperty(isolate, module_scope_object, module_name, - module_object, NONE); - - if (instance->has_memory_object()) { - Handle name; - // TODO(duongn): extend the logic when multiple memories are supported. - const uint32_t memory_index = 0; - if (!WasmInstanceObject::GetMemoryNameOrNull(isolate, instance, - memory_index) - .ToHandle(&name)) { - const char* label = "memory%d"; - name = PrintFToOneByteString(isolate, label, memory_index); - } - Handle memory_object(instance->memory_object(), isolate); - JSObject::AddProperty(isolate, module_scope_object, name, memory_object, - NONE); - } - - auto& globals = instance->module()->globals; - if (globals.size() > 0) { - Handle globals_obj = - isolate->factory()->NewJSObjectWithNullProto(); - Handle globals_name = - isolate->factory()->InternalizeString(StaticCharVector("globals")); - JSObject::AddProperty(isolate, module_scope_object, globals_name, - globals_obj, NONE); - - for (uint32_t i = 0; i < globals.size(); ++i) { - Handle name; - if (!WasmInstanceObject::GetGlobalNameOrNull(isolate, instance, i) - .ToHandle(&name)) { - const char* label = "global%d"; - name = PrintFToOneByteString(isolate, label, i); - } - WasmValue value = - WasmInstanceObject::GetGlobalValue(instance, globals[i]); - Handle value_obj = WasmValueToObject(isolate, value); - LookupIterator it(isolate, globals_obj, name, globals_obj, - LookupIterator::OWN_SKIP_INTERCEPTOR); - JSObject::CreateDataProperty(&it, value_obj).Check(); - } - } - return module_scope_object; -} - class DebugInfoImpl { public: explicit DebugInfoImpl(NativeModule* native_module) @@ -284,68 +151,6 @@ class DebugInfoImpl { return module->functions[scope.code->index()]; } - Handle GetLocalScopeObject(Isolate* isolate, Address pc, Address fp, - Address debug_break_fp) { - FrameInspectionScope scope(this, pc); - Handle local_scope_object = - isolate->factory()->NewJSObjectWithNullProto(); - - if (!scope.is_inspectable()) return local_scope_object; - - auto* module = native_module_->module(); - auto* function = &module->functions[scope.code->index()]; - - // Fill parameters and locals. - int num_locals = static_cast(scope.debug_side_table->num_locals()); - DCHECK_LE(static_cast(function->sig->parameter_count()), num_locals); - for (int i = 0; i < num_locals; ++i) { - Handle name; - if (!GetLocalNameString(isolate, native_module_, function->func_index, i) - .ToHandle(&name)) { - name = PrintFToOneByteString(isolate, "var%d", i); - } - WasmValue value = - GetValue(scope.debug_side_table_entry, i, fp, debug_break_fp); - Handle value_obj = WasmValueToObject(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 GetStackScopeObject(Isolate* isolate, Address pc, Address fp, - Address debug_break_fp) { - FrameInspectionScope scope(this, pc); - Handle stack_scope_obj = - isolate->factory()->NewJSObjectWithNullProto(); - - if (!scope.is_inspectable()) return stack_scope_obj; - - // Fill stack values. - // 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. - int num_locals = static_cast(scope.debug_side_table->num_locals()); - int value_count = scope.debug_side_table_entry->num_values(); - for (int i = num_locals; i < value_count; ++i) { - WasmValue value = - GetValue(scope.debug_side_table_entry, i, fp, debug_break_fp); - Handle value_obj = WasmValueToObject(isolate, value); - JSObject::AddDataElement(stack_scope_obj, - static_cast(i - num_locals), value_obj, - NONE); - } - return stack_scope_obj; - } - WireBytesRef GetLocalName(int func_index, int local_index) { base::MutexGuard guard(&mutex_); if (!local_names_) { @@ -846,18 +651,6 @@ const wasm::WasmFunction& DebugInfo::GetFunctionAtAddress(Address pc) { return impl_->GetFunctionAtAddress(pc); } -Handle DebugInfo::GetLocalScopeObject(Isolate* isolate, Address pc, - Address fp, - Address debug_break_fp) { - return impl_->GetLocalScopeObject(isolate, pc, fp, debug_break_fp); -} - -Handle DebugInfo::GetStackScopeObject(Isolate* isolate, Address pc, - Address fp, - Address debug_break_fp) { - return impl_->GetStackScopeObject(isolate, pc, fp, debug_break_fp); -} - WireBytesRef DebugInfo::GetLocalName(int func_index, int local_index) { return impl_->GetLocalName(func_index, local_index); } @@ -1246,615 +1039,5 @@ MaybeHandle WasmScript::CheckBreakPoints(Isolate* isolate, return break_points_hit; } -namespace wasm { -namespace { - -// Helper for unpacking a maybe name that makes a default with an index if -// the name is empty. If the name is not empty, it's prefixed with a $. -Handle GetNameOrDefault(Isolate* isolate, - MaybeHandle maybe_name, - const char* default_name_prefix, - uint32_t index) { - Handle name; - if (maybe_name.ToHandle(&name)) { - name = isolate->factory() - ->NewConsString( - isolate->factory()->NewStringFromAsciiChecked("$"), name) - .ToHandleChecked(); - return isolate->factory()->InternalizeString(name); - } - EmbeddedVector value; - int len = SNPrintF(value, "%s%u", default_name_prefix, index); - return isolate->factory()->InternalizeString(value.SubVector(0, len)); -} - -enum DebugProxyId { - kFunctionsProxy, - kGlobalsProxy, - kMemoriesProxy, - kTablesProxy, - kLastInstanceProxyId = kTablesProxy, - - kContextProxy, - kLocalsProxy, - kStackProxy, - kLastProxyId = kStackProxy, - - kNumProxies = kLastProxyId + 1, - kNumInstanceProxies = kLastInstanceProxyId + 1 -}; - -// Creates a FixedArray with the given |length| as cache on-demand on -// the |object|, stored under the |wasm_debug_proxy_cache_symbol|. -// This is currently used to cache the debug proxy object maps on the -// JSGlobalObject (per native context), and various debug proxy objects -// (functions, globals, tables, and memories) on the WasmInstanceObject. -Handle GetOrCreateDebugProxyCache(Isolate* isolate, - Handle object, - int length) { - Handle cache; - Handle symbol = isolate->factory()->wasm_debug_proxy_cache_symbol(); - if (!Object::GetProperty(isolate, object, symbol).ToHandle(&cache) || - cache->IsUndefined(isolate)) { - cache = isolate->factory()->NewFixedArrayWithHoles(length); - Object::SetProperty(isolate, object, symbol, cache).Check(); - } else { - DCHECK_EQ(length, Handle::cast(cache)->length()); - } - return Handle::cast(cache); -} - -// Creates a Map for the given debug proxy |id| using the |create_template_fn| -// on-demand and caches this map in the global object. The map is derived from -// the FunctionTemplate returned by |create_template_fn| and has it's prototype -// set to |null| and is marked non-extensible. -Handle GetOrCreateDebugProxyMap( - Isolate* isolate, DebugProxyId id, - v8::Local (*create_template_fn)(v8::Isolate*)) { - Handle maps = GetOrCreateDebugProxyCache( - isolate, isolate->global_object(), kNumProxies); - if (!maps->is_the_hole(isolate, id)) { - return handle(Map::cast(maps->get(id)), isolate); - } - auto tmp = (*create_template_fn)(reinterpret_cast(isolate)); - auto fun = ApiNatives::InstantiateFunction(Utils::OpenHandle(*tmp)) - .ToHandleChecked(); - auto map = JSFunction::GetDerivedMap(isolate, fun, fun).ToHandleChecked(); - Map::SetPrototype(isolate, map, isolate->factory()->null_value()); - map->set_is_extensible(false); - maps->set(id, *map); - return map; -} - -// Base class for debug proxies, offers indexed access. The subclasses -// need to implement |Count| and |Get| methods appropriately. -template -struct IndexedDebugProxy { - static constexpr DebugProxyId kId = id; - - static Handle Create(Isolate* isolate, Handle provider) { - auto object_map = - GetOrCreateDebugProxyMap(isolate, kId, &T::CreateTemplate); - auto object = isolate->factory()->NewJSObjectFromMap(object_map); - object->SetEmbedderField(kProviderField, *provider); - return object; - } - - enum { - kProviderField, - kFieldCount, - }; - - static v8::Local CreateTemplate(v8::Isolate* isolate) { - Local templ = v8::FunctionTemplate::New(isolate); - templ->SetClassName( - v8::String::NewFromUtf8(isolate, T::kClassName).ToLocalChecked()); - templ->InstanceTemplate()->SetInternalFieldCount(T::kFieldCount); - templ->InstanceTemplate()->SetHandler( - v8::IndexedPropertyHandlerConfiguration( - &T::IndexedGetter, {}, &T::IndexedQuery, {}, &T::IndexedEnumerator, - {}, &T::IndexedDescriptor, {}, - v8::PropertyHandlerFlags::kHasNoSideEffect)); - return templ; - } - - template - static Isolate* GetIsolate(const PropertyCallbackInfo& info) { - return reinterpret_cast(info.GetIsolate()); - } - - template - static Handle GetHolder(const PropertyCallbackInfo& info) { - return Handle::cast(Utils::OpenHandle(*info.Holder())); - } - - static Handle GetProvider(Handle holder, - Isolate* isolate) { - return handle(Provider::cast(holder->GetEmbedderField(kProviderField)), - isolate); - } - - template - static Handle GetProvider(const PropertyCallbackInfo& info) { - return GetProvider(GetHolder(info), GetIsolate(info)); - } - - static void IndexedGetter(uint32_t index, - const PropertyCallbackInfo& info) { - auto isolate = GetIsolate(info); - auto provider = GetProvider(info); - if (index < T::Count(isolate, provider)) { - auto value = T::Get(isolate, provider, index); - info.GetReturnValue().Set(Utils::ToLocal(value)); - } - } - - static void IndexedDescriptor(uint32_t index, - const PropertyCallbackInfo& info) { - auto isolate = GetIsolate(info); - auto provider = GetProvider(info); - if (index < T::Count(isolate, provider)) { - PropertyDescriptor descriptor; - descriptor.set_configurable(false); - descriptor.set_enumerable(true); - descriptor.set_writable(false); - descriptor.set_value(T::Get(isolate, provider, index)); - info.GetReturnValue().Set(Utils::ToLocal(descriptor.ToObject(isolate))); - } - } - - static void IndexedQuery(uint32_t index, - const PropertyCallbackInfo& info) { - if (index < T::Count(GetIsolate(info), GetProvider(info))) { - info.GetReturnValue().Set(Integer::New( - info.GetIsolate(), - PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly)); - } - } - - static void IndexedEnumerator(const PropertyCallbackInfo& info) { - auto isolate = GetIsolate(info); - auto count = T::Count(isolate, GetProvider(info)); - auto indices = isolate->factory()->NewFixedArray(count); - for (uint32_t index = 0; index < count; ++index) { - indices->set(index, Smi::FromInt(index)); - } - info.GetReturnValue().Set( - Utils::ToLocal(isolate->factory()->NewJSArrayWithElements( - indices, PACKED_SMI_ELEMENTS))); - } -}; - -// Extends |IndexedDebugProxy| with named access, where the names are computed -// on-demand, and all names are assumed to start with a dollar char ($). This -// is important in order to scale to Wasm modules with hundreds of thousands -// of functions in them. -template -struct NamedDebugProxy : IndexedDebugProxy { - enum { - kProviderField, - kNameTableField, - kFieldCount, - }; - - static v8::Local CreateTemplate(v8::Isolate* isolate) { - auto templ = IndexedDebugProxy::CreateTemplate(isolate); - templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( - &T::NamedGetter, {}, &T::NamedQuery, {}, &T::NamedEnumerator, {}, - &T::NamedDescriptor, {}, v8::PropertyHandlerFlags::kHasNoSideEffect)); - return templ; - } - - static void IndexedEnumerator(const PropertyCallbackInfo& info) { - info.GetReturnValue().Set(v8::Array::New(info.GetIsolate())); - } - - static Handle GetNameTable(Handle holder, - Isolate* isolate) { - Handle table_or_undefined(holder->GetEmbedderField(kNameTableField), - isolate); - if (!table_or_undefined->IsUndefined(isolate)) { - return Handle::cast(table_or_undefined); - } - auto provider = T::GetProvider(holder, isolate); - auto count = T::Count(isolate, provider); - auto table = NameDictionary::New(isolate, count); - for (uint32_t index = 0; index < count; ++index) { - HandleScope scope(isolate); - auto key = T::GetName(isolate, provider, index); - if (table->FindEntry(isolate, key).is_found()) continue; - Handle value(Smi::FromInt(index), isolate); - table = NameDictionary::Add(isolate, table, key, value, - PropertyDetails::Empty()); - } - holder->SetEmbedderField(kNameTableField, *table); - return table; - } - - template - static base::Optional FindName( - Local name, const PropertyCallbackInfo& info) { - if (!name->IsString()) return {}; - auto name_str = Utils::OpenHandle(*name.As()); - if (name_str->length() == 0 || name_str->Get(0) != '$') return {}; - auto isolate = T::GetIsolate(info); - auto table = GetNameTable(T::GetHolder(info), isolate); - auto entry = table->FindEntry(isolate, name_str); - if (entry.is_found()) return Smi::ToInt(table->ValueAt(entry)); - return {}; - } - - static void NamedGetter(Local name, - const PropertyCallbackInfo& info) { - if (auto index = FindName(name, info)) T::IndexedGetter(*index, info); - } - - static void NamedQuery(Local name, - const PropertyCallbackInfo& info) { - if (auto index = FindName(name, info)) T::IndexedQuery(*index, info); - } - - static void NamedDescriptor(Local name, - const PropertyCallbackInfo& info) { - if (auto index = FindName(name, info)) T::IndexedDescriptor(*index, info); - } - - static void NamedEnumerator(const PropertyCallbackInfo& info) { - auto isolate = T::GetIsolate(info); - auto table = GetNameTable(T::GetHolder(info), isolate); - auto names = NameDictionary::IterationIndices(isolate, table); - for (int i = 0; i < names->length(); ++i) { - InternalIndex entry(Smi::ToInt(names->get(i))); - names->set(i, table->NameAt(entry)); - } - info.GetReturnValue().Set(Utils::ToLocal( - isolate->factory()->NewJSArrayWithElements(names, PACKED_ELEMENTS))); - } -}; - -// This class implements the "functions" proxy. -struct FunctionsProxy : NamedDebugProxy { - static constexpr char const* kClassName = "Functions"; - - static uint32_t Count(Isolate* isolate, Handle instance) { - return static_cast(instance->module()->functions.size()); - } - - static Handle Get(Isolate* isolate, - Handle instance, - uint32_t index) { - return WasmInstanceObject::GetOrCreateWasmExternalFunction(isolate, - instance, index); - } - - static Handle GetName(Isolate* isolate, - Handle instance, - uint32_t index) { - wasm::ModuleWireBytes wire_bytes( - instance->module_object().native_module()->wire_bytes()); - auto* module = instance->module(); - wasm::WireBytesRef name_ref = - module->lazily_generated_names.LookupFunctionName( - wire_bytes, index, VectorOf(module->export_table)); - Vector name_vec = wire_bytes.GetNameOrNull(name_ref); - return GetNameOrDefault( - isolate, - name_vec.empty() ? MaybeHandle() - : isolate->factory()->NewStringFromUtf8(name_vec), - "$func", index); - } -}; - -// This class implements the "globals" proxy. -struct GlobalsProxy : NamedDebugProxy { - static constexpr char const* kClassName = "Globals"; - - static uint32_t Count(Isolate* isolate, Handle instance) { - return static_cast(instance->module()->globals.size()); - } - - static Handle Get(Isolate* isolate, - Handle instance, - uint32_t index) { - return WasmValueToObject(isolate, - WasmInstanceObject::GetGlobalValue( - instance, instance->module()->globals[index])); - } - - static Handle GetName(Isolate* isolate, - Handle instance, - uint32_t index) { - return GetNameOrDefault( - isolate, - WasmInstanceObject::GetGlobalNameOrNull(isolate, instance, index), - "$global", index); - } -}; - -// This class implements the "memories" proxy. -struct MemoriesProxy : NamedDebugProxy { - static constexpr char const* kClassName = "Memories"; - - static uint32_t Count(Isolate* isolate, Handle instance) { - return instance->has_memory_object() ? 1 : 0; - } - - static Handle Get(Isolate* isolate, - Handle instance, - uint32_t index) { - return handle(instance->memory_object(), isolate); - } - - static Handle GetName(Isolate* isolate, - Handle instance, - uint32_t index) { - return GetNameOrDefault( - isolate, - WasmInstanceObject::GetMemoryNameOrNull(isolate, instance, index), - "$memory", index); - } -}; - -// This class implements the "tables" proxy. -struct TablesProxy : NamedDebugProxy { - static constexpr char const* kClassName = "Tables"; - - static uint32_t Count(Isolate* isolate, Handle instance) { - return instance->tables().length(); - } - - static Handle Get(Isolate* isolate, - Handle instance, - uint32_t index) { - return handle(instance->tables().get(index), isolate); - } - - static Handle GetName(Isolate* isolate, - Handle instance, - uint32_t index) { - return GetNameOrDefault( - isolate, - WasmInstanceObject::GetTableNameOrNull(isolate, instance, index), - "$table", index); - } -}; - -// This class implements the "locals" proxy. -struct LocalsProxy : NamedDebugProxy { - static constexpr char const* kClassName = "Locals"; - - static Handle Create(Isolate* isolate, WasmFrame* frame) { - auto debug_info = frame->native_module()->GetDebugInfo(); - // TODO(bmeurer): Check if pc is inspectable. - int count = debug_info->GetNumLocals(frame->pc()); - auto function = debug_info->GetFunctionAtAddress(frame->pc()); - auto values = isolate->factory()->NewFixedArray(count + 2); - for (int i = 0; i < count; ++i) { - auto value = WasmValueToObject( - isolate, debug_info->GetLocalValue(i, frame->pc(), frame->fp(), - frame->callee_fp())); - values->set(i, *value); - } - values->set(count + 0, frame->wasm_instance().module_object()); - values->set(count + 1, Smi::FromInt(function.func_index)); - return NamedDebugProxy::Create(isolate, values); - } - - static uint32_t Count(Isolate* isolate, Handle values) { - return values->length() - 2; - } - - static Handle Get(Isolate* isolate, Handle values, - uint32_t index) { - return handle(values->get(index), isolate); - } - - static Handle GetName(Isolate* isolate, Handle values, - uint32_t index) { - uint32_t count = Count(isolate, values); - auto native_module = - WasmModuleObject::cast(values->get(count + 0)).native_module(); - auto function_index = Smi::ToInt(Smi::cast(values->get(count + 1))); - wasm::ModuleWireBytes module_wire_bytes(native_module->wire_bytes()); - auto name_vec = module_wire_bytes.GetNameOrNull( - native_module->GetDebugInfo()->GetLocalName(function_index, index)); - return GetNameOrDefault( - isolate, - name_vec.empty() ? MaybeHandle() - : isolate->factory()->NewStringFromUtf8(name_vec), - "$var", index); - } -}; - -// This class implements the "stack" proxy (which offers only indexed access). -struct StackProxy : IndexedDebugProxy { - static constexpr char const* kClassName = "Stack"; - - static Handle Create(Isolate* isolate, WasmFrame* frame) { - auto debug_info = - frame->wasm_instance().module_object().native_module()->GetDebugInfo(); - int count = debug_info->GetStackDepth(frame->pc()); - auto values = isolate->factory()->NewFixedArray(count); - for (int i = 0; i < count; ++i) { - auto value = WasmValueToObject( - isolate, debug_info->GetStackValue(i, frame->pc(), frame->fp(), - frame->callee_fp())); - values->set(i, *value); - } - return IndexedDebugProxy::Create(isolate, values); - } - - static uint32_t Count(Isolate* isolate, Handle values) { - return values->length(); - } - - static Handle Get(Isolate* isolate, Handle values, - uint32_t index) { - return handle(values->get(index), isolate); - } -}; - -// Creates an instance of the |Proxy| on-demand and caches that on the -// |instance|. -template -Handle GetOrCreateInstanceProxy(Isolate* isolate, - Handle instance) { - STATIC_ASSERT(Proxy::kId < kNumInstanceProxies); - Handle proxies = - GetOrCreateDebugProxyCache(isolate, instance, kNumInstanceProxies); - if (!proxies->is_the_hole(isolate, Proxy::kId)) { - return handle(JSObject::cast(proxies->get(Proxy::kId)), isolate); - } - Handle proxy = Proxy::Create(isolate, instance); - proxies->set(Proxy::kId, *proxy); - return proxy; -} - -// This class implements the debug proxy for a given Wasm frame. The debug -// proxy is used when evaluating JavaScript expressions on a wasm frame via -// the inspector |Runtime.evaluateOnCallFrame()| API and enables developers -// and extensions to inspect the WebAssembly engine state from JavaScript. -// The proxy provides the following interface: -// -// type WasmSimdValue = Uint8Array; -// type WasmValue = number | bigint | object | WasmSimdValue; -// type WasmFunction = (... args : WasmValue[]) = > WasmValue; -// interface WasmInterface { -// $globalX: WasmValue; -// $varX: WasmValue; -// $funcX(a : WasmValue /*, ...*/) : WasmValue; -// readonly $memoryX : WebAssembly.Memory; -// readonly $tableX : WebAssembly.Table; -// -// readonly instance : WebAssembly.Instance; -// readonly module : WebAssembly.Module; -// -// readonly memories : {[nameOrIndex:string | number] : WebAssembly.Memory}; -// readonly tables : {[nameOrIndex:string | number] : WebAssembly.Table}; -// readonly stack : WasmValue[]; -// readonly globals : {[nameOrIndex:string | number] : WasmValue}; -// readonly locals : {[nameOrIndex:string | number] : WasmValue}; -// readonly functions : {[nameOrIndex:string | number] : WasmFunction}; -// } -// -// The wasm index spaces memories, tables, stack, globals, locals, and -// functions are JSObjects with interceptors that lazily produce values -// either by index or by name (except for stack). -// Only the names are reported by APIs such as Object.keys() and -// Object.getOwnPropertyNames(), since the indices are not meant to be -// used interactively by developers (in Chrome DevTools), but are provided -// for WebAssembly language extensions. Also note that these JSObjects -// all have null prototypes, to not confuse context lookup and to make -// their purpose as dictionaries clear. -// -// See http://doc/1VZOJrU2VsqOZe3IUzbwQWQQSZwgGySsm5119Ust1gUA and -// http://bit.ly/devtools-wasm-entities for more details. -class ContextProxy { - public: - static Handle Create(WasmFrame* frame) { - Isolate* isolate = frame->isolate(); - auto object_map = - GetOrCreateDebugProxyMap(isolate, kContextProxy, &CreateTemplate); - auto object = isolate->factory()->NewJSObjectFromMap(object_map); - Handle instance(frame->wasm_instance(), isolate); - object->SetEmbedderField(kInstanceField, *instance); - Handle locals = LocalsProxy::Create(isolate, frame); - object->SetEmbedderField(kLocalsField, *locals); - Handle stack = StackProxy::Create(isolate, frame); - object->SetEmbedderField(kStackField, *stack); - return object; - } - - private: - enum { - kInstanceField, - kLocalsField, - kStackField, - kFieldCount, - }; - - static v8::Local CreateTemplate(v8::Isolate* isolate) { - Local templ = v8::FunctionTemplate::New(isolate); - templ->InstanceTemplate()->SetInternalFieldCount(kFieldCount); - templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( - &NamedGetter, {}, {}, {}, {}, {}, {}, {}, - static_cast( - static_cast( - v8::PropertyHandlerFlags::kOnlyInterceptStrings) | - static_cast( - v8::PropertyHandlerFlags::kHasNoSideEffect)))); - return templ; - } - - static MaybeHandle GetNamedProperty(Isolate* isolate, - Handle holder, - Handle name) { - if (name->length() == 0) return {}; - Handle instance( - WasmInstanceObject::cast(holder->GetEmbedderField(kInstanceField)), - isolate); - if (name->IsOneByteEqualTo(StaticCharVector("instance"))) { - return instance; - } - if (name->IsOneByteEqualTo(StaticCharVector("module"))) { - return handle(instance->module_object(), isolate); - } - if (name->IsOneByteEqualTo(StaticCharVector("locals"))) { - return handle(holder->GetEmbedderField(kLocalsField), isolate); - } - if (name->IsOneByteEqualTo(StaticCharVector("stack"))) { - return handle(holder->GetEmbedderField(kStackField), isolate); - } - if (name->IsOneByteEqualTo(StaticCharVector("memories"))) { - return GetOrCreateInstanceProxy(isolate, instance); - } - if (name->IsOneByteEqualTo(StaticCharVector("tables"))) { - return GetOrCreateInstanceProxy(isolate, instance); - } - if (name->IsOneByteEqualTo(StaticCharVector("globals"))) { - return GetOrCreateInstanceProxy(isolate, instance); - } - if (name->IsOneByteEqualTo(StaticCharVector("functions"))) { - return GetOrCreateInstanceProxy(isolate, instance); - } - if (name->Get(0) == '$') { - const char* kDelegateNames[] = {"memories", "locals", "tables", - "functions", "globals"}; - for (auto delegate_name : kDelegateNames) { - Handle delegate; - ASSIGN_RETURN_ON_EXCEPTION( - isolate, delegate, - JSObject::GetProperty(isolate, holder, delegate_name), Object); - if (!delegate->IsUndefined(isolate)) { - Handle value; - ASSIGN_RETURN_ON_EXCEPTION( - isolate, value, Object::GetProperty(isolate, delegate, name), - Object); - if (!value->IsUndefined(isolate)) return value; - } - } - } - return {}; - } - - static void NamedGetter(Local name, - const PropertyCallbackInfo& info) { - auto name_string = Handle::cast(Utils::OpenHandle(*name)); - auto isolate = reinterpret_cast(info.GetIsolate()); - auto holder = Handle::cast(Utils::OpenHandle(*info.Holder())); - Handle value; - if (GetNamedProperty(isolate, holder, name_string).ToHandle(&value)) { - info.GetReturnValue().Set(Utils::ToLocal(value)); - } - } -}; - -} // namespace - -Handle GetJSDebugProxy(WasmFrame* frame) { - return ContextProxy::Create(frame); -} - -} // namespace wasm } // namespace internal } // namespace v8 diff --git a/src/wasm/wasm-debug.h b/src/wasm/wasm-debug.h index f2567f018e..337578fe06 100644 --- a/src/wasm/wasm-debug.h +++ b/src/wasm/wasm-debug.h @@ -20,12 +20,9 @@ namespace internal { template class Handle; -class JSObject; -class JSProxy; template class Vector; class WasmFrame; -class WasmInstanceObject; namespace wasm { @@ -137,10 +134,6 @@ class DebugSideTable { std::vector entries_; }; -// Get the module scope for a given instance. This will contain the wasm memory -// (if the instance has a memory) and the values of all globals. -Handle GetModuleScopeObject(Handle); - // Debug info per NativeModule, created lazily on demand. // Implementation in {wasm-debug.cc} using PIMPL. class V8_EXPORT_PRIVATE DebugInfo { @@ -161,12 +154,6 @@ class V8_EXPORT_PRIVATE DebugInfo { WasmValue GetStackValue(int index, Address pc, Address fp, Address debug_break_fp); - Handle GetLocalScopeObject(Isolate*, Address pc, Address fp, - Address debug_break_fp); - - Handle GetStackScopeObject(Isolate*, Address pc, Address fp, - Address debug_break_fp); - WireBytesRef GetLocalName(int func_index, int local_index); void SetBreakpoint(int func_index, int offset, Isolate* current_isolate); @@ -195,8 +182,6 @@ class V8_EXPORT_PRIVATE DebugInfo { std::unique_ptr impl_; }; -Handle GetJSDebugProxy(WasmFrame* frame); - } // namespace wasm } // namespace internal } // namespace v8