607 "PineMap key must be a Pine fundamental or enum type");
618 static_assert(std::is_trivially_copyable_v<KeyRep> &&
619 std::is_trivially_copyable_v<ValueRep>,
620 "map external representations must be copyable as Store bytes");
632 static constexpr bool kHeapValue =
638 static constexpr std::uint64_t kValueOffset = kKeyOffset +
sizeof(KeyRep);
639 static constexpr std::uint64_t kSlotBytes = (kValueOffset +
sizeof(ValueRep) + 7) / 8 * 8;
640 static constexpr std::uint64_t kCapacity =
static_cast<std::uint64_t
>(
max_pairs);
641 static constexpr std::uint32_t kBucketBits = 16;
642 static constexpr std::uint64_t kBucketBytes =
643 (std::uint64_t{1} << kBucketBits) *
sizeof(std::uint32_t);
645 static constexpr const char* kNaIdError =
646 "map operation on na ID";
647 static constexpr const char* kInvalidSnapshotError =
648 "map restore from invalid snapshot";
649 static constexpr const char* kCapacityError =
650 "map cannot contain more than 50000 key-value pairs";
657 void swap(EntryRoot& other)
noexcept { std::swap(
id, other.id); }
661 void swap(IndexRoot& other)
noexcept { std::swap(
id, other.id); }
664 static_assert(
noexcept(std::declval<EntryRoot&>().swap(
665 std::declval<EntryRoot&>())),
666 "PineMap ordered storage must support no-throw swap");
667 static_assert(
noexcept(std::declval<IndexRoot&>().swap(
668 std::declval<IndexRoot&>())),
669 "PineMap index must support no-throw swap");
672 std::shared_ptr<detail::MapStoreHolder> holder;
675 ReadCache read_cache;
676 std::vector<Value> heap_values;
679 Storage(
const Storage&) =
delete;
680 Storage& operator=(
const Storage&) =
delete;
684 std::lock_guard<std::recursive_mutex> lock(holder->mutex);
686 auto tx = holder->store->begin_cleanup();
688 release_entries(*
this, load_header(*
this));
689 holder->store->release(entries.id);
692 holder->store->release(index.id);
694 }
catch (
const detail::o1::RollbackOnly&) {
701 detail::o1::Store& store()
const {
return *holder->store; }
705 std::conditional_t<kHeapValue, std::vector<Value>, detail::MapSequence<Value>>;
727 "PineMap::Snapshot supports primitive Pine values only; "
728 "UDT and collection handles require recursive codegen checkpointing");
730 std::shared_ptr<Storage> identity_;
731 std::shared_ptr<detail::MapImage> state_;
733 Snapshot(std::shared_ptr<Storage> identity, std::shared_ptr<detail::MapImage> state)
734 : identity_(std::move(identity)), state_(std::move(state)) {}
757 if (
this != &other) storage_ = other.storage_;
762 auto storage = std::make_shared<Storage>();
764 std::lock_guard<std::recursive_mutex> lock(storage->holder->mutex);
765 auto tx = storage->store().scope();
766 create_objects(*storage);
768 return PineMap(std::move(storage));
771 [[nodiscard]]
bool is_na() const noexcept {
return !storage_; }
775 Value
put(Key key, Value value) {
776 Storage& storage = require_storage();
777 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
778 storage.read_cache.clear();
779 auto tx = storage.store().scope();
780 Value previous = [&] {
782 return put_locked(storage, key, std::move(value));
794 [[nodiscard]] Value
get(
const Key& key)
const {
795 Storage& storage = require_storage();
796 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
797 const std::uint64_t hash = hash_key(key);
798 if (
const auto* cached = find_cached(storage, key, hash))
799 return read_cached_value(storage, *cached);
801 const Located found = locate(storage, header, key, hash);
803 const std::uint32_t slot = found.slot - 1;
804 if constexpr (kHeapValue) {
805 record_cache(storage, hash, slot, found.key, ValueRep{});
806 return storage.heap_values[slot];
808 const ValueRep rep = load_value_rep(storage, slot);
809 record_cache(storage, hash, slot, found.key, rep);
817 Storage& storage = require_storage();
818 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
819 storage.read_cache.clear();
820 auto tx = storage.store().scope();
822 const Located found = locate(storage, header, key, hash_key(key));
825 const std::uint32_t slot = found.slot - 1;
827 Value previous = read_value(storage, slot);
829 if constexpr (!kHeapValue)
833 if (found.previous == 0) {
834 save_bucket(storage, found.bucket, links.
chain);
838 save_links(storage, found.previous - 1, before);
841 if (links.
prev != 0) {
844 save_links(storage, links.
prev - 1, before);
848 if (links.
next != 0) {
851 save_links(storage, links.
next - 1, after);
859 save_links(storage, slot, dead);
862 save_header(storage, header);
863 if constexpr (kHeapValue) storage.heap_values[slot] = Value{};
868 [[nodiscard]]
bool contains(
const Key& key)
const {
869 Storage& storage = require_storage();
870 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
871 const std::uint64_t hash = hash_key(key);
872 if (find_cached(storage, key, hash) !=
nullptr)
return true;
874 const Located found = locate<true>(storage, header, key, hash);
875 if (found.slot == 0)
return false;
876 record_cache(storage, hash, found.slot - 1, found.key, found.value);
880 [[nodiscard]]
int size()
const {
881 Storage& storage = require_storage();
882 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
884 if (header.
count >
static_cast<std::uint32_t
>(std::numeric_limits<int>::max())) {
886 "map.size: result exceeds int range");
888 return static_cast<int>(header.
count);
895 template <
class Sink,
class HashKey,
class HashValue>
896 void hash_into(Sink& sink, HashKey hash_key_value, HashValue hash_value)
const {
897 Storage& storage = require_storage();
898 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
899 const auto header = load_header(storage);
900 sink.u(header.count);
901 for (std::uint32_t at = header.head; at != 0;) {
902 hash_key_value(sink, read_key(storage, at - 1));
903 hash_value(sink, read_value(storage, at - 1));
904 at = load_links(storage, at - 1).next;
909 Storage& storage = require_storage();
910 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
911 storage.read_cache.clear();
912 auto tx = storage.store().scope();
915 release_entries(storage, load_header(storage));
917 storage.heap_values.clear();
924 Storage& target = require_storage();
925 Storage& from =
source.require_storage();
926 if (storage_ ==
source.storage_)
return;
928 std::unique_lock<std::recursive_mutex> target_lock(target.holder->mutex, std::defer_lock);
929 std::unique_lock<std::recursive_mutex> source_lock(from.holder->mutex, std::defer_lock);
930 if (target.holder == from.holder) {
933 std::lock(target_lock, source_lock);
936 target.read_cache.clear();
939 std::size_t new_keys = 0;
940 for (std::uint32_t at = source_header.
head; at != 0;) {
941 const Key key = read_key(from, at - 1);
942 if (locate(target, target_header, key, hash_key(key)).slot == 0) ++new_keys;
943 at = load_links(from, at - 1).next;
945 const auto available =
static_cast<std::size_t
>(
max_pairs) -
946 static_cast<std::size_t
>(target_header.
count);
947 if (new_keys > available) {
954 auto tx = target.store().scope();
955 for (std::uint32_t at = source_header.
head; at != 0;) {
956 const Key key = read_key(from, at - 1);
957 (void)put_locked(target, key, read_value(from, at - 1));
958 at = load_links(from, at - 1).next;
966 Storage& storage = require_storage();
967 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
968 auto tx = storage.store().scope();
970 auto image = make_image(storage,
static_cast<std::uint64_t
>(header.
count) *
sizeof(KeyRep));
971 fill_image<Key>(storage, header, image->id, [&storage](std::uint32_t slot) {
972 return load_key_rep(storage, slot);
974 image->cleanup = &release_sequence<Key>;
983 [[nodiscard]] ValuesResult
values()
const {
984 Storage& storage = require_storage();
985 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
987 if constexpr (kHeapValue) {
988 std::vector<Value> result;
989 result.reserve(header.
count);
990 for (std::uint32_t at = header.
head; at != 0;) {
991 result.push_back(storage.heap_values[at - 1]);
992 at = load_links(storage, at - 1).next;
996 auto tx = storage.store().scope();
997 auto image = make_image(storage,
998 static_cast<std::uint64_t
>(header.
count) *
sizeof(ValueRep));
999 fill_image<Value>(storage, header, image->id, [&storage](std::uint32_t slot) {
1000 return load_value_rep(storage, slot);
1002 image->cleanup = &release_sequence<Value>;
1015 Storage&
source = require_storage();
1016 auto storage = std::make_shared<Storage>();
1017 storage->holder =
source.holder;
1018 std::lock_guard<std::recursive_mutex> lock(
source.holder->mutex);
1019 auto tx =
source.store().scope();
1020 create_objects(*storage);
1022 copy_range(
source.store(),
source.entries.id, storage->entries.id,
1023 kHeaderBytes +
static_cast<std::uint64_t
>(header.
high_water) * kSlotBytes);
1024 rebuild_index(*storage, header);
1025 retain_entries(*storage, header);
1026 if constexpr (kHeapValue) storage->heap_values =
source.heap_values;
1028 return PineMap(std::move(storage));
1031 template <
typename T = Value,
1034 std::is_same_v<T, Value>,
int> = 0>
1036 Storage& storage = require_storage();
1037 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
1038 auto tx = storage.store().scope();
1040 const std::uint64_t bytes =
1041 kHeaderBytes +
static_cast<std::uint64_t
>(header.
high_water) * kSlotBytes;
1042 auto image = make_image(storage, bytes);
1043 copy_range(storage.store(), storage.entries.id, image->id, bytes);
1044 image_entry_refs(storage.store(), image->id,
true);
1045 image->cleanup = &release_snapshot;
1047 return Snapshot(storage_, std::move(image));
1050 template <
typename T = Value,
1053 std::is_same_v<T, Value>,
int> = 0>
1063 Storage& storage = *
snapshot.identity_;
1064 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
1065 storage.read_cache.clear();
1066 auto tx = storage.store().scope();
1068 read_exact(storage.store(),
snapshot.state_->id, 0, &header,
sizeof header);
1069 release_entries(storage, load_header(storage));
1070 copy_range(storage.store(),
snapshot.state_->id, storage.entries.id,
1072 retain_entries(storage, header);
1073 rebuild_index(storage, header);
1083 std::uint32_t slot = 0;
1084 std::uint32_t previous = 0;
1085 std::uint32_t head = 0;
1086 std::uint32_t bucket = 0;
1091 [[nodiscard]] Storage& require_storage()
const {
1096 explicit PineMap(std::shared_ptr<Storage> storage)
1097 : storage_(std::move(storage)) {}
1099 static void create_objects(Storage& storage) {
1101 storage.entries.id = store.create(kHeaderBytes + kCapacity * kSlotBytes, 8);
1102 storage.index.id = store.create(kBucketBytes, 8);
1109 void* destination, std::size_t bytes) {
1110 if (store.read(
id, offset, destination, bytes) != bytes) {
1116 const void*
source, std::size_t bytes) {
1117 if (store.write(
id, offset,
source, bytes) != bytes) {
1125 std::uint64_t bytes) {
1126 unsigned char buffer[4096];
1127 std::uint64_t done = 0;
1128 while (done < bytes) {
1129 const std::size_t chunk =
1130 static_cast<std::size_t
>(std::min<std::uint64_t>(
sizeof buffer, bytes - done));
1131 read_exact(store, from, done, buffer, chunk);
1139 read_exact(storage.store(), storage.entries.id, 0, &header,
sizeof header);
1144 write_exact(storage.store(), storage.entries.id, 0, &header,
sizeof header);
1147 static std::uint64_t slot_offset(std::uint32_t slot) {
1148 return kHeaderBytes +
static_cast<std::uint64_t
>(slot) * kSlotBytes;
1153 read_exact(storage.store(), storage.entries.id, slot_offset(slot), &links,
sizeof links);
1157 static void save_links(Storage& storage, std::uint32_t slot,
const detail::MapLinks& links) {
1158 write_exact(storage.store(), storage.entries.id, slot_offset(slot), &links,
sizeof links);
1161 static KeyRep load_key_rep(Storage& storage, std::uint32_t slot) {
1163 read_exact(storage.store(), storage.entries.id, slot_offset(slot) + kKeyOffset, &rep,
1168 static ValueRep load_value_rep(Storage& storage, std::uint32_t slot) {
1170 read_exact(storage.store(), storage.entries.id, slot_offset(slot) + kValueOffset, &rep,
1175 static void save_value_rep(Storage& storage, std::uint32_t slot,
const ValueRep& rep) {
1176 write_exact(storage.store(), storage.entries.id, slot_offset(slot) + kValueOffset, &rep,
1181 static void save_slot(Storage& storage, std::uint32_t slot,
const detail::MapLinks& links,
1182 const KeyRep& key,
const ValueRep& value) {
1183 unsigned char raw[kSlotBytes] = {};
1184 std::memcpy(raw, &links,
sizeof links);
1185 std::memcpy(raw + kKeyOffset, &key,
sizeof key);
1186 std::memcpy(raw + kValueOffset, &value,
sizeof value);
1187 write_exact(storage.store(), storage.entries.id, slot_offset(slot), raw,
sizeof raw);
1190 static std::uint32_t load_bucket(Storage& storage, std::uint32_t bucket) {
1191 std::uint32_t head = 0;
1192 read_exact(storage.store(), storage.index.id,
1193 static_cast<std::uint64_t
>(bucket) *
sizeof head, &head,
sizeof head);
1197 static void save_bucket(Storage& storage, std::uint32_t bucket, std::uint32_t head) {
1199 static_cast<std::uint64_t
>(bucket) *
sizeof head, &head,
sizeof head);
1202 static std::uint64_t hash_key(
const Key& key) {
1206 static std::uint32_t bucket_of(std::uint64_t hash)
noexcept {
1207 return static_cast<std::uint32_t
>((hash * UINT64_C(0x9e3779b97f4a7c15)) >>
1208 (64 - kBucketBits));
1211 static bool key_matches(
detail::o1::Store& store,
const KeyRep& rep,
const Key& key) {
1222 static const typename ReadCache::Entry* find_cached(Storage& storage,
const Key& key,
1223 std::uint64_t hash) {
1226 if (storage.read_cache.epoch != storage.store().rollback_epoch()) {
1227 storage.read_cache.clear();
1232 for (
const auto& entry : storage.read_cache.entries) {
1233 if (entry.valid && entry.hash == hash &&
1234 key_matches(storage.store(), entry.key, key))
1240 static Value read_cached_value(Storage& storage,
const typename ReadCache::Entry& entry) {
1241 if constexpr (kHeapValue) {
1242 return storage.heap_values[entry.slot];
1248 static void record_cache(Storage& storage, std::uint64_t hash, std::uint32_t slot,
1249 const KeyRep& key,
const ValueRep& value) {
1250 storage.read_cache.epoch = storage.store().rollback_epoch();
1251 storage.read_cache.record(hash, slot, key, value);
1254 static Key read_key(Storage& storage, std::uint32_t slot) {
1258 static Value read_value(Storage& storage, std::uint32_t slot) {
1259 if constexpr (kHeapValue) {
1260 return storage.heap_values[slot];
1266 static void set_heap_value(Storage& storage, std::uint32_t slot, Value&& value) {
1267 if (slot >= storage.heap_values.size()) storage.heap_values.resize(slot + 1);
1268 storage.heap_values[slot] = std::move(value);
1273 template <
bool WithValue = false>
1274 static Located locate(Storage& storage,
const detail::MapHeader& header,
const Key& key,
1275 std::uint64_t hash) {
1277 found.bucket = bucket_of(hash);
1278 std::uint32_t at = load_bucket(storage, found.bucket);
1279 std::uint32_t before = 0;
1281 while (at != 0 && at <= header.high_water) {
1283 if (links.bucket != found.bucket)
break;
1288 if (links.hash == hash) {
1289 if constexpr (WithValue && !kHeapValue) {
1293 unsigned char raw[
sizeof(KeyRep) +
sizeof(ValueRep)];
1294 read_exact(storage.store(), storage.entries.id,
1295 slot_offset(at - 1) + kKeyOffset, raw,
sizeof raw);
1296 std::memcpy(&found.key, raw,
sizeof(KeyRep));
1297 std::memcpy(&found.value, raw +
sizeof(KeyRep),
sizeof(ValueRep));
1299 found.key = load_key_rep(storage, at - 1);
1301 if (key_matches(storage.store(), found.key, key)) {
1303 found.previous = before;
1313 static Value put_locked(Storage& storage,
const Key& key, Value value) {
1315 const std::uint64_t hash = hash_key(key);
1316 const Located found = locate(storage, before, key, hash);
1317 if (found.slot != 0) {
1318 const std::uint32_t slot = found.slot - 1;
1319 Value previous = read_value(storage, slot);
1320 if constexpr (kHeapValue) {
1321 set_heap_value(storage, slot, std::move(value));
1323 const ValueRep old = load_value_rep(storage, slot);
1324 save_value_rep(storage, slot,
1331 if (before.count >=
static_cast<std::uint32_t
>(max_pairs)) {
1340 std::uint32_t slot = 0;
1341 if (before.free_head != 0) {
1342 slot = before.free_head - 1;
1343 after.free_head = load_links(storage, slot).next;
1345 slot = before.high_water;
1346 after.high_water = before.high_water + 1;
1348 if constexpr (kHeapValue) set_heap_value(storage, slot, std::move(value));
1352 links.prev = before.tail;
1354 links.chain = found.head;
1355 links.bucket = found.bucket;
1356 after.count = before.count + 1;
1357 after.tail = slot + 1;
1358 if (before.tail == 0) after.head = slot + 1;
1359 save_slot(storage, slot, links, key_rep, value_rep);
1360 if (before.tail != 0) {
1362 last.next = slot + 1;
1363 save_links(storage, before.tail - 1, last);
1365 save_bucket(storage, found.bucket, slot + 1);
1366 save_header(storage, after);
1370 static void release_entries(Storage& storage,
const detail::MapHeader& header) {
1371 for (std::uint32_t at = header.head; at != 0;) {
1373 if constexpr (!kHeapValue)
1375 at = load_links(storage, at - 1).next;
1380 for (std::uint32_t at = header.head; at != 0;) {
1382 if constexpr (!kHeapValue)
1384 at = load_links(storage, at - 1).next;
1388 static void image_entry_refs(
detail::o1::Store& store, StoreId image,
bool retain) {
1390 read_exact(store, image, 0, &header,
sizeof header);
1391 for (std::uint32_t at = header.head; at != 0;) {
1392 const auto offset = slot_offset(at - 1);
1396 read_exact(store, image, offset, &links,
sizeof links);
1397 read_exact(store, image, offset + kKeyOffset, &key,
sizeof key);
1398 read_exact(store, image, offset + kValueOffset, &value,
sizeof value);
1410 static void release_snapshot(
detail::o1::Store& store, StoreId image, std::uint64_t) {
1411 image_entry_refs(store, image,
false);
1414 template <
typename Elem>
1415 static void release_sequence(
detail::o1::Store& store, StoreId image, std::uint64_t bytes) {
1417 for (std::uint64_t offset = 0; offset < bytes; offset +=
sizeof(Rep)) {
1419 read_exact(store, image, offset, &rep,
sizeof rep);
1428 for (std::uint32_t at = header.head; at != 0;) {
1430 save_bucket(storage, links.bucket, at);
1435 static std::shared_ptr<detail::MapImage> make_image(Storage& storage, std::uint64_t bytes) {
1436 const StoreId
id = storage.store().create(bytes == 0 ? 1 : bytes, 8);
1437 return std::make_shared<detail::MapImage>(storage.holder,
id, bytes);
1441 template <
typename Elem,
typename Fetch>
1442 static void fill_image(Storage& storage,
const detail::MapHeader& header, StoreId image,
1445 constexpr std::size_t kPerChunk = std::max<std::size_t>(1, 4096 /
sizeof(Rep));
1446 Rep chunk[kPerChunk] = {};
1447 std::size_t filled = 0;
1448 std::uint64_t written = 0;
1449 for (std::uint32_t at = header.head; at != 0;) {
1450 const Rep rep = fetch(at - 1);
1452 chunk[filled++] = rep;
1453 at = load_links(storage, at - 1).next;
1454 if (filled == kPerChunk) {
1455 write_exact(storage.store(), image, written *
sizeof(Rep), chunk,
1456 filled *
sizeof(Rep));
1462 write_exact(storage.store(), image, written *
sizeof(Rep), chunk, filled *
sizeof(Rep));
1466 std::shared_ptr<Storage> storage_;