blob: 97cf262ea202a008de0e17ccb14fd593be24af3f [file] [log] [blame]
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "google/protobuf/map.h"
#include <algorithm>
#include <functional>
#include <iterator>
#include <string>
#include <type_traits>
#include "absl/hash/hash.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/message_lite.h"
// Must be included last.
#include "google/protobuf/port_def.inc"
namespace google {
namespace protobuf {
namespace internal {
const TableEntryPtr kGlobalEmptyTable[kGlobalEmptyTableSize] = {};
NodeBase* UntypedMapBase::DestroyTree(Tree* tree) {
NodeBase* head = tree->empty() ? nullptr : tree->begin()->second;
if (alloc_.arena() == nullptr) {
delete tree;
}
return head;
}
void UntypedMapBase::EraseFromTree(map_index_t b,
typename Tree::iterator tree_it) {
ABSL_DCHECK(TableEntryIsTree(b));
Tree* tree = TableEntryToTree(table_[b]);
if (tree_it != tree->begin()) {
NodeBase* prev = std::prev(tree_it)->second;
prev->next = prev->next->next;
}
tree->erase(tree_it);
if (tree->empty()) {
DestroyTree(tree);
table_[b] = TableEntryPtr{};
}
}
map_index_t UntypedMapBase::VariantBucketNumber(VariantKey key) const {
return BucketNumberFromHash(key.Hash());
}
void UntypedMapBase::InsertUniqueInTree(map_index_t b, GetKey get_key,
NodeBase* node) {
if (TableEntryIsNonEmptyList(b)) {
// To save in binary size, we delegate to an out-of-line function to do
// the conversion.
table_[b] = ConvertToTree(TableEntryToNode(table_[b]), get_key);
}
ABSL_DCHECK(TableEntryIsTree(b))
<< (void*)table_[b] << " " << (uintptr_t)table_[b];
Tree* tree = TableEntryToTree(table_[b]);
auto it = tree->try_emplace(get_key(node), node).first;
// Maintain the linked list of the nodes in the tree.
// For simplicity, they are in the same order as the tree iteration.
if (it != tree->begin()) {
NodeBase* prev = std::prev(it)->second;
prev->next = node;
}
auto next = std::next(it);
node->next = next != tree->end() ? next->second : nullptr;
}
void UntypedMapBase::TransferTree(Tree* tree, GetKey get_key) {
NodeBase* node = DestroyTree(tree);
do {
NodeBase* next = node->next;
map_index_t b = VariantBucketNumber(get_key(node));
// This is similar to InsertUnique, but with erasure.
if (TableEntryIsEmpty(b)) {
InsertUniqueInList(b, node);
index_of_first_non_null_ = (std::min)(index_of_first_non_null_, b);
} else if (TableEntryIsNonEmptyList(b) && !TableEntryIsTooLong(b)) {
InsertUniqueInList(b, node);
} else {
InsertUniqueInTree(b, get_key, node);
}
node = next;
} while (node != nullptr);
}
TableEntryPtr UntypedMapBase::ConvertToTree(NodeBase* node, GetKey get_key) {
auto* tree = Arena::Create<Tree>(alloc_.arena(), typename Tree::key_compare(),
typename Tree::allocator_type(alloc_));
for (; node != nullptr; node = node->next) {
tree->try_emplace(get_key(node), node);
}
ABSL_DCHECK_EQ(MapTreeLengthThreshold(), tree->size());
// Relink the nodes.
NodeBase* next = nullptr;
auto it = tree->end();
do {
node = (--it)->second;
node->next = next;
next = node;
} while (it != tree->begin());
return TreeToTableEntry(tree);
}
void UntypedMapBase::ClearTable(const ClearInput input) {
ABSL_DCHECK_NE(num_buckets_, kGlobalEmptyTableSize);
if (alloc_.arena() == nullptr) {
const auto loop = [=](auto destroy_node) {
const TableEntryPtr* table = table_;
for (map_index_t b = index_of_first_non_null_, end = num_buckets_;
b < end; ++b) {
NodeBase* node =
PROTOBUF_PREDICT_FALSE(internal::TableEntryIsTree(table[b]))
? DestroyTree(TableEntryToTree(table[b]))
: TableEntryToNode(table[b]);
while (node != nullptr) {
NodeBase* next = node->next;
destroy_node(node);
SizedDelete(node, SizeFromInfo(input.size_info));
node = next;
}
}
};
switch (input.destroy_bits) {
case 0:
loop([](NodeBase*) {});
break;
case kKeyIsString:
loop([](NodeBase* node) {
static_cast<std::string*>(node->GetVoidKey())->~basic_string();
});
break;
case kValueIsString:
loop([size_info = input.size_info](NodeBase* node) {
static_cast<std::string*>(node->GetVoidValue(size_info))
->~basic_string();
});
break;
case kKeyIsString | kValueIsString:
loop([size_info = input.size_info](NodeBase* node) {
static_cast<std::string*>(node->GetVoidKey())->~basic_string();
static_cast<std::string*>(node->GetVoidValue(size_info))
->~basic_string();
});
break;
case kValueIsProto:
loop([size_info = input.size_info](NodeBase* node) {
static_cast<MessageLite*>(node->GetVoidValue(size_info))
->DestroyInstance(false);
});
break;
case kKeyIsString | kValueIsProto:
loop([size_info = input.size_info](NodeBase* node) {
static_cast<std::string*>(node->GetVoidKey())->~basic_string();
static_cast<MessageLite*>(node->GetVoidValue(size_info))
->DestroyInstance(false);
});
break;
case kUseDestructFunc:
loop(input.destroy_node);
break;
}
}
if (input.reset_table) {
std::fill(table_, table_ + num_buckets_, TableEntryPtr{});
num_elements_ = 0;
index_of_first_non_null_ = num_buckets_;
} else {
DeleteTable(table_, num_buckets_);
}
}
auto UntypedMapBase::FindFromTree(map_index_t b, VariantKey key,
Tree::iterator* it) const -> NodeAndBucket {
Tree* tree = TableEntryToTree(table_[b]);
auto tree_it = tree->find(key);
if (it != nullptr) *it = tree_it;
if (tree_it != tree->end()) {
return {tree_it->second, b};
}
return {nullptr, b};
}
size_t UntypedMapBase::SpaceUsedInTable(size_t sizeof_node) const {
size_t size = 0;
// The size of the table.
size += sizeof(void*) * num_buckets_;
// All the nodes.
size += sizeof_node * num_elements_;
// For each tree, count the overhead of those nodes.
// Two buckets at a time because we only care about trees.
for (map_index_t b = 0; b < num_buckets_; ++b) {
if (TableEntryIsTree(b)) {
size += sizeof(Tree);
size += sizeof(Tree::value_type) * TableEntryToTree(table_[b])->size();
}
}
return size;
}
} // namespace internal
} // namespace protobuf
} // namespace google
#include "google/protobuf/port_undef.inc"