[inspector] Move Wasm related inspector functionality to src/debug.
Previously the implementation of the scope iterator objects and the debug proxy lived in src/wasm, and they are now being moved to src/debug, to better align with the JavaScript debugging interface, which also lives in src/debug. Bug: chromium:1162229, chromium:1071432 Change-Id: I7f89ced88a1231ad6a923be6e85a93f1876a2024 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2621084 Reviewed-by: Yang Guo <yangguo@chromium.org> Reviewed-by: Clemens Backes <clemensb@chromium.org> Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Cr-Commit-Position: refs/heads/master@{#72007}
This commit is contained in:
parent
b3d09001eb
commit
55865f7706
2
BUILD.gn
2
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",
|
||||
|
@ -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<Object> DebugEvaluate::Local(Isolate* isolate,
|
||||
WasmFrame* frame = WasmFrame::cast(it.frame());
|
||||
Handle<SharedFunctionInfo> outer_info(
|
||||
isolate->native_context()->empty_function().shared(), isolate);
|
||||
Handle<JSObject> context_extension = wasm::GetJSDebugProxy(frame);
|
||||
Handle<JSObject> context_extension = GetJSDebugProxy(frame);
|
||||
Handle<ScopeInfo> scope_info =
|
||||
ScopeInfo::CreateForWithScope(isolate, Handle<ScopeInfo>::null());
|
||||
Handle<Context> context = isolate->factory()->NewWithContext(
|
||||
|
@ -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<v8::Object> DebugWasmScopeIterator::GetObject() {
|
||||
case debug::ScopeIterator::ScopeTypeModule: {
|
||||
Handle<WasmInstanceObject> 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 {};
|
||||
|
810
src/debug/debug-wasm-support.cc
Normal file
810
src/debug/debug-wasm-support.cc
Normal file
@ -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 <bool internal, typename... Args>
|
||||
Handle<String> 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<char, kMaxStrLen> value;
|
||||
int len = SNPrintF(value, format, args...);
|
||||
CHECK(len > 0 && len < value.length());
|
||||
Vector<const uint8_t> name =
|
||||
Vector<const uint8_t>::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<Object> 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<JSArrayBuffer> 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<String> 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<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);
|
||||
|
||||
Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
|
||||
Handle<String> module_name =
|
||||
isolate->factory()->InternalizeString(StaticCharVector("module"));
|
||||
JSObject::AddProperty(isolate, module_scope_object, module_name,
|
||||
module_object, NONE);
|
||||
|
||||
if (instance->has_memory_object()) {
|
||||
Handle<String> 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<true>(isolate, label, memory_index);
|
||||
}
|
||||
Handle<WasmMemoryObject> 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<JSObject> globals_obj =
|
||||
isolate->factory()->NewJSObjectWithNullProto();
|
||||
Handle<String> 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<String> name;
|
||||
if (!WasmInstanceObject::GetGlobalNameOrNull(isolate, instance, i)
|
||||
.ToHandle(&name)) {
|
||||
const char* label = "global%d";
|
||||
name = PrintFToOneByteString<true>(isolate, label, i);
|
||||
}
|
||||
wasm::WasmValue value =
|
||||
WasmInstanceObject::GetGlobalValue(instance, globals[i]);
|
||||
Handle<Object> 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<JSObject> 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<JSObject> local_scope_object =
|
||||
isolate->factory()->NewJSObjectWithNullProto();
|
||||
|
||||
// Fill parameters and locals.
|
||||
int num_locals = debug_info->GetNumLocals(frame->pc());
|
||||
DCHECK_LE(static_cast<int>(function.sig->parameter_count()), num_locals);
|
||||
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);
|
||||
}
|
||||
wasm::WasmValue value = debug_info->GetLocalValue(
|
||||
i, frame->pc(), frame->fp(), frame->callee_fp());
|
||||
Handle<Object> 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<JSObject> GetStackScopeObject(WasmFrame* frame) {
|
||||
Isolate* isolate = frame->isolate();
|
||||
auto native_module = frame->native_module();
|
||||
auto debug_info = native_module->GetDebugInfo();
|
||||
Handle<JSObject> 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<Object> 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<String> GetNameOrDefault(Isolate* isolate,
|
||||
MaybeHandle<String> maybe_name,
|
||||
const char* default_name_prefix,
|
||||
uint32_t index) {
|
||||
Handle<String> name;
|
||||
if (maybe_name.ToHandle(&name)) {
|
||||
name = isolate->factory()
|
||||
->NewConsString(
|
||||
isolate->factory()->NewStringFromAsciiChecked("$"), name)
|
||||
.ToHandleChecked();
|
||||
return isolate->factory()->InternalizeString(name);
|
||||
}
|
||||
EmbeddedVector<char, 64> 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<FixedArray> GetOrCreateDebugProxyCache(Isolate* isolate,
|
||||
Handle<Object> object,
|
||||
int length) {
|
||||
Handle<Object> cache;
|
||||
Handle<Symbol> 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<FixedArray>::cast(cache)->length());
|
||||
}
|
||||
return Handle<FixedArray>::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<Map> GetOrCreateDebugProxyMap(
|
||||
Isolate* isolate, DebugProxyId id,
|
||||
v8::Local<v8::FunctionTemplate> (*create_template_fn)(v8::Isolate*)) {
|
||||
Handle<FixedArray> 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<v8::Isolate*>(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 <typename T, DebugProxyId id, typename Provider>
|
||||
struct IndexedDebugProxy {
|
||||
static constexpr DebugProxyId kId = id;
|
||||
|
||||
static Handle<JSObject> Create(Isolate* isolate, Handle<Provider> 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<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate) {
|
||||
Local<v8::FunctionTemplate> 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 <typename V>
|
||||
static Isolate* GetIsolate(const PropertyCallbackInfo<V>& info) {
|
||||
return reinterpret_cast<Isolate*>(info.GetIsolate());
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
static Handle<JSObject> GetHolder(const PropertyCallbackInfo<V>& info) {
|
||||
return Handle<JSObject>::cast(Utils::OpenHandle(*info.Holder()));
|
||||
}
|
||||
|
||||
static Handle<Provider> GetProvider(Handle<JSObject> holder,
|
||||
Isolate* isolate) {
|
||||
return handle(Provider::cast(holder->GetEmbedderField(kProviderField)),
|
||||
isolate);
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
static Handle<Provider> GetProvider(const PropertyCallbackInfo<V>& info) {
|
||||
return GetProvider(GetHolder(info), GetIsolate(info));
|
||||
}
|
||||
|
||||
static void IndexedGetter(uint32_t index,
|
||||
const PropertyCallbackInfo<v8::Value>& 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<v8::Value>& 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<v8::Integer>& 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<v8::Array>& 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 <typename T, DebugProxyId id, typename Provider = WasmInstanceObject>
|
||||
struct NamedDebugProxy : IndexedDebugProxy<T, id, Provider> {
|
||||
enum {
|
||||
kProviderField,
|
||||
kNameTableField,
|
||||
kFieldCount,
|
||||
};
|
||||
|
||||
static v8::Local<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate) {
|
||||
auto templ = IndexedDebugProxy<T, id, Provider>::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<v8::Array>& info) {
|
||||
info.GetReturnValue().Set(v8::Array::New(info.GetIsolate()));
|
||||
}
|
||||
|
||||
static Handle<NameDictionary> GetNameTable(Handle<JSObject> holder,
|
||||
Isolate* isolate) {
|
||||
Handle<Object> table_or_undefined(holder->GetEmbedderField(kNameTableField),
|
||||
isolate);
|
||||
if (!table_or_undefined->IsUndefined(isolate)) {
|
||||
return Handle<NameDictionary>::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<Smi> value(Smi::FromInt(index), isolate);
|
||||
table = NameDictionary::Add(isolate, table, key, value,
|
||||
PropertyDetails::Empty());
|
||||
}
|
||||
holder->SetEmbedderField(kNameTableField, *table);
|
||||
return table;
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
static base::Optional<uint32_t> FindName(
|
||||
Local<v8::Name> name, const PropertyCallbackInfo<V>& info) {
|
||||
if (!name->IsString()) return {};
|
||||
auto name_str = Utils::OpenHandle(*name.As<v8::String>());
|
||||
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<v8::Name> name,
|
||||
const PropertyCallbackInfo<v8::Value>& info) {
|
||||
if (auto index = FindName(name, info)) T::IndexedGetter(*index, info);
|
||||
}
|
||||
|
||||
static void NamedQuery(Local<v8::Name> name,
|
||||
const PropertyCallbackInfo<v8::Integer>& info) {
|
||||
if (auto index = FindName(name, info)) T::IndexedQuery(*index, info);
|
||||
}
|
||||
|
||||
static void NamedDescriptor(Local<v8::Name> name,
|
||||
const PropertyCallbackInfo<v8::Value>& info) {
|
||||
if (auto index = FindName(name, info)) T::IndexedDescriptor(*index, info);
|
||||
}
|
||||
|
||||
static void NamedEnumerator(const PropertyCallbackInfo<v8::Array>& 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<FunctionsProxy, kFunctionsProxy> {
|
||||
static constexpr char const* kClassName = "Functions";
|
||||
|
||||
static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
|
||||
return static_cast<uint32_t>(instance->module()->functions.size());
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return WasmInstanceObject::GetOrCreateWasmExternalFunction(isolate,
|
||||
instance, index);
|
||||
}
|
||||
|
||||
static Handle<String> GetName(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> 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<const char> name_vec = wire_bytes.GetNameOrNull(name_ref);
|
||||
return GetNameOrDefault(
|
||||
isolate,
|
||||
name_vec.empty() ? MaybeHandle<String>()
|
||||
: isolate->factory()->NewStringFromUtf8(name_vec),
|
||||
"$func", index);
|
||||
}
|
||||
};
|
||||
|
||||
// This class implements the "globals" proxy.
|
||||
struct GlobalsProxy : NamedDebugProxy<GlobalsProxy, kGlobalsProxy> {
|
||||
static constexpr char const* kClassName = "Globals";
|
||||
|
||||
static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
|
||||
return static_cast<uint32_t>(instance->module()->globals.size());
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return WasmValueToObject(isolate,
|
||||
WasmInstanceObject::GetGlobalValue(
|
||||
instance, instance->module()->globals[index]));
|
||||
}
|
||||
|
||||
static Handle<String> GetName(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return GetNameOrDefault(
|
||||
isolate,
|
||||
WasmInstanceObject::GetGlobalNameOrNull(isolate, instance, index),
|
||||
"$global", index);
|
||||
}
|
||||
};
|
||||
|
||||
// This class implements the "memories" proxy.
|
||||
struct MemoriesProxy : NamedDebugProxy<MemoriesProxy, kMemoriesProxy> {
|
||||
static constexpr char const* kClassName = "Memories";
|
||||
|
||||
static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
|
||||
return instance->has_memory_object() ? 1 : 0;
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return handle(instance->memory_object(), isolate);
|
||||
}
|
||||
|
||||
static Handle<String> GetName(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return GetNameOrDefault(
|
||||
isolate,
|
||||
WasmInstanceObject::GetMemoryNameOrNull(isolate, instance, index),
|
||||
"$memory", index);
|
||||
}
|
||||
};
|
||||
|
||||
// This class implements the "tables" proxy.
|
||||
struct TablesProxy : NamedDebugProxy<TablesProxy, kTablesProxy> {
|
||||
static constexpr char const* kClassName = "Tables";
|
||||
|
||||
static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
|
||||
return instance->tables().length();
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return handle(instance->tables().get(index), isolate);
|
||||
}
|
||||
|
||||
static Handle<String> GetName(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return GetNameOrDefault(
|
||||
isolate,
|
||||
WasmInstanceObject::GetTableNameOrNull(isolate, instance, index),
|
||||
"$table", index);
|
||||
}
|
||||
};
|
||||
|
||||
// This class implements the "locals" proxy.
|
||||
struct LocalsProxy : NamedDebugProxy<LocalsProxy, kLocalsProxy, FixedArray> {
|
||||
static constexpr char const* kClassName = "Locals";
|
||||
|
||||
static Handle<JSObject> 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<FixedArray> values) {
|
||||
return values->length() - 2;
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate, Handle<FixedArray> values,
|
||||
uint32_t index) {
|
||||
return handle(values->get(index), isolate);
|
||||
}
|
||||
|
||||
static Handle<String> GetName(Isolate* isolate, Handle<FixedArray> 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<String>()
|
||||
: isolate->factory()->NewStringFromUtf8(name_vec),
|
||||
"$var", index);
|
||||
}
|
||||
};
|
||||
|
||||
// This class implements the "stack" proxy (which offers only indexed access).
|
||||
struct StackProxy : IndexedDebugProxy<StackProxy, kStackProxy, FixedArray> {
|
||||
static constexpr char const* kClassName = "Stack";
|
||||
|
||||
static Handle<JSObject> 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<FixedArray> values) {
|
||||
return values->length();
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate, Handle<FixedArray> 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 <typename Proxy>
|
||||
Handle<JSObject> GetOrCreateInstanceProxy(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance) {
|
||||
STATIC_ASSERT(Proxy::kId < kNumInstanceProxies);
|
||||
Handle<FixedArray> proxies =
|
||||
GetOrCreateDebugProxyCache(isolate, instance, kNumInstanceProxies);
|
||||
if (!proxies->is_the_hole(isolate, Proxy::kId)) {
|
||||
return handle(JSObject::cast(proxies->get(Proxy::kId)), isolate);
|
||||
}
|
||||
Handle<JSObject> 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<JSObject> Create(WasmFrame* frame) {
|
||||
Isolate* isolate = frame->isolate();
|
||||
auto object_map =
|
||||
GetOrCreateDebugProxyMap(isolate, kContextProxy, &CreateTemplate);
|
||||
auto object = isolate->factory()->NewJSObjectFromMap(object_map);
|
||||
Handle<WasmInstanceObject> instance(frame->wasm_instance(), isolate);
|
||||
object->SetEmbedderField(kInstanceField, *instance);
|
||||
Handle<JSObject> locals = LocalsProxy::Create(isolate, frame);
|
||||
object->SetEmbedderField(kLocalsField, *locals);
|
||||
Handle<JSObject> stack = StackProxy::Create(isolate, frame);
|
||||
object->SetEmbedderField(kStackField, *stack);
|
||||
return object;
|
||||
}
|
||||
|
||||
private:
|
||||
enum {
|
||||
kInstanceField,
|
||||
kLocalsField,
|
||||
kStackField,
|
||||
kFieldCount,
|
||||
};
|
||||
|
||||
static v8::Local<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate) {
|
||||
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
||||
templ->InstanceTemplate()->SetInternalFieldCount(kFieldCount);
|
||||
templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
||||
&NamedGetter, {}, {}, {}, {}, {}, {}, {},
|
||||
static_cast<v8::PropertyHandlerFlags>(
|
||||
static_cast<unsigned>(
|
||||
v8::PropertyHandlerFlags::kOnlyInterceptStrings) |
|
||||
static_cast<unsigned>(
|
||||
v8::PropertyHandlerFlags::kHasNoSideEffect))));
|
||||
return templ;
|
||||
}
|
||||
|
||||
static MaybeHandle<Object> GetNamedProperty(Isolate* isolate,
|
||||
Handle<JSObject> holder,
|
||||
Handle<String> name) {
|
||||
if (name->length() == 0) return {};
|
||||
Handle<WasmInstanceObject> 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<MemoriesProxy>(isolate, instance);
|
||||
}
|
||||
if (name->IsOneByteEqualTo(StaticCharVector("tables"))) {
|
||||
return GetOrCreateInstanceProxy<TablesProxy>(isolate, instance);
|
||||
}
|
||||
if (name->IsOneByteEqualTo(StaticCharVector("globals"))) {
|
||||
return GetOrCreateInstanceProxy<GlobalsProxy>(isolate, instance);
|
||||
}
|
||||
if (name->IsOneByteEqualTo(StaticCharVector("functions"))) {
|
||||
return GetOrCreateInstanceProxy<FunctionsProxy>(isolate, instance);
|
||||
}
|
||||
if (name->Get(0) == '$') {
|
||||
const char* kDelegateNames[] = {"memories", "locals", "tables",
|
||||
"functions", "globals"};
|
||||
for (auto delegate_name : kDelegateNames) {
|
||||
Handle<Object> delegate;
|
||||
ASSIGN_RETURN_ON_EXCEPTION(
|
||||
isolate, delegate,
|
||||
JSObject::GetProperty(isolate, holder, delegate_name), Object);
|
||||
if (!delegate->IsUndefined(isolate)) {
|
||||
Handle<Object> value;
|
||||
ASSIGN_RETURN_ON_EXCEPTION(
|
||||
isolate, value, Object::GetProperty(isolate, delegate, name),
|
||||
Object);
|
||||
if (!value->IsUndefined(isolate)) return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static void NamedGetter(Local<v8::Name> name,
|
||||
const PropertyCallbackInfo<v8::Value>& info) {
|
||||
auto name_string = Handle<String>::cast(Utils::OpenHandle(*name));
|
||||
auto isolate = reinterpret_cast<Isolate*>(info.GetIsolate());
|
||||
auto holder = Handle<JSObject>::cast(Utils::OpenHandle(*info.Holder()));
|
||||
Handle<Object> value;
|
||||
if (GetNamedProperty(isolate, holder, name_string).ToHandle(&value)) {
|
||||
info.GetReturnValue().Set(Utils::ToLocal(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Handle<JSObject> GetJSDebugProxy(WasmFrame* frame) {
|
||||
return ContextProxy::Create(frame);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
25
src/debug/debug-wasm-support.h
Normal file
25
src/debug/debug-wasm-support.h
Normal file
@ -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 <typename T>
|
||||
class Handle;
|
||||
class JSObject;
|
||||
class WasmFrame;
|
||||
class WasmInstanceObject;
|
||||
|
||||
Handle<JSObject> GetModuleScopeObject(Handle<WasmInstanceObject> instance);
|
||||
Handle<JSObject> GetLocalScopeObject(WasmFrame* frame);
|
||||
Handle<JSObject> GetStackScopeObject(WasmFrame* frame);
|
||||
Handle<JSObject> GetJSDebugProxy(WasmFrame* frame);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_DEBUG_DEBUG_WASM_SUPPORT_H_
|
@ -7,19 +7,13 @@
|
||||
#include <iomanip>
|
||||
#include <unordered_map>
|
||||
|
||||
#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 <bool internal, typename... Args>
|
||||
Handle<String> 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<char, kMaxStrLen> value;
|
||||
int len = SNPrintF(value, format, args...);
|
||||
CHECK(len > 0 && len < value.length());
|
||||
Vector<const uint8_t> name =
|
||||
Vector<const uint8_t>::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<Object> 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<JSArrayBuffer> 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<String> GetLocalNameString(Isolate* isolate,
|
||||
NativeModule* native_module,
|
||||
int func_index, int local_index) {
|
||||
WireBytesRef name_ref =
|
||||
native_module->GetDebugInfo()->GetLocalName(func_index, local_index);
|
||||
ModuleWireBytes wire_bytes{native_module->wire_bytes()};
|
||||
// Bounds were checked during decoding.
|
||||
DCHECK(wire_bytes.BoundsCheck(name_ref));
|
||||
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<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);
|
||||
|
||||
Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
|
||||
Handle<String> module_name =
|
||||
isolate->factory()->InternalizeString(StaticCharVector("module"));
|
||||
JSObject::AddProperty(isolate, module_scope_object, module_name,
|
||||
module_object, NONE);
|
||||
|
||||
if (instance->has_memory_object()) {
|
||||
Handle<String> 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<true>(isolate, label, memory_index);
|
||||
}
|
||||
Handle<WasmMemoryObject> 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<JSObject> globals_obj =
|
||||
isolate->factory()->NewJSObjectWithNullProto();
|
||||
Handle<String> 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<String> name;
|
||||
if (!WasmInstanceObject::GetGlobalNameOrNull(isolate, instance, i)
|
||||
.ToHandle(&name)) {
|
||||
const char* label = "global%d";
|
||||
name = PrintFToOneByteString<true>(isolate, label, i);
|
||||
}
|
||||
WasmValue value =
|
||||
WasmInstanceObject::GetGlobalValue(instance, globals[i]);
|
||||
Handle<Object> 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<JSObject> GetLocalScopeObject(Isolate* isolate, Address pc, Address fp,
|
||||
Address debug_break_fp) {
|
||||
FrameInspectionScope scope(this, pc);
|
||||
Handle<JSObject> 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<int>(scope.debug_side_table->num_locals());
|
||||
DCHECK_LE(static_cast<int>(function->sig->parameter_count()), num_locals);
|
||||
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 = 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<JSObject> GetStackScopeObject(Isolate* isolate, Address pc, Address fp,
|
||||
Address debug_break_fp) {
|
||||
FrameInspectionScope scope(this, pc);
|
||||
Handle<JSObject> 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<int>(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<Object> value_obj = WasmValueToObject(isolate, value);
|
||||
JSObject::AddDataElement(stack_scope_obj,
|
||||
static_cast<uint32_t>(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<JSObject> DebugInfo::GetLocalScopeObject(Isolate* isolate, Address pc,
|
||||
Address fp,
|
||||
Address debug_break_fp) {
|
||||
return impl_->GetLocalScopeObject(isolate, pc, fp, debug_break_fp);
|
||||
}
|
||||
|
||||
Handle<JSObject> 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<FixedArray> 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<String> GetNameOrDefault(Isolate* isolate,
|
||||
MaybeHandle<String> maybe_name,
|
||||
const char* default_name_prefix,
|
||||
uint32_t index) {
|
||||
Handle<String> name;
|
||||
if (maybe_name.ToHandle(&name)) {
|
||||
name = isolate->factory()
|
||||
->NewConsString(
|
||||
isolate->factory()->NewStringFromAsciiChecked("$"), name)
|
||||
.ToHandleChecked();
|
||||
return isolate->factory()->InternalizeString(name);
|
||||
}
|
||||
EmbeddedVector<char, 64> 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<FixedArray> GetOrCreateDebugProxyCache(Isolate* isolate,
|
||||
Handle<Object> object,
|
||||
int length) {
|
||||
Handle<Object> cache;
|
||||
Handle<Symbol> 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<FixedArray>::cast(cache)->length());
|
||||
}
|
||||
return Handle<FixedArray>::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<Map> GetOrCreateDebugProxyMap(
|
||||
Isolate* isolate, DebugProxyId id,
|
||||
v8::Local<v8::FunctionTemplate> (*create_template_fn)(v8::Isolate*)) {
|
||||
Handle<FixedArray> 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<v8::Isolate*>(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 <typename T, DebugProxyId id, typename Provider>
|
||||
struct IndexedDebugProxy {
|
||||
static constexpr DebugProxyId kId = id;
|
||||
|
||||
static Handle<JSObject> Create(Isolate* isolate, Handle<Provider> 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<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate) {
|
||||
Local<v8::FunctionTemplate> 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 <typename V>
|
||||
static Isolate* GetIsolate(const PropertyCallbackInfo<V>& info) {
|
||||
return reinterpret_cast<Isolate*>(info.GetIsolate());
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
static Handle<JSObject> GetHolder(const PropertyCallbackInfo<V>& info) {
|
||||
return Handle<JSObject>::cast(Utils::OpenHandle(*info.Holder()));
|
||||
}
|
||||
|
||||
static Handle<Provider> GetProvider(Handle<JSObject> holder,
|
||||
Isolate* isolate) {
|
||||
return handle(Provider::cast(holder->GetEmbedderField(kProviderField)),
|
||||
isolate);
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
static Handle<Provider> GetProvider(const PropertyCallbackInfo<V>& info) {
|
||||
return GetProvider(GetHolder(info), GetIsolate(info));
|
||||
}
|
||||
|
||||
static void IndexedGetter(uint32_t index,
|
||||
const PropertyCallbackInfo<v8::Value>& 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<v8::Value>& 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<v8::Integer>& 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<v8::Array>& 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 <typename T, DebugProxyId id, typename Provider = WasmInstanceObject>
|
||||
struct NamedDebugProxy : IndexedDebugProxy<T, id, Provider> {
|
||||
enum {
|
||||
kProviderField,
|
||||
kNameTableField,
|
||||
kFieldCount,
|
||||
};
|
||||
|
||||
static v8::Local<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate) {
|
||||
auto templ = IndexedDebugProxy<T, id, Provider>::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<v8::Array>& info) {
|
||||
info.GetReturnValue().Set(v8::Array::New(info.GetIsolate()));
|
||||
}
|
||||
|
||||
static Handle<NameDictionary> GetNameTable(Handle<JSObject> holder,
|
||||
Isolate* isolate) {
|
||||
Handle<Object> table_or_undefined(holder->GetEmbedderField(kNameTableField),
|
||||
isolate);
|
||||
if (!table_or_undefined->IsUndefined(isolate)) {
|
||||
return Handle<NameDictionary>::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<Smi> value(Smi::FromInt(index), isolate);
|
||||
table = NameDictionary::Add(isolate, table, key, value,
|
||||
PropertyDetails::Empty());
|
||||
}
|
||||
holder->SetEmbedderField(kNameTableField, *table);
|
||||
return table;
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
static base::Optional<uint32_t> FindName(
|
||||
Local<v8::Name> name, const PropertyCallbackInfo<V>& info) {
|
||||
if (!name->IsString()) return {};
|
||||
auto name_str = Utils::OpenHandle(*name.As<v8::String>());
|
||||
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<v8::Name> name,
|
||||
const PropertyCallbackInfo<v8::Value>& info) {
|
||||
if (auto index = FindName(name, info)) T::IndexedGetter(*index, info);
|
||||
}
|
||||
|
||||
static void NamedQuery(Local<v8::Name> name,
|
||||
const PropertyCallbackInfo<v8::Integer>& info) {
|
||||
if (auto index = FindName(name, info)) T::IndexedQuery(*index, info);
|
||||
}
|
||||
|
||||
static void NamedDescriptor(Local<v8::Name> name,
|
||||
const PropertyCallbackInfo<v8::Value>& info) {
|
||||
if (auto index = FindName(name, info)) T::IndexedDescriptor(*index, info);
|
||||
}
|
||||
|
||||
static void NamedEnumerator(const PropertyCallbackInfo<v8::Array>& 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<FunctionsProxy, kFunctionsProxy> {
|
||||
static constexpr char const* kClassName = "Functions";
|
||||
|
||||
static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
|
||||
return static_cast<uint32_t>(instance->module()->functions.size());
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return WasmInstanceObject::GetOrCreateWasmExternalFunction(isolate,
|
||||
instance, index);
|
||||
}
|
||||
|
||||
static Handle<String> GetName(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> 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<const char> name_vec = wire_bytes.GetNameOrNull(name_ref);
|
||||
return GetNameOrDefault(
|
||||
isolate,
|
||||
name_vec.empty() ? MaybeHandle<String>()
|
||||
: isolate->factory()->NewStringFromUtf8(name_vec),
|
||||
"$func", index);
|
||||
}
|
||||
};
|
||||
|
||||
// This class implements the "globals" proxy.
|
||||
struct GlobalsProxy : NamedDebugProxy<GlobalsProxy, kGlobalsProxy> {
|
||||
static constexpr char const* kClassName = "Globals";
|
||||
|
||||
static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
|
||||
return static_cast<uint32_t>(instance->module()->globals.size());
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return WasmValueToObject(isolate,
|
||||
WasmInstanceObject::GetGlobalValue(
|
||||
instance, instance->module()->globals[index]));
|
||||
}
|
||||
|
||||
static Handle<String> GetName(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return GetNameOrDefault(
|
||||
isolate,
|
||||
WasmInstanceObject::GetGlobalNameOrNull(isolate, instance, index),
|
||||
"$global", index);
|
||||
}
|
||||
};
|
||||
|
||||
// This class implements the "memories" proxy.
|
||||
struct MemoriesProxy : NamedDebugProxy<MemoriesProxy, kMemoriesProxy> {
|
||||
static constexpr char const* kClassName = "Memories";
|
||||
|
||||
static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
|
||||
return instance->has_memory_object() ? 1 : 0;
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return handle(instance->memory_object(), isolate);
|
||||
}
|
||||
|
||||
static Handle<String> GetName(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return GetNameOrDefault(
|
||||
isolate,
|
||||
WasmInstanceObject::GetMemoryNameOrNull(isolate, instance, index),
|
||||
"$memory", index);
|
||||
}
|
||||
};
|
||||
|
||||
// This class implements the "tables" proxy.
|
||||
struct TablesProxy : NamedDebugProxy<TablesProxy, kTablesProxy> {
|
||||
static constexpr char const* kClassName = "Tables";
|
||||
|
||||
static uint32_t Count(Isolate* isolate, Handle<WasmInstanceObject> instance) {
|
||||
return instance->tables().length();
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return handle(instance->tables().get(index), isolate);
|
||||
}
|
||||
|
||||
static Handle<String> GetName(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance,
|
||||
uint32_t index) {
|
||||
return GetNameOrDefault(
|
||||
isolate,
|
||||
WasmInstanceObject::GetTableNameOrNull(isolate, instance, index),
|
||||
"$table", index);
|
||||
}
|
||||
};
|
||||
|
||||
// This class implements the "locals" proxy.
|
||||
struct LocalsProxy : NamedDebugProxy<LocalsProxy, kLocalsProxy, FixedArray> {
|
||||
static constexpr char const* kClassName = "Locals";
|
||||
|
||||
static Handle<JSObject> 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<FixedArray> values) {
|
||||
return values->length() - 2;
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate, Handle<FixedArray> values,
|
||||
uint32_t index) {
|
||||
return handle(values->get(index), isolate);
|
||||
}
|
||||
|
||||
static Handle<String> GetName(Isolate* isolate, Handle<FixedArray> 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<String>()
|
||||
: isolate->factory()->NewStringFromUtf8(name_vec),
|
||||
"$var", index);
|
||||
}
|
||||
};
|
||||
|
||||
// This class implements the "stack" proxy (which offers only indexed access).
|
||||
struct StackProxy : IndexedDebugProxy<StackProxy, kStackProxy, FixedArray> {
|
||||
static constexpr char const* kClassName = "Stack";
|
||||
|
||||
static Handle<JSObject> 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<FixedArray> values) {
|
||||
return values->length();
|
||||
}
|
||||
|
||||
static Handle<Object> Get(Isolate* isolate, Handle<FixedArray> 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 <typename Proxy>
|
||||
Handle<JSObject> GetOrCreateInstanceProxy(Isolate* isolate,
|
||||
Handle<WasmInstanceObject> instance) {
|
||||
STATIC_ASSERT(Proxy::kId < kNumInstanceProxies);
|
||||
Handle<FixedArray> proxies =
|
||||
GetOrCreateDebugProxyCache(isolate, instance, kNumInstanceProxies);
|
||||
if (!proxies->is_the_hole(isolate, Proxy::kId)) {
|
||||
return handle(JSObject::cast(proxies->get(Proxy::kId)), isolate);
|
||||
}
|
||||
Handle<JSObject> 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<JSObject> Create(WasmFrame* frame) {
|
||||
Isolate* isolate = frame->isolate();
|
||||
auto object_map =
|
||||
GetOrCreateDebugProxyMap(isolate, kContextProxy, &CreateTemplate);
|
||||
auto object = isolate->factory()->NewJSObjectFromMap(object_map);
|
||||
Handle<WasmInstanceObject> instance(frame->wasm_instance(), isolate);
|
||||
object->SetEmbedderField(kInstanceField, *instance);
|
||||
Handle<JSObject> locals = LocalsProxy::Create(isolate, frame);
|
||||
object->SetEmbedderField(kLocalsField, *locals);
|
||||
Handle<JSObject> stack = StackProxy::Create(isolate, frame);
|
||||
object->SetEmbedderField(kStackField, *stack);
|
||||
return object;
|
||||
}
|
||||
|
||||
private:
|
||||
enum {
|
||||
kInstanceField,
|
||||
kLocalsField,
|
||||
kStackField,
|
||||
kFieldCount,
|
||||
};
|
||||
|
||||
static v8::Local<v8::FunctionTemplate> CreateTemplate(v8::Isolate* isolate) {
|
||||
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
|
||||
templ->InstanceTemplate()->SetInternalFieldCount(kFieldCount);
|
||||
templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
|
||||
&NamedGetter, {}, {}, {}, {}, {}, {}, {},
|
||||
static_cast<v8::PropertyHandlerFlags>(
|
||||
static_cast<unsigned>(
|
||||
v8::PropertyHandlerFlags::kOnlyInterceptStrings) |
|
||||
static_cast<unsigned>(
|
||||
v8::PropertyHandlerFlags::kHasNoSideEffect))));
|
||||
return templ;
|
||||
}
|
||||
|
||||
static MaybeHandle<Object> GetNamedProperty(Isolate* isolate,
|
||||
Handle<JSObject> holder,
|
||||
Handle<String> name) {
|
||||
if (name->length() == 0) return {};
|
||||
Handle<WasmInstanceObject> 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<MemoriesProxy>(isolate, instance);
|
||||
}
|
||||
if (name->IsOneByteEqualTo(StaticCharVector("tables"))) {
|
||||
return GetOrCreateInstanceProxy<TablesProxy>(isolate, instance);
|
||||
}
|
||||
if (name->IsOneByteEqualTo(StaticCharVector("globals"))) {
|
||||
return GetOrCreateInstanceProxy<GlobalsProxy>(isolate, instance);
|
||||
}
|
||||
if (name->IsOneByteEqualTo(StaticCharVector("functions"))) {
|
||||
return GetOrCreateInstanceProxy<FunctionsProxy>(isolate, instance);
|
||||
}
|
||||
if (name->Get(0) == '$') {
|
||||
const char* kDelegateNames[] = {"memories", "locals", "tables",
|
||||
"functions", "globals"};
|
||||
for (auto delegate_name : kDelegateNames) {
|
||||
Handle<Object> delegate;
|
||||
ASSIGN_RETURN_ON_EXCEPTION(
|
||||
isolate, delegate,
|
||||
JSObject::GetProperty(isolate, holder, delegate_name), Object);
|
||||
if (!delegate->IsUndefined(isolate)) {
|
||||
Handle<Object> value;
|
||||
ASSIGN_RETURN_ON_EXCEPTION(
|
||||
isolate, value, Object::GetProperty(isolate, delegate, name),
|
||||
Object);
|
||||
if (!value->IsUndefined(isolate)) return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static void NamedGetter(Local<v8::Name> name,
|
||||
const PropertyCallbackInfo<v8::Value>& info) {
|
||||
auto name_string = Handle<String>::cast(Utils::OpenHandle(*name));
|
||||
auto isolate = reinterpret_cast<Isolate*>(info.GetIsolate());
|
||||
auto holder = Handle<JSObject>::cast(Utils::OpenHandle(*info.Holder()));
|
||||
Handle<Object> value;
|
||||
if (GetNamedProperty(isolate, holder, name_string).ToHandle(&value)) {
|
||||
info.GetReturnValue().Set(Utils::ToLocal(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Handle<JSObject> GetJSDebugProxy(WasmFrame* frame) {
|
||||
return ContextProxy::Create(frame);
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -20,12 +20,9 @@ namespace internal {
|
||||
|
||||
template <typename T>
|
||||
class Handle;
|
||||
class JSObject;
|
||||
class JSProxy;
|
||||
template <typename T>
|
||||
class Vector;
|
||||
class WasmFrame;
|
||||
class WasmInstanceObject;
|
||||
|
||||
namespace wasm {
|
||||
|
||||
@ -137,10 +134,6 @@ class DebugSideTable {
|
||||
std::vector<Entry> 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<JSObject> GetModuleScopeObject(Handle<WasmInstanceObject>);
|
||||
|
||||
// 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<JSObject> GetLocalScopeObject(Isolate*, Address pc, Address fp,
|
||||
Address debug_break_fp);
|
||||
|
||||
Handle<JSObject> 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<DebugInfoImpl> impl_;
|
||||
};
|
||||
|
||||
Handle<JSObject> GetJSDebugProxy(WasmFrame* frame);
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user