Commit d4f31caa authored by Jakob Kummerow's avatar Jakob Kummerow Committed by Commit Bot

[wasm-gc][inspector] Debugging support for WasmGC

This adds support for WasmGC objects (structs/arrays) to the
inspector backend. For prettier printing, it also adds support
for reading the "type" and "field" subsections of the "name"
section in Wasm modules.

This patch includes a revert of most of commit
crrev.com/987a7f4a because
types are more complicated now.

Bug: v8:7748, chromium:1177784
Change-Id: Icec52cbbb32291b0e773b40be6771a678c6ec79b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2715193
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: 's avatarManos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73212}
parent ae9aeb5a
......@@ -771,6 +771,14 @@ bool WasmValueObject::IsWasmValueObject(Local<Value> that) {
i::Handle<i::Object> obj = Utils::OpenHandle(*that);
return obj->IsWasmValueObject();
}
Local<String> WasmValueObject::type() const {
i::Handle<i::WasmValueObject> object =
i::Handle<i::WasmValueObject>::cast(Utils::OpenHandle(this));
i::Isolate* isolate = object->GetIsolate();
i::Handle<i::String> type(object->type(), isolate);
return Utils::ToLocal(type);
}
#endif // V8_ENABLE_WEBASSEMBLY
Local<Function> GetBuiltin(Isolate* v8_isolate, Builtin builtin) {
......
......@@ -634,6 +634,8 @@ class V8_EXPORT_PRIVATE WasmValueObject : public v8::Object {
return static_cast<WasmValueObject*>(value);
}
v8::Local<v8::String> type() const;
private:
static void CheckCast(v8::Value* obj);
};
......
......@@ -20,6 +20,7 @@ OBJECT_CONSTRUCTORS_IMPL(WasmValueObject, JSObject)
CAST_ACCESSOR(WasmValueObject)
ACCESSORS(WasmValueObject, type, String, kTypeOffset)
ACCESSORS(WasmValueObject, value, Object, kValueOffset)
} // namespace internal
......
This diff is collapsed.
......@@ -36,6 +36,7 @@ class WasmValueObject : public JSObject {
public:
DECL_CAST(WasmValueObject)
DECL_ACCESSORS(type, String)
DECL_ACCESSORS(value, Object)
// Dispatched behavior.
......@@ -44,20 +45,21 @@ class WasmValueObject : public JSObject {
// Layout description.
#define WASM_VALUE_FIELDS(V) \
V(kTypeOffset, kTaggedSize) \
V(kValueOffset, kTaggedSize) \
V(kSize, 0)
DEFINE_FIELD_OFFSET_CONSTANTS(JSObject::kHeaderSize, WASM_VALUE_FIELDS)
#undef WASM_VALUE_FIELDS
// Indices of in-object properties.
static constexpr int kValueIndex = 0;
static constexpr int kTypeIndex = 0;
static constexpr int kValueIndex = 1;
enum Type { kExternRef, kF32, kF64, kI32, kI64, kV128, kNumTypes };
static Handle<WasmValueObject> New(Isolate* isolate, Type type,
static Handle<WasmValueObject> New(Isolate* isolate, Handle<String> type,
Handle<Object> value);
static Handle<WasmValueObject> New(Isolate* isolate,
const wasm::WasmValue& value);
const wasm::WasmValue& value,
Handle<WasmModuleObject> module);
OBJECT_CONSTRUCTORS(WasmValueObject, JSObject);
};
......
......@@ -305,6 +305,13 @@ String16 descriptionForCollection(v8::Isolate* isolate,
return String16::concat(className, '(', String16::fromInteger(length), ')');
}
String16 descriptionForWasmValueObject(
v8::Local<v8::Context> context,
v8::Local<v8::debug::WasmValueObject> object) {
v8::Isolate* isolate = context->GetIsolate();
return toProtocolString(isolate, object->type());
}
String16 descriptionForEntry(v8::Local<v8::Context> context,
v8::Local<v8::Object> object) {
v8::Isolate* isolate = context->GetIsolate();
......@@ -1701,7 +1708,7 @@ std::unique_ptr<ValueMirror> ValueMirror::create(v8::Local<v8::Context> context,
value.As<v8::debug::WasmValueObject>();
return std::make_unique<ObjectMirror>(
value, RemoteObject::SubtypeEnum::Wasmvalue,
descriptionForObject(isolate, object));
descriptionForWasmValueObject(context, object));
}
#endif // V8_ENABLE_WEBASSEMBLY
V8InternalValueType internalType =
......
......@@ -91,21 +91,6 @@ class StackTransferRecipe {
DCHECK(load_dst_regs_.is_empty());
}
#if DEBUG
bool CheckCompatibleStackSlotTypes(ValueKind dst, ValueKind src) {
if (is_object_reference(dst)) {
// Since Liftoff doesn't do accurate type tracking (e.g. on loop back
// edges), we only care that pointer types stay amongst pointer types.
// It's fine if ref/optref overwrite each other.
DCHECK(is_object_reference(src));
} else {
// All other types (primitive numbers, RTTs, bottom/stmt) must be equal.
DCHECK_EQ(dst, src);
}
return true; // Dummy so this can be called via DCHECK.
}
#endif
V8_INLINE void TransferStackSlot(const VarState& dst, const VarState& src) {
DCHECK(CheckCompatibleStackSlotTypes(dst.kind(), src.kind()));
if (dst.is_reg()) {
......@@ -1223,6 +1208,21 @@ std::ostream& operator<<(std::ostream& os, VarState slot) {
UNREACHABLE();
}
#if DEBUG
bool CheckCompatibleStackSlotTypes(ValueKind a, ValueKind b) {
if (is_object_reference(a)) {
// Since Liftoff doesn't do accurate type tracking (e.g. on loop back
// edges), we only care that pointer types stay amongst pointer types.
// It's fine if ref/optref overwrite each other.
DCHECK(is_object_reference(b));
} else {
// All other types (primitive numbers, RTTs, bottom/stmt) must be equal.
DCHECK_EQ(a, b);
}
return true; // Dummy so this can be called via DCHECK.
}
#endif
} // namespace wasm
} // namespace internal
} // namespace v8
......@@ -1589,6 +1589,10 @@ class LiftoffStackSlots {
LiftoffAssembler* const asm_;
};
#if DEBUG
bool CheckCompatibleStackSlotTypes(ValueKind a, ValueKind b);
#endif
} // namespace wasm
} // namespace internal
} // namespace v8
......
......@@ -2846,7 +2846,7 @@ class LiftoffCompiler {
ValueType type = index < static_cast<int>(__ num_locals())
? decoder->local_type(index)
: decoder->stack_value(decoder_stack_index--)->type;
DCHECK_EQ(slot.kind(), type.kind());
DCHECK(CheckCompatibleStackSlotTypes(slot.kind(), type.kind()));
value.type = type;
switch (slot.loc()) {
case kIntConst:
......
......@@ -2416,11 +2416,12 @@ void DecodeFunctionNames(const byte* module_start, const byte* module_end,
}
}
LocalNames DecodeLocalNames(Vector<const uint8_t> module_bytes) {
NameMap DecodeNameMap(Vector<const uint8_t> module_bytes,
uint8_t name_section_kind) {
Decoder decoder(module_bytes);
if (!FindNameSection(&decoder)) return LocalNames{{}};
if (!FindNameSection(&decoder)) return NameMap{{}};
std::vector<LocalNamesPerFunction> functions;
std::vector<NameAssoc> names;
while (decoder.ok() && decoder.more()) {
uint8_t name_type = decoder.consume_u8("name type");
if (name_type & 0x80) break; // no varuint7
......@@ -2428,35 +2429,67 @@ LocalNames DecodeLocalNames(Vector<const uint8_t> module_bytes) {
uint32_t name_payload_len = decoder.consume_u32v("name payload length");
if (!decoder.checkAvailable(name_payload_len)) break;
if (name_type != NameSectionKindCode::kLocal) {
if (name_type != name_section_kind) {
decoder.consume_bytes(name_payload_len, "name subsection payload");
continue;
}
uint32_t local_names_count = decoder.consume_u32v("local names count");
for (uint32_t i = 0; i < local_names_count; ++i) {
uint32_t func_index = decoder.consume_u32v("function index");
if (func_index > kMaxInt) continue;
std::vector<LocalName> names;
uint32_t num_names = decoder.consume_u32v("namings count");
for (uint32_t k = 0; k < num_names; ++k) {
uint32_t local_index = decoder.consume_u32v("local index");
WireBytesRef name = consume_string(&decoder, false, "local name");
uint32_t count = decoder.consume_u32v("names count");
for (uint32_t i = 0; i < count; i++) {
uint32_t index = decoder.consume_u32v("index");
WireBytesRef name = consume_string(&decoder, false, "name");
if (!decoder.ok()) break;
if (index > kMaxInt) continue;
if (!validate_utf8(&decoder, name)) continue;
names.emplace_back(static_cast<int>(index), name);
}
}
std::stable_sort(names.begin(), names.end(), NameAssoc::IndexLess{});
return NameMap{std::move(names)};
}
IndirectNameMap DecodeIndirectNameMap(Vector<const uint8_t> module_bytes,
uint8_t name_section_kind) {
Decoder decoder(module_bytes);
if (!FindNameSection(&decoder)) return IndirectNameMap{{}};
std::vector<IndirectNameMapEntry> entries;
while (decoder.ok() && decoder.more()) {
uint8_t name_type = decoder.consume_u8("name type");
if (name_type & 0x80) break; // no varuint7
uint32_t name_payload_len = decoder.consume_u32v("name payload length");
if (!decoder.checkAvailable(name_payload_len)) break;
if (name_type != name_section_kind) {
decoder.consume_bytes(name_payload_len, "name subsection payload");
continue;
}
uint32_t outer_count = decoder.consume_u32v("outer count");
for (uint32_t i = 0; i < outer_count; ++i) {
uint32_t outer_index = decoder.consume_u32v("outer index");
if (outer_index > kMaxInt) continue;
std::vector<NameAssoc> names;
uint32_t inner_count = decoder.consume_u32v("inner count");
for (uint32_t k = 0; k < inner_count; ++k) {
uint32_t inner_index = decoder.consume_u32v("inner index");
WireBytesRef name = consume_string(&decoder, false, "name");
if (!decoder.ok()) break;
if (local_index > kMaxInt) continue;
if (inner_index > kMaxInt) continue;
// Ignore non-utf8 names.
if (!validate_utf8(&decoder, name)) continue;
names.emplace_back(static_cast<int>(local_index), name);
names.emplace_back(static_cast<int>(inner_index), name);
}
// Use stable sort to get deterministic names (the first one declared)
// even in the presence of duplicates.
std::stable_sort(names.begin(), names.end(), LocalName::IndexLess{});
functions.emplace_back(static_cast<int>(func_index), std::move(names));
std::stable_sort(names.begin(), names.end(), NameAssoc::IndexLess{});
entries.emplace_back(static_cast<int>(outer_index), std::move(names));
}
}
std::stable_sort(functions.begin(), functions.end(),
LocalNamesPerFunction::FunctionIndexLess{});
return LocalNames{std::move(functions)};
std::stable_sort(entries.begin(), entries.end(),
IndirectNameMapEntry::IndexLess{});
return IndirectNameMap{std::move(entries)};
}
#undef TRACE
......
......@@ -50,15 +50,18 @@ struct AsmJsOffsets {
};
using AsmJsOffsetsResult = Result<AsmJsOffsets>;
class LocalName {
// The class names "NameAssoc", "NameMap", and "IndirectNameMap" match
// the terms used by the spec:
// https://webassembly.github.io/spec/core/bikeshed/index.html#name-section%E2%91%A0
class NameAssoc {
public:
LocalName(int index, WireBytesRef name) : index_(index), name_(name) {}
NameAssoc(int index, WireBytesRef name) : index_(index), name_(name) {}
int index() const { return index_; }
WireBytesRef name() const { return name_; }
struct IndexLess {
bool operator()(const LocalName& a, const LocalName& b) const {
bool operator()(const NameAssoc& a, const NameAssoc& b) const {
return a.index() < b.index();
}
};
......@@ -68,62 +71,71 @@ class LocalName {
WireBytesRef name_;
};
class LocalNamesPerFunction {
class NameMap {
public:
// For performance reasons, {LocalNamesPerFunction} should not be copied.
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(LocalNamesPerFunction);
// For performance reasons, {NameMap} should not be copied.
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(NameMap);
LocalNamesPerFunction(int function_index, std::vector<LocalName> names)
: function_index_(function_index), names_(std::move(names)) {
explicit NameMap(std::vector<NameAssoc> names) : names_(std::move(names)) {
DCHECK(
std::is_sorted(names_.begin(), names_.end(), LocalName::IndexLess{}));
std::is_sorted(names_.begin(), names_.end(), NameAssoc::IndexLess{}));
}
int function_index() const { return function_index_; }
WireBytesRef GetName(int local_index) {
auto it =
std::lower_bound(names_.begin(), names_.end(),
LocalName{local_index, {}}, LocalName::IndexLess{});
WireBytesRef GetName(int index) {
auto it = std::lower_bound(names_.begin(), names_.end(),
NameAssoc{index, {}}, NameAssoc::IndexLess{});
if (it == names_.end()) return {};
if (it->index() != local_index) return {};
if (it->index() != index) return {};
return it->name();
}
struct FunctionIndexLess {
bool operator()(const LocalNamesPerFunction& a,
const LocalNamesPerFunction& b) const {
return a.function_index() < b.function_index();
private:
std::vector<NameAssoc> names_;
};
class IndirectNameMapEntry : public NameMap {
public:
// For performance reasons, {IndirectNameMapEntry} should not be copied.
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(IndirectNameMapEntry);
IndirectNameMapEntry(int index, std::vector<NameAssoc> names)
: NameMap(std::move(names)), index_(index) {}
int index() const { return index_; }
struct IndexLess {
bool operator()(const IndirectNameMapEntry& a,
const IndirectNameMapEntry& b) const {
return a.index() < b.index();
}
};
private:
int function_index_;
std::vector<LocalName> names_;
int index_;
};
class LocalNames {
class IndirectNameMap {
public:
// For performance reasons, {LocalNames} should not be copied.
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(LocalNames);
// For performance reasons, {IndirectNameMap} should not be copied.
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(IndirectNameMap);
explicit LocalNames(std::vector<LocalNamesPerFunction> functions)
explicit IndirectNameMap(std::vector<IndirectNameMapEntry> functions)
: functions_(std::move(functions)) {
DCHECK(std::is_sorted(functions_.begin(), functions_.end(),
LocalNamesPerFunction::FunctionIndexLess{}));
IndirectNameMapEntry::IndexLess{}));
}
WireBytesRef GetName(int function_index, int local_index) {
auto it = std::lower_bound(functions_.begin(), functions_.end(),
LocalNamesPerFunction{function_index, {}},
LocalNamesPerFunction::FunctionIndexLess{});
IndirectNameMapEntry{function_index, {}},
IndirectNameMapEntry::IndexLess{});
if (it == functions_.end()) return {};
if (it->function_index() != function_index) return {};
if (it->index() != function_index) return {};
return it->GetName(local_index);
}
private:
std::vector<LocalNamesPerFunction> functions_;
std::vector<IndirectNameMapEntry> functions_;
};
enum class DecodingMethod {
......@@ -179,11 +191,14 @@ void DecodeFunctionNames(const byte* module_start, const byte* module_end,
std::unordered_map<uint32_t, WireBytesRef>* names,
const Vector<const WasmExport> export_table);
// Decode the local names assignment from the name section.
// Decode the requested subsection of the name section.
// The result will be empty if no name section is present. On encountering an
// error in the name section, returns all information decoded up to the first
// error.
LocalNames DecodeLocalNames(Vector<const uint8_t> module_bytes);
NameMap DecodeNameMap(Vector<const uint8_t> module_bytes,
uint8_t name_section_kind);
IndirectNameMap DecodeIndirectNameMap(Vector<const uint8_t> module_bytes,
uint8_t name_section_kind);
class ModuleDecoderImpl;
......
......@@ -109,7 +109,21 @@ constexpr uint8_t kDefaultCompilationHint = 0x0;
constexpr uint8_t kNoCompilationHint = kMaxUInt8;
// Binary encoding of name section kinds.
enum NameSectionKindCode : uint8_t { kModule = 0, kFunction = 1, kLocal = 2 };
enum NameSectionKindCode : uint8_t {
kModule = 0,
kFunction = 1,
kLocal = 2,
// https://github.com/WebAssembly/extended-name-section/
kLabel = 3,
kType = 4,
kTable = 5,
kMemory = 6,
kGlobal = 7,
kElementSegment = 8,
kDataSegment = 9,
// https://github.com/WebAssembly/gc/issues/193
kField = 10
};
constexpr size_t kWasmPageSize = 0x10000;
constexpr uint32_t kWasmPageSizeLog2 = 16;
......
......@@ -126,10 +126,10 @@ class DebugInfoImpl {
}
WasmValue GetLocalValue(int local, Address pc, Address fp,
Address debug_break_fp) {
Address debug_break_fp, Isolate* isolate) {
FrameInspectionScope scope(this, pc);
return GetValue(scope.debug_side_table, scope.debug_side_table_entry, local,
fp, debug_break_fp);
fp, debug_break_fp, isolate);
}
int GetStackDepth(Address pc) {
......@@ -141,13 +141,13 @@ class DebugInfoImpl {
}
WasmValue GetStackValue(int index, Address pc, Address fp,
Address debug_break_fp) {
Address debug_break_fp, Isolate* isolate) {
FrameInspectionScope scope(this, pc);
int num_locals = scope.debug_side_table->num_locals();
int value_count = scope.debug_side_table_entry->stack_height();
if (num_locals + index >= value_count) return {};
return GetValue(scope.debug_side_table, scope.debug_side_table_entry,
num_locals + index, fp, debug_break_fp);
num_locals + index, fp, debug_break_fp, isolate);
}
const WasmFunction& GetFunctionAtAddress(Address pc) {
......@@ -189,15 +189,33 @@ class DebugInfoImpl {
return {};
}
WireBytesRef GetTypeName(int type_index) {
base::MutexGuard guard(&mutex_);
if (!type_names_) {
type_names_ = std::make_unique<NameMap>(DecodeNameMap(
native_module_->wire_bytes(), NameSectionKindCode::kType));
}
return type_names_->GetName(type_index);
}
WireBytesRef GetLocalName(int func_index, int local_index) {
base::MutexGuard guard(&mutex_);
if (!local_names_) {
local_names_ = std::make_unique<LocalNames>(
DecodeLocalNames(native_module_->wire_bytes()));
local_names_ = std::make_unique<IndirectNameMap>(DecodeIndirectNameMap(
native_module_->wire_bytes(), NameSectionKindCode::kLocal));
}
return local_names_->GetName(func_index, local_index);
}
WireBytesRef GetFieldName(int struct_index, int field_index) {
base::MutexGuard guard(&mutex_);
if (!field_names_) {
field_names_ = std::make_unique<IndirectNameMap>(DecodeIndirectNameMap(
native_module_->wire_bytes(), NameSectionKindCode::kField));
}
return field_names_->GetName(struct_index, field_index);
}
// If the top frame is a Wasm frame and its position is not in the list of
// breakpoints, return that position. Return 0 otherwise.
// This is used to generate a "dead breakpoint" in Liftoff, which is necessary
......@@ -537,7 +555,7 @@ class DebugInfoImpl {
WasmValue GetValue(const DebugSideTable* debug_side_table,
const DebugSideTable::Entry* debug_side_table_entry,
int index, Address stack_frame_base,
Address debug_break_fp) const {
Address debug_break_fp, Isolate* isolate) const {
const auto* value =
debug_side_table->FindValue(debug_side_table_entry, index);
if (value->is_constant()) {
......@@ -561,9 +579,17 @@ class DebugInfoImpl {
return WasmValue((uint64_t{high_word} << 32) | low_word);
}
if (reg.is_gp()) {
return value->type == kWasmI32
? WasmValue(ReadUnalignedValue<uint32_t>(gp_addr(reg.gp())))
: WasmValue(ReadUnalignedValue<uint64_t>(gp_addr(reg.gp())));
if (value->type == kWasmI32) {
return WasmValue(ReadUnalignedValue<uint32_t>(gp_addr(reg.gp())));
} else if (value->type == kWasmI64) {
return WasmValue(ReadUnalignedValue<uint64_t>(gp_addr(reg.gp())));
} else if (value->type.is_reference()) {
Handle<Object> obj(
Object(ReadUnalignedValue<Address>(gp_addr(reg.gp()))), isolate);
return WasmValue(obj, value->type);
} else {
UNREACHABLE();
}
}
DCHECK(reg.is_fp() || reg.is_fp_pair());
// ifdef here to workaround unreachable code for is_fp_pair.
......@@ -598,11 +624,21 @@ class DebugInfoImpl {
return WasmValue(ReadUnalignedValue<float>(stack_address));
case kF64:
return WasmValue(ReadUnalignedValue<double>(stack_address));
case kS128: {
case kS128:
return WasmValue(Simd128(ReadUnalignedValue<int16>(stack_address)));
case kRef:
case kOptRef:
case kRtt:
case kRttWithDepth: {
Handle<Object> obj(Object(ReadUnalignedValue<Address>(stack_address)),
isolate);
return WasmValue(obj, value->type);
}
default:
UNIMPLEMENTED();
case kI8:
case kI16:
case kStmt:
case kBottom:
UNREACHABLE();
}
}
......@@ -701,8 +737,12 @@ class DebugInfoImpl {
std::pair<wasm::WireBytesRef, wasm::WireBytesRef>>>
import_names_;
// Names of types, lazily decoded from the wire bytes.
std::unique_ptr<NameMap> type_names_;
// Names of locals, lazily decoded from the wire bytes.
std::unique_ptr<LocalNames> local_names_;
std::unique_ptr<IndirectNameMap> local_names_;
// Names of struct fields, lazily decoded from the wire bytes.
std::unique_ptr<IndirectNameMap> field_names_;
// Isolate-specific data.
std::unordered_map<Isolate*, PerIsolateDebugData> per_isolate_data_;
......@@ -716,15 +756,15 @@ DebugInfo::~DebugInfo() = default;
int DebugInfo::GetNumLocals(Address pc) { return impl_->GetNumLocals(pc); }
WasmValue DebugInfo::GetLocalValue(int local, Address pc, Address fp,
Address debug_break_fp) {
return impl_->GetLocalValue(local, pc, fp, debug_break_fp);
Address debug_break_fp, Isolate* isolate) {
return impl_->GetLocalValue(local, pc, fp, debug_break_fp, isolate);
}
int DebugInfo::GetStackDepth(Address pc) { return impl_->GetStackDepth(pc); }
WasmValue DebugInfo::GetStackValue(int index, Address pc, Address fp,
Address debug_break_fp) {
return impl_->GetStackValue(index, pc, fp, debug_break_fp);
Address debug_break_fp, Isolate* isolate) {
return impl_->GetStackValue(index, pc, fp, debug_break_fp, isolate);
}
const wasm::WasmFunction& DebugInfo::GetFunctionAtAddress(Address pc) {
......@@ -741,10 +781,18 @@ std::pair<WireBytesRef, WireBytesRef> DebugInfo::GetImportName(
return impl_->GetImportName(code, index);
}
WireBytesRef DebugInfo::GetTypeName(int type_index) {
return impl_->GetTypeName(type_index);
}
WireBytesRef DebugInfo::GetLocalName(int func_index, int local_index) {
return impl_->GetLocalName(func_index, local_index);
}
WireBytesRef DebugInfo::GetFieldName(int struct_index, int field_index) {
return impl_->GetFieldName(struct_index, field_index);
}
void DebugInfo::SetBreakpoint(int func_index, int offset,
Isolate* current_isolate) {
impl_->SetBreakpoint(func_index, offset, current_isolate);
......
......@@ -26,7 +26,7 @@ class WasmFrame;
namespace wasm {
class DebugInfoImpl;
class LocalNames;
class IndirectNameMap;
class NativeModule;
class WasmCode;
class WireBytesRef;
......@@ -171,13 +171,13 @@ class V8_EXPORT_PRIVATE DebugInfo {
// the {WasmDebugBreak} frame (if any).
int GetNumLocals(Address pc);
WasmValue GetLocalValue(int local, Address pc, Address fp,
Address debug_break_fp);
Address debug_break_fp, Isolate* isolate);
int GetStackDepth(Address pc);
const wasm::WasmFunction& GetFunctionAtAddress(Address pc);
WasmValue GetStackValue(int index, Address pc, Address fp,
Address debug_break_fp);
Address debug_break_fp, Isolate* isolate);
// Returns the name of the entity (with the given |index| and |kind|) derived
// from the exports table. If the entity is not exported, an empty reference
......@@ -190,7 +190,9 @@ class V8_EXPORT_PRIVATE DebugInfo {
std::pair<WireBytesRef, WireBytesRef> GetImportName(ImportExportKindCode kind,
uint32_t index);
WireBytesRef GetTypeName(int type_index);
WireBytesRef GetLocalName(int func_index, int local_index);
WireBytesRef GetFieldName(int struct_index, int field_index);
void SetBreakpoint(int func_index, int offset, Isolate* current_isolate);
......
......@@ -1602,7 +1602,8 @@ wasm::WasmValue WasmInstanceObject::GetGlobalValue(
uint32_t global_index = 0; // The index into the buffer.
std::tie(global_buffer, global_index) =
GetGlobalBufferAndIndex(instance, global);
return wasm::WasmValue(handle(global_buffer->get(global_index), isolate));
return wasm::WasmValue(handle(global_buffer->get(global_index), isolate),
global.type);
}
Address ptr = reinterpret_cast<Address>(GetGlobalStorage(instance, global));
using wasm::Simd128;
......@@ -1617,6 +1618,65 @@ wasm::WasmValue WasmInstanceObject::GetGlobalValue(
}
}
wasm::WasmValue WasmStruct::GetFieldValue(uint32_t index) {
wasm::ValueType field_type = type()->field(index);
int field_offset = WasmStruct::kHeaderSize + type()->field_offset(index);
Address field_address = GetFieldAddress(field_offset);
using wasm::Simd128;
switch (field_type.kind()) {
#define CASE_TYPE(valuetype, ctype) \
case wasm::valuetype: \
return wasm::WasmValue(base::ReadLittleEndianValue<ctype>(field_address));
CASE_TYPE(kI8, int8_t)
CASE_TYPE(kI16, int16_t)
FOREACH_WASMVALUE_CTYPES(CASE_TYPE)
#undef CASE_TYPE
case wasm::kRef:
case wasm::kOptRef: {
Handle<Object> ref(TaggedField<Object>::load(*this, field_offset),
GetIsolateFromWritableObject(*this));
return wasm::WasmValue(ref, field_type);
}
case wasm::kRtt:
case wasm::kRttWithDepth:
// TODO(7748): Expose RTTs to DevTools.
UNIMPLEMENTED();
case wasm::kStmt:
case wasm::kBottom:
UNREACHABLE();
}
}
wasm::WasmValue WasmArray::GetElement(uint32_t index) {
wasm::ValueType element_type = type()->element_type();
int element_offset =
WasmArray::kHeaderSize + index * element_type.element_size_bytes();
Address element_address = GetFieldAddress(element_offset);
using wasm::Simd128;
switch (element_type.kind()) {
#define CASE_TYPE(value_type, ctype) \
case wasm::value_type: \
return wasm::WasmValue(base::ReadLittleEndianValue<ctype>(element_address));
CASE_TYPE(kI8, int8_t)
CASE_TYPE(kI16, int16_t)
FOREACH_WASMVALUE_CTYPES(CASE_TYPE)
#undef CASE_TYPE
case wasm::kRef:
case wasm::kOptRef: {
Handle<Object> ref(TaggedField<Object>::load(*this, element_offset),
GetIsolateFromWritableObject(*this));
return wasm::WasmValue(ref, element_type);
}
case wasm::kRtt:
case wasm::kRttWithDepth:
// TODO(7748): Expose RTTs to DevTools.
UNIMPLEMENTED();
case wasm::kStmt:
case wasm::kBottom:
UNREACHABLE();
}
}
// static
Handle<WasmExceptionObject> WasmExceptionObject::New(
Isolate* isolate, const wasm::FunctionSig* sig,
......
......@@ -914,6 +914,8 @@ class WasmStruct : public TorqueGeneratedWasmStruct<WasmStruct, HeapObject> {
inline ObjectSlot RawField(int raw_offset);
wasm::WasmValue GetFieldValue(uint32_t field_index);
DECL_CAST(WasmStruct)
DECL_PRINTER(WasmStruct)
......@@ -928,6 +930,8 @@ class WasmArray : public TorqueGeneratedWasmArray<WasmArray, HeapObject> {
inline wasm::ArrayType* type() const;
static inline wasm::ArrayType* GcSafeType(Map map);
wasm::WasmValue GetElement(uint32_t index);
static inline int SizeFor(Map map, int length);
static inline int GcSafeSizeFor(Map map, int length);
......
......@@ -66,17 +66,18 @@ FOREACH_SIMD_TYPE(DECLARE_CAST)
// - name (for to_<name>() method)
// - wasm type
// - c type
#define FOREACH_WASMVAL_TYPE(V) \
V(i32, kWasmI32, int32_t) \
V(u32, kWasmI32, uint32_t) \
V(i64, kWasmI64, int64_t) \
V(u64, kWasmI64, uint64_t) \
V(f32, kWasmF32, float) \
V(f32_boxed, kWasmF32, Float32) \
V(f64, kWasmF64, double) \
V(f64_boxed, kWasmF64, Float64) \
V(s128, kWasmS128, Simd128) \
V(externref, kWasmExternRef, Handle<Object>)
#define FOREACH_PRIMITIVE_WASMVAL_TYPE(V) \
V(i8, kWasmI8, int8_t) \
V(i16, kWasmI16, int16_t) \
V(i32, kWasmI32, int32_t) \
V(u32, kWasmI32, uint32_t) \
V(i64, kWasmI64, int64_t) \
V(u64, kWasmI64, uint64_t) \
V(f32, kWasmF32, float) \
V(f32_boxed, kWasmF32, Float32) \
V(f64, kWasmF64, double) \
V(f64_boxed, kWasmF64, Float64) \
V(s128, kWasmS128, Simd128)
ASSERT_TRIVIALLY_COPYABLE(Handle<Object>);
......@@ -100,9 +101,21 @@ class WasmValue {
return base::ReadUnalignedValue<ctype>( \
reinterpret_cast<Address>(bit_pattern_)); \
}
FOREACH_WASMVAL_TYPE(DEFINE_TYPE_SPECIFIC_METHODS)
FOREACH_PRIMITIVE_WASMVAL_TYPE(DEFINE_TYPE_SPECIFIC_METHODS)
#undef DEFINE_TYPE_SPECIFIC_METHODS
WasmValue(Handle<Object> ref, ValueType type) : type_(type), bit_pattern_{} {
static_assert(sizeof(Handle<Object>) <= sizeof(bit_pattern_),
"bit_pattern_ must be large enough to fit a Handle");
base::WriteUnalignedValue<Handle<Object>>(
reinterpret_cast<Address>(bit_pattern_), ref);
}
Handle<Object> to_ref() const {
DCHECK(type_.is_reference());
return base::ReadUnalignedValue<Handle<Object>>(
reinterpret_cast<Address>(bit_pattern_));
}
ValueType type() const { return type_; }
// Checks equality of type and bit pattern (also for float and double values).
......@@ -137,7 +150,7 @@ class WasmValue {
inline ctype WasmValue::to() const { \
return to_##name(); \
}
FOREACH_WASMVAL_TYPE(DECLARE_CAST)
FOREACH_PRIMITIVE_WASMVAL_TYPE(DECLARE_CAST)
#undef DECLARE_CAST
} // namespace wasm
......
......@@ -237,7 +237,7 @@ class CollectValuesBreakHandler : public debug::DebugDelegate {
CHECK_EQ(expected.locals.size(), num_locals);
for (int i = 0; i < num_locals; ++i) {
WasmValue local_value = debug_info->GetLocalValue(
i, frame->pc(), frame->fp(), frame->callee_fp());
i, frame->pc(), frame->fp(), frame->callee_fp(), isolate_);
CHECK_EQ(WasmValWrapper{expected.locals[i]}, WasmValWrapper{local_value});
}
......@@ -245,7 +245,7 @@ class CollectValuesBreakHandler : public debug::DebugDelegate {
CHECK_EQ(expected.stack.size(), stack_depth);
for (int i = 0; i < stack_depth; ++i) {
WasmValue stack_value = debug_info->GetStackValue(
i, frame->pc(), frame->fp(), frame->callee_fp());
i, frame->pc(), frame->fp(), frame->callee_fp(), isolate_);
CHECK_EQ(WasmValWrapper{expected.stack[i]}, WasmValWrapper{stack_value});
}
......
......@@ -531,6 +531,19 @@ class WasmRunnerBase : public InitializedHandleScope {
static bool trap_happened;
};
template <typename T>
inline WasmValue WasmValueInitializer(T value) {
return WasmValue(value);
}
template <>
inline WasmValue WasmValueInitializer(int8_t value) {
return WasmValue(static_cast<int32_t>(value));
}
template <>
inline WasmValue WasmValueInitializer(int16_t value) {
return WasmValue(static_cast<int32_t>(value));
}
template <typename ReturnType, typename... ParamTypes>
class WasmRunner : public WasmRunnerBase {
public:
......@@ -586,7 +599,7 @@ class WasmRunner : public WasmRunnerBase {
ReturnType CallInterpreter(ParamTypes... p) {
interpreter()->Reset();
std::array<WasmValue, sizeof...(p)> args{{WasmValue(p)...}};
std::array<WasmValue, sizeof...(p)> args{{WasmValueInitializer(p)...}};
interpreter()->InitFrame(function(), args.data());
interpreter()->Run();
CHECK_GT(interpreter()->NumInterpretedCalls(), 0);
......
......@@ -1345,25 +1345,23 @@ class WasmInterpreterInternals {
StackValue(WasmValue v, WasmInterpreterInternals* impl, sp_t index)
: value_(v) {
if (IsReferenceValue()) {
value_ = WasmValue(Handle<Object>::null());
value_ = WasmValue(Handle<Object>::null(), value_.type());
int ref_index = static_cast<int>(index);
impl->reference_stack_->set(ref_index, *v.to_externref());
impl->reference_stack_->set(ref_index, *v.to_ref());
}
}
WasmValue ExtractValue(WasmInterpreterInternals* impl, sp_t index) {
if (!IsReferenceValue()) return value_;
DCHECK(value_.to_externref().is_null());
DCHECK(value_.to_ref().is_null());
int ref_index = static_cast<int>(index);
Isolate* isolate = impl->isolate_;
Handle<Object> ref(impl->reference_stack_->get(ref_index), isolate);
DCHECK(!ref->IsTheHole(isolate));
return WasmValue(ref);
return WasmValue(ref, value_.type());
}
bool IsReferenceValue() const {
return value_.type().is_reference_to(HeapType::kExtern);
}
bool IsReferenceValue() const { return value_.type().is_reference(); }
void ClearValue(WasmInterpreterInternals* impl, sp_t index) {
if (!IsReferenceValue()) return;
......@@ -1433,7 +1431,7 @@ class WasmInterpreterInternals {
FOREACH_WASMVALUE_CTYPES(CASE_TYPE)
#undef CASE_TYPE
case kOptRef: {
val = WasmValue(isolate_->factory()->null_value());
val = WasmValue(isolate_->factory()->null_value(), p);
break;
}
case kRef: // TODO(7748): Implement.
......@@ -1933,7 +1931,7 @@ class WasmInterpreterInternals {
WasmTableObject::cast(instance_object_->tables().get(imm.index)),
isolate_);
auto delta = Pop().to<uint32_t>();
auto value = Pop().to_externref();
auto value = Pop().to_ref();
int32_t result = WasmTableObject::Grow(isolate_, table, delta, value);
Push(WasmValue(result));
*len += imm.length;
......@@ -1956,7 +1954,7 @@ class WasmInterpreterInternals {
code->at(pc + 2));
HandleScope handle_scope(isolate_);
auto count = Pop().to<uint32_t>();
auto value = Pop().to_externref();
auto value = Pop().to_ref();
auto start = Pop().to<uint32_t>();
auto table = handle(
......@@ -3036,7 +3034,8 @@ class WasmInterpreterInternals {
SimdLaneImmediate<Decoder::kNoValidation> lane_imm(
decoder, code->at(pc + *len + imm.length));
Push(WasmValue(value.val[LANE(lane_imm.lane, value)]));
Push(WasmValue(
static_cast<result_type>(value.val[LANE(lane_imm.lane, value)])));
// ExecuteStore will update the len, so pass it unchanged here.
if (!ExecuteStore<result_type, load_type>(decoder, code, pc, len, rep,
......@@ -3184,8 +3183,8 @@ class WasmInterpreterInternals {
case HeapType::kExtern:
case HeapType::kFunc:
case HeapType::kAny: {
Handle<Object> externref = value.to_externref();
encoded_values->set(encoded_index++, *externref);
Handle<Object> ref = value.to_ref();
encoded_values->set(encoded_index++, *ref);
break;
}
case HeapType::kBottom:
......@@ -3306,9 +3305,9 @@ class WasmInterpreterInternals {
case HeapType::kExtern:
case HeapType::kFunc:
case HeapType::kAny: {
Handle<Object> externref(encoded_values->get(encoded_index++),
isolate_);
value = WasmValue(externref);
Handle<Object> ref(encoded_values->get(encoded_index++),
isolate_);
value = WasmValue(ref, sig->GetParam(i));
break;
}
default:
......@@ -3555,7 +3554,8 @@ class WasmInterpreterInternals {
HeapTypeImmediate<Decoder::kNoValidation> imm(
WasmFeatures::All(), &decoder, code->at(pc + 1), module());
len = 1 + imm.length;
Push(WasmValue(isolate_->factory()->null_value()));
Push(WasmValue(isolate_->factory()->null_value(),
ValueType::Ref(imm.type, kNullable)));
break;
}
case kExprRefFunc: {
......@@ -3566,7 +3566,7 @@ class WasmInterpreterInternals {
Handle<WasmExternalFunction> function =
WasmInstanceObject::GetOrCreateWasmExternalFunction(
isolate_, instance_object_, imm.index);
Push(WasmValue(function));
Push(WasmValue(function, kWasmFuncRef));
len = 1 + imm.length;
break;
}
......@@ -3712,7 +3712,7 @@ class WasmInterpreterInternals {
std::tie(global_buffer, global_index) =
WasmInstanceObject::GetGlobalBufferAndIndex(instance_object_,
global);
Handle<Object> ref = Pop().to_externref();
Handle<Object> ref = Pop().to_ref();
global_buffer->set(global_index, *ref);
break;
}
......@@ -3741,7 +3741,7 @@ class WasmInterpreterInternals {
}
Handle<Object> value =
WasmTableObject::Get(isolate_, table, entry_index);
Push(WasmValue(value));
Push(WasmValue(value, table->type()));
len = 1 + imm.length;
break;
}
......@@ -3753,7 +3753,7 @@ class WasmInterpreterInternals {
WasmTableObject::cast(instance_object_->tables().get(imm.index)),
isolate_);
uint32_t table_size = table->current_length();
Handle<Object> value = Pop().to_externref();
Handle<Object> value = Pop().to_ref();
uint32_t entry_index = Pop().to<uint32_t>();
if (entry_index >= table_size) {
return DoTrap(kTrapTableOutOfBounds, pc);
......@@ -3903,7 +3903,7 @@ class WasmInterpreterInternals {
case kExprRefIsNull: {
len = 1;
HandleScope handle_scope(isolate_); // Avoid leaking handles.
uint32_t result = Pop().to_externref()->IsNull() ? 1 : 0;
uint32_t result = Pop().to_ref()->IsNull() ? 1 : 0;
Push(WasmValue(result));
break;
}
......@@ -4022,6 +4022,8 @@ class WasmInterpreterInternals {
void Push(WasmValue val) {
DCHECK_NE(kWasmStmt, val.type());
DCHECK_NE(kWasmI8, val.type());
DCHECK_NE(kWasmI16, val.type());
DCHECK_LE(1, stack_limit_ - sp_);
DCHECK(StackValue::IsClearedValue(this, StackHeight()));
StackValue stack_value(val, this, StackHeight());
......@@ -4116,7 +4118,7 @@ class WasmInterpreterInternals {
case kRef:
case kOptRef: {
if (val.type().is_reference_to(HeapType::kExtern)) {
Handle<Object> ref = val.to_externref();
Handle<Object> ref = val.to_ref();
if (ref->IsNull()) {
PrintF("ref:null");
} else {
......
......@@ -66,7 +66,8 @@ OwnedVector<WasmValue> MakeDefaultInterpreterArguments(Isolate* isolate,
break;
case kOptRef:
arguments[i] =
WasmValue(Handle<Object>::cast(isolate->factory()->null_value()));
WasmValue(Handle<Object>::cast(isolate->factory()->null_value()),
sig->GetParam(i));
break;
case kRef:
case kRtt:
......
Tests GC object inspection.
Running test: test
Instantiating.
Waiting for wasm script (ignoring first non-wasm script).
Setting breakpoint at offset 107 on script wasm://wasm/22e4830a
Calling main()
Paused:
Script wasm://wasm/22e4830a byte offset 107: Wasm opcode 0x21 (kExprLocalSet)
Scope:
at main (0:107):
- scope (wasm-expression-stack):
0: Array ((ref $ArrC))
object details:
0: Struct ((ref null $StrA))
length: 1 (number)
- scope (local):
$varA: Struct ((ref null $StrA))
$varB: null ((ref null $ArrC))
object details:
$byte: 127 (i8)
$word: 32767 (i16)
$pointer: Struct ((ref $StrB))
- scope (module):
instance: exports: "main" (Function)
module: Module
functions: "$main": (Function)
globals: "$global0": function 0() { [native code] } ((ref null $type3))
at (anonymous) (0:17):
-- skipped
exports.main returned!
// 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.
// Flags: --experimental-wasm-gc
utils.load('test/inspector/wasm-inspector-test.js');
const {session, contextGroup, Protocol} =
InspectorTest.start('Tests GC object inspection.');
session.setupScriptMap();
const module_bytes = [
0x00, 0x61, 0x73, 0x6d, 1, 0, 0, 0, // wasm magic
0x01, // type section
0x16, // section length
0x04, // number of types
// type 0: struct $StrA (field ($byte i8) ($word i16) ($pointer (ref $StrB)))
0x5f, // struct
0x03, // field count
0x7a, 0x01, // mut i8
0x79, 0x00, // i16
0x6b, 0x01, 0x01, // mut ref $StrB
// type 1: struct $StrB (field ($next (ref null $StrA)))
0x5f, // struct
0x01, // field count
0x6c, 0x00, 0x01, // mut ref null $StrA
// type 2: array $ArrC (mut (ref null $StrA))
0x5e, // array
0x6c, 0x00, 0x01, // mut ref null $StrA
// type 3: func
0x60, // signature
0x00, // number of params
0x00, // number of results
0x03, // function section
0x02, // section length
0x01, // number of functions
0x03, // function 0: signature 3
// This is just so that function index 0 counts as declared.
0x06, // global section
0x07, // section length
0x01, // number of globals
0x6c, 0x03, // type of global: ref null $sig3
0x00, // immutable
0xd2, 0x00, 0x0b, // initializer: ref.func $func1; end
0x07, // export section
0x08, // section length
0x01, // number of exports
0x04, // length of "main"
0x6d, 0x61, 0x69, 0x6e, // "main"
0x00, // kind: function
0x00, // index: 0
/////////////////////////// CODE SECTION //////////////////////////
0x0a, // code section
0x35, // section length
0x01, // number of functions
0x33, // function 0: size
0x02, // number of locals
0x01, 0x6c, 0x00, // (local $varA (ref null $StrA))
0x01, 0x6c, 0x02, // (local $varC (ref null $ArrC))
// $varA := new $StrA(127, 32767, new $StrB(null))
0x41, 0xFF, 0x00, // i32.const 127
0x41, 0xFF, 0xFF, 0x01, // i32.const 32767
0xfb, 0x30, 0x01, // rtt.canon $StrB
0xfb, 0x02, 0x01, // struct.new_default_with_rtt $StrB
0xfb, 0x30, 0x00, // rtt.canon $StrA
0xfb, 0x01, 0x00, // struct.new_with_rtt $StrA
0x22, 0x00, // local.tee $varA
// $varA.$pointer.$next = $varA
0xfb, 0x03, 0x00, 0x02, // struct.get $StrA $pointer
0x20, 0x00, // local.get $varA
0xfb, 0x06, 0x01, 0x00, // struct.set $StrB $next
// $varC := new $ArrC($varA)
0x20, 0x00, // local.get $varA -- value
0x41, 0x01, // i32.const 1 -- length
0xfb, 0x30, 0x02, // rtt.canon $ArrC
0xfb, 0x11, 0x02, // array.new_with_rtt $ArrC
0x21, 0x01, // local.set $varC
0x0b, // end
/////////////////////////// NAME SECTION //////////////////////////
0x00, // name section
0x4d, // section length
0x04, // length of "name"
0x6e, 0x61, 0x6d, 0x65, // "name"
0x02, // "local names" subsection
0x0f, // length of subsection
0x01, // number of entries
0x00, // for function 0
0x02, // number of entries for function 0
0x00, // local index
0x04, // length of "varA"
0x76, 0x61, 0x72, 0x41, // "varA"
0x01, // local index
0x04, // length of "varB"
0x76, 0x61, 0x72, 0x42, // "varB"
0x04, // "type names" subsection
0x13, // length of subsection
0x03, // number of entries
0x00, // type index
0x04, // name length
0x53, 0x74, 0x72, 0x41, // "StrA"
0x01, // type index
0x04, // name length
0x53, 0x74, 0x72, 0x42, // "StrB"
0x02, // type index
0x04, // name length
0x41, 0x72, 0x72, 0x43, // "ArrC"
0x0a, // "field names" subsection
0x20, // length of subsection
0x02, // number of types
0x00, // for type $StrA
0x03, // number of entries for $StrA
0x00, // field index 0
0x04, // length of "byte"
0x62, 0x79, 0x74, 0x65, // "byte"
0x01, // field index 1
0x04, // length of "word"
0x77, 0x6f, 0x72, 0x64, // "word"
0x02, // field index 2
0x07, // length of "pointer"
0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, // "pointer"
0x01, // for type $StrB
0x01, // number of entries for $StrB
0x00, // field index
0x04, // length of "next"
0x6e, 0x65, 0x78, 0x74, // "next"
];
const getResult = msg => msg.result || InspectorTest.logMessage(msg);
function setBreakpoint(offset, scriptId, scriptUrl) {
InspectorTest.log(
'Setting breakpoint at offset ' + offset + ' on script ' + scriptUrl);
return Protocol.Debugger
.setBreakpoint({
'location':
{'scriptId': scriptId, 'lineNumber': 0, 'columnNumber': offset}
})
.then(getResult);
}
Protocol.Debugger.onPaused(async msg => {
let loc = msg.params.callFrames[0].location;
InspectorTest.log('Paused:');
await session.logSourceLocation(loc);
InspectorTest.log('Scope:');
for (var frame of msg.params.callFrames) {
var functionName = frame.functionName || '(anonymous)';
var lineNumber = frame.location.lineNumber;
var columnNumber = frame.location.columnNumber;
InspectorTest.log(`at ${functionName} (${lineNumber}:${columnNumber}):`);
if (!/^wasm/.test(frame.url)) {
InspectorTest.log(' -- skipped');
continue;
}
for (var scope of frame.scopeChain) {
InspectorTest.logObject(' - scope (' + scope.type + '):');
var properties = await Protocol.Runtime.getProperties(
{'objectId': scope.object.objectId});
await WasmInspectorTest.dumpScopeProperties(properties);
if (scope.type === 'wasm-expression-stack' || scope.type === 'local') {
for (var value of properties.result.result) {
var details = await Protocol.Runtime.getProperties(
{objectId: value.value.objectId});
var nested_value =
details.result.result.find(({name}) => name === 'value');
if (!nested_value.value.objectId) continue;
details = await Protocol.Runtime.getProperties(
{objectId: nested_value.value.objectId});
InspectorTest.log(' object details:');
await WasmInspectorTest.dumpScopeProperties(details);
}
}
}
}
Protocol.Debugger.resume();
});
InspectorTest.runAsyncTestSuite([
async function test() {
await Protocol.Debugger.enable();
InspectorTest.log('Instantiating.');
// Spawn asynchronously:
WasmInspectorTest.instantiate(module_bytes);
InspectorTest.log(
'Waiting for wasm script (ignoring first non-wasm script).');
// Ignore javascript and full module wasm script, get scripts for functions.
const [, {params: wasm_script}] =
await Protocol.Debugger.onceScriptParsed(2);
let offset = 107; // "local.set $varC" at the end.
await setBreakpoint(offset, wasm_script.scriptId, wasm_script.url);
InspectorTest.log('Calling main()');
await WasmInspectorTest.evalWithUrl('instance.exports.main()', 'runWasm');
InspectorTest.log('exports.main returned!');
}
]);
......@@ -51,12 +51,13 @@ WasmInspectorTest.dumpScopeProperties = async function(message) {
};
WasmInspectorTest.getWasmValue = async function(value) {
let msg = await Protocol.Runtime.getProperties({objectId: value.objectId});
let msg = await Protocol.Runtime.getProperties({ objectId: value.objectId });
printIfFailure(msg);
const value_type = msg.result.result.find(({name}) => name === 'type');
const value_value = msg.result.result.find(({name}) => name === 'value');
return `${
value_value.value.unserializableValue ??
value_value.value.description ??
value_value.value.value} (${value_type.value.value})`;
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment