Commit f13c807e authored by Z Nguyen-Huu's avatar Z Nguyen-Huu Committed by Commit Bot

[wasm] WAT-compatible naming for memory

Currently, only one memory is supported.

For memory, we would use name as follows.
1. If import: use <import_module>.<field_name> from WasmImport.
2. If export: use <field_name> from WasmExport.
3. Use memory<index>.

Doc: https://docs.google.com/document/d/1XoXWONLBgZWQ9dhtoMpQPvD0fnnWA50OorsuSXfME3g
Bug: v8:10242
Change-Id: Ifd342bcd86ac302f5b43f2ee88a8ea21a28b5a0f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2132724
Commit-Queue: Z Nguyen-Huu <duongn@microsoft.com>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarKim-Anh Tran <kimanh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67020}
parent 1766012c
......@@ -2257,17 +2257,18 @@ void DecodeFunctionNames(const byte* module_start, const byte* module_end,
}
}
void DecodeGlobalNames(
const Vector<const WasmImport> import_table,
void GenerateNamesFromImportsAndExports(
ImportExportKindCode kind, const Vector<const WasmImport> import_table,
const Vector<const WasmExport> export_table,
std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>*
names) {
DCHECK_NOT_NULL(names);
DCHECK(names->empty());
DCHECK(kind == kExternalGlobal || kind == kExternalMemory);
// Extract from import table.
for (const WasmImport& imp : import_table) {
if (imp.kind != kExternalGlobal) continue;
if (imp.kind != kind) continue;
if (!imp.module_name.is_set() || !imp.field_name.is_set()) continue;
if (names->count(imp.index) == 0) {
names->insert(std::make_pair(
......@@ -2277,7 +2278,7 @@ void DecodeGlobalNames(
// Extract from export table.
for (const WasmExport& exp : export_table) {
if (exp.kind != kExternalGlobal) continue;
if (exp.kind != kind) continue;
if (!exp.name.is_set()) continue;
if (names->count(exp.index) == 0) {
names->insert(
......
......@@ -168,10 +168,10 @@ 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 global names from import table and export table. Returns the
// result as an unordered map.
void DecodeGlobalNames(
const Vector<const WasmImport> import_table,
// Decode the global or memory names from import table and export table. Returns
// the result as an unordered map.
void GenerateNamesFromImportsAndExports(
ImportExportKindCode kind, const Vector<const WasmImport> import_table,
const Vector<const WasmExport> export_table,
std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>* names);
......
......@@ -195,8 +195,10 @@ void WasmCode::LogCode(Isolate* isolate) const {
if (IsAnonymous()) return;
ModuleWireBytes wire_bytes(native_module()->wire_bytes());
WireBytesRef name_ref = native_module()->module()->function_names.Lookup(
wire_bytes, index(), VectorOf(native_module()->module()->export_table));
WireBytesRef name_ref =
native_module()->module()->lazily_generated_names.LookupFunctionName(
wire_bytes, index(),
VectorOf(native_module()->module()->export_table));
WasmName name = wire_bytes.GetNameOrNull(name_ref);
const std::string& source_map_url = native_module()->module()->source_map_url;
......
......@@ -162,8 +162,9 @@ static bool VerifyEvaluatorInterface(const WasmModule* raw_module,
const ModuleWireBytes& bytes,
ErrorThrower* thrower) {
for (const WasmFunction& F : raw_module->functions) {
WireBytesRef name_ref = raw_module->function_names.Lookup(
bytes, F.func_index, VectorOf(raw_module->export_table));
WireBytesRef name_ref =
raw_module->lazily_generated_names.LookupFunctionName(
bytes, F.func_index, VectorOf(raw_module->export_table));
std::string name(bytes.start() + name_ref.offset(),
bytes.start() + name_ref.end_offset());
if (F.exported && name == "wasm_format") {
......
......@@ -519,8 +519,15 @@ Handle<JSObject> GetGlobalScopeObject(Handle<WasmInstanceObject> instance) {
Handle<JSObject> global_scope_object =
isolate->factory()->NewJSObjectWithNullProto();
if (instance->has_memory_object()) {
Handle<String> name =
isolate->factory()->InternalizeString(StaticCharVector("memory"));
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<JSArrayBuffer> memory_buffer(
instance->memory_object().array_buffer(), isolate);
Handle<JSTypedArray> uint8_array = isolate->factory()->NewJSTypedArray(
......
......@@ -30,7 +30,7 @@ namespace wasm {
// static
const uint32_t WasmElemSegment::kNullIndex;
WireBytesRef DecodedFunctionNames::Lookup(
WireBytesRef LazilyGeneratedNames::LookupFunctionName(
const ModuleWireBytes& wire_bytes, uint32_t function_index,
Vector<const WasmExport> export_table) const {
base::MutexGuard lock(&mutex_);
......@@ -44,18 +44,23 @@ WireBytesRef DecodedFunctionNames::Lookup(
return it->second;
}
std::pair<WireBytesRef, WireBytesRef> DecodedGlobalNames::Lookup(
uint32_t global_index, Vector<const WasmImport> import_table,
std::pair<WireBytesRef, WireBytesRef>
LazilyGeneratedNames::LookupNameFromImportsAndExports(
ImportExportKindCode kind, uint32_t index,
Vector<const WasmImport> import_table,
Vector<const WasmExport> export_table) const {
base::MutexGuard lock(&mutex_);
if (!global_names_) {
global_names_.reset(
DCHECK(kind == kExternalGlobal || kind == kExternalMemory);
auto& names = kind == kExternalGlobal ? global_names_ : memory_names_;
if (!names) {
names.reset(
new std::unordered_map<uint32_t,
std::pair<WireBytesRef, WireBytesRef>>());
DecodeGlobalNames(import_table, export_table, global_names_.get());
GenerateNamesFromImportsAndExports(kind, import_table, export_table,
names.get());
}
auto it = global_names_->find(global_index);
if (it == global_names_->end()) return {};
auto it = names->find(index);
if (it == names->end()) return {};
return it->second;
}
......@@ -118,7 +123,7 @@ int GetContainingWasmFunction(const WasmModule* module, uint32_t byte_offset) {
return func_index;
}
void DecodedFunctionNames::AddForTesting(int function_index,
void LazilyGeneratedNames::AddForTesting(int function_index,
WireBytesRef name) {
base::MutexGuard lock(&mutex_);
if (!function_names_) {
......@@ -192,7 +197,7 @@ WasmName ModuleWireBytes::GetNameOrNull(WireBytesRef ref) const {
// Get a string stored in the module bytes representing a function name.
WasmName ModuleWireBytes::GetNameOrNull(const WasmFunction* function,
const WasmModule* module) const {
return GetNameOrNull(module->function_names.Lookup(
return GetNameOrNull(module->lazily_generated_names.LookupFunctionName(
*this, function->func_index, VectorOf(module->export_table)));
}
......
......@@ -191,34 +191,33 @@ enum ModuleOrigin : uint8_t {
struct ModuleWireBytes;
class V8_EXPORT_PRIVATE DecodedFunctionNames {
class V8_EXPORT_PRIVATE LazilyGeneratedNames {
public:
WireBytesRef Lookup(const ModuleWireBytes& wire_bytes,
uint32_t function_index,
Vector<const WasmExport> export_table) const;
WireBytesRef LookupFunctionName(const ModuleWireBytes& wire_bytes,
uint32_t function_index,
Vector<const WasmExport> export_table) const;
// For memory and global.
std::pair<WireBytesRef, WireBytesRef> LookupNameFromImportsAndExports(
ImportExportKindCode kind, uint32_t index,
const Vector<const WasmImport> import_table,
const Vector<const WasmExport> export_table) const;
void AddForTesting(int function_index, WireBytesRef name);
private:
// {function_names_} is populated lazily after decoding, and therefore needs a
// mutex to protect concurrent modifications from multiple {WasmModuleObject}.
// {function_names_}, {global_names_} and {memory_names_} are
// populated lazily after decoding, and therefore need a mutex to protect
// concurrent modifications from multiple {WasmModuleObject}.
mutable base::Mutex mutex_;
mutable std::unique_ptr<std::unordered_map<uint32_t, WireBytesRef>>
function_names_;
};
class V8_EXPORT_PRIVATE DecodedGlobalNames {
public:
std::pair<WireBytesRef, WireBytesRef> Lookup(
uint32_t global_index, Vector<const WasmImport> import_table,
Vector<const WasmExport> export_table) const;
private:
// {global_names_} is populated lazily after decoding, and therefore needs a
// mutex to protect concurrent modifications from multiple {WasmModuleObject}.
mutable base::Mutex mutex_;
mutable std::unique_ptr<
std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>>
global_names_;
mutable std::unique_ptr<
std::unordered_map<uint32_t, std::pair<WireBytesRef, WireBytesRef>>>
memory_names_;
};
class V8_EXPORT_PRIVATE AsmJsOffsetInformation {
......@@ -286,8 +285,7 @@ struct V8_EXPORT_PRIVATE WasmModule {
SignatureMap signature_map; // canonicalizing map for signature indexes.
ModuleOrigin origin = kWasmOrigin; // origin of the module
DecodedFunctionNames function_names;
DecodedGlobalNames global_names;
LazilyGeneratedNames lazily_generated_names;
std::string source_map_url;
// Asm.js source position information. Only available for modules compiled
......
......@@ -241,9 +241,10 @@ MaybeHandle<String> WasmModuleObject::GetFunctionNameOrNull(
Isolate* isolate, Handle<WasmModuleObject> module_object,
uint32_t func_index) {
DCHECK_LT(func_index, module_object->module()->functions.size());
wasm::WireBytesRef name = module_object->module()->function_names.Lookup(
wasm::ModuleWireBytes(module_object->native_module()->wire_bytes()),
func_index, VectorOf(module_object->module()->export_table));
wasm::WireBytesRef name =
module_object->module()->lazily_generated_names.LookupFunctionName(
wasm::ModuleWireBytes(module_object->native_module()->wire_bytes()),
func_index, VectorOf(module_object->module()->export_table));
if (!name.is_set()) return {};
return ExtractUtf8StringFromModuleBytes(isolate, module_object, name,
kNoInternalize);
......@@ -267,8 +268,9 @@ Vector<const uint8_t> WasmModuleObject::GetRawFunctionName(
uint32_t func_index) {
DCHECK_GT(module()->functions.size(), func_index);
wasm::ModuleWireBytes wire_bytes(native_module()->wire_bytes());
wasm::WireBytesRef name_ref = module()->function_names.Lookup(
wire_bytes, func_index, VectorOf(module()->export_table));
wasm::WireBytesRef name_ref =
module()->lazily_generated_names.LookupFunctionName(
wire_bytes, func_index, VectorOf(module()->export_table));
wasm::WasmName name = wire_bytes.GetNameOrNull(name_ref);
return Vector<const uint8_t>::cast(name);
}
......@@ -1523,27 +1525,48 @@ WasmInstanceObject::GetGlobalBufferAndIndex(Handle<WasmInstanceObject> instance,
MaybeHandle<String> WasmInstanceObject::GetGlobalNameOrNull(
Isolate* isolate, Handle<WasmInstanceObject> instance,
uint32_t global_index) {
return WasmInstanceObject::GetNameFromImportsAndExportsOrNull(
isolate, instance, wasm::ImportExportKindCode::kExternalGlobal,
global_index);
}
// static
MaybeHandle<String> WasmInstanceObject::GetMemoryNameOrNull(
Isolate* isolate, Handle<WasmInstanceObject> instance,
uint32_t global_index) {
return WasmInstanceObject::GetNameFromImportsAndExportsOrNull(
isolate, instance, wasm::ImportExportKindCode::kExternalMemory,
global_index);
}
// static
MaybeHandle<String> WasmInstanceObject::GetNameFromImportsAndExportsOrNull(
Isolate* isolate, Handle<WasmInstanceObject> instance,
wasm::ImportExportKindCode kind, uint32_t index) {
DCHECK(kind == wasm::ImportExportKindCode::kExternalGlobal ||
kind == wasm::ImportExportKindCode::kExternalMemory);
wasm::ModuleWireBytes wire_bytes(
instance->module_object().native_module()->wire_bytes());
// This is pair of <module_name, field_name>.
// If field_name is not set then we don't generate a name. Else if module_name
// is set then it is imported global. Otherwise it is exported global.
// is set then it is an imported one. Otherwise it is an exported one.
std::pair<wasm::WireBytesRef, wasm::WireBytesRef> name_ref =
instance->module()->global_names.Lookup(
global_index, VectorOf(instance->module()->import_table),
VectorOf(instance->module()->export_table));
instance->module()
->lazily_generated_names.LookupNameFromImportsAndExports(
kind, index, VectorOf(instance->module()->import_table),
VectorOf(instance->module()->export_table));
if (!name_ref.second.is_set()) return {};
Vector<const char> field_name = wire_bytes.GetNameOrNull(name_ref.second);
if (!name_ref.first.is_set()) {
return isolate->factory()->NewStringFromUtf8(VectorOf(field_name));
}
Vector<const char> module_name = wire_bytes.GetNameOrNull(name_ref.first);
std::string global_name;
global_name.append(module_name.begin(), module_name.end());
global_name.append(".");
global_name.append(field_name.begin(), field_name.end());
return isolate->factory()->NewStringFromUtf8(VectorOf(global_name));
std::string full_name;
full_name.append(module_name.begin(), module_name.end());
full_name.append(".");
full_name.append(field_name.begin(), field_name.end());
return isolate->factory()->NewStringFromUtf8(VectorOf(full_name));
}
// static
......
......@@ -572,9 +572,19 @@ class WasmInstanceObject : public JSObject {
Handle<WasmInstanceObject>,
uint32_t global_index);
// Get the name of a memory in the given instance by index.
static MaybeHandle<String> GetMemoryNameOrNull(Isolate*,
Handle<WasmInstanceObject>,
uint32_t memory_index);
OBJECT_CONSTRUCTORS(WasmInstanceObject, JSObject);
private:
// Get the name in the given instance by index and kind.
static MaybeHandle<String> GetNameFromImportsAndExportsOrNull(
Isolate*, Handle<WasmInstanceObject>, wasm::ImportExportKindCode kind,
uint32_t index);
static void InitDataSegmentArrays(Handle<WasmInstanceObject>,
Handle<WasmModuleObject>);
static void InitElemSegmentArrays(Handle<WasmInstanceObject>,
......
......@@ -164,7 +164,7 @@ uint32_t TestingModuleBuilder::AddFunction(const FunctionSig* sig,
test_module_->num_declared_functions);
if (name) {
Vector<const byte> name_vec = Vector<const byte>::cast(CStrVector(name));
test_module_->function_names.AddForTesting(
test_module_->lazily_generated_names.AddForTesting(
index, {AddBytes(name_vec), static_cast<uint32_t>(name_vec.length())});
}
if (interpreter_) {
......
Test wasm memory names
Waiting for wasm script to be parsed.
Setting breakpoint in wasm.
Running main.
Paused in debugger.
name: memory0
Finished.
Waiting for wasm script to be parsed.
Setting breakpoint in wasm.
Running main.
Paused in debugger.
name: exported_memory
Finished.
Waiting for wasm script to be parsed.
Setting breakpoint in wasm.
Running main.
Paused in debugger.
name: module_name.imported_mem
Finished.
// Copyright 2020 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.
let {session, contextGroup, Protocol} =
InspectorTest.start('Test wasm memory names');
utils.load('test/mjsunit/wasm/wasm-module-builder.js');
let func;
// No name memory.
function createModuleBytesUnnamedMemory() {
let builder = new WasmModuleBuilder();
builder.addMemory(1, 1);
func = builder.addFunction('main', kSig_i_i)
.addBody([kExprI32Const, 0, kExprI32LoadMem, 0, 0])
.exportAs('main');
return JSON.stringify(builder.toArray());
}
// Exported memory.
function createModuleBytesExportedMemory() {
let builder = new WasmModuleBuilder();
var memory = builder.addMemory(1, 1);
builder.addExportOfKind('exported_memory', kExternalMemory);
func = builder.addFunction('main', kSig_i_i)
.addBody([kExprI32Const, 0, kExprI32LoadMem, 0, 0])
.exportAs('main');
return JSON.stringify(builder.toArray());
}
// Imported memory.
function createModuleBytesImportedMemory() {
let builder = new WasmModuleBuilder();
builder.addImportedMemory('module_name', 'imported_mem', 0, 1);
func = builder.addFunction('main', kSig_i_i)
.addBody([kExprI32Const, 0, kExprI32LoadMem, 0, 0])
.exportAs('main');
return JSON.stringify(builder.toArray());
}
function createInstance(moduleBytes) {
let module = new WebAssembly.Module((new Uint8Array(moduleBytes)).buffer);
let memory = new WebAssembly.Memory({initial: 1, maximum: 1});
instance =
new WebAssembly.Instance(module, {module_name: {imported_mem: memory}});
}
async function logMemoryName(msg, Protocol) {
let callFrames = msg.params.callFrames;
InspectorTest.log('Paused in debugger.');
let scopeChain = callFrames[0].scopeChain;
for (let scope of scopeChain) {
if (scope.type != 'global') continue;
let globalObjectProps = (await Protocol.Runtime.getProperties({
'objectId': scope.object.objectId
})).result.result;
for (let prop of globalObjectProps) {
InspectorTest.log(`name: ${prop.name}`);
}
}
}
async function check(moduleBytes) {
Protocol.Runtime.evaluate({
expression: `
createInstance(${moduleBytes});`
});
InspectorTest.log('Waiting for wasm script to be parsed.');
let scriptId;
while (true) {
let msg = await Protocol.Debugger.onceScriptParsed();
if (msg.params.url.startsWith('wasm://')) {
scriptId = msg.params.scriptId;
break;
}
}
InspectorTest.log('Setting breakpoint in wasm.');
await Protocol.Debugger.setBreakpoint(
{location: {scriptId, lineNumber: 0, columnNumber: func.body_offset}});
InspectorTest.log('Running main.');
Protocol.Runtime.evaluate({expression: 'instance.exports.main()'});
let msg = await Protocol.Debugger.oncePaused();
await logMemoryName(msg, Protocol);
await Protocol.Debugger.resume();
InspectorTest.log('Finished.');
}
contextGroup.addScript(`
let instance;
${createInstance.toString()}`);
(async function test() {
try {
Protocol.Debugger.enable();
await check(createModuleBytesUnnamedMemory());
await check(createModuleBytesExportedMemory());
await check(createModuleBytesImportedMemory());
} catch (exc) {
InspectorTest.log(`Failed with exception: ${exc}.`);
} finally {
InspectorTest.completeTest();
}
})();
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