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(
names) {
DCHECK_NOT_NULL(names);
DCHECK(names->empty());
DCHECK(kind == kExternalGlobal || kind == kExternalMemory);
DCHECK(kind == kExternalGlobal || kind == kExternalMemory ||
kind == kExternalTable);
// Extract from import table.
for (const WasmImport& imp : import_table) {
......
......@@ -49,6 +49,59 @@ Handle<String> PrintFToOneByteString(Isolate* isolate, const char* format,
: isolate->factory()->NewStringFromOneByte(name).ToHandleChecked();
}
MaybeHandle<JSObject> CreateFunctionTablesObject(
Handle<WasmInstanceObject> instance) {
Isolate* isolate = instance->GetIsolate();
auto tables = 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<ByteArray> bytes;
switch (value.type().kind()) {
......@@ -302,6 +355,14 @@ Handle<JSObject> GetModuleScopeObject(Handle<WasmInstanceObject> instance) {
NONE);
}
Handle<JSObject> function_tables_obj;
if (CreateFunctionTablesObject(instance).ToHandle(&function_tables_obj)) {
Handle<String> tables_name = isolate->factory()->InternalizeString(
StaticCharVector("function tables"));
JSObject::AddProperty(isolate, module_scope_object, tables_name,
function_tables_obj, NONE);
}
auto& globals = instance->module()->globals;
if (globals.size() > 0) {
Handle<JSObject> globals_obj =
......
......@@ -50,8 +50,11 @@ LazilyGeneratedNames::LookupNameFromImportsAndExports(
Vector<const WasmImport> import_table,
Vector<const WasmExport> export_table) const {
base::MutexGuard lock(&mutex_);
DCHECK(kind == kExternalGlobal || kind == kExternalMemory);
auto& names = kind == kExternalGlobal ? global_names_ : memory_names_;
DCHECK(kind == kExternalGlobal || kind == kExternalMemory ||
kind == kExternalTable);
auto& names = kind == kExternalGlobal
? global_names_
: kind == kExternalMemory ? memory_names_ : table_names_;
if (!names) {
names.reset(
new std::unordered_map<uint32_t,
......
......@@ -206,7 +206,7 @@ class V8_EXPORT_PRIVATE LazilyGeneratedNames {
void AddForTesting(int function_index, WireBytesRef name);
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
// concurrent modifications from multiple {WasmModuleObject}.
mutable base::Mutex mutex_;
......@@ -218,6 +218,9 @@ class V8_EXPORT_PRIVATE LazilyGeneratedNames {
mutable std::unique_ptr<
std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>>
memory_names_;
mutable std::unique_ptr<
std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>>
table_names_;
};
class V8_EXPORT_PRIVATE AsmJsOffsetInformation {
......
......@@ -1522,10 +1522,19 @@ MaybeHandle<String> WasmInstanceObject::GetGlobalNameOrNull(
// static
MaybeHandle<String> WasmInstanceObject::GetMemoryNameOrNull(
Isolate* isolate, Handle<WasmInstanceObject> instance,
uint32_t global_index) {
uint32_t memory_index) {
return WasmInstanceObject::GetNameFromImportsAndExportsOrNull(
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
......@@ -1533,7 +1542,8 @@ MaybeHandle<String> WasmInstanceObject::GetNameFromImportsAndExportsOrNull(
Isolate* isolate, Handle<WasmInstanceObject> instance,
wasm::ImportExportKindCode kind, uint32_t index) {
DCHECK(kind == wasm::ImportExportKindCode::kExternalGlobal ||
kind == wasm::ImportExportKindCode::kExternalMemory);
kind == wasm::ImportExportKindCode::kExternalMemory ||
kind == wasm::ImportExportKindCode::kExternalTable);
wasm::ModuleWireBytes wire_bytes(
instance->module_object().native_module()->wire_bytes());
......
......@@ -578,6 +578,11 @@ class WasmInstanceObject : public JSObject {
Handle<WasmInstanceObject>,
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);
private:
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-type-reflection
let {session, contextGroup, Protocol} = InspectorTest.start(
'Test retrieving scope information from compiled Liftoff frames');
session.setupScriptMap();
......@@ -65,7 +67,7 @@ async function instantiateWasm() {
// Add a global, memory and exports to populate the module scope.
builder.addGlobal(kWasmI32, true).exportAs('exported_global');
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
// stack values are shown correctly in Liftoff code.
......@@ -73,7 +75,7 @@ async function instantiateWasm() {
// function B.
// Function B has a local with a constant value (not using a register), the
// parameter will be held in a register.
builder.addFunction('A (liftoff)', kSig_v_i)
const main = builder.addFunction('A (liftoff)', kSig_v_i)
.addBody([
// Call function 'B', forwarding param 0.
kExprLocalGet, 0, kExprCallFunction, 1
......@@ -105,6 +107,9 @@ async function instantiateWasm() {
kExprI32Const, 47, kExprLocalSet, 1,
]);
// Append function to table to test function table output.
builder.appendToTable([main.index]);
var module_bytes = builder.toArray();
breakpointLocation = func.body_offset;
......@@ -115,8 +120,17 @@ async function instantiateWasm() {
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);
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.');
......@@ -150,6 +164,7 @@ async function getScopeValues(name, value) {
if (value.type == 'object') {
if (value.subtype == 'typedarray') return value.description;
if (name == 'instance') return dumpInstanceProperties(value);
if (name == 'function tables') return dumpTables(value);
let msg = await Protocol.Runtime.getProperties({objectId: value.objectId});
printIfFailure(msg);
......@@ -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) {
function invokeGetter(property) {
return this[JSON.parse(property)];
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-type-reflection
let {session, contextGroup, Protocol} = InspectorTest.start(
'Test retrieving scope information when pausing in wasm functions');
session.setupScriptMap();
......@@ -70,11 +72,11 @@ async function instantiateWasm() {
// Add a global, memory and exports to populate the module scope.
builder.addGlobal(kWasmI32, true).exportAs('exported_global');
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
// 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.
...wasmF32Const(7.2), kExprLocalSet, 1,
// Call function 'func', forwarding param 0.
......@@ -82,7 +84,7 @@ async function instantiateWasm() {
]).exportAs('main');
// 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(
{i32_count: 1, i64_count: 1, f64_count: 3},
['i32Arg', undefined, 'i64_local', 'unicode☼f64', '0', '0'])
......@@ -105,6 +107,9 @@ async function instantiateWasm() {
kExprI32Const, 15, kExprGlobalSet, 0,
]);
// Append function to table to test function table output.
builder.appendToTable([main.index]);
let moduleBytes = builder.toArray();
breakpointLocation = func.body_offset;
......@@ -115,8 +120,17 @@ async function instantiateWasm() {
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);
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.');
......@@ -148,6 +162,7 @@ async function getScopeValues(name, value) {
if (value.type == 'object') {
if (value.subtype == 'typedarray') return value.description;
if (name == 'instance') return dumpInstanceProperties(value);
if (name == 'function tables') return dumpTables(value);
let msg = await Protocol.Runtime.getProperties({objectId: value.objectId});
printIfFailure(msg);
......@@ -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) {
function invokeGetter(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