604 "PineMap key must be a Pine fundamental or enum type");
617 static_assert(std::is_trivially_copyable_v<KeyRep> &&
618 std::is_trivially_copyable_v<ValueRep>,
619 "map external representations must be copyable as Store bytes");
631 static constexpr bool kHeapValue =
637 static constexpr std::uint64_t kValueOffset = kKeyOffset +
sizeof(KeyRep);
638 static constexpr std::uint64_t kSlotBytes = (kValueOffset +
sizeof(ValueRep) + 7) / 8 * 8;
640 static constexpr const char* kNaIdError =
641 "map operation on na ID";
642 static constexpr const char* kInvalidSnapshotError =
643 "map restore from invalid snapshot";
650 void swap(EntryRoot& other)
noexcept { std::swap(
id, other.id); }
654 void swap(IndexRoot& other)
noexcept { std::swap(
id, other.id); }
657 static_assert(
noexcept(std::declval<EntryRoot&>().swap(
658 std::declval<EntryRoot&>())),
659 "PineMap ordered storage must support no-throw swap");
660 static_assert(
noexcept(std::declval<IndexRoot&>().swap(
661 std::declval<IndexRoot&>())),
662 "PineMap index must support no-throw swap");
665 std::shared_ptr<detail::MapStoreHolder> holder;
668 ReadCache read_cache;
670 std::array<std::uint32_t, ReadCache::capacity> read_cache_slot_high{};
671 std::vector<Value> heap_values;
674 Storage(
const Storage&) =
delete;
675 Storage& operator=(
const Storage&) =
delete;
679 std::lock_guard<std::recursive_mutex> lock(holder->mutex);
681 auto tx = holder->store->begin_cleanup();
683 release_entries(*
this, load_header(*
this));
684 holder->store->release(entries.id);
687 holder->store->release(index.id);
689 }
catch (
const detail::o1::RollbackOnly&) {
696 detail::o1::Store& store()
const {
return *holder->store; }
700 std::conditional_t<kHeapValue, std::vector<Value>, detail::MapSequence<Value>>;
722 "PineMap::Snapshot supports primitive Pine values only; "
723 "UDT and collection handles require recursive codegen checkpointing");
725 std::shared_ptr<Storage> identity_;
726 std::shared_ptr<detail::MapImage> state_;
728 Snapshot(std::shared_ptr<Storage> identity, std::shared_ptr<detail::MapImage> state)
729 : identity_(std::move(identity)), state_(std::move(state)) {}
752 if (
this != &other) storage_ = other.storage_;
763 auto storage = std::make_shared<Storage>();
765 std::lock_guard<std::recursive_mutex> lock(storage->holder->mutex);
766 auto tx = storage->store().scope();
767 create_objects(*storage, maximum);
769 return PineMap(std::move(storage));
772 [[nodiscard]]
bool is_na() const noexcept {
return !storage_; }
776 Value
put(Key key, Value value) {
777 Storage& storage = require_storage();
778 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
779 storage.read_cache.clear();
780 auto tx = storage.store().scope();
781 Value previous = [&] {
783 return put_locked(storage, key, std::move(value));
795 [[nodiscard]] Value
get(
const Key& key)
const {
796 Storage& storage = require_storage();
797 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
798 const std::uint64_t hash = hash_key(key);
799 if (
const auto* cached = find_cached(storage, key, hash))
800 return read_cached_value(storage, *cached);
802 const Located found = locate(storage, header, key, hash);
804 const std::uint64_t slot = found.slot - 1;
805 if constexpr (kHeapValue) {
806 record_cache(storage, hash, slot, found.key, ValueRep{});
807 return storage.heap_values[slot];
809 const ValueRep rep = load_value_rep(storage, slot);
810 record_cache(storage, hash, slot, found.key, rep);
818 Storage& storage = require_storage();
819 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
820 storage.read_cache.clear();
821 auto tx = storage.store().scope();
823 const Located found = locate(storage, header, key, hash_key(key));
826 const std::uint64_t slot = found.slot - 1;
828 Value previous = read_value(storage, slot);
830 if constexpr (!kHeapValue)
834 if (found.previous == 0) {
835 save_bucket(storage, found.bucket, links.
chain);
839 save_links(storage, found.previous - 1, before);
842 if (links.
prev != 0) {
845 save_links(storage, links.
prev - 1, before);
849 if (links.
next != 0) {
852 save_links(storage, links.
next - 1, after);
860 save_links(storage, slot, dead);
863 save_header(storage, header);
864 if constexpr (kHeapValue) storage.heap_values[slot] = Value{};
869 [[nodiscard]]
bool contains(
const Key& key)
const {
870 Storage& storage = require_storage();
871 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
872 const std::uint64_t hash = hash_key(key);
873 if (find_cached(storage, key, hash) !=
nullptr)
return true;
875 const Located found = locate<true>(storage, header, key, hash);
876 if (found.slot == 0)
return false;
877 record_cache(storage, hash, found.slot - 1, found.key, found.value);
882 [[nodiscard]] std::int64_t
size()
const {
883 Storage& storage = require_storage();
884 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
886 if (header.
count >
static_cast<std::uint64_t
>(std::numeric_limits<std::int64_t>::max())) {
888 "map.size: result exceeds int64 range");
890 return static_cast<std::int64_t
>(header.
count);
897 template <
class Sink,
class HashKey,
class HashValue>
898 void hash_into(Sink& sink, HashKey hash_key_value, HashValue hash_value)
const {
899 Storage& storage = require_storage();
900 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
901 const auto header = load_header(storage);
902 sink.u(header.count);
903 for (std::uint64_t at = header.head; at != 0;) {
904 hash_key_value(sink, read_key(storage, at - 1));
905 hash_value(sink, read_value(storage, at - 1));
906 at = load_links(storage, at - 1).next;
910 if (header.max_pairs != 0) {
911 sink.u(UINT64_C(0x6d61702d6c696d31));
912 sink.u(header.max_pairs);
917 Storage& storage = require_storage();
918 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
919 storage.read_cache.clear();
920 auto tx = storage.store().scope();
923 auto header = load_header(storage);
924 release_entries(storage, header);
925 header.count = header.head = header.tail = header.high_water = header.free_head = 0;
926 save_header(storage, header);
927 storage.heap_values.clear();
934 Storage& target = require_storage();
935 Storage& from =
source.require_storage();
936 if (storage_ ==
source.storage_)
return;
938 std::unique_lock<std::recursive_mutex> target_lock(target.holder->mutex, std::defer_lock);
939 std::unique_lock<std::recursive_mutex> source_lock(from.holder->mutex, std::defer_lock);
940 if (target.holder == from.holder) {
943 std::lock(target_lock, source_lock);
946 target.read_cache.clear();
949 std::uint64_t new_keys = 0;
950 for (std::uint64_t at = source_header.
head; at != 0;) {
951 const Key key = read_key(from, at - 1);
952 if (locate(target, target_header, key, hash_key(key)).slot == 0) {
962 at = load_links(from, at - 1).next;
965 auto tx = target.store().scope();
966 for (std::uint64_t at = source_header.
head; at != 0;) {
967 const Key key = read_key(from, at - 1);
968 (void)put_locked(target, key, read_value(from, at - 1));
969 at = load_links(from, at - 1).next;
977 Storage& storage = require_storage();
978 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
979 auto tx = storage.store().scope();
981 auto image = make_image(storage,
static_cast<std::uint64_t
>(header.
count) *
sizeof(KeyRep));
982 fill_image<Key>(storage, header, image->id, [&storage](std::uint64_t slot) {
983 return load_key_rep(storage, slot);
985 image->cleanup = &release_sequence<Key>;
994 [[nodiscard]] ValuesResult
values()
const {
995 Storage& storage = require_storage();
996 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
998 if constexpr (kHeapValue) {
999 std::vector<Value> result;
1000 result.reserve(header.
count);
1001 for (std::uint64_t at = header.
head; at != 0;) {
1002 result.push_back(storage.heap_values[at - 1]);
1003 at = load_links(storage, at - 1).next;
1007 auto tx = storage.store().scope();
1008 auto image = make_image(storage,
1009 static_cast<std::uint64_t
>(header.
count) *
sizeof(ValueRep));
1010 fill_image<Value>(storage, header, image->id, [&storage](std::uint64_t slot) {
1011 return load_value_rep(storage, slot);
1013 image->cleanup = &release_sequence<Value>;
1026 Storage&
source = require_storage();
1027 auto storage = std::make_shared<Storage>();
1028 storage->holder =
source.holder;
1029 std::lock_guard<std::recursive_mutex> lock(
source.holder->mutex);
1030 auto tx =
source.store().scope();
1032 create_objects(*storage, header.
max_pairs);
1033 resize_exact(*storage, storage->entries.id, arena_bytes(header.
capacity));
1034 copy_range(
source.store(),
source.entries.id, storage->entries.id,
1035 kHeaderBytes +
static_cast<std::uint64_t
>(header.
high_water) * kSlotBytes);
1036 rebuild_index(*storage, header);
1037 retain_entries(*storage, header);
1038 if constexpr (kHeapValue) storage->heap_values =
source.heap_values;
1040 return PineMap(std::move(storage));
1043 template <
typename T = Value,
1046 std::is_same_v<T, Value>,
int> = 0>
1048 Storage& storage = require_storage();
1049 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
1050 auto tx = storage.store().scope();
1052 const std::uint64_t bytes =
1053 kHeaderBytes +
static_cast<std::uint64_t
>(header.
high_water) * kSlotBytes;
1054 auto image = make_image(storage, bytes);
1055 copy_range(storage.store(), storage.entries.id, image->id, bytes);
1056 image_entry_refs(storage.store(), image->id,
true);
1057 image->cleanup = &release_snapshot;
1059 return Snapshot(storage_, std::move(image));
1062 template <
typename T = Value,
1065 std::is_same_v<T, Value>,
int> = 0>
1075 Storage& storage = *
snapshot.identity_;
1076 std::lock_guard<std::recursive_mutex> lock(storage.holder->mutex);
1077 storage.read_cache.clear();
1078 auto tx = storage.store().scope();
1080 read_exact(storage.store(),
snapshot.state_->id, 0, &header,
sizeof header);
1081 release_entries(storage, load_header(storage));
1082 resize_exact(storage, storage.entries.id, arena_bytes(header.capacity));
1083 copy_range(storage.store(),
snapshot.state_->id, storage.entries.id,
1085 retain_entries(storage, header);
1086 rebuild_index(storage, header);
1096 std::uint64_t slot = 0;
1097 std::uint64_t previous = 0;
1098 std::uint64_t head = 0;
1099 std::uint64_t bucket = 0;
1104 [[nodiscard]] Storage& require_storage()
const {
1109 explicit PineMap(std::shared_ptr<Storage> storage)
1110 : storage_(std::move(storage)) {}
1112 static void create_objects(Storage& storage, std::uint64_t maximum) {
1114 storage.entries.id = store.create(kHeaderBytes, 8);
1115 storage.index.id = store.create(
sizeof(std::uint64_t), 8);
1118 if (maximum != 0) save_header(storage, header);
1122 static std::uint64_t arena_bytes(std::uint64_t capacity) {
1123 if (capacity > (std::numeric_limits<std::uint64_t>::max() - kHeaderBytes) / kSlotBytes)
1125 return kHeaderBytes + capacity * kSlotBytes;
1128 static void resize_exact(Storage& storage, StoreId
id, std::uint64_t bytes) {
1129 if (!storage.store().resize(
id, bytes))
1133 static std::uint32_t bucket_bits_for(std::uint64_t capacity) {
1134 std::uint32_t bits = 0;
1135 std::uint64_t buckets = 1;
1136 while (capacity > buckets / 2 + buckets / 4) {
1138 if (buckets > std::numeric_limits<std::uint64_t>::max() /
1139 (2 *
sizeof(std::uint64_t)))
1148 const auto addressable =
1149 (std::numeric_limits<std::uint64_t>::max() - kHeaderBytes) / kSlotBytes;
1150 if (header.capacity >= addressable)
1152 std::uint64_t step = std::min(growth_step_pairs, addressable - header.capacity);
1153 if (header.max_pairs != 0)
1154 step = std::min(step, header.max_pairs - header.capacity);
1155 header.capacity += step;
1156 resize_exact(storage, storage.entries.id, arena_bytes(header.capacity));
1157 const auto bits = bucket_bits_for(header.capacity);
1158 if (bits != header.bucket_bits) {
1159 header.bucket_bits = bits;
1160 rebuild_index(storage, header);
1164 save_header(storage, header);
1168 void* destination, std::size_t bytes) {
1169 if (store.read(
id, offset, destination, bytes) != bytes) {
1175 const void*
source, std::size_t bytes) {
1176 if (store.write(
id, offset,
source, bytes) != bytes) {
1184 std::uint64_t bytes) {
1185 unsigned char buffer[4096];
1186 std::uint64_t done = 0;
1187 while (done < bytes) {
1188 const std::size_t chunk =
1189 static_cast<std::size_t
>(std::min<std::uint64_t>(
sizeof buffer, bytes - done));
1190 read_exact(store, from, done, buffer, chunk);
1198 read_exact(storage.store(), storage.entries.id, 0, &header,
sizeof header);
1203 write_exact(storage.store(), storage.entries.id, 0, &header,
sizeof header);
1206 static std::uint64_t slot_offset(std::uint64_t slot) {
1207 return kHeaderBytes +
static_cast<std::uint64_t
>(slot) * kSlotBytes;
1212 read_exact(storage.store(), storage.entries.id, slot_offset(slot), &links,
sizeof links);
1216 static void save_links(Storage& storage, std::uint64_t slot,
const detail::MapLinks& links) {
1217 write_exact(storage.store(), storage.entries.id, slot_offset(slot), &links,
sizeof links);
1220 static KeyRep load_key_rep(Storage& storage, std::uint64_t slot) {
1222 read_exact(storage.store(), storage.entries.id, slot_offset(slot) + kKeyOffset, &rep,
1227 static ValueRep load_value_rep(Storage& storage, std::uint64_t slot) {
1229 read_exact(storage.store(), storage.entries.id, slot_offset(slot) + kValueOffset, &rep,
1234 static void save_value_rep(Storage& storage, std::uint64_t slot,
const ValueRep& rep) {
1235 write_exact(storage.store(), storage.entries.id, slot_offset(slot) + kValueOffset, &rep,
1240 static void save_slot(Storage& storage, std::uint64_t slot,
const detail::MapLinks& links,
1241 const KeyRep& key,
const ValueRep& value) {
1242 unsigned char raw[kSlotBytes] = {};
1243 std::memcpy(raw, &links,
sizeof links);
1244 std::memcpy(raw + kKeyOffset, &key,
sizeof key);
1245 std::memcpy(raw + kValueOffset, &value,
sizeof value);
1246 write_exact(storage.store(), storage.entries.id, slot_offset(slot), raw,
sizeof raw);
1249 static std::uint64_t load_bucket(Storage& storage, std::uint64_t bucket) {
1250 std::uint64_t head = 0;
1251 read_exact(storage.store(), storage.index.id,
1252 static_cast<std::uint64_t
>(bucket) *
sizeof head, &head,
sizeof head);
1256 static void save_bucket(Storage& storage, std::uint64_t bucket, std::uint64_t head) {
1258 static_cast<std::uint64_t
>(bucket) *
sizeof head, &head,
sizeof head);
1261 static std::uint64_t hash_key(
const Key& key) {
1265 static std::uint64_t bucket_of(std::uint64_t hash, std::uint32_t bits)
noexcept {
1266 return bits == 0 ? 0 : (hash * UINT64_C(0x9e3779b97f4a7c15)) >> (64 - bits);
1269 static bool key_matches(
detail::o1::Store& store,
const KeyRep& rep,
const Key& key) {
1280 static const typename ReadCache::Entry* find_cached(Storage& storage,
const Key& key,
1281 std::uint64_t hash) {
1284 if (storage.read_cache.epoch != storage.store().rollback_epoch()) {
1285 storage.read_cache.clear();
1290 for (
const auto& entry : storage.read_cache.entries) {
1291 if (entry.valid && entry.hash == hash &&
1292 key_matches(storage.store(), entry.key, key))
1298 static Value read_cached_value(Storage& storage,
const typename ReadCache::Entry& entry) {
1299 if constexpr (kHeapValue) {
1300 const auto cache_index = &entry - storage.read_cache.entries.data();
1301 const std::uint64_t slot =
1302 (std::uint64_t{storage.read_cache_slot_high[cache_index]} << 32) | entry.slot;
1303 return storage.heap_values[slot];
1309 static void record_cache(Storage& storage, std::uint64_t hash, std::uint64_t slot,
1310 const KeyRep& key,
const ValueRep& value) {
1311 storage.read_cache.epoch = storage.store().rollback_epoch();
1312 storage.read_cache_slot_high[storage.read_cache.next] =
1313 static_cast<std::uint32_t
>(slot >> 32);
1314 storage.read_cache.record(hash,
static_cast<std::uint32_t
>(slot), key, value);
1317 static Key read_key(Storage& storage, std::uint64_t slot) {
1321 static Value read_value(Storage& storage, std::uint64_t slot) {
1322 if constexpr (kHeapValue) {
1323 return storage.heap_values[slot];
1329 static void set_heap_value(Storage& storage, std::uint64_t slot, Value&& value) {
1330 if (slot >= storage.heap_values.size()) storage.heap_values.resize(slot + 1);
1331 storage.heap_values[slot] = std::move(value);
1336 template <
bool WithValue = false>
1337 static Located locate(Storage& storage,
const detail::MapHeader& header,
const Key& key,
1338 std::uint64_t hash) {
1340 found.bucket = bucket_of(hash, header.bucket_bits);
1341 std::uint64_t at = load_bucket(storage, found.bucket);
1342 std::uint64_t before = 0;
1344 while (at != 0 && at <= header.high_water) {
1346 if (links.bucket != found.bucket)
break;
1351 if (links.hash == hash) {
1352 if constexpr (WithValue && !kHeapValue) {
1356 unsigned char raw[
sizeof(KeyRep) +
sizeof(ValueRep)];
1357 read_exact(storage.store(), storage.entries.id,
1358 slot_offset(at - 1) + kKeyOffset, raw,
sizeof raw);
1359 std::memcpy(&found.key, raw,
sizeof(KeyRep));
1360 std::memcpy(&found.value, raw +
sizeof(KeyRep),
sizeof(ValueRep));
1362 found.key = load_key_rep(storage, at - 1);
1364 if (key_matches(storage.store(), found.key, key)) {
1366 found.previous = before;
1376 static Value put_locked(Storage& storage,
const Key& key, Value value) {
1378 const std::uint64_t hash = hash_key(key);
1379 Located found = locate(storage, before, key, hash);
1380 if (found.slot != 0) {
1381 const std::uint64_t slot = found.slot - 1;
1382 Value previous = read_value(storage, slot);
1383 if constexpr (kHeapValue) {
1384 set_heap_value(storage, slot, std::move(value));
1386 const ValueRep old = load_value_rep(storage, slot);
1387 save_value_rep(storage, slot,
1394 if (before.max_pairs != 0 && before.count >= before.max_pairs) {
1397 if (before.free_head == 0 && before.high_water == before.capacity) {
1398 grow(storage, before);
1399 found = locate(storage, before, key, hash);
1407 std::uint64_t slot = 0;
1408 if (before.free_head != 0) {
1409 slot = before.free_head - 1;
1410 after.free_head = load_links(storage, slot).next;
1412 slot = before.high_water;
1413 after.high_water = before.high_water + 1;
1415 if constexpr (kHeapValue) set_heap_value(storage, slot, std::move(value));
1419 links.prev = before.tail;
1421 links.chain = found.head;
1422 links.bucket = found.bucket;
1423 after.count = before.count + 1;
1424 after.tail = slot + 1;
1425 if (before.tail == 0) after.head = slot + 1;
1426 save_slot(storage, slot, links, key_rep, value_rep);
1427 if (before.tail != 0) {
1429 last.next = slot + 1;
1430 save_links(storage, before.tail - 1, last);
1432 save_bucket(storage, found.bucket, slot + 1);
1433 save_header(storage, after);
1437 static void release_entries(Storage& storage,
const detail::MapHeader& header) {
1438 for (std::uint64_t at = header.head; at != 0;) {
1440 if constexpr (!kHeapValue)
1442 at = load_links(storage, at - 1).next;
1447 for (std::uint64_t at = header.head; at != 0;) {
1449 if constexpr (!kHeapValue)
1451 at = load_links(storage, at - 1).next;
1455 static void image_entry_refs(
detail::o1::Store& store, StoreId image,
bool retain) {
1457 read_exact(store, image, 0, &header,
sizeof header);
1458 for (std::uint64_t at = header.head; at != 0;) {
1459 const auto offset = slot_offset(at - 1);
1463 read_exact(store, image, offset, &links,
sizeof links);
1464 read_exact(store, image, offset + kKeyOffset, &key,
sizeof key);
1465 read_exact(store, image, offset + kValueOffset, &value,
sizeof value);
1477 static void release_snapshot(
detail::o1::Store& store, StoreId image, std::uint64_t) {
1478 image_entry_refs(store, image,
false);
1481 template <
typename Elem>
1482 static void release_sequence(
detail::o1::Store& store, StoreId image, std::uint64_t bytes) {
1484 for (std::uint64_t offset = 0; offset < bytes; offset +=
sizeof(Rep)) {
1486 read_exact(store, image, offset, &rep,
sizeof rep);
1495 resize_exact(storage, storage.index.id, 0);
1496 resize_exact(storage, storage.index.id,
1497 (std::uint64_t{1} << header.bucket_bits) *
sizeof(std::uint64_t));
1498 for (std::uint64_t at = header.head; at != 0;) {
1499 auto links = load_links(storage, at - 1);
1500 links.bucket = bucket_of(links.hash, header.bucket_bits);
1501 links.chain = load_bucket(storage, links.bucket);
1502 save_links(storage, at - 1, links);
1503 save_bucket(storage, links.bucket, at);
1508 static std::shared_ptr<detail::MapImage> make_image(Storage& storage, std::uint64_t bytes) {
1509 const StoreId
id = storage.store().create(bytes == 0 ? 1 : bytes, 8);
1510 return std::make_shared<detail::MapImage>(storage.holder,
id, bytes);
1514 template <
typename Elem,
typename Fetch>
1515 static void fill_image(Storage& storage,
const detail::MapHeader& header, StoreId image,
1518 constexpr std::size_t kPerChunk = std::max<std::size_t>(1, 4096 /
sizeof(Rep));
1519 Rep chunk[kPerChunk] = {};
1520 std::size_t filled = 0;
1521 std::uint64_t written = 0;
1522 for (std::uint64_t at = header.head; at != 0;) {
1523 const Rep rep = fetch(at - 1);
1525 chunk[filled++] = rep;
1526 at = load_links(storage, at - 1).next;
1527 if (filled == kPerChunk) {
1528 write_exact(storage.store(), image, written *
sizeof(Rep), chunk,
1529 filled *
sizeof(Rep));
1535 write_exact(storage.store(), image, written *
sizeof(Rep), chunk, filled *
sizeof(Rep));
1539 std::shared_ptr<Storage> storage_;