Commit f38e4e5f authored by Kim-Anh Tran's avatar Kim-Anh Tran Committed by Commit Bot

[wasm][debug] Expose wasm function tables in scope view

Bug: chromium:1081735
Change-Id: Iab58b303ec718a15653ba80fefbb873ef93df003
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2218284
Commit-Queue: Kim-Anh Tran <kimanh@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68153}
parent 2293a59f
...@@ -2362,7 +2362,8 @@ void GenerateNamesFromImportsAndExports( ...@@ -2362,7 +2362,8 @@ void GenerateNamesFromImportsAndExports(
names) { names) {
DCHECK_NOT_NULL(names); DCHECK_NOT_NULL(names);
DCHECK(names->empty()); DCHECK(names->empty());
DCHECK(kind == kExternalGlobal || kind == kExternalMemory); DCHECK(kind == kExternalGlobal || kind == kExternalMemory ||
kind == kExternalTable);
// Extract from import table. // Extract from import table.
for (const WasmImport& imp : import_table) { for (const WasmImport& imp : import_table) {
......
...@@ -49,6 +49,59 @@ Handle<String> PrintFToOneByteString(Isolate* isolate, const char* format, ...@@ -49,6 +49,59 @@ Handle<String> PrintFToOneByteString(Isolate* isolate, const char* format,
: isolate->factory()->NewStringFromOneByte(name).ToHandleChecked(); : isolate->factory()->NewStringFromOneByte(name).ToHandleChecked();
} }
MaybeHandle<JSObject> CreateFunctionTablesObject(
Handle<WasmInstanceObject> instance) {
Isolate* isolate = instance->GetIsolate();
auto tables = instance->tables();
if (tables.length() == 0) return MaybeHandle<JSObject>();
const char* table_label = "table%d";
Handle<JSObject> tables_obj = isolate->factory()->NewJSObjectWithNullProto();
for (int table_index = 0; table_index < tables.length(); ++table_index) {
auto func_table =
handle(WasmTableObject::cast(tables.get(table_index)), isolate);
if (func_table->type() != kWasmFuncRef) continue;
Handle<String> table_name;
if (!WasmInstanceObject::GetTableNameOrNull(isolate, instance, table_index)
.ToHandle(&table_name)) {
table_name =
PrintFToOneByteString<true>(isolate, table_label, table_index);
}
Handle<JSObject> func_table_obj =
isolate->factory()->NewJSObjectWithNullProto();
JSObject::AddProperty(isolate, tables_obj, table_name, func_table_obj,
NONE);
for (int i = 0; i < func_table->current_length(); ++i) {
Handle<Object> func = WasmTableObject::Get(isolate, func_table, i);
DCHECK(!WasmCapiFunction::IsWasmCapiFunction(*func));
if (func->IsNull(isolate)) continue;
Handle<String> func_name;
Handle<JSObject> func_obj =
isolate->factory()->NewJSObjectWithNullProto();
if (WasmExportedFunction::IsWasmExportedFunction(*func)) {
auto target_func = Handle<WasmExportedFunction>::cast(func);
auto target_instance = handle(target_func->instance(), isolate);
auto module = handle(target_instance->module_object(), isolate);
func_name = WasmModuleObject::GetFunctionName(
isolate, module, target_func->function_index());
} else if (WasmJSFunction::IsWasmJSFunction(*func)) {
auto target_func = Handle<JSFunction>::cast(func);
func_name = JSFunction::GetName(target_func);
if (func_name->length() == 0) {
func_name = isolate->factory()->InternalizeUtf8String("anonymous");
}
}
JSObject::AddProperty(isolate, func_obj, func_name, func, NONE);
JSObject::AddDataElement(func_table_obj, i, func_obj, NONE);
}
}
return tables_obj;
}
Handle<Object> WasmValueToValueObject(Isolate* isolate, WasmValue value) { Handle<Object> WasmValueToValueObject(Isolate* isolate, WasmValue value) {
Handle<ByteArray> bytes; Handle<ByteArray> bytes;
switch (value.type().kind()) { switch (value.type().kind()) {
...@@ -302,6 +355,14 @@ Handle<JSObject> GetModuleScopeObject(Handle<WasmInstanceObject> instance) { ...@@ -302,6 +355,14 @@ Handle<JSObject> GetModuleScopeObject(Handle<WasmInstanceObject> instance) {
NONE); NONE);
} }
Handle<JSObject> function_tables_obj;
if (CreateFunctionTablesObject(instance).ToHandle(&function_tables_obj)) {
Handle<String> tables_name = isolate->factory()->InternalizeString(
StaticCharVector("function tables"));
JSObject::AddProperty(isolate, module_scope_object, tables_name,
function_tables_obj, NONE);
}
auto& globals = instance->module()->globals; auto& globals = instance->module()->globals;
if (globals.size() > 0) { if (globals.size() > 0) {
Handle<JSObject> globals_obj = Handle<JSObject> globals_obj =
......
...@@ -50,8 +50,11 @@ LazilyGeneratedNames::LookupNameFromImportsAndExports( ...@@ -50,8 +50,11 @@ LazilyGeneratedNames::LookupNameFromImportsAndExports(
Vector<const WasmImport> import_table, Vector<const WasmImport> import_table,
Vector<const WasmExport> export_table) const { Vector<const WasmExport> export_table) const {
base::MutexGuard lock(&mutex_); base::MutexGuard lock(&mutex_);
DCHECK(kind == kExternalGlobal || kind == kExternalMemory); DCHECK(kind == kExternalGlobal || kind == kExternalMemory ||
auto& names = kind == kExternalGlobal ? global_names_ : memory_names_; kind == kExternalTable);
auto& names = kind == kExternalGlobal
? global_names_
: kind == kExternalMemory ? memory_names_ : table_names_;
if (!names) { if (!names) {
names.reset( names.reset(
new std::unordered_map<uint32_t, new std::unordered_map<uint32_t,
......
...@@ -206,7 +206,7 @@ class V8_EXPORT_PRIVATE LazilyGeneratedNames { ...@@ -206,7 +206,7 @@ class V8_EXPORT_PRIVATE LazilyGeneratedNames {
void AddForTesting(int function_index, WireBytesRef name); void AddForTesting(int function_index, WireBytesRef name);
private: private:
// {function_names_}, {global_names_} and {memory_names_} are // {function_names_}, {global_names_}, {memory_names_} and {table_names_} are
// populated lazily after decoding, and therefore need a mutex to protect // populated lazily after decoding, and therefore need a mutex to protect
// concurrent modifications from multiple {WasmModuleObject}. // concurrent modifications from multiple {WasmModuleObject}.
mutable base::Mutex mutex_; mutable base::Mutex mutex_;
...@@ -218,6 +218,9 @@ class V8_EXPORT_PRIVATE LazilyGeneratedNames { ...@@ -218,6 +218,9 @@ class V8_EXPORT_PRIVATE LazilyGeneratedNames {
mutable std::unique_ptr< mutable std::unique_ptr<
std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>> std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>>
memory_names_; memory_names_;
mutable std::unique_ptr<
std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>>
table_names_;
}; };
class V8_EXPORT_PRIVATE AsmJsOffsetInformation { class V8_EXPORT_PRIVATE AsmJsOffsetInformation {
......
...@@ -1522,10 +1522,19 @@ MaybeHandle<String> WasmInstanceObject::GetGlobalNameOrNull( ...@@ -1522,10 +1522,19 @@ MaybeHandle<String> WasmInstanceObject::GetGlobalNameOrNull(
// static // static
MaybeHandle<String> WasmInstanceObject::GetMemoryNameOrNull( MaybeHandle<String> WasmInstanceObject::GetMemoryNameOrNull(
Isolate* isolate, Handle<WasmInstanceObject> instance, Isolate* isolate, Handle<WasmInstanceObject> instance,
uint32_t global_index) { uint32_t memory_index) {
return WasmInstanceObject::GetNameFromImportsAndExportsOrNull( return WasmInstanceObject::GetNameFromImportsAndExportsOrNull(
isolate, instance, wasm::ImportExportKindCode::kExternalMemory, isolate, instance, wasm::ImportExportKindCode::kExternalMemory,
global_index); memory_index);
}
// static
MaybeHandle<String> WasmInstanceObject::GetTableNameOrNull(
Isolate* isolate, Handle<WasmInstanceObject> instance,
uint32_t table_index) {
return WasmInstanceObject::GetNameFromImportsAndExportsOrNull(
isolate, instance, wasm::ImportExportKindCode::kExternalTable,
table_index);
} }
// static // static
...@@ -1533,7 +1542,8 @@ MaybeHandle<String> WasmInstanceObject::GetNameFromImportsAndExportsOrNull( ...@@ -1533,7 +1542,8 @@ MaybeHandle<String> WasmInstanceObject::GetNameFromImportsAndExportsOrNull(
Isolate* isolate, Handle<WasmInstanceObject> instance, Isolate* isolate, Handle<WasmInstanceObject> instance,
wasm::ImportExportKindCode kind, uint32_t index) { wasm::ImportExportKindCode kind, uint32_t index) {
DCHECK(kind == wasm::ImportExportKindCode::kExternalGlobal || DCHECK(kind == wasm::ImportExportKindCode::kExternalGlobal ||
kind == wasm::ImportExportKindCode::kExternalMemory); kind == wasm::ImportExportKindCode::kExternalMemory ||
kind == wasm::ImportExportKindCode::kExternalTable);
wasm::ModuleWireBytes wire_bytes( wasm::ModuleWireBytes wire_bytes(
instance->module_object().native_module()->wire_bytes()); instance->module_object().native_module()->wire_bytes());
......
...@@ -578,6 +578,11 @@ class WasmInstanceObject : public JSObject { ...@@ -578,6 +578,11 @@ class WasmInstanceObject : public JSObject {
Handle<WasmInstanceObject>, Handle<WasmInstanceObject>,
uint32_t memory_index); uint32_t memory_index);
// Get the name of a table in the given instance by index.
static MaybeHandle<String> GetTableNameOrNull(Isolate*,
Handle<WasmInstanceObject>,
uint32_t table_index);
OBJECT_CONSTRUCTORS(WasmInstanceObject, JSObject); OBJECT_CONSTRUCTORS(WasmInstanceObject, JSObject);
private: private:
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// Flags: --experimental-wasm-type-reflection
let {session, contextGroup, Protocol} = InspectorTest.start( let {session, contextGroup, Protocol} = InspectorTest.start(
'Test retrieving scope information from compiled Liftoff frames'); 'Test retrieving scope information from compiled Liftoff frames');
session.setupScriptMap(); session.setupScriptMap();
...@@ -65,7 +67,7 @@ async function instantiateWasm() { ...@@ -65,7 +67,7 @@ async function instantiateWasm() {
// Add a global, memory and exports to populate the module scope. // Add a global, memory and exports to populate the module scope.
builder.addGlobal(kWasmI32, true).exportAs('exported_global'); builder.addGlobal(kWasmI32, true).exportAs('exported_global');
builder.addMemory(1,1).exportMemoryAs('exported_memory'); builder.addMemory(1,1).exportMemoryAs('exported_memory');
builder.addTable(kWasmAnyFunc, 0).exportAs('exported_table'); builder.addTable(kWasmAnyFunc, 3).exportAs('exported_table');
// Add two functions without breakpoint, to check that locals and operand // Add two functions without breakpoint, to check that locals and operand
// stack values are shown correctly in Liftoff code. // stack values are shown correctly in Liftoff code.
...@@ -73,7 +75,7 @@ async function instantiateWasm() { ...@@ -73,7 +75,7 @@ async function instantiateWasm() {
// function B. // function B.
// Function B has a local with a constant value (not using a register), the // Function B has a local with a constant value (not using a register), the
// parameter will be held in a register. // parameter will be held in a register.
builder.addFunction('A (liftoff)', kSig_v_i) const main = builder.addFunction('A (liftoff)', kSig_v_i)
.addBody([ .addBody([
// Call function 'B', forwarding param 0. // Call function 'B', forwarding param 0.
kExprLocalGet, 0, kExprCallFunction, 1 kExprLocalGet, 0, kExprCallFunction, 1
...@@ -105,6 +107,9 @@ async function instantiateWasm() { ...@@ -105,6 +107,9 @@ async function instantiateWasm() {
kExprI32Const, 47, kExprLocalSet, 1, kExprI32Const, 47, kExprLocalSet, 1,
]); ]);
// Append function to table to test function table output.
builder.appendToTable([main.index]);
var module_bytes = builder.toArray(); var module_bytes = builder.toArray();
breakpointLocation = func.body_offset; breakpointLocation = func.body_offset;
...@@ -115,8 +120,17 @@ async function instantiateWasm() { ...@@ -115,8 +120,17 @@ async function instantiateWasm() {
view[i] = bytes[i] | 0; view[i] = bytes[i] | 0;
} }
// Create WasmJS functions to test the function tables output.
const js_func = function js_func() { return 7; };
const wasmjs_func = new WebAssembly.Function({parameters:[], results:['i32']}, js_func);
const wasmjs_anonymous_func = new WebAssembly.Function({parameters:[], results:['i32']}, _ => 7);
var module = new WebAssembly.Module(buffer); var module = new WebAssembly.Module(buffer);
return new WebAssembly.Instance(module); const instance = new WebAssembly.Instance(module);
instance.exports.exported_table.set(0, wasmjs_func);
instance.exports.exported_table.set(1, wasmjs_anonymous_func);
return instance;
} }
InspectorTest.log('Installing instantiate code.'); InspectorTest.log('Installing instantiate code.');
...@@ -150,6 +164,7 @@ async function getScopeValues(name, value) { ...@@ -150,6 +164,7 @@ async function getScopeValues(name, value) {
if (value.type == 'object') { if (value.type == 'object') {
if (value.subtype == 'typedarray') return value.description; if (value.subtype == 'typedarray') return value.description;
if (name == 'instance') return dumpInstanceProperties(value); if (name == 'instance') return dumpInstanceProperties(value);
if (name == 'function tables') return dumpTables(value);
let msg = await Protocol.Runtime.getProperties({objectId: value.objectId}); let msg = await Protocol.Runtime.getProperties({objectId: value.objectId});
printIfFailure(msg); printIfFailure(msg);
...@@ -169,6 +184,38 @@ async function dumpScopeProperties(message) { ...@@ -169,6 +184,38 @@ async function dumpScopeProperties(message) {
} }
} }
function recursiveGetPropertiesWrapper(value, depth) {
return recursiveGetProperties({result: {result: [value]}}, depth);
}
async function recursiveGetProperties(value, depth) {
if (depth > 0) {
const properties = await Promise.all(value.result.result.map(
x => {return Protocol.Runtime.getProperties({objectId: x.value.objectId});}));
const recursiveProperties = await Promise.all(properties.map(
x => {return recursiveGetProperties(x, depth - 1);}));
return recursiveProperties.flat();
}
return value;
}
async function dumpTables(tablesObj) {
let msg = await Protocol.Runtime.getProperties({objectId: tablesObj.objectId});
var tables_str = [];
for (var table of msg.result.result) {
const func_entries = await recursiveGetPropertiesWrapper(table, 2);
var functions = [];
for (var func of func_entries) {
for (var value of func.result.result) {
functions.push(`${value.name}: ${value.value.description}`);
}
}
const functions_str = functions.join(', ');
tables_str.push(` ${table.name}: ${functions_str}`);
}
return '\n' + tables_str.join('\n');
}
async function dumpInstanceProperties(instanceObj) { async function dumpInstanceProperties(instanceObj) {
function invokeGetter(property) { function invokeGetter(property) {
return this[JSON.parse(property)]; return this[JSON.parse(property)];
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// Flags: --experimental-wasm-type-reflection
let {session, contextGroup, Protocol} = InspectorTest.start( let {session, contextGroup, Protocol} = InspectorTest.start(
'Test retrieving scope information when pausing in wasm functions'); 'Test retrieving scope information when pausing in wasm functions');
session.setupScriptMap(); session.setupScriptMap();
...@@ -70,11 +72,11 @@ async function instantiateWasm() { ...@@ -70,11 +72,11 @@ async function instantiateWasm() {
// Add a global, memory and exports to populate the module scope. // Add a global, memory and exports to populate the module scope.
builder.addGlobal(kWasmI32, true).exportAs('exported_global'); builder.addGlobal(kWasmI32, true).exportAs('exported_global');
builder.addMemory(1,1).exportMemoryAs('exported_memory'); builder.addMemory(1,1).exportMemoryAs('exported_memory');
builder.addTable(kWasmAnyFunc, 0).exportAs('exported_table'); builder.addTable(kWasmAnyFunc, 3).exportAs('exported_table');
// Add a function without breakpoint, to check that locals are shown // Add a function without breakpoint, to check that locals are shown
// correctly in compiled code. // correctly in compiled code.
builder.addFunction('call_func', kSig_v_i).addLocals({f32_count: 1}).addBody([ const main = builder.addFunction('call_func', kSig_v_i).addLocals({f32_count: 1}).addBody([
// Set local 1 to 7.2. // Set local 1 to 7.2.
...wasmF32Const(7.2), kExprLocalSet, 1, ...wasmF32Const(7.2), kExprLocalSet, 1,
// Call function 'func', forwarding param 0. // Call function 'func', forwarding param 0.
...@@ -82,7 +84,7 @@ async function instantiateWasm() { ...@@ -82,7 +84,7 @@ async function instantiateWasm() {
]).exportAs('main'); ]).exportAs('main');
// A second function which will be stepped through. // A second function which will be stepped through.
let func = builder.addFunction('func', kSig_v_i) const func = builder.addFunction('func', kSig_v_i)
.addLocals( .addLocals(
{i32_count: 1, i64_count: 1, f64_count: 3}, {i32_count: 1, i64_count: 1, f64_count: 3},
['i32Arg', undefined, 'i64_local', 'unicode☼f64', '0', '0']) ['i32Arg', undefined, 'i64_local', 'unicode☼f64', '0', '0'])
...@@ -105,6 +107,9 @@ async function instantiateWasm() { ...@@ -105,6 +107,9 @@ async function instantiateWasm() {
kExprI32Const, 15, kExprGlobalSet, 0, kExprI32Const, 15, kExprGlobalSet, 0,
]); ]);
// Append function to table to test function table output.
builder.appendToTable([main.index]);
let moduleBytes = builder.toArray(); let moduleBytes = builder.toArray();
breakpointLocation = func.body_offset; breakpointLocation = func.body_offset;
...@@ -115,8 +120,17 @@ async function instantiateWasm() { ...@@ -115,8 +120,17 @@ async function instantiateWasm() {
view[i] = bytes[i] | 0; view[i] = bytes[i] | 0;
} }
// Create WasmJS functions to test the function tables output.
const js_func = function js_func() { return 7; };
const wasmjs_func = new WebAssembly.Function({parameters:[], results:['i32']}, js_func);
const wasmjs_anonymous_func = new WebAssembly.Function({parameters:[], results:['i32']}, _ => 7);
var module = new WebAssembly.Module(buffer); var module = new WebAssembly.Module(buffer);
return new WebAssembly.Instance(module); const instance = new WebAssembly.Instance(module);
instance.exports.exported_table.set(0, wasmjs_func);
instance.exports.exported_table.set(1, wasmjs_anonymous_func);
return instance;
} }
InspectorTest.log('Calling instantiate function.'); InspectorTest.log('Calling instantiate function.');
...@@ -148,6 +162,7 @@ async function getScopeValues(name, value) { ...@@ -148,6 +162,7 @@ async function getScopeValues(name, value) {
if (value.type == 'object') { if (value.type == 'object') {
if (value.subtype == 'typedarray') return value.description; if (value.subtype == 'typedarray') return value.description;
if (name == 'instance') return dumpInstanceProperties(value); if (name == 'instance') return dumpInstanceProperties(value);
if (name == 'function tables') return dumpTables(value);
let msg = await Protocol.Runtime.getProperties({objectId: value.objectId}); let msg = await Protocol.Runtime.getProperties({objectId: value.objectId});
printIfFailure(msg); printIfFailure(msg);
...@@ -167,6 +182,38 @@ async function dumpScopeProperties(message) { ...@@ -167,6 +182,38 @@ async function dumpScopeProperties(message) {
} }
} }
function recursiveGetPropertiesWrapper(value, depth) {
return recursiveGetProperties({result: {result: [value]}}, depth);
}
async function recursiveGetProperties(value, depth) {
if (depth > 0) {
const properties = await Promise.all(value.result.result.map(
x => {return Protocol.Runtime.getProperties({objectId: x.value.objectId});}));
const recursiveProperties = await Promise.all(properties.map(
x => {return recursiveGetProperties(x, depth - 1);}));
return recursiveProperties.flat();
}
return value;
}
async function dumpTables(tablesObj) {
let msg = await Protocol.Runtime.getProperties({objectId: tablesObj.objectId});
var tables_str = [];
for (var table of msg.result.result) {
const func_entries = await recursiveGetPropertiesWrapper(table, 2);
var functions = [];
for (var func of func_entries) {
for (var value of func.result.result) {
functions.push(`${value.name}: ${value.value.description}`);
}
}
const functions_str = functions.join(', ');
tables_str.push(` ${table.name}: ${functions_str}`);
}
return '\n' + tables_str.join('\n');
}
async function dumpInstanceProperties(instanceObj) { async function dumpInstanceProperties(instanceObj) {
function invokeGetter(property) { function invokeGetter(property) {
return this[JSON.parse(property)]; return this[JSON.parse(property)];
......
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