Commit 9983fda8 authored by Jakob Kummerow's avatar Jakob Kummerow Committed by V8 LUCI CQ

[wasm] Introduce NamesProvider

NamesProvider class:
This consolidates logic used so far for the debugger interface.
It also adds support for the "extended name section" proposal:
https://github.com/WebAssembly/extended-name-section

StringBuilder class:
Like std::ostringstream, but 4x faster for this use case.

This lays the groundwork for an updated Wasm disassembler.

Bug: v8:12917
Change-Id: I98aa258147834bc0e314ba98c5927b4cd6070b8f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3720714Reviewed-by: 's avatarPhilip Pfaffe <pfaffe@chromium.org>
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81446}
parent 73ba62bc
......@@ -2512,6 +2512,8 @@ filegroup(
"src/wasm/module-decoder.h",
"src/wasm/module-instantiate.cc",
"src/wasm/module-instantiate.h",
"src/wasm/names-provider.cc",
"src/wasm/names-provider.h",
"src/wasm/object-access.h",
"src/wasm/signature-map.cc",
"src/wasm/signature-map.h",
......@@ -2520,6 +2522,7 @@ filegroup(
"src/wasm/stacks.h",
"src/wasm/streaming-decoder.cc",
"src/wasm/streaming-decoder.h",
"src/wasm/string-builder.h",
"src/wasm/struct-types.h",
"src/wasm/sync-streaming-decoder.cc",
"src/wasm/value-type.cc",
......
......@@ -3610,15 +3610,18 @@ v8_header_set("v8_internal_headers") {
"src/wasm/module-compiler.h",
"src/wasm/module-decoder.h",
"src/wasm/module-instantiate.h",
"src/wasm/names-provider.h",
"src/wasm/object-access.h",
"src/wasm/signature-map.h",
"src/wasm/simd-shuffle.h",
"src/wasm/stacks.h",
"src/wasm/streaming-decoder.h",
"src/wasm/string-builder.h",
"src/wasm/struct-types.h",
"src/wasm/value-type.h",
"src/wasm/wasm-arguments.h",
"src/wasm/wasm-code-manager.h",
"src/wasm/wasm-debug.h",
"src/wasm/wasm-engine.h",
"src/wasm/wasm-external-refs.h",
"src/wasm/wasm-feature-flags.h",
......@@ -4694,6 +4697,7 @@ v8_source_set("v8_base_without_compiler") {
"src/wasm/module-compiler.cc",
"src/wasm/module-decoder.cc",
"src/wasm/module-instantiate.cc",
"src/wasm/names-provider.cc",
"src/wasm/signature-map.cc",
"src/wasm/simd-shuffle.cc",
"src/wasm/streaming-decoder.cc",
......@@ -4701,7 +4705,6 @@ v8_source_set("v8_base_without_compiler") {
"src/wasm/value-type.cc",
"src/wasm/wasm-code-manager.cc",
"src/wasm/wasm-debug.cc",
"src/wasm/wasm-debug.h",
"src/wasm/wasm-engine.cc",
"src/wasm/wasm-external-refs.cc",
"src/wasm/wasm-features.cc",
......
......@@ -10,6 +10,8 @@
#include "src/debug/debug-wasm-objects-inl.h"
#include "src/execution/frames-inl.h"
#include "src/objects/property-descriptor.h"
#include "src/wasm/names-provider.h"
#include "src/wasm/string-builder.h"
#include "src/wasm/wasm-debug.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-value.h"
......@@ -18,53 +20,10 @@ namespace v8 {
namespace internal {
namespace {
// Helper for unpacking a maybe name that makes a default with an index if
// the name is empty. If the name is not empty, it's prefixed with a $.
Handle<String> GetNameOrDefault(Isolate* isolate,
MaybeHandle<String> maybe_name,
const char* default_name_prefix,
uint32_t index) {
Handle<String> name;
if (maybe_name.ToHandle(&name)) {
name = isolate->factory()
->NewConsString(
isolate->factory()->NewStringFromAsciiChecked("$"), name)
.ToHandleChecked();
return isolate->factory()->InternalizeString(name);
}
base::EmbeddedVector<char, 64> value;
int len = SNPrintF(value, "%s%u", default_name_prefix, index);
return isolate->factory()->InternalizeString(value.SubVector(0, len));
}
MaybeHandle<String> GetNameFromImportsAndExportsOrNull(
Isolate* isolate, Handle<WasmInstanceObject> instance,
wasm::ImportExportKindCode kind, uint32_t index) {
auto debug_info = instance->module_object().native_module()->GetDebugInfo();
wasm::ModuleWireBytes wire_bytes(
instance->module_object().native_module()->wire_bytes());
auto import_name_ref = debug_info->GetImportName(kind, index);
if (!import_name_ref.first.is_empty()) {
base::ScopedVector<char> name(import_name_ref.first.length() + 1 +
import_name_ref.second.length());
auto name_begin = &name.first(), name_end = name_begin;
auto module_name = wire_bytes.GetNameOrNull(import_name_ref.first);
name_end = std::copy(module_name.begin(), module_name.end(), name_end);
*name_end++ = '.';
auto field_name = wire_bytes.GetNameOrNull(import_name_ref.second);
name_end = std::copy(field_name.begin(), field_name.end(), name_end);
return isolate->factory()->NewStringFromUtf8(
base::VectorOf(name_begin, name_end - name_begin));
}
auto export_name_ref = debug_info->GetExportName(kind, index);
if (!export_name_ref.is_empty()) {
auto name = wire_bytes.GetNameOrNull(export_name_ref);
return isolate->factory()->NewStringFromUtf8(name);
}
return {};
using StringBuilder = wasm::StringBuilder;
Handle<String> ToInternalString(StringBuilder& sb, Isolate* isolate) {
return isolate->factory()->InternalizeString(
base::VectorOf(sb.start(), sb.length()));
}
enum DebugProxyId {
......@@ -352,12 +311,11 @@ struct GlobalsProxy : NamedDebugProxy<GlobalsProxy, kGlobalsProxy> {
static Handle<String> GetName(Isolate* isolate,
Handle<WasmInstanceObject> instance,
uint32_t index) {
return GetNameOrDefault(
isolate,
GetNameFromImportsAndExportsOrNull(
isolate, instance, wasm::ImportExportKindCode::kExternalGlobal,
index),
"$global", index);
wasm::NamesProvider* names =
instance->module_object().native_module()->GetNamesProvider();
StringBuilder sb;
names->PrintGlobalName(sb, index);
return ToInternalString(sb, isolate);
}
};
......@@ -378,12 +336,11 @@ struct MemoriesProxy : NamedDebugProxy<MemoriesProxy, kMemoriesProxy> {
static Handle<String> GetName(Isolate* isolate,
Handle<WasmInstanceObject> instance,
uint32_t index) {
return GetNameOrDefault(
isolate,
GetNameFromImportsAndExportsOrNull(
isolate, instance, wasm::ImportExportKindCode::kExternalMemory,
index),
"$memory", index);
wasm::NamesProvider* names =
instance->module_object().native_module()->GetNamesProvider();
StringBuilder sb;
names->PrintMemoryName(sb, index);
return ToInternalString(sb, isolate);
}
};
......@@ -404,12 +361,11 @@ struct TablesProxy : NamedDebugProxy<TablesProxy, kTablesProxy> {
static Handle<String> GetName(Isolate* isolate,
Handle<WasmInstanceObject> instance,
uint32_t index) {
return GetNameOrDefault(
isolate,
GetNameFromImportsAndExportsOrNull(
isolate, instance, wasm::ImportExportKindCode::kExternalTable,
index),
"$table", index);
wasm::NamesProvider* names =
instance->module_object().native_module()->GetNamesProvider();
StringBuilder sb;
names->PrintTableName(sb, index);
return ToInternalString(sb, isolate);
}
};
......@@ -454,14 +410,10 @@ struct LocalsProxy : NamedDebugProxy<LocalsProxy, kLocalsProxy, FixedArray> {
auto native_module =
WasmModuleObject::cast(values->get(count + 0)).native_module();
auto function_index = Smi::ToInt(Smi::cast(values->get(count + 1)));
wasm::ModuleWireBytes module_wire_bytes(native_module->wire_bytes());
auto name_vec = module_wire_bytes.GetNameOrNull(
native_module->GetDebugInfo()->GetLocalName(function_index, index));
return GetNameOrDefault(
isolate,
name_vec.empty() ? MaybeHandle<String>()
: isolate->factory()->NewStringFromUtf8(name_vec),
"$var", index);
wasm::NamesProvider* names = native_module->GetNamesProvider();
StringBuilder sb;
names->PrintLocalName(sb, function_index, index);
return ToInternalString(sb, isolate);
}
};
......@@ -772,22 +724,9 @@ Handle<String> WasmSimd128ToString(Isolate* isolate, wasm::Simd128 s128) {
Handle<String> GetRefTypeName(Isolate* isolate, wasm::ValueType type,
wasm::NativeModule* module) {
DCHECK(type.is_object_reference());
std::ostringstream name;
if (type.heap_type().is_generic()) {
name << type.name();
} else {
name << "(ref " << (type.is_nullable() ? "null " : "") << "$";
wasm::ModuleWireBytes module_wire_bytes(module->wire_bytes());
base::Vector<const char> module_name = module_wire_bytes.GetNameOrNull(
module->GetDebugInfo()->GetTypeName(type.ref_index()));
if (module_name.empty()) {
name << "type" << type.ref_index();
} else {
name.write(module_name.begin(), module_name.size());
}
name << ")";
}
return isolate->factory()->InternalizeString(base::VectorOf(name.str()));
StringBuilder name;
module->GetNamesProvider()->PrintValueType(name, type);
return ToInternalString(name, isolate);
}
} // namespace
......@@ -866,14 +805,10 @@ struct StructProxy : NamedDebugProxy<StructProxy, kStructProxy, FixedArray> {
wasm::NativeModule* native_module =
WasmModuleObject::cast(data->get(kModuleIndex)).native_module();
int struct_type_index = Smi::ToInt(Smi::cast(data->get(kTypeIndexIndex)));
wasm::ModuleWireBytes module_wire_bytes(native_module->wire_bytes());
base::Vector<const char> name_vec = module_wire_bytes.GetNameOrNull(
native_module->GetDebugInfo()->GetFieldName(struct_type_index, index));
return GetNameOrDefault(
isolate,
name_vec.empty() ? MaybeHandle<String>()
: isolate->factory()->NewStringFromUtf8(name_vec),
"$field", index);
wasm::NamesProvider* names = native_module->GetNamesProvider();
StringBuilder sb;
names->PrintFieldName(sb, struct_type_index, index);
return ToInternalString(sb, isolate);
}
};
......@@ -1017,18 +952,14 @@ Handle<String> GetWasmFunctionDebugName(Isolate* isolate,
Handle<WasmInstanceObject> instance,
uint32_t func_index) {
Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
MaybeHandle<String> maybe_name = WasmModuleObject::GetFunctionNameOrNull(
isolate, module_object, func_index);
if (module_object->is_asm_js()) {
// In case of asm.js, we use the names from the function declarations.
return maybe_name.ToHandleChecked();
}
if (maybe_name.is_null()) {
maybe_name = GetNameFromImportsAndExportsOrNull(
isolate, instance, wasm::ImportExportKindCode::kExternalFunction,
func_index);
}
return GetNameOrDefault(isolate, maybe_name, "$func", func_index);
wasm::NamesProvider* names =
module_object->native_module()->GetNamesProvider();
StringBuilder sb;
wasm::NamesProvider::FunctionNamesBehavior behavior =
module_object->is_asm_js() ? wasm::NamesProvider::kWasmInternal
: wasm::NamesProvider::kDevTools;
names->PrintFunctionName(sb, func_index, behavior);
return ToInternalString(sb, isolate);
}
Handle<ArrayList> AddWasmInstanceObjectInternalProperties(
......
......@@ -1274,6 +1274,8 @@ class ModuleDecoderImpl : public Decoder {
// Ignore all but the first occurrence of name section.
if (!has_seen_unordered_section(kNameSectionCode)) {
set_seen_unordered_section(kNameSectionCode);
module_->name_section = {buffer_offset_,
static_cast<uint32_t>(end_ - start_)};
// Use an inner decoder so that errors don't fail the outer decoder.
Decoder inner(start_, pc_, end_, buffer_offset_);
// Decode all name subsections.
......@@ -1744,9 +1746,9 @@ class ModuleDecoderImpl : public Decoder {
void VerifyFunctionBody(AccountingAllocator* allocator, uint32_t func_num,
const ModuleWireBytes& wire_bytes,
const WasmModule* module, WasmFunction* function) {
WasmFunctionName func_name(function,
wire_bytes.GetNameOrNull(function, module));
if (FLAG_trace_wasm_decoder) {
WasmFunctionName func_name(function,
wire_bytes.GetNameOrNull(function, module));
StdoutStream{} << "Verifying wasm function " << func_name << std::endl;
}
FunctionBody body = {
......@@ -1762,6 +1764,8 @@ class ModuleDecoderImpl : public Decoder {
// location.
if (result.failed() && intermediate_error_.empty()) {
// Wrap the error message from the function decoder.
WasmFunctionName func_name(function,
wire_bytes.GetNameOrNull(function, module));
std::ostringstream error_msg;
error_msg << "in function " << func_name << ": "
<< result.error().message();
......@@ -2561,10 +2565,7 @@ bool FindNameSection(Decoder* decoder) {
} // namespace
void DecodeFunctionNames(const byte* module_start, const byte* module_end,
std::unordered_map<uint32_t, WireBytesRef>* names) {
DCHECK_NOT_NULL(names);
DCHECK(names->empty());
std::unordered_map<uint32_t, WireBytesRef>& names) {
Decoder decoder(module_start, module_end);
if (FindNameSection(&decoder)) {
while (decoder.ok() && decoder.more()) {
......@@ -2589,52 +2590,71 @@ void DecodeFunctionNames(const byte* module_start, const byte* module_end,
// You can even assign to the same function multiple times (last valid
// one wins).
if (decoder.ok() && validate_utf8(&decoder, name)) {
names->insert(std::make_pair(function_index, name));
names.insert(std::make_pair(function_index, name));
}
}
}
}
}
NameMap DecodeNameMap(base::Vector<const uint8_t> module_bytes,
uint8_t name_section_kind) {
Decoder decoder(module_bytes);
if (!FindNameSection(&decoder)) return NameMap{{}};
namespace {
void DecodeNameMap(NameMap& target, Decoder& decoder) {
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
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 count = decoder.consume_u32v("names count");
names.reserve(count);
for (uint32_t i = 0; i < count; i++) {
uint32_t index = decoder.consume_u32v("index");
WireBytesRef name =
consume_string(&decoder, StringValidation::kNone, "name");
if (!decoder.ok()) break;
if (index > kMaxInt) continue;
if (name.is_empty()) continue; // Empty names are useless.
if (!validate_utf8(&decoder, name)) continue;
names.emplace_back(static_cast<int>(index), name);
}
std::stable_sort(names.begin(), names.end(), NameAssoc::IndexLess{});
target = NameMap{std::move(names)};
}
uint32_t count = decoder.consume_u32v("names count");
for (uint32_t i = 0; i < count; i++) {
uint32_t index = decoder.consume_u32v("index");
void DecodeIndirectNameMap(IndirectNameMap& target, Decoder& decoder) {
std::vector<IndirectNameMapEntry> entries;
uint32_t outer_count = decoder.consume_u32v("outer count");
entries.reserve(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");
names.reserve(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, StringValidation::kNone, "name");
if (!decoder.ok()) break;
if (index > kMaxInt) continue;
if (inner_index > kMaxInt) continue;
if (name.is_empty()) continue; // Empty names are useless.
if (!validate_utf8(&decoder, name)) continue;
names.emplace_back(static_cast<int>(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(), NameAssoc::IndexLess{});
entries.emplace_back(static_cast<int>(outer_index), std::move(names));
}
std::stable_sort(names.begin(), names.end(), NameAssoc::IndexLess{});
return NameMap{std::move(names)};
std::stable_sort(entries.begin(), entries.end(),
IndirectNameMapEntry::IndexLess{});
target = IndirectNameMap{std::move(entries)};
}
IndirectNameMap DecodeIndirectNameMap(base::Vector<const uint8_t> module_bytes,
uint8_t name_section_kind) {
Decoder decoder(module_bytes);
if (!FindNameSection(&decoder)) return IndirectNameMap{{}};
} // namespace
std::vector<IndirectNameMapEntry> entries;
DecodedNameSection::DecodedNameSection(base::Vector<const uint8_t> wire_bytes,
WireBytesRef name_section) {
if (name_section.is_empty()) return; // No name section.
Decoder decoder(wire_bytes.begin() + name_section.offset(),
wire_bytes.begin() + name_section.end_offset(),
name_section.offset());
while (decoder.ok() && decoder.more()) {
uint8_t name_type = decoder.consume_u8("name type");
if (name_type & 0x80) break; // no varuint7
......@@ -2642,36 +2662,44 @@ IndirectNameMap DecodeIndirectNameMap(base::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 != 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, StringValidation::kNone, "name");
if (!decoder.ok()) break;
if (inner_index > kMaxInt) continue;
// Ignore non-utf8 names.
if (!validate_utf8(&decoder, name)) continue;
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(), NameAssoc::IndexLess{});
entries.emplace_back(static_cast<int>(outer_index), std::move(names));
switch (name_type) {
case kModuleCode:
case kFunctionCode:
// Already handled elsewhere.
decoder.consume_bytes(name_payload_len);
break;
case kLocalCode:
DecodeIndirectNameMap(local_names_, decoder);
break;
case kLabelCode:
DecodeIndirectNameMap(label_names_, decoder);
break;
case kTypeCode:
DecodeNameMap(type_names_, decoder);
break;
case kTableCode:
DecodeNameMap(table_names_, decoder);
break;
case kMemoryCode:
DecodeNameMap(memory_names_, decoder);
break;
case kGlobalCode:
DecodeNameMap(global_names_, decoder);
break;
case kElementSegmentCode:
DecodeNameMap(element_segment_names_, decoder);
break;
case kDataSegmentCode:
DecodeNameMap(data_segment_names_, decoder);
break;
case kFieldCode:
DecodeIndirectNameMap(field_names_, decoder);
break;
case kTagCode:
DecodeNameMap(tag_names_, decoder);
break;
}
}
std::stable_sort(entries.begin(), entries.end(),
IndirectNameMapEntry::IndexLess{});
return IndirectNameMap{std::move(entries)};
}
#undef TRACE
......
......@@ -80,7 +80,7 @@ class NameAssoc {
class NameMap {
public:
// For performance reasons, {NameMap} should not be copied.
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(NameMap);
MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(NameMap);
explicit NameMap(std::vector<NameAssoc> names) : names_(std::move(names)) {
DCHECK(
......@@ -123,7 +123,7 @@ class IndirectNameMapEntry : public NameMap {
class IndirectNameMap {
public:
// For performance reasons, {IndirectNameMap} should not be copied.
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(IndirectNameMap);
MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(IndirectNameMap);
explicit IndirectNameMap(std::vector<IndirectNameMapEntry> functions)
: functions_(std::move(functions)) {
......@@ -144,6 +144,26 @@ class IndirectNameMap {
std::vector<IndirectNameMapEntry> functions_;
};
class DecodedNameSection {
public:
explicit DecodedNameSection(base::Vector<const uint8_t> wire_bytes,
WireBytesRef name_section);
private:
friend class NamesProvider;
IndirectNameMap local_names_;
IndirectNameMap label_names_;
NameMap type_names_;
NameMap table_names_;
NameMap memory_names_;
NameMap global_names_;
NameMap element_segment_names_;
NameMap data_segment_names_;
IndirectNameMap field_names_;
NameMap tag_names_;
};
enum class DecodingMethod {
kSync,
kAsync,
......@@ -195,16 +215,7 @@ AsmJsOffsetsResult DecodeAsmJsOffsets(
// unordered map. Only names with valid utf8 encoding are stored and conflicts
// are resolved by choosing the last name read.
void DecodeFunctionNames(const byte* module_start, const byte* module_end,
std::unordered_map<uint32_t, WireBytesRef>* names);
// 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.
NameMap DecodeNameMap(base::Vector<const uint8_t> module_bytes,
uint8_t name_section_kind);
IndirectNameMap DecodeIndirectNameMap(base::Vector<const uint8_t> module_bytes,
uint8_t name_section_kind);
std::unordered_map<uint32_t, WireBytesRef>& names);
class ModuleDecoderImpl;
......
This diff is collapsed.
// Copyright 2022 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.
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif // !V8_ENABLE_WEBASSEMBLY
#ifndef V8_WASM_NAMES_PROVIDER_H_
#define V8_WASM_NAMES_PROVIDER_H_
#include <map>
#include <string>
#include "src/base/vector.h"
#include "src/wasm/wasm-module.h"
namespace v8 {
namespace internal {
namespace wasm {
class DecodedNameSection;
class StringBuilder;
class NamesProvider {
public:
// {kWasmInternal}: only return raw name from name section.
// {kDevTools}: prepend '$', use import/export names as fallback,
// or "$funcN" as default.
enum FunctionNamesBehavior : bool { kWasmInternal = false, kDevTools = true };
enum IndexAsComment : bool {
kDontPrintIndex = false,
kIndexAsComment = true
};
NamesProvider(const WasmModule* module,
base::Vector<const uint8_t> wire_bytes);
~NamesProvider();
// Returns {false} if {devtools_behavior} == false and no name for
// {function_index} was present in the name section.
void PrintFunctionName(StringBuilder& out, uint32_t function_index,
FunctionNamesBehavior behavior = kWasmInternal,
IndexAsComment index_as_comment = kDontPrintIndex);
void PrintLocalName(StringBuilder& out, uint32_t function_index,
uint32_t local_index,
IndexAsComment index_as_comment = kDontPrintIndex);
void PrintLabelName(StringBuilder& out, uint32_t function_index,
uint32_t label_index, uint32_t fallback_index);
void PrintTypeName(StringBuilder& out, uint32_t type_index,
IndexAsComment index_as_comment = kDontPrintIndex);
void PrintTableName(StringBuilder& out, uint32_t table_index,
IndexAsComment index_as_comment = kDontPrintIndex);
void PrintMemoryName(StringBuilder& out, uint32_t memory_index,
IndexAsComment index_as_comment = kDontPrintIndex);
void PrintGlobalName(StringBuilder& out, uint32_t global_index,
IndexAsComment index_as_comment = kDontPrintIndex);
void PrintElementSegmentName(StringBuilder& out,
uint32_t element_segment_index);
void PrintDataSegmentName(StringBuilder& out, uint32_t data_segment_index);
void PrintFieldName(StringBuilder& out, uint32_t struct_index,
uint32_t field_index,
IndexAsComment index_as_comment = kDontPrintIndex);
void PrintTagName(StringBuilder& out, uint32_t tag_index,
IndexAsComment index_as_comment = kDontPrintIndex);
void PrintHeapType(StringBuilder& out, HeapType type);
void PrintValueType(StringBuilder& out, ValueType type);
private:
void DecodeNamesIfNotYetDone();
void ComputeFunctionNamesFromImportsExports();
void ComputeNamesFromImportsExports();
void ComputeImportName(const WasmImport& import,
std::map<uint32_t, std::string>& target);
void ComputeExportName(const WasmExport& ex,
std::map<uint32_t, std::string>& target);
void WriteRef(StringBuilder& out, WireBytesRef ref);
// Lazy loading must guard against concurrent modifications from multiple
// {WasmModuleObject}s.
base::Mutex mutex_;
bool has_decoded_{false};
bool has_computed_function_import_names_{false};
bool has_computed_import_names_{false};
const WasmModule* module_;
base::Vector<const uint8_t> wire_bytes_;
std::unique_ptr<DecodedNameSection> name_section_names_{};
std::map<uint32_t, std::string> import_export_function_names_;
std::map<uint32_t, std::string> import_export_table_names_;
std::map<uint32_t, std::string> import_export_memory_names_;
std::map<uint32_t, std::string> import_export_global_names_;
std::map<uint32_t, std::string> import_export_tag_names_;
};
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_NAMES_PROVIDER_H_
// Copyright 2022 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.
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif // !V8_ENABLE_WEBASSEMBLY
#ifndef V8_WASM_STRING_BUILDER_H_
#define V8_WASM_STRING_BUILDER_H_
#include <cstring>
#include <string>
#include <vector>
#include "src/common/globals.h"
namespace v8 {
namespace internal {
namespace wasm {
// Similar to std::ostringstream, but about 4x faster.
// This base class works best for small-ish strings (up to kChunkSize); for
// producing large amounts of text, you probably want a subclass like
// MultiLineStringBuilder.
class StringBuilder {
public:
explicit StringBuilder() : on_growth_(kReplacePreviousChunk) {}
explicit StringBuilder(const StringBuilder&) = delete;
StringBuilder& operator=(const StringBuilder&) = delete;
~StringBuilder() {
for (char* chunk : chunks_) delete[] chunk;
}
// Reserves space for {n} characters and returns a pointer to its beginning.
// Clients *must* write all {n} characters after calling this!
// Don't call this directly, use operator<< overloads instead.
char* write(size_t n) {
if (remaining_bytes_ < n) Grow();
char* result = cursor_;
cursor_ += n;
remaining_bytes_ -= n;
return result;
}
// Convenience wrappers.
void write(const byte* data, size_t n) {
char* ptr = write(n);
memcpy(ptr, data, n);
}
void write(const char* data, size_t n) {
char* ptr = write(n);
memcpy(ptr, data, n);
}
const char* start() const { return start_; }
const char* cursor() const { return cursor_; }
size_t length() const { return static_cast<size_t>(cursor_ - start_); }
protected:
enum OnGrowth : bool { kKeepOldChunks, kReplacePreviousChunk };
// Useful for subclasses that divide the text into ranges, e.g. lines.
explicit StringBuilder(OnGrowth on_growth) : on_growth_(on_growth) {}
void start_here() { start_ = cursor_; }
void rewind_to_start() {
remaining_bytes_ += length();
cursor_ = start_;
}
private:
void Grow() {
size_t used = length();
// Safety net for super-long strings/lines.
size_t chunk_size = used < kChunkSize ? kChunkSize : used * 2;
char* new_chunk = new char[chunk_size];
memcpy(new_chunk, start_, used);
if (on_growth_ == kKeepOldChunks) {
chunks_.push_back(new_chunk);
}
start_ = new_chunk;
cursor_ = new_chunk + used;
remaining_bytes_ = chunk_size - used;
}
// Start small, to be cheap for the common case.
static constexpr size_t kStackSize = 256;
// If we have to grow, grow in big steps.
static constexpr size_t kChunkSize = 1024 * 1024;
char stack_buffer_[kStackSize];
std::vector<char*> chunks_; // A very simple Zone, essentially.
char* start_ = stack_buffer_;
char* cursor_ = stack_buffer_;
size_t remaining_bytes_ = kStackSize;
const OnGrowth on_growth_;
};
inline StringBuilder& operator<<(StringBuilder& sb, const char* str) {
size_t len = strlen(str);
char* ptr = sb.write(len);
memcpy(ptr, str, len);
return sb;
}
inline StringBuilder& operator<<(StringBuilder& sb, char c) {
*sb.write(1) = c;
return sb;
}
inline StringBuilder& operator<<(StringBuilder& sb, const std::string& s) {
sb.write(s.data(), s.length());
return sb;
}
inline StringBuilder& operator<<(StringBuilder& sb, uint32_t n) {
if (n == 0) {
*sb.write(1) = '0';
return sb;
}
static constexpr size_t kBufferSize = 10; // Just enough for a uint32.
char buffer[kBufferSize];
char* end = buffer + kBufferSize;
char* out = end;
while (n != 0) {
*(--out) = '0' + (n % 10);
n /= 10;
}
sb.write(out, static_cast<size_t>(end - out));
return sb;
}
inline StringBuilder& operator<<(StringBuilder& sb, int value) {
if (value >= 0) {
sb << static_cast<uint32_t>(value);
} else {
sb << "-" << ((~static_cast<uint32_t>(value)) + 1);
}
return sb;
}
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_STRING_BUILDER_H_
......@@ -32,6 +32,7 @@
#include "src/wasm/jump-table-assembler.h"
#include "src/wasm/memory-protection-key.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/names-provider.h"
#include "src/wasm/wasm-debug.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-import-wrapper-cache.h"
......@@ -2503,6 +2504,16 @@ DebugInfo* NativeModule::GetDebugInfo() {
return debug_info_.get();
}
NamesProvider* NativeModule::GetNamesProvider() {
DCHECK(HasWireBytes());
base::RecursiveMutexGuard guard(&allocation_mutex_);
if (!names_provider_) {
names_provider_ =
std::make_unique<NamesProvider>(module_.get(), wire_bytes());
}
return names_provider_.get();
}
void WasmCodeManager::FreeNativeModule(
base::Vector<VirtualMemory> owned_code_space, size_t committed_size) {
base::MutexGuard lock(&native_modules_mutex_);
......
......@@ -42,6 +42,7 @@ class Isolate;
namespace wasm {
class DebugInfo;
class NamesProvider;
class NativeModule;
struct WasmCompilationResult;
class WasmEngine;
......@@ -847,6 +848,9 @@ class V8_EXPORT_PRIVATE NativeModule final {
// Get or create the debug info for this NativeModule.
DebugInfo* GetDebugInfo();
// Get or create the NamesProvider. Requires {HasWireBytes()}.
NamesProvider* GetNamesProvider();
uint32_t* tiering_budget_array() { return tiering_budgets_.get(); }
Counters* counters() const { return code_allocator_.counters(); }
......@@ -990,6 +994,8 @@ class V8_EXPORT_PRIVATE NativeModule final {
// mutex.
std::unique_ptr<DebugInfo> debug_info_;
std::unique_ptr<NamesProvider> names_provider_;
TieringState tiering_state_ = kTieredUp;
// Cache both baseline and top-tier code if we are debugging, to speed up
......
......@@ -143,7 +143,9 @@ enum NameSectionKindCode : uint8_t {
kElementSegmentCode = 8,
kDataSegmentCode = 9,
// https://github.com/WebAssembly/gc/issues/193
kFieldCode = 10
kFieldCode = 10,
// https://github.com/WebAssembly/exception-handling/pull/213
kTagCode = 11,
};
// What to do when treating a stringref as WTF-8 and we see an isolated
......
......@@ -157,66 +157,6 @@ class DebugInfoImpl {
return module->functions[scope.code->index()];
}
WireBytesRef GetExportName(ImportExportKindCode kind, uint32_t index) {
base::MutexGuard guard(&mutex_);
if (!export_names_) {
export_names_ =
std::make_unique<std::map<ImportExportKey, WireBytesRef>>();
for (auto exp : native_module_->module()->export_table) {
auto exp_key = std::make_pair(exp.kind, exp.index);
if (export_names_->find(exp_key) != export_names_->end()) continue;
export_names_->insert(std::make_pair(exp_key, exp.name));
}
}
auto it = export_names_->find(std::make_pair(kind, index));
if (it != export_names_->end()) return it->second;
return {};
}
std::pair<WireBytesRef, WireBytesRef> GetImportName(ImportExportKindCode kind,
uint32_t index) {
base::MutexGuard guard(&mutex_);
if (!import_names_) {
import_names_ = std::make_unique<
std::map<ImportExportKey, std::pair<WireBytesRef, WireBytesRef>>>();
for (auto imp : native_module_->module()->import_table) {
import_names_->insert(
std::make_pair(std::make_pair(imp.kind, imp.index),
std::make_pair(imp.module_name, imp.field_name)));
}
}
auto it = import_names_->find(std::make_pair(kind, index));
if (it != import_names_->end()) return it->second;
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::kTypeCode));
}
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<IndirectNameMap>(DecodeIndirectNameMap(
native_module_->wire_bytes(), NameSectionKindCode::kLocalCode));
}
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::kFieldCode));
}
return field_names_->GetName(struct_index, field_index);
}
// If the frame 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
......@@ -765,21 +705,6 @@ class DebugInfoImpl {
};
std::vector<CachedDebuggingCode> cached_debugging_code_;
// Names of exports, lazily derived from the exports table.
std::unique_ptr<std::map<ImportExportKey, wasm::WireBytesRef>> export_names_;
// Names of imports, lazily derived from the imports table.
std::unique_ptr<std::map<ImportExportKey,
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<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_;
};
......@@ -807,28 +732,6 @@ const wasm::WasmFunction& DebugInfo::GetFunctionAtAddress(Address pc) {
return impl_->GetFunctionAtAddress(pc);
}
WireBytesRef DebugInfo::GetExportName(ImportExportKindCode code,
uint32_t index) {
return impl_->GetExportName(code, index);
}
std::pair<WireBytesRef, WireBytesRef> DebugInfo::GetImportName(
ImportExportKindCode code, uint32_t index) {
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);
......
......@@ -183,21 +183,6 @@ class V8_EXPORT_PRIVATE DebugInfo {
WasmValue GetStackValue(int index, Address pc, Address 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
// will be returned instead.
WireBytesRef GetExportName(ImportExportKindCode kind, uint32_t index);
// Returns the module and field name of the entity (with the given |index|
// and |kind|) derived from the imports table. If the entity is not imported,
// a pair of empty references will be returned instead.
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);
// Returns true if we stay inside the passed frame (or a called frame) after
......
......@@ -8,17 +8,9 @@
#include <memory>
#include "src/api/api-inl.h"
#include "src/base/platform/wrappers.h"
#include "src/codegen/assembler-inl.h"
#include "src/compiler/wasm-compiler.h"
#include "src/debug/interface-types.h"
#include "src/execution/frames-inl.h"
#include "src/execution/simulator.h"
#include "src/init/v8.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/objects.h"
#include "src/objects/property-descriptor.h"
#include "src/snapshot/snapshot.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-init-expr.h"
......@@ -32,18 +24,23 @@ namespace internal {
namespace wasm {
WireBytesRef LazilyGeneratedNames::LookupFunctionName(
const ModuleWireBytes& wire_bytes, uint32_t function_index) const {
const ModuleWireBytes& wire_bytes, uint32_t function_index) {
base::MutexGuard lock(&mutex_);
if (!function_names_) {
function_names_.reset(new std::unordered_map<uint32_t, WireBytesRef>());
DecodeFunctionNames(wire_bytes.start(), wire_bytes.end(),
function_names_.get());
if (!has_functions_) {
has_functions_ = true;
DecodeFunctionNames(wire_bytes.start(), wire_bytes.end(), function_names_);
}
auto it = function_names_->find(function_index);
if (it == function_names_->end()) return WireBytesRef();
auto it = function_names_.find(function_index);
if (it == function_names_.end()) return WireBytesRef();
return it->second;
}
bool LazilyGeneratedNames::Has(uint32_t function_index) {
DCHECK(has_functions_);
base::MutexGuard lock(&mutex_);
return function_names_.find(function_index) != function_names_.end();
}
// static
int MaxNumExportWrappers(const WasmModule* module) {
// For each signature there may exist a wrapper, both for imported and
......@@ -130,10 +127,7 @@ int GetSubtypingDepth(const WasmModule* module, uint32_t type_index) {
void LazilyGeneratedNames::AddForTesting(int function_index,
WireBytesRef name) {
base::MutexGuard lock(&mutex_);
if (!function_names_) {
function_names_.reset(new std::unordered_map<uint32_t, WireBytesRef>());
}
function_names_->insert(std::make_pair(function_index, name));
function_names_.insert(std::make_pair(function_index, name));
}
AsmJsOffsetInformation::AsmJsOffsetInformation(
......
......@@ -217,17 +217,17 @@ struct ModuleWireBytes;
class V8_EXPORT_PRIVATE LazilyGeneratedNames {
public:
WireBytesRef LookupFunctionName(const ModuleWireBytes& wire_bytes,
uint32_t function_index) const;
uint32_t function_index);
void AddForTesting(int function_index, WireBytesRef name);
bool Has(uint32_t function_index);
private:
// {function_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_;
// Lazy loading must guard against concurrent modifications from multiple
// {WasmModuleObject}s.
base::Mutex mutex_;
bool has_functions_{false};
std::unordered_map<uint32_t, WireBytesRef> function_names_;
};
class V8_EXPORT_PRIVATE AsmJsOffsetInformation {
......@@ -422,6 +422,9 @@ struct V8_EXPORT_PRIVATE WasmModule {
// ID and length).
WireBytesRef code = {0, 0};
WireBytesRef name = {0, 0};
// Position and size of the name section (payload only, i.e. without section
// ID and length).
WireBytesRef name_section = {0, 0};
void add_type(TypeDefinition type) {
types.push_back(type);
......@@ -503,7 +506,7 @@ struct V8_EXPORT_PRIVATE WasmModule {
mutable TypeFeedbackStorage type_feedback;
ModuleOrigin origin = kWasmOrigin; // origin of the module
LazilyGeneratedNames lazily_generated_names;
mutable LazilyGeneratedNames lazily_generated_names;
WasmDebugSymbols debug_symbols;
// Asm.js source position information. Only available for modules compiled
......
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