Commit 838a220c authored by Jakob Kummerow's avatar Jakob Kummerow Committed by V8 LUCI CQ

[wasm] AdaptiveMap for DecodedNameSection

This is a performance improvement; no change in functional
behavior is intended.
AdaptiveMap is an abstraction over a std::map or a std::vector:
after being initialized iteratively with a set of entries, it
can switch to dense vector-based storage if that would be more
efficient.
The motivation is that we expect most name sections, if they
are present at all, to give fairly complete information, so the
dense mode will likely be the typical case. However, it's easy
enough to support sparse mode as well, and parsing the name
section into a std::map at first is particularly convenient for
cases where we can't guess the expected number of entries, such
as for function locals.

Change-Id: Ia17f27576a3061eb05c912f7081411d6f38137e6
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3726150Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81474}
parent c651551f
......@@ -2563,7 +2563,7 @@ bool FindNameSection(Decoder* decoder) {
} // namespace
void DecodeFunctionNames(const byte* module_start, const byte* module_end,
std::unordered_map<uint32_t, WireBytesRef>& names) {
NameMap& names) {
Decoder decoder(module_start, module_end);
if (FindNameSection(&decoder)) {
while (decoder.ok() && decoder.more()) {
......@@ -2588,19 +2588,18 @@ 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.Put(function_index, name);
}
}
}
}
names.FinishInitialization();
}
namespace {
void DecodeNameMap(NameMap& target, Decoder& decoder) {
std::vector<NameAssoc> names;
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 =
......@@ -2609,22 +2608,18 @@ void DecodeNameMap(NameMap& target, Decoder& decoder) {
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);
target.Put(index, name);
}
std::stable_sort(names.begin(), names.end(), NameAssoc::IndexLess{});
target = NameMap{std::move(names)};
target.FinishInitialization();
}
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;
NameMap 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 =
......@@ -2633,16 +2628,12 @@ void DecodeIndirectNameMap(IndirectNameMap& target, Decoder& decoder) {
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>(inner_index), name);
names.Put(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));
names.FinishInitialization();
target.Put(outer_index, std::move(names));
}
std::stable_sort(entries.begin(), entries.end(),
IndirectNameMapEntry::IndexLess{});
target = IndirectNameMap{std::move(entries)};
target.FinishInitialization();
}
} // namespace
......
......@@ -56,94 +56,6 @@ struct AsmJsOffsets {
};
using AsmJsOffsetsResult = Result<AsmJsOffsets>;
// The class names "NameAssoc", "NameMap", and "IndirectNameMap" match
// the terms used by the spec:
// https://webassembly.github.io/spec/core/bikeshed/index.html#name-section%E2%91%A0
class NameAssoc {
public:
NameAssoc(int index, WireBytesRef name) : index_(index), name_(name) {}
int index() const { return index_; }
WireBytesRef name() const { return name_; }
struct IndexLess {
bool operator()(const NameAssoc& a, const NameAssoc& b) const {
return a.index() < b.index();
}
};
private:
int index_;
WireBytesRef name_;
};
class NameMap {
public:
// For performance reasons, {NameMap} should not be copied.
MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(NameMap);
explicit NameMap(std::vector<NameAssoc> names) : names_(std::move(names)) {
DCHECK(
std::is_sorted(names_.begin(), names_.end(), NameAssoc::IndexLess{}));
}
WireBytesRef GetName(int index) {
auto it = std::lower_bound(names_.begin(), names_.end(),
NameAssoc{index, {}}, NameAssoc::IndexLess{});
if (it == names_.end()) return {};
if (it->index() != index) return {};
return it->name();
}
private:
std::vector<NameAssoc> names_;
};
class IndirectNameMapEntry : public NameMap {
public:
// For performance reasons, {IndirectNameMapEntry} should not be copied.
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(IndirectNameMapEntry);
IndirectNameMapEntry(int index, std::vector<NameAssoc> names)
: NameMap(std::move(names)), index_(index) {}
int index() const { return index_; }
struct IndexLess {
bool operator()(const IndirectNameMapEntry& a,
const IndirectNameMapEntry& b) const {
return a.index() < b.index();
}
};
private:
int index_;
};
class IndirectNameMap {
public:
// For performance reasons, {IndirectNameMap} should not be copied.
MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(IndirectNameMap);
explicit IndirectNameMap(std::vector<IndirectNameMapEntry> functions)
: functions_(std::move(functions)) {
DCHECK(std::is_sorted(functions_.begin(), functions_.end(),
IndirectNameMapEntry::IndexLess{}));
}
WireBytesRef GetName(int function_index, int local_index) {
auto it = std::lower_bound(functions_.begin(), functions_.end(),
IndirectNameMapEntry{function_index, {}},
IndirectNameMapEntry::IndexLess{});
if (it == functions_.end()) return {};
if (it->index() != function_index) return {};
return it->GetName(local_index);
}
private:
std::vector<IndirectNameMapEntry> functions_;
};
class DecodedNameSection {
public:
explicit DecodedNameSection(base::Vector<const uint8_t> wire_bytes,
......@@ -215,7 +127,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);
NameMap& names);
class ModuleDecoderImpl;
......
......@@ -55,27 +55,19 @@ void NamesProvider::ComputeNamesFromImportsExports() {
case kExternalFunction:
continue; // Functions are handled separately.
case kExternalTable:
if (name_section_names_->table_names_.GetName(import.index).is_set()) {
continue;
}
if (name_section_names_->table_names_.Has(import.index)) continue;
ComputeImportName(import, import_export_table_names_);
break;
case kExternalMemory:
if (name_section_names_->memory_names_.GetName(import.index).is_set()) {
continue;
}
if (name_section_names_->memory_names_.Has(import.index)) continue;
ComputeImportName(import, import_export_memory_names_);
break;
case kExternalGlobal:
if (name_section_names_->global_names_.GetName(import.index).is_set()) {
continue;
}
if (name_section_names_->global_names_.Has(import.index)) continue;
ComputeImportName(import, import_export_global_names_);
break;
case kExternalTag:
if (name_section_names_->tag_names_.GetName(import.index).is_set()) {
continue;
}
if (name_section_names_->tag_names_.Has(import.index)) continue;
ComputeImportName(import, import_export_tag_names_);
break;
}
......@@ -85,27 +77,19 @@ void NamesProvider::ComputeNamesFromImportsExports() {
case kExternalFunction:
continue; // Functions are handled separately.
case kExternalTable:
if (name_section_names_->table_names_.GetName(ex.index).is_set()) {
continue;
}
if (name_section_names_->table_names_.Has(ex.index)) continue;
ComputeExportName(ex, import_export_table_names_);
break;
case kExternalMemory:
if (name_section_names_->memory_names_.GetName(ex.index).is_set()) {
continue;
}
if (name_section_names_->memory_names_.Has(ex.index)) continue;
ComputeExportName(ex, import_export_memory_names_);
break;
case kExternalGlobal:
if (name_section_names_->global_names_.GetName(ex.index).is_set()) {
continue;
}
if (name_section_names_->global_names_.Has(ex.index)) continue;
ComputeExportName(ex, import_export_global_names_);
break;
case kExternalTag:
if (name_section_names_->tag_names_.GetName(ex.index).is_set()) {
continue;
}
if (name_section_names_->tag_names_.Has(ex.index)) continue;
ComputeExportName(ex, import_export_tag_names_);
break;
}
......@@ -224,12 +208,25 @@ void NamesProvider::PrintFunctionName(StringBuilder& out,
}
}
WireBytesRef Get(const NameMap& map, uint32_t index) {
const WireBytesRef* result = map.Get(index);
if (!result) return {};
return *result;
}
WireBytesRef Get(const IndirectNameMap& map, uint32_t outer_index,
uint32_t inner_index) {
const NameMap* inner = map.Get(outer_index);
if (!inner) return {};
return Get(*inner, inner_index);
}
void NamesProvider::PrintLocalName(StringBuilder& out, uint32_t function_index,
uint32_t local_index,
IndexAsComment index_as_comment) {
DecodeNamesIfNotYetDone();
WireBytesRef ref =
name_section_names_->local_names_.GetName(function_index, local_index);
Get(name_section_names_->local_names_, function_index, local_index);
if (ref.is_set()) {
out << '$';
WriteRef(out, ref);
......@@ -244,7 +241,7 @@ void NamesProvider::PrintLabelName(StringBuilder& out, uint32_t function_index,
uint32_t fallback_index) {
DecodeNamesIfNotYetDone();
WireBytesRef ref =
name_section_names_->label_names_.GetName(function_index, label_index);
Get(name_section_names_->label_names_, function_index, label_index);
if (ref.is_set()) {
out << '$';
WriteRef(out, ref);
......@@ -256,7 +253,7 @@ void NamesProvider::PrintLabelName(StringBuilder& out, uint32_t function_index,
void NamesProvider::PrintTypeName(StringBuilder& out, uint32_t type_index,
IndexAsComment index_as_comment) {
DecodeNamesIfNotYetDone();
WireBytesRef ref = name_section_names_->type_names_.GetName(type_index);
WireBytesRef ref = Get(name_section_names_->type_names_, type_index);
if (ref.is_set()) {
out << '$';
WriteRef(out, ref);
......@@ -268,7 +265,7 @@ void NamesProvider::PrintTypeName(StringBuilder& out, uint32_t type_index,
void NamesProvider::PrintTableName(StringBuilder& out, uint32_t table_index,
IndexAsComment index_as_comment) {
DecodeNamesIfNotYetDone();
WireBytesRef ref = name_section_names_->table_names_.GetName(table_index);
WireBytesRef ref = Get(name_section_names_->table_names_, table_index);
if (ref.is_set()) {
out << '$';
WriteRef(out, ref);
......@@ -286,7 +283,7 @@ void NamesProvider::PrintTableName(StringBuilder& out, uint32_t table_index,
void NamesProvider::PrintMemoryName(StringBuilder& out, uint32_t memory_index,
IndexAsComment index_as_comment) {
DecodeNamesIfNotYetDone();
WireBytesRef ref = name_section_names_->memory_names_.GetName(memory_index);
WireBytesRef ref = Get(name_section_names_->memory_names_, memory_index);
if (ref.is_set()) {
out << '$';
WriteRef(out, ref);
......@@ -305,7 +302,7 @@ void NamesProvider::PrintMemoryName(StringBuilder& out, uint32_t memory_index,
void NamesProvider::PrintGlobalName(StringBuilder& out, uint32_t global_index,
IndexAsComment index_as_comment) {
DecodeNamesIfNotYetDone();
WireBytesRef ref = name_section_names_->global_names_.GetName(global_index);
WireBytesRef ref = Get(name_section_names_->global_names_, global_index);
if (ref.is_set()) {
out << '$';
WriteRef(out, ref);
......@@ -324,8 +321,8 @@ void NamesProvider::PrintGlobalName(StringBuilder& out, uint32_t global_index,
void NamesProvider::PrintElementSegmentName(StringBuilder& out,
uint32_t element_segment_index) {
DecodeNamesIfNotYetDone();
WireBytesRef ref = name_section_names_->element_segment_names_.GetName(
element_segment_index);
WireBytesRef ref =
Get(name_section_names_->element_segment_names_, element_segment_index);
if (ref.is_set()) {
out << '$';
WriteRef(out, ref);
......@@ -338,7 +335,7 @@ void NamesProvider::PrintDataSegmentName(StringBuilder& out,
uint32_t data_segment_index) {
DecodeNamesIfNotYetDone();
WireBytesRef ref =
name_section_names_->data_segment_names_.GetName(data_segment_index);
Get(name_section_names_->data_segment_names_, data_segment_index);
if (ref.is_set()) {
out << '$';
WriteRef(out, ref);
......@@ -352,7 +349,7 @@ void NamesProvider::PrintFieldName(StringBuilder& out, uint32_t struct_index,
IndexAsComment index_as_comment) {
DecodeNamesIfNotYetDone();
WireBytesRef ref =
name_section_names_->field_names_.GetName(struct_index, field_index);
Get(name_section_names_->field_names_, struct_index, field_index);
if (ref.is_set()) {
out << '$';
WriteRef(out, ref);
......@@ -364,7 +361,7 @@ void NamesProvider::PrintFieldName(StringBuilder& out, uint32_t struct_index,
void NamesProvider::PrintTagName(StringBuilder& out, uint32_t tag_index,
IndexAsComment index_as_comment) {
DecodeNamesIfNotYetDone();
WireBytesRef ref = name_section_names_->tag_names_.GetName(tag_index);
WireBytesRef ref = Get(name_section_names_->tag_names_, tag_index);
if (ref.is_set()) {
out << '$';
WriteRef(out, ref);
......
......@@ -30,7 +30,6 @@ class WasmFrame;
namespace wasm {
class DebugInfoImpl;
class IndirectNameMap;
class NativeModule;
class WasmCode;
class WireBytesRef;
......
......@@ -23,6 +23,29 @@ namespace v8 {
namespace internal {
namespace wasm {
template <class Value>
void AdaptiveMap<Value>::FinishInitialization() {
uint32_t count = 0;
uint32_t max = 0;
DCHECK_EQ(mode_, kInitializing);
for (const auto& entry : *map_) {
count++;
max = std::max(max, entry.first);
}
if (count >= (max + 1) / kLoadFactor) {
mode_ = kDense;
vector_.resize(max + 1);
for (auto& entry : *map_) {
vector_[entry.first] = std::move(entry.second);
}
map_.reset();
} else {
mode_ = kSparse;
}
}
template void NameMap::FinishInitialization();
template void IndirectNameMap::FinishInitialization();
WireBytesRef LazilyGeneratedNames::LookupFunctionName(
const ModuleWireBytes& wire_bytes, uint32_t function_index) {
base::MutexGuard lock(&mutex_);
......@@ -30,15 +53,15 @@ WireBytesRef LazilyGeneratedNames::LookupFunctionName(
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();
return it->second;
const WireBytesRef* result = function_names_.Get(function_index);
if (!result) return WireBytesRef();
return *result;
}
bool LazilyGeneratedNames::Has(uint32_t function_index) {
DCHECK(has_functions_);
base::MutexGuard lock(&mutex_);
return function_names_.find(function_index) != function_names_.end();
return function_names_.Get(function_index) != nullptr;
}
// static
......@@ -127,7 +150,7 @@ int GetSubtypingDepth(const WasmModule* module, uint32_t type_index) {
void LazilyGeneratedNames::AddForTesting(int function_index,
WireBytesRef name) {
base::MutexGuard lock(&mutex_);
function_names_.insert(std::make_pair(function_index, name));
function_names_.Put(function_index, name);
}
AsmJsOffsetInformation::AsmJsOffsetInformation(
......
......@@ -212,6 +212,74 @@ enum ModuleOrigin : uint8_t {
((origin) == kWasmOrigin ? (counters)->prefix##_wasm_##suffix() \
: (counters)->prefix##_asm_##suffix())
// Uses a map as backing storage when sparsely, or a vector when densely
// populated. Requires {Value} to implement `bool is_set()` to identify
// uninitialized objects.
template <class Value>
class AdaptiveMap {
public:
AdaptiveMap() : map_(new MapType()) {}
explicit AdaptiveMap(const AdaptiveMap&) = delete;
AdaptiveMap& operator=(const AdaptiveMap&) = delete;
AdaptiveMap(AdaptiveMap&& other) V8_NOEXCEPT { *this = std::move(other); }
AdaptiveMap& operator=(AdaptiveMap&& other) V8_NOEXCEPT {
mode_ = other.mode_;
vector_.swap(other.vector_);
map_.swap(other.map_);
return *this;
}
void FinishInitialization();
bool is_set() const { return mode_ != kInitializing; }
void Put(uint32_t key, const Value& value) {
DCHECK(mode_ == kInitializing);
map_->insert(std::make_pair(key, value));
}
void Put(uint32_t key, Value&& value) {
DCHECK(mode_ == kInitializing);
map_->insert(std::make_pair(key, std::move(value)));
}
const Value* Get(uint32_t key) const {
if (mode_ == kDense) {
if (key >= vector_.size()) return nullptr;
if (!vector_[key].is_set()) return nullptr;
return &vector_[key];
} else {
DCHECK(mode_ == kSparse || mode_ == kInitializing);
auto it = map_->find(key);
if (it == map_->end()) return nullptr;
return &it->second;
}
}
bool Has(uint32_t key) const {
if (mode_ == kDense) {
return key < vector_.size() && vector_[key].is_set();
} else {
DCHECK(mode_ == kSparse || mode_ == kInitializing);
return map_->find(key) != map_->end();
}
}
private:
static constexpr uint32_t kLoadFactor = 4;
using MapType = std::map<uint32_t, Value>;
enum Mode { kDense, kSparse, kInitializing };
Mode mode_{kInitializing};
std::vector<Value> vector_;
std::unique_ptr<MapType> map_;
};
using NameMap = AdaptiveMap<WireBytesRef>;
using IndirectNameMap = AdaptiveMap<AdaptiveMap<WireBytesRef>>;
struct ModuleWireBytes;
class V8_EXPORT_PRIVATE LazilyGeneratedNames {
......@@ -227,7 +295,7 @@ class V8_EXPORT_PRIVATE LazilyGeneratedNames {
// {WasmModuleObject}s.
base::Mutex mutex_;
bool has_functions_{false};
std::unordered_map<uint32_t, WireBytesRef> function_names_;
NameMap function_names_;
};
class V8_EXPORT_PRIVATE AsmJsOffsetInformation {
......
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