pstore2
hamt_map.hpp
Go to the documentation of this file.
1 //===- include/pstore/core/hamt_map.hpp -------------------*- mode: C++ -*-===//
2 //* _ _ *
3 //* | |__ __ _ _ __ ___ | |_ _ __ ___ __ _ _ __ *
4 //* | '_ \ / _` | '_ ` _ \| __| | '_ ` _ \ / _` | '_ \ *
5 //* | | | | (_| | | | | | | |_ | | | | | | (_| | |_) | *
6 //* |_| |_|\__,_|_| |_| |_|\__| |_| |_| |_|\__,_| .__/ *
7 //* |_| *
8 //===----------------------------------------------------------------------===//
9 //
10 // Part of the pstore project, under the Apache License v2.0 with LLVM Exceptions.
11 // See https://github.com/SNSystems/pstore/blob/master/LICENSE.txt for license
12 // information.
13 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
14 //
15 //===----------------------------------------------------------------------===//
18 
19 #ifndef PSTORE_CORE_HAMT_MAP_HPP
20 #define PSTORE_CORE_HAMT_MAP_HPP
21 
24 
25 namespace pstore {
26 
27  class transaction_base;
28 
29  namespace index {
30 
31  inline index_base::~index_base () = default;
32 
33 #ifdef _WIN32
34 # pragma warning(push)
35 # pragma warning(disable : 4521)
36 #endif
37  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
48  class hamt_map final : public index_base {
49  using hash_type = details::hash_type;
54 
58  template <typename K, typename V>
59  struct pair_types_compatible
60  : std::integral_constant<bool,
61  serialize::is_compatible<KeyType, K>::value &&
62  serialize::is_compatible<ValueType, V>::value> {};
63 
64  public:
65  using key_equal = KeyEqual;
66  using key_type = KeyType;
67  using mapped_type = ValueType;
68  using value_type = std::pair<KeyType const, ValueType>;
69 
71  template <bool IsConstIterator = true>
72  class iterator_base {
73  // Make iterator_base<true> a friend class of iterator_base<false> so the copy
74  // constructor can access the private member variables.
75  friend class iterator_base<true>;
76  friend class hamt_map;
77  using parent_stack =
79  using index_pointer =
81 
82  public:
83  using iterator_category = std::forward_iterator_tag;
84  using value_type = hamt_map<KeyType, ValueType, Hash, KeyEqual>::value_type;
85  using difference_type = std::ptrdiff_t;
86  using pointer = value_type *;
87  using reference = value_type &;
88 
91  using value_reference_type =
92  typename std::conditional<IsConstIterator, value_type const &,
93  value_type &>::type;
94 
97  using value_pointer_type =
98  typename std::conditional<IsConstIterator, value_type const *,
99  value_type *>::type;
100 
101  using database_reference =
102  typename std::conditional<IsConstIterator, database const &, database &>::type;
103 
104  iterator_base (database_reference db, parent_stack && parents, hamt_map const * idx)
105  : db_{db}
106  , visited_parents_ (std::move (parents))
107  , index_ (idx) {}
108 
109  iterator_base (iterator_base const & other) noexcept
110  : db_{other.db_}
111  , visited_parents_ (other.visited_parents_)
112  , index_ (other.index_) {
113  pos_.reset ();
114  }
115  iterator_base (iterator_base && other) noexcept
116  : db_{other.db_}
117  , visited_parents_{std::move (other.visited_parents_)}
118  , index_{other.index_}
119  , pos_{std::move (other.pos_)} {}
120 
123  template <bool Enable = IsConstIterator,
124  typename = typename std::enable_if<Enable>::type>
125  // NOLINTNEXTLINE(hicpp-explicit-conversions)
126  iterator_base (iterator_base<false> const & other) noexcept
127  : db_{other.db_}
128  , visited_parents_ (other.visited_parents_)
129  , index_ (other.index_) {
130  pos_.reset ();
131  }
132 
133  ~iterator_base () noexcept = default;
134 
135  iterator_base & operator= (iterator_base const & rhs) noexcept {
136  if (&rhs != this) {
137  assert (&db_ == &rhs.db_);
138  visited_parents_ = rhs.visited_parents_;
139  index_ = rhs.index_;
140  pos_.reset ();
141  }
142  return *this;
143  }
144 
145  iterator_base & operator= (iterator_base && rhs) noexcept {
146  if (&rhs != this) {
147  assert (&db_ == &rhs.db_);
148  visited_parents_ = std::move (rhs.visited_parents_);
149  index_ = rhs.index_;
150  pos_ = std::move (rhs.pos_);
151  }
152  return *this;
153  }
154 
155  bool operator== (iterator_base const & other) const {
156  return index_ == other.index_ && visited_parents_ == other.visited_parents_;
157  }
158 
159  bool operator!= (iterator_base const & other) const { return !operator== (other); }
160 
163  value_reference_type operator* () const {
164  if (pos_ == nullptr) {
165  pos_ = std::make_unique<value_type> (
166  index_->load_leaf_node (db_, this->get_address ()));
167  }
168  return *pos_;
169  }
170 
171  value_pointer_type operator-> () const { return &operator* (); }
172 
174  iterator_base & operator++ ();
175 
177  iterator_base operator++ (int) {
178  iterator_base const old (*this);
179  ++(*this);
180  return old;
181  }
182 
185  address get_address () const {
186  PSTORE_ASSERT (visited_parents_.size () >= 1);
187  details::parent_type const & parent = visited_parents_.top ();
188  PSTORE_ASSERT (parent.node.is_leaf () && parent.position == details::not_found);
189  return parent.node.to_address ();
190  }
191 
192  private:
193  void increment_internal_node ();
194 
198  void move_to_left_most_child (index_pointer node);
199 
200  unsigned get_shift_bits () const {
201  PSTORE_ASSERT (visited_parents_.size () >= 1);
202  return static_cast<unsigned> ((visited_parents_.size () - 1) *
203  details::hash_index_bits);
204  }
205 
206  database_reference db_;
207  parent_stack visited_parents_;
208  hamt_map const * index_;
209  mutable std::unique_ptr<value_type> pos_;
210  };
211 
214 
221  explicit hamt_map (
222  database const & db,
224  Hash const & hash = Hash (), KeyEqual const & equal = KeyEqual ());
225  hamt_map (hamt_map const &) = delete;
226  hamt_map (hamt_map &&) noexcept = delete;
227 
228  ~hamt_map () override { this->clear (); }
229 
230  hamt_map & operator= (hamt_map const &) = delete;
231  hamt_map & operator= (hamt_map &&) noexcept = delete;
232 
235 
236  range<database, hamt_map, iterator> make_range (database & db) { return {db, *this}; }
238  make_range (database const & db) const {
239  return {db, *this};
240  }
241 
243  iterator begin (database & db) { return make_begin_iterator (db, *this); }
244  const_iterator begin (database const & db) const {
245  return make_begin_iterator (db, *this);
246  }
247  const_iterator cbegin (database const & db) const {
248  return make_begin_iterator (db, *this);
249  }
250 
252  iterator end (database & db) { return make_end_iterator (db, *this); }
253  const_iterator end (database const & db) const { return make_end_iterator (db, *this); }
254  const_iterator cend (database const & db) const {
255  return make_end_iterator (db, *this);
256  }
257 
259 
262 
264  bool empty () const noexcept {
265  PSTORE_ASSERT (root_.is_empty () == (size_ == 0));
266  return size_ == 0;
267  }
268 
270  std::size_t size () const noexcept { return size_; }
272 
275 
287  template <typename OtherKeyType, typename OtherValueType,
288  typename = typename std::enable_if<
289  pair_types_compatible<OtherKeyType, OtherValueType>::value>::type>
291  std::pair<OtherKeyType, OtherValueType> const & value)
292  -> std::pair<iterator, bool>;
293 
307  template <typename OtherKeyType, typename OtherValueType,
308  typename = typename std::enable_if<
309  pair_types_compatible<OtherKeyType, OtherValueType>::value>::type>
310  auto insert_or_assign (transaction_base & transaction,
311  std::pair<OtherKeyType, OtherValueType> const & value)
312  -> std::pair<iterator, bool>;
313 
328  template <typename OtherKeyType, typename OtherValueType,
329  typename = typename std::enable_if<
330  pair_types_compatible<OtherKeyType, OtherValueType>::value>::type>
331  auto insert_or_assign (transaction_base & transaction, OtherKeyType const & key,
332  OtherValueType const & value) -> std::pair<iterator, bool>;
334 
337 
346  template <typename OtherKeyType,
347  typename = typename std::enable_if<
349  const_iterator find (database const & db, OtherKeyType const & key) const;
350 
358  template <typename OtherKeyType,
359  typename = typename std::enable_if<
361  bool contains (database const & db, OtherKeyType const & key) const {
362  return this->find (db, key) != this->end (db);
363  }
365 
371  typed_address<header_block> flush (transaction_base & transaction, unsigned generation);
372 
376 
378  value_type load_leaf_node (database const & db, address addr) const;
379 
381  index_pointer root () const noexcept { return root_; }
383 
384  private:
385  static constexpr std::array<std::uint8_t, 8> index_signature{
386  {'I', 'n', 'd', 'x', 'H', 'e', 'd', 'r'}};
387 
389  template <typename OtherValueType>
390  address store_leaf_node (transaction_base & transaction, OtherValueType const & v,
392 
394  void clear (index_pointer node, unsigned shifts);
395 
397  void clear () {
398  if (root_.is_heap ()) {
399  this->clear (root_, 0 /*shifts*/);
400  root_.clear ();
401  }
402  }
403 
405  key_type get_key (database const & db, address addr) const;
406 
409  template <typename OtherValueType>
410  auto insert_into_leaf (transaction_base & transaction,
411  index_pointer const & existing_leaf,
412  OtherValueType const & new_leaf, hash_type existing_hash,
413  hash_type hash, unsigned shifts,
415 
433  template <typename OtherValueType>
434  auto insert_into_internal (transaction_base & transaction, index_pointer node,
435  OtherValueType const & value, hash_type hash,
436  unsigned shifts, gsl::not_null<parent_stack *> parents,
437  bool is_upsert) -> std::pair<index_pointer, bool>;
438 
439  template <typename OtherValueType>
440  auto insert_into_linear (transaction_base & transaction, index_pointer node,
441  OtherValueType const & value,
442  gsl::not_null<parent_stack *> parents, bool is_upsert)
443  -> std::pair<index_pointer, bool>;
444 
447  template <typename OtherValueType>
448  auto insert_node (transaction_base & transaction, index_pointer node,
449  OtherValueType const & value, hash_type hash, unsigned shifts,
450  gsl::not_null<parent_stack *> parents, bool is_upsert)
451  -> std::pair<index_pointer, bool>;
452 
453  template <typename Database, typename HamtMap,
454  typename Iterator =
456  static Iterator make_begin_iterator (Database && db, HamtMap && m);
457  template <typename Database, typename HamtMap,
458  typename Iterator =
460  static Iterator make_end_iterator (Database && db, HamtMap && m);
461 
464  template <typename OtherValueType>
465  std::pair<iterator, bool> insert_or_upsert (transaction_base & transaction,
466  OtherValueType const & value,
467  bool is_upsert);
468 
475  void delete_node (index_pointer node, unsigned shifts);
476 
483  typed_address<header_block> write_header_block (transaction_base & transaction);
484 
485  static constexpr auto internal_nodes_per_chunk =
486  std::size_t{256} * 1024 / sizeof (internal_node);
487 
497  // TODO: we allocate nodes at their maximum size even though they may only contain two
498  // members. This is rather wasteful and will prevent us from moving to larger hash
499  // sizes due to the bloated memory consumption.
501  chunked_sequence<internal_node, internal_nodes_per_chunk,
502  internal_node::size_bytes (details::hash_size)>;
503  std::unique_ptr<internal_nodes_container> internals_container_;
504 
505  unsigned revision_;
506  index_pointer root_;
507  std::size_t size_ = 0;
509  Hash hash_;
511  key_equal equal_;
512  };
513 #ifdef _WIN32
514 # pragma warning(pop)
515 #endif
516 
517  //* _ _ _ _ *
518  //* (_) |_ ___ _ __ __ _| |_ ___ _ __ | |__ __ _ ___ ___ *
519  //* | | __/ _ \ '__/ _` | __/ _ \| '__| | '_ \ / _` / __|/ _ \ *
520  //* | | || __/ | | (_| | || (_) | | | |_) | (_| \__ \ __/ *
521  //* |_|\__\___|_| \__,_|\__\___/|_| |_.__/ \__,_|___/\___| *
522  //* *
523  // Prefix increment
524  // ~~~~~~~~~~~~~~~~
525  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
526  template <bool IsConstIterator>
527  auto
529  -> iterator_base & {
530  pos_.reset ();
531  PSTORE_ASSERT (!visited_parents_.empty ());
532  this->increment_internal_node ();
533  return *this;
534  }
535 
536 
537  // increment internal node
538  // ~~~~~~~~~~~~~~~~~~~~~~~
546  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
547  template <bool IsConstIterator>
548  void hamt_map<KeyType, ValueType, Hash,
550 
551  visited_parents_.pop ();
552 
553  if (!visited_parents_.empty ()) {
554  std::shared_ptr<void const> node;
555  internal_node const * internal = nullptr;
556  linear_node const * linear = nullptr;
557 
558  details::parent_type parent = visited_parents_.top ();
559  unsigned const shifts = this->get_shift_bits ();
560  bool const is_internal_node = details::depth_is_internal_node (shifts);
561  std::size_t size = 0;
562  if (is_internal_node) {
563  std::tie (node, internal) = internal_node::get_node (db_, parent.node);
564  PSTORE_ASSERT (internal != nullptr);
565  size = internal->size ();
566  } else {
567  std::tie (node, linear) = linear_node::get_node (db_, parent.node);
568  PSTORE_ASSERT (linear != nullptr);
569  size = linear->size ();
570  }
571 
572  PSTORE_ASSERT (!parent.node.is_leaf () && parent.position < size);
573  ++parent.position;
574 
575  if (parent.position >= size) {
576  this->increment_internal_node ();
577  } else {
578  // Update the parent.
579  visited_parents_.top ().position = parent.position;
580 
581  // Visit the child.
582  if (is_internal_node) {
583  index_pointer child = (*internal)[parent.position];
584  if (child.is_internal ()) {
585  this->move_to_left_most_child (child);
586  } else {
587  visited_parents_.push (details::parent_type{child});
588  }
589  } else {
590  visited_parents_.push (
591  details::parent_type{index_pointer{(*linear)[parent.position]}});
592  }
593  }
594  }
595  }
596 
597  // move to left most child
598  // ~~~~~~~~~~~~~~~~~~~~~~~
599  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
600  template <bool IsConstIterator>
602  IsConstIterator>::move_to_left_most_child (index_pointer node) {
603 
604  std::shared_ptr<void const> store_node;
605  while (!node.is_leaf ()) {
606  visited_parents_.push (details::parent_type{node, 0});
607  if (visited_parents_.size () <= details::max_internal_depth) {
608  internal_node const * internal = nullptr;
609  std::tie (store_node, internal) = internal_node::get_node (db_, node);
610  PSTORE_ASSERT (!store_node || store_node.get () == internal);
611  node = (*internal)[0];
612  } else {
613  linear_node const * linear = nullptr;
614  std::tie (store_node, linear) = linear_node::get_node (db_, node);
615  node = (*linear)[0];
616  }
617  }
618 
619  // Push the leaf on the stack.
620  visited_parents_.push (details::parent_type{node});
621  }
622 
623 
624  //* _ _ *
625  //* | |_ __ _ _ __| |_ _ __ __ _ _ __ *
626  //* | ' \/ _` | ' \ _| | ' \/ _` | '_ \ *
627  //* |_||_\__,_|_|_|_\__| |_|_|_\__,_| .__/ *
628  //* |_| *
629  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
630  constexpr std::array<std::uint8_t, 8>
632 
633  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
635  database const & db, typed_address<header_block> const pos, Hash const & hash,
636  KeyEqual const & equal)
637  : internals_container_{std::make_unique<internal_nodes_container> ()}
638  , revision_{db.get_current_revision ()}
639  , hash_{hash}
640  , equal_{equal} {
641 
642  if (pos != typed_address<header_block>::null ()) {
643  // 'pos' points to the index header block which gives us the tree root and size.
644  std::shared_ptr<header_block const> const hb = db.getro (pos);
645  // Check that this block appears to be sensible.
646 #if PSTORE_SIGNATURE_CHECKS_ENABLED
647  if (hb->signature != index_signature) {
648  raise (pstore::error_code::index_corrupt);
649  }
650 #endif
651 
652  {
653  auto const root = index_pointer{hb->root};
654  if (root.is_heap () || (hb->size == 0U && !root.is_empty ()) ||
655  (hb->size > 0U && root.is_empty ()) ||
656  (hb->size == 1U && !root.is_leaf ()) ||
657  (hb->size > 1U && !root.is_internal ())) {
658 
659  raise (pstore::error_code::index_corrupt);
660  }
661  }
662  size_ = hb->size;
663  root_ = hb->root;
664  }
665  }
666 
667  // clear
668  // ~~~~~
669  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
671  unsigned shifts) {
672  PSTORE_ASSERT (node.is_heap () && !node.is_leaf ());
673  if (details::depth_is_internal_node (shifts)) {
674  auto * const internal = node.untag<internal_node *> ();
675  // Recursively release the children of this internal node.
676  for (auto p : *internal) {
677  if (p.is_heap ()) {
678  this->clear (p, shifts + details::hash_index_bits);
679  }
680  }
681  }
682 
683  this->delete_node (node, shifts);
684  }
685 
686  // load leaf node
687  // ~~~~~~~~~~~~~~
688  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
690  address const addr) const
691  -> value_type {
692 
693  return serialize::read<std::pair<KeyType, ValueType>> (
695  }
696 
697  // get key
698  // ~~~~~~~
699  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
701  address const addr) const
702  -> key_type {
703 
704  return serialize::read<KeyType> (serialize::archive::database_reader{db, addr});
705  }
706 
707  // store leaf node
708  // ~~~~~~~~~~~~~~~
709  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
710  template <typename OtherValueType>
712  transaction_base & transaction, OtherValueType const & v,
713  gsl::not_null<parent_stack *> const parents) {
714 
715  // Make sure the alignment of leaf node is 4 to ensure that the two LSB are guaranteed
716  // 0. If 'v' has greater alignment, serialize::write() will add additional padding.
717  constexpr auto aligned_to = std::size_t{4};
718  static_assert ((details::internal_node_bit | details::heap_node_bit) == aligned_to - 1,
719  "expected required alignment to be 4");
720  transaction.allocate (0, aligned_to);
721 
722  // Now write the node and return where it went.
723  address const result =
724  serialize::write (serialize::archive::make_writer (transaction), v);
725  PSTORE_ASSERT ((result.absolute () & (aligned_to - 1U)) == 0U);
726  parents->push (details::parent_type{index_pointer{result}});
727 
728  return result;
729  }
730 
731  // insert into leaf
732  // ~~~~~~~~~~~~~~~~
733  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
734  template <typename OtherValueType>
736  transaction_base & transaction, index_pointer const & existing_leaf,
737  OtherValueType const & new_leaf, hash_type existing_hash, hash_type hash,
738  unsigned shifts, gsl::not_null<parent_stack *> parents) -> index_pointer {
739 
740  if (details::depth_is_internal_node (shifts)) {
741  auto const new_hash = hash & details::hash_index_mask;
742  auto const old_hash = existing_hash & details::hash_index_mask;
743  if (new_hash != old_hash) {
744  address const leaf_addr =
745  this->store_leaf_node (transaction, new_leaf, parents);
746  auto const internal_ptr = index_pointer{
747  internal_node::allocate (internals_container_.get (), existing_leaf,
748  index_pointer{leaf_addr}, old_hash, new_hash)};
749  parents->push (details::parent_type{
750  internal_ptr, internal_node::get_new_index (new_hash, old_hash)});
751  return internal_ptr;
752  }
753 
754  // We've found a (partial) hash collision. Replace this leaf node with an internal
755  // node. The existing key must be replaced with a sub-hash table and the next 6 bit
756  // hash of the existing key computed. If there's still a collision, we repeat the
757  // process. The new and existing keys are then inserted in the new sub-hash table.
758  // As long as the partial hashes match, we have to create single element internal
759  // nodes to represent them. This should happen very rarely with a reasonably good
760  // hash function.
761 
762  shifts += details::hash_index_bits;
763  hash >>= details::hash_index_bits;
764  existing_hash >>= details::hash_index_bits;
765 
766  index_pointer const leaf_ptr = this->insert_into_leaf (
767  transaction, existing_leaf, new_leaf, existing_hash, hash, shifts, parents);
768  auto const internal_ptr = index_pointer{
769  internal_node::allocate (internals_container_.get (), leaf_ptr, old_hash)};
770  parents->push (details::parent_type{internal_ptr, 0U});
771  return internal_ptr;
772  }
773 
774  // We ran out of hash bits: create a new linear node.
775  auto const linear_ptr = index_pointer{
776  linear_node::allocate (existing_leaf.to_address (),
777  this->store_leaf_node (transaction, new_leaf, parents))
778  .release ()};
779  parents->push (details::parent_type{linear_ptr, 1U});
780  return linear_ptr;
781  }
782 
783  // delete node
784  // ~~~~~~~~~~~
785  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
787  unsigned const shifts) {
788  if (node.is_heap ()) {
789  PSTORE_ASSERT (!node.is_leaf ());
790  if (details::depth_is_internal_node (shifts)) {
791  // Internal nodes are owned by internals_container_. Don't delete them here. If
792  // this ever changes, then add something like: delete
793  // node.untag<internal_node *> ();
794  } else {
795  delete node.untag<linear_node *> ();
796  }
797  }
798  }
799 
800  // insert into internal
801  // ~~~~~~~~~~~~~~~~~~~~
802  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
803  template <typename OtherValueType>
805  transaction_base & transaction, index_pointer node, OtherValueType const & value,
806  hash_type hash, unsigned shifts, gsl::not_null<parent_stack *> parents, bool is_upsert)
807  -> std::pair<index_pointer, bool> {
808 
809  std::shared_ptr<internal_node const> iptr;
810  internal_node const * internal = nullptr;
811  std::tie (iptr, internal) = internal_node::get_node (transaction.db (), node);
812  PSTORE_ASSERT (internal != nullptr);
813 
814  // Now work out which of the children we're going to be visiting next.
815  index_pointer child_slot;
816  auto index = std::size_t{0};
817  std::tie (child_slot, index) = internal->lookup (hash & details::hash_index_mask);
818 
819  // If this slot isn't used, then ensure the node is on the heap, write the new leaf node
820  // and point to it.
821  if (index == details::not_found) {
822  internal_node * const inode =
823  internal_node::make_writable (internals_container_.get (), node, *internal);
824  inode->insert_child (
825  hash, index_pointer{this->store_leaf_node (transaction, value, parents)},
826  parents);
827  return {index_pointer{inode}, false};
828  }
829 
830  shifts += details::hash_index_bits;
831  hash >>= details::hash_index_bits;
832 
833  // update child_slot
834  bool key_exists;
835  index_pointer new_child;
836  std::tie (new_child, key_exists) = this->insert_node (transaction, child_slot, value,
837  hash, shifts, parents, is_upsert);
838 
839  // If the insertion resulted in our child node being reallocated, then this node needs
840  // to be heap-allocated and the child reference updated. The original child pointer may
841  // also need to be freed.
842 
843  std::unique_ptr<internal_node> new_node;
844  if (new_child != child_slot) {
845  internal_node * const inode =
846  internal_node::make_writable (internals_container_.get (), node, *internal);
847 
848  // Release a previous heap-allocated instance.
849  index_pointer & child = (*inode)[index];
850  this->delete_node (child, shifts);
851  child = new_child;
852  node = inode;
853  }
854 
855  parents->push (details::parent_type{node, index});
856  new_node.release ();
857  return {node, key_exists};
858  }
859 
860  // insert into linear
861  // ~~~~~~~~~~~~~~~~~~
862  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
863  template <typename OtherValueType>
865  transaction_base & transaction, index_pointer const node, OtherValueType const & value,
866  gsl::not_null<parent_stack *> parents, bool const is_upsert)
867  -> std::pair<index_pointer, bool> {
868 
869  index_pointer result;
870  bool key_exists = false;
871 
872  std::shared_ptr<linear_node const> lptr;
873  linear_node const * orig_node = nullptr;
874  std::tie (lptr, orig_node) = linear_node::get_node (transaction.db (), node);
875  PSTORE_ASSERT (orig_node != nullptr);
876 
877  index_pointer child_slot;
878  auto index = std::size_t{0};
879 
880  std::tie (child_slot, index) =
881  orig_node->lookup<KeyType> (transaction.db (), value.first, equal_);
882  if (index == details::not_found) {
883  // The key wasn't present in the node so we simply append it.
884  // TODO: keep these entries sorted?
885 
886  // Load into memory with space for 1 new child node.
887  std::unique_ptr<linear_node> new_node = linear_node::allocate_from (*orig_node, 1U);
888  index = orig_node->size ();
889  (*new_node)[index] = this->store_leaf_node (transaction, value, parents);
890  result = new_node.release ();
891  } else {
892  key_exists = true;
893  if (is_upsert) {
894  std::unique_ptr<linear_node> new_node;
895  linear_node * lnode = nullptr;
896 
897  if (node.is_heap ()) {
898  // If the node is already on the heap then there's no need to reallocate it.
899  lnode = node.untag<linear_node *> ();
900  result = node;
901  } else {
902  // Load into memory but no extra space.
903  new_node = linear_node::allocate_from (*orig_node, 0U);
904  lnode = new_node.get ();
905  result = lnode;
906  }
907  (*lnode)[index] = this->store_leaf_node (transaction, value, parents);
908  new_node.release ();
909  } else {
910  parents->push (details::parent_type{index_pointer{(*orig_node)[index]}});
911 
912  // We didn't modify the node so our return value is the original node index
913  // pointer.
914  result = node;
915  }
916  }
917 
918  parents->push (details::parent_type{result, index});
919  return {result, key_exists};
920  }
921 
922  // insert node
923  // ~~~~~~~~~~~
924  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
925  template <typename OtherValueType>
927  transaction_base & transaction, index_pointer const node, OtherValueType const & value,
928  hash_type hash, unsigned shifts, gsl::not_null<parent_stack *> parents, bool is_upsert)
929  -> std::pair<index_pointer, bool> {
930 
931  index_pointer result;
932  bool key_exists = false;
933  if (node.is_leaf ()) { // This node is a leaf node.
934  key_type const existing_key =
935  get_key (transaction.db (), node.to_address ()); // Read key.
936  if (equal_ (value.first, existing_key)) {
937  if (is_upsert) {
938  result = this->store_leaf_node (transaction, value, parents);
939  } else {
940  parents->push (details::parent_type{node});
941  result = node;
942  }
943  key_exists = true;
944  } else {
945  auto const existing_hash =
946  static_cast<hash_type> ((hash_ (existing_key) >> shifts));
947  result = this->insert_into_leaf (transaction, node, value, existing_hash, hash,
948  shifts, parents);
949  }
950  } else {
951  // This node is an internal or a linear node.
952  if (details::depth_is_internal_node (shifts)) {
953  std::tie (result, key_exists) = this->insert_into_internal (
954  transaction, node, value, hash, shifts, parents, is_upsert);
955  } else {
956  std::tie (result, key_exists) =
957  this->insert_into_linear (transaction, node, value, parents, is_upsert);
958  }
959  }
960 
961  return std::make_pair (result, key_exists);
962  }
963 
964  // insert or upsert
965  // ~~~~~~~~~~~~~~~~
966  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
967  template <typename OtherValueType>
969  transaction_base & transaction, OtherValueType const & value, bool is_upsert)
970  -> std::pair<iterator, bool> {
971 
972  database & db = transaction.db ();
973  if (revision_ != db.get_current_revision ()) {
974  raise (error_code::index_not_latest_revision);
975  }
976 
977  parent_stack parents;
978  if (this->empty ()) {
979  root_ = this->store_leaf_node (transaction, value, &parents);
980  size_ = 1;
981  return std::make_pair (iterator (db, std::move (parents), this), true);
982  }
983 
984  parent_stack reverse_parents;
985  bool key_exists = false;
986  auto hash = static_cast<hash_type> (hash_ (value.first));
987  std::tie (root_, key_exists) = this->insert_node (
988  transaction, root_, value, hash, 0 /* shifts */, &reverse_parents, is_upsert);
989  while (!reverse_parents.empty ()) {
990  parents.push (reverse_parents.top ());
991  reverse_parents.pop ();
992  }
993  if (!key_exists) {
994  ++size_;
995  }
996  return std::make_pair (iterator (db, std::move (parents), this), !key_exists);
997  }
998 
999  // insert
1000  // ~~~~~~
1001  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
1002  template <typename OtherKeyType, typename OtherValueType, typename>
1004  transaction_base & transaction, std::pair<OtherKeyType, OtherValueType> const & value)
1005  -> std::pair<iterator, bool> {
1006 
1007  return this->insert_or_upsert (transaction, value, false /*is_upsert*/);
1008  }
1009 
1010  // insert or assign
1011  // ~~~~~~~~~~~~~~~~
1012  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
1013  template <typename OtherKeyType, typename OtherValueType, typename>
1015  transaction_base & transaction, std::pair<OtherKeyType, OtherValueType> const & value)
1016  -> std::pair<iterator, bool> {
1017 
1018  return this->insert_or_upsert (transaction, value, true /*is_upsert*/);
1019  }
1020 
1021  // insert or assign
1022  // ~~~~~~~~~~~~~~~~
1023  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
1024  template <typename OtherKeyType, typename OtherValueType, typename>
1026  transaction_base & transaction, OtherKeyType const & key, OtherValueType const & value)
1027  -> std::pair<iterator, bool> {
1028 
1029  return this->insert_or_assign (transaction, std::make_pair (key, value));
1030  }
1031 
1032  // flush
1033  // ~~~~~
1034  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
1037  unsigned const generation) {
1038  if (revision_ != transaction.db ().get_current_revision ()) {
1039  raise (error_code::index_not_latest_revision);
1040  }
1041 
1042  // If the root is a leaf, there's nothing to do. If not, we start to recursively flush
1043  // the tree.
1044  if (!root_.is_address ()) {
1045  PSTORE_ASSERT (root_.is_internal ());
1046  root_ = root_.untag<internal_node *> ()->flush (transaction, 0 /*shifts*/);
1047  PSTORE_ASSERT (root_.is_address ());
1048  // Don't delete the internal node here. They are owned by internals_container_. If
1049  // this ever changes, then use something like 'delete internal' here.
1050  }
1051 
1052  auto const header_addr = this->size () > 0U ? this->write_header_block (transaction)
1054 
1055  // Release all of the in-heap internal nodes that we have now flushed.
1056  internals_container_->clear ();
1057 
1058  // Update the revision number into which the index will be flushed.
1059  revision_ = generation;
1060 
1061  return header_addr;
1062  }
1063 
1064  // write header block
1065  // ~~~~~~~~~~~~~~~~~~
1066  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
1069  transaction_base & transaction) {
1070  PSTORE_ASSERT (this->root ().is_address ());
1071  auto const pos = transaction.alloc_rw<header_block> ();
1072  pos.first->signature = index_signature;
1073  pos.first->size = this->size ();
1074  pos.first->root = this->root ().to_address ();
1075  return pos.second;
1076  }
1077 
1078  // find
1079  // ~~~~
1080  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
1081  template <typename OtherKeyType, typename>
1083  OtherKeyType const & key) const
1084  -> const_iterator {
1085  if (empty ()) {
1086  return this->cend (db);
1087  }
1088 
1089  auto hash = static_cast<hash_type> (hash_ (key));
1090  unsigned bit_shifts = 0;
1091  index_pointer node = root_;
1092  parent_stack parents;
1093 
1094  std::shared_ptr<void const> store_node;
1095  while (!node.is_leaf ()) {
1096  index_pointer child_node;
1097  auto index = std::size_t{0};
1098 
1099  if (details::depth_is_internal_node (bit_shifts)) {
1100  // It's an internal node.
1101  internal_node const * internal = nullptr;
1102  std::tie (store_node, internal) = internal_node::get_node (db, node);
1103  std::tie (child_node, index) =
1104  internal->lookup (hash & details::hash_index_mask);
1105  } else {
1106  // It's a linear node.
1107  linear_node const * linear = nullptr;
1108  std::tie (store_node, linear) = linear_node::get_node (db, node);
1109  std::tie (child_node, index) = linear->lookup<KeyType> (db, key, equal_);
1110  }
1111 
1112  if (index == details::not_found) {
1113  return this->cend (db);
1114  }
1115  parents.push (details::parent_type{node, index});
1116 
1117  // Go to next sub-trie level
1118  node = child_node;
1119  bit_shifts += details::hash_index_bits;
1120  hash >>= details::hash_index_bits;
1121  }
1122  // It's a leaf node.
1123  PSTORE_ASSERT (node.is_leaf ());
1124  key_type const existing_key = get_key (db, node.to_address ());
1125  if (equal_ (existing_key, key)) {
1126  parents.push (details::parent_type{node});
1127  return const_iterator (db, std::move (parents), this);
1128  }
1129  return this->cend (db);
1130  }
1131 
1132  // make begin iterator
1133  // ~~~~~~~~~~~~~~~~~~~
1134  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
1135  template <typename Database, typename HamtMap, typename Iterator>
1137  HamtMap && m) {
1138  Iterator result{db, parent_stack (), &m};
1139  if (!m.root_.is_empty ()) {
1140  result.move_to_left_most_child (m.root_);
1141  }
1142  return result;
1143  }
1144 
1145  // make end iterator
1146  // ~~~~~~~~~~~~~~~~~
1147  template <typename KeyType, typename ValueType, typename Hash, typename KeyEqual>
1148  template <typename Database, typename HamtMap, typename Iterator>
1150  HamtMap && m) {
1151  return {db, parent_stack (), &m};
1152  }
1153 
1154  } // namespace index
1155 } // namespace pstore
1156 #endif // PSTORE_CORE_HAMT_MAP_HPP
const_iterator find(database const &db, OtherKeyType const &key) const
Finds an element with key equivalent to key.
void pop()
Remove the top element from the stack.
Definition: array_stack.hpp:105
This class provides a common base from which each of the real index types derives.
Definition: hamt_map_fwd.hpp:29
static auto get_node(database const &db, index_pointer node) -> std::pair< std::shared_ptr< internal_node const >, internal_node const *>
Return a pointer to an internal node.
Definition: hamt_map_types.cpp:285
hamt_map(database const &db, typed_address< header_block > pos=typed_address< header_block >::null(), Hash const &hash=Hash(), KeyEqual const &equal=KeyEqual())
An associative container that contains key-value pairs with unique keys.
Definition: hamt_map.hpp:634
An archive-reader which reads data from a database.
Definition: db_archive.hpp:102
bool contains(database const &db, OtherKeyType const &key) const
Checks if there is an element with key equivalent to key in the container.
Definition: hamt_map.hpp:361
Definition: address.hpp:81
typename std::conditional< IsConstIterator, value_type const &, value_type & >::type value_reference_type
For const_iterator: define value_reference_type to be a &#39;value_type const &&#39; For iterator: define val...
Definition: hamt_map.hpp:93
typed_address< header_block > flush(transaction_base &transaction, unsigned generation)
Flush any modified index nodes to the store.
Definition: hamt_map.hpp:1036
auto insert(transaction_base &transaction, std::pair< OtherKeyType, OtherValueType > const &value) -> std::pair< iterator, bool >
Inserts an element into the hamt_map if the hamt_map doesn&#39;t already contain an element with an equiv...
Definition: hamt_map.hpp:1003
bool empty() const noexcept
Checks whether the container is empty.
Definition: hamt_map.hpp:264
auto index(gsl::czstring str, std::size_t pos) -> gsl::czstring
Returns a pointer to the beginning of the pos&#39;th UTF-8 codepoint in the buffer at str or nullptr if e...
Definition: utf.cpp:49
A linear node.
Definition: hamt_map_types.hpp:298
static constexpr std::size_t size_bytes(std::size_t const num_children) noexcept
Returns the number of bytes occupied by an in-store internal node with the given number of child node...
Definition: hamt_map_types.hpp:646
std::pair< std::shared_ptr< void >, address > alloc_rw(std::size_t size, unsigned align)
Allocates sufficient space in the transaction for &#39;size&#39; bytes at an alignment given by &#39;align&#39; and r...
Definition: transaction.cpp:75
A class used to keep the pointer to parent node and the child slot.
Definition: hamt_map_types.hpp:264
virtual address allocate(std::uint64_t size, unsigned align)
Definition: transaction.cpp:52
Definition: transaction.hpp:191
Definition: address.hpp:231
A simple wrapper for std::array which provides the functionality of a stack, specifically a FILO (fir...
Definition: array_stack.hpp:39
iterator end(database &db)
Returns an iterator to the end of the container.
Definition: hamt_map.hpp:252
Definition: gsl.hpp:589
A Hash array mapped trie index type for the pstore.
Definition: hamt_map.hpp:48
auto make_writer(transaction_base &transaction) noexcept -> database_writer
A convenience function which simplifies the construction of a database_writer instance if the caller ...
Definition: db_archive.hpp:92
bool is_heap() const noexcept
Returns true if the index_pointer is pointing to a heap node, false otherwise.
Definition: hamt_map_types.hpp:215
bool is_leaf() const noexcept
Returns true if the index_pointer contains the address of a value in the store, false otherwise...
Definition: hamt_map_types.hpp:211
typename std::conditional< IsConstIterator, value_type const *, value_type * >::type value_pointer_type
For const_iterator: define value_pointer_type to be a &#39;value_type const *&#39; For regular iterator: defi...
Definition: hamt_map.hpp:99
The address of an instance of this type is passed to the hamt_map ctor to load an existing index...
Definition: hamt_map_types.hpp:96
static auto get_node(database const &db, index_pointer const node) -> std::pair< std::shared_ptr< linear_node const >, linear_node const *>
Returns a pointer to a linear node which may be in-heap or in-store.
Definition: hamt_map_types.cpp:120
unsigned get_current_revision() const
Returns the generation number to which the database is synced.
Definition: database.hpp:262
iterator_base(iterator_base< false > const &other) noexcept
Copy constructor.
Definition: hamt_map.hpp:126
Provides serialization capabilities for common types.
address get_address() const
Returns the pstore address of the serialized value_type instance to which the iterator is currently p...
Definition: hamt_map.hpp:185
The begin() and end() functions for both hamt_map and hamt_set take an extra parameter – the owning ...
Definition: hamt_map_fwd.hpp:40
value_type load_leaf_node(database const &db, address addr) const
Read a leaf node from a store.
Definition: hamt_map.hpp:689
An internal trie node.
Definition: hamt_map_types.hpp:508
Definition: nonpod2.cpp:40
Definition: database.hpp:40
auto insert_or_assign(transaction_base &transaction, std::pair< OtherKeyType, OtherValueType > const &value) -> std::pair< iterator, bool >
If a key equivalent to value first already exists in the container, assigns value second to the mappe...
Definition: hamt_map.hpp:1014
std::size_t size() const noexcept
Returns the number of elements.
Definition: hamt_map.hpp:270
void insert_child(hash_type const hash, index_pointer const leaf, gsl::not_null< parent_stack *> parents)
Insert a child into the internal node (this).
Definition: hamt_map_types.cpp:300
std::size_t size() const
Returns the number of elements.
Definition: hamt_map_types.hpp:399
Inner class that describes a const_iterator and &#39;regular&#39; iterator.
Definition: hamt_map.hpp:72
constexpr bool empty() const noexcept
Checks whether the stack is empty.
Definition: array_stack.hpp:63
void push(value_type const &value)
Inserts an element at the top of the container.
Definition: array_stack.hpp:93
Chunked-sequence is a sequence-container which uses a list of large blocks ("chunks") to ensure very ...
Definition: chunked_sequence.hpp:60
auto lookup(database const &db, OtherKeyType const &key, KeyEqual equal) const -> std::pair< index_pointer const, std::size_t >
Search the linear node and return the child slot if the key exists.
Definition: hamt_map_types.hpp:483
iterator begin(database &db)
Returns an iterator to the beginning of the container.
Definition: hamt_map.hpp:243
If the two types T1 and T2 have a compatible representation when serialized, provides the member cons...
Definition: types.hpp:321
reference top() noexcept
Acceses the top element.
Definition: array_stack.hpp:78
index_pointer root() const noexcept
Returns the index root pointer.
Definition: hamt_map.hpp:381
Types used by the HAMT index.
An index pointer is either a database address or a pointer to volatile RAM.
Definition: hamt_map_types.hpp:132
typename std::conditional< std::is_const< typename std::remove_reference< T >::type >::value, RC, R >::type type
If T is const, R const otherwise R.
Definition: inherit_const.hpp:112
The database transaction class.
Definition: transaction.hpp:43