1 #ifndef CLIFFC_HASHTABLE_H
2 #define CLIFFC_HASHTABLE_H
9 #define MODEL_ASSERT assert
11 #include <model-assert.h>
16 #include <cdsannotate.h>
17 #include <specannotation.h>
18 #include <model_memory.h>
24 This header file declares and defines a simplified version of Cliff Click's
25 NonblockingHashMap. It contains all the necessary structrues and main
26 functions. In simplified_cliffc_hashtable.cc file, it has the definition for
30 template<typename TypeK, typename TypeV>
31 class cliffc_hashtable;
34 Corresponding the the Object[] array in Cliff Click's Java implementation.
35 It keeps the first two slots for CHM (Hashtable control unit) and the hash
36 records (an array of hash used for fast negative key-equality check).
44 int real_size = sz * 2 + 2;
45 _data = new atomic<void*>[real_size];
46 // The control block should be initialized in resize()
47 // Init the hash record array
48 int *hashes = new int[_size];
50 for (i = 0; i < _size; i++) {
53 // Init the data to Null slot
54 for (i = 2; i < real_size; i++) {
55 _data[i].store(NULL, memory_order_relaxed);
57 _data[1].store(hashes, memory_order_release);
61 int *hashes = (int*) _data[1].load(memory_order_relaxed);
71 slot(bool prime, void *ptr) {
79 TypeK must have defined function "int hashCode()" which return the hash
80 code for the its object, and "int equals(TypeK anotherKey)" which is
81 used to judge equality.
82 TypeK and TypeV should define their own copy constructor.
89 template<typename TypeK, typename TypeV>
90 class cliffc_hashtable {
92 # The synchronization we have for the hashtable gives us the property of
93 # serializability, so we should have a sequential hashtable when we check the
94 # correctness. The key thing is to identify all the commit point.
99 CLASS = cliffc_hashtable;
106 map = new_spec_table_default(equals_key);
107 id_map = new_spec_table_default(equals_id);
111 bool equals_key(void *ptr1, void *ptr2) {
112 TypeK *key1 = (TypeK*) ptr1,
113 *key2 = (TypeK*) ptr2;
114 if (key1 == NULL || key2 == NULL)
116 return key1->equals(key2);
120 bool equals_val(void *ptr1, void *ptr2) {
121 TypeV *val1 = (TypeV*) ptr1,
122 *val2 = (TypeV*) ptr2;
123 if (val1 == NULL || val2 == NULL)
125 return val1->equals(val2);
129 bool equals_id(void *ptr1, void *ptr2) {
130 id_tag_t *id1 = (id_tag_t*) ptr1,
131 *id2 = (id_tag_t*) ptr2;
132 if (id1 == NULL || id2 == NULL)
134 return (*id1).tag == (*id2).tag;
138 # Update the tag for the current key slot if the corresponding tag
139 # is NULL, otherwise just return that tag. It will update the next
140 # available tag too if it requires a new tag for that key slot.
141 id_tag_t getKeyTag(TypeK *key) {
142 if (spec_table_contains(id_map, key)) {
143 id_tag_t *cur_tag = MODEL_MALLOC(sizeof(id_tag_t));
144 *cur_tag = current(tag);
145 spec_table_put(id_map, key, cur_tag);
149 id_tag_t *res = (id_tag_t*) spec_table_get(id_map, key);
166 PutIfAbsent(COND_PutIfAbsentSucc),
168 RemoveIfMatch(COND_RemoveIfMatchSucc),
170 ReplaceIfMatch(COND_ReplaceIfMatchSucc)
173 //Write_interface -> Read_interface
181 The control structure for the hashtable
185 friend class cliffc_hashtable;
187 atomic<kvs_data*> _newkvs;
189 // Size of active K,V pairs
192 // Count of used slots
195 // The next part of the table to copy
196 atomic_int _copy_idx;
198 // Work-done reporting
199 atomic_int _copy_done;
203 _newkvs.store(NULL, memory_order_relaxed);
204 _size.store(size, memory_order_relaxed);
205 _slots.store(0, memory_order_relaxed);
207 _copy_idx.store(0, memory_order_relaxed);
208 _copy_done.store(0, memory_order_release);
215 // Heuristic to decide if the table is too full
216 bool table_full(int reprobe_cnt, int len) {
218 reprobe_cnt >= REPROBE_LIMIT &&
219 _slots.load(memory_order_relaxed) >= reprobe_limit(len);
222 kvs_data* resize(cliffc_hashtable *topmap, kvs_data *kvs) {
223 //model_print("resizing...\n");
224 kvs_data *newkvs = _newkvs.load(memory_order_acquire);
228 // No copy in-progress, start one; Only double the table size
229 int oldlen = kvs->_size;
230 int sz = _size.load(memory_order_relaxed);
233 // Just follow Cliff Click's heuristic to decide the new size
234 if (sz >= (oldlen >> 2)) { // If we are 25% full
235 newsz = oldlen << 1; // Double size
236 if (sz >= (oldlen >> 1))
237 newsz = oldlen << 2; // Double double size
240 // We do not record the record timestamp
241 if (newsz <= oldlen) newsz = oldlen << 1;
242 // Do not shrink ever
243 if (newsz < oldlen) newsz = oldlen;
245 // Last check cause the 'new' below is expensive
246 newkvs = _newkvs.load(memory_order_acquire);
247 if (newkvs != NULL) return newkvs;
249 newkvs = new kvs_data(newsz);
250 void *chm = (void*) new CHM(sz);
251 newkvs->_data[0].store(chm, memory_order_release);
253 kvs_data *cur_newkvs;
254 // Another check after the slow allocation
255 if ((cur_newkvs = _newkvs.load(memory_order_acquire)) != NULL)
257 // CAS the _newkvs to the allocated table
258 kvs_data *desired = (kvs_data*) NULL;
259 kvs_data *expected = (kvs_data*) newkvs;
260 if (!_newkvs.compare_exchange_strong(desired, expected, memory_order_release,
261 memory_order_release)) {
262 // Should clean the allocated area
264 newkvs = _newkvs.load(memory_order_acquire);
269 void help_copy_impl(cliffc_hashtable *topmap, kvs_data *oldkvs,
271 MODEL_ASSERT (get_chm(oldkvs) == this);
272 kvs_data *newkvs = _newkvs.load(memory_order_acquire);
273 int oldlen = oldkvs->_size;
274 int min_copy_work = oldlen > 1024 ? 1024 : oldlen;
276 // Just follow Cliff Click's code here
277 int panic_start = -1;
279 while (_copy_done.load(memory_order_acquire) < oldlen) {
280 copyidx = _copy_idx.load(memory_order_acquire);
281 if (panic_start == -1) { // No painc
282 copyidx = _copy_idx.load(memory_order_acquire);
283 while (copyidx < (oldlen << 1) &&
284 !_copy_idx.compare_exchange_strong(copyidx, copyidx +
285 min_copy_work, memory_order_release, memory_order_release))
286 copyidx = _copy_idx.load(memory_order_relaxed);
287 if (!(copyidx < (oldlen << 1)))
288 panic_start = copyidx;
291 // Now copy the chunk of work we claimed
293 for (int i = 0; i < min_copy_work; i++)
294 if (copy_slot(topmap, (copyidx + i) & (oldlen - 1), oldkvs,
298 copy_check_and_promote(topmap, oldkvs, workdone);
300 copyidx += min_copy_work;
301 if (!copy_all && panic_start == -1)
302 return; // We are done with the work we claim
304 copy_check_and_promote(topmap, oldkvs, 0); // See if we can promote
307 kvs_data* copy_slot_and_check(cliffc_hashtable *topmap, kvs_data
308 *oldkvs, int idx, void *should_help) {
309 kvs_data *newkvs = _newkvs.load(memory_order_acquire);
310 // We're only here cause the caller saw a Prime
311 if (copy_slot(topmap, idx, oldkvs, newkvs))
312 copy_check_and_promote(topmap, oldkvs, 1); // Record the slot copied
313 return (should_help == NULL) ? newkvs : topmap->help_copy(newkvs);
316 void copy_check_and_promote(cliffc_hashtable *topmap, kvs_data*
317 oldkvs, int workdone) {
318 int oldlen = oldkvs->_size;
319 int copyDone = _copy_done.load(memory_order_relaxed);
322 copyDone = _copy_done.load(memory_order_relaxed);
323 if (_copy_done.compare_exchange_weak(copyDone, copyDone +
324 workdone, memory_order_relaxed, memory_order_relaxed))
329 // Promote the new table to the current table
330 if (copyDone + workdone == oldlen &&
331 topmap->_kvs.load(memory_order_acquire) == oldkvs) {
332 kvs_data *newkvs = _newkvs.load(memory_order_acquire);
333 topmap->_kvs.compare_exchange_strong(oldkvs, newkvs, memory_order_release,
334 memory_order_release);
338 bool copy_slot(cliffc_hashtable *topmap, int idx, kvs_data *oldkvs,
341 while ((key_slot = key(oldkvs, idx)) == NULL)
342 CAS_key(oldkvs, idx, NULL, TOMBSTONE);
344 // First CAS old to Prime
345 slot *oldval = val(oldkvs, idx);
346 while (!is_prime(oldval)) {
347 slot *box = (oldval == NULL || oldval == TOMBSTONE)
348 ? TOMBPRIME : new slot(true, oldval->_ptr);
349 if (CAS_val(oldkvs, idx, oldval, box)) {
350 if (box == TOMBPRIME)
351 return 1; // Copy done
352 // Otherwise we CAS'd the box
353 oldval = box; // Record updated oldval
356 oldval = val(oldkvs, idx); // Else re-try
359 if (oldval == TOMBPRIME) return false; // Copy already completed here
361 slot *old_unboxed = new slot(false, oldval->_ptr);
362 int copied_into_new = (putIfMatch(topmap, newkvs, key_slot, old_unboxed,
365 // Old value is exposed in the new table
366 while (!CAS_val(oldkvs, idx, oldval, TOMBPRIME))
367 oldval = val(oldkvs, idx);
369 return copied_into_new;
376 static const int Default_Init_Size = 4; // Intial table size
378 static slot* const MATCH_ANY;
379 static slot* const NO_MATCH_OLD;
381 static slot* const TOMBPRIME;
382 static slot* const TOMBSTONE;
384 static const int REPROBE_LIMIT = 10; // Forces a table-resize
386 atomic<kvs_data*> _kvs;
390 // Should initialize the CHM for the construction of the table
391 // For other CHM in kvs_data, they should be initialzed in resize()
392 // because the size is determined dynamically
393 kvs_data *kvs = new kvs_data(Default_Init_Size);
396 void *chm = (void*) new CHM(0);
397 kvs->_data[0].store(chm, memory_order_relaxed);
398 _kvs.store(kvs, memory_order_release);
402 cliffc_hashtable(int init_size) {
403 // Should initialize the CHM for the construction of the table
404 // For other CHM in kvs_data, they should be initialzed in resize()
405 // because the size is determined dynamically
412 kvs_data *kvs = new kvs_data(init_size);
413 void *chm = (void*) new CHM(0);
414 kvs->_data[0].store(chm, memory_order_relaxed);
415 _kvs.store(kvs, memory_order_release);
421 @Commit_point_set: Get_Success_Point1 | Get_Success_Point2 | Get_Success_Point3
424 void *_Old_Val = spec_table_get(map, key);
426 equals_val(_Old_Val, __RET__)
429 TypeV* get(TypeK *key) {
430 slot *key_slot = new slot(false, key);
431 int fullhash = hash(key_slot);
432 kvs_data *kvs = _kvs.load(memory_order_acquire);
433 slot *V = get_impl(this, kvs, key_slot, fullhash);
434 if (V == NULL) return NULL;
435 MODEL_ASSERT (!is_prime(V));
436 return (TypeV*) V->_ptr;
442 @Commit_point_set: Write_Success_Point
445 # Remember this old value at checking point
446 void *_Old_Val = spec_table_get(map, key);
447 spec_table_put(map, key, val);
449 equals_val(__RET__, _Old_Val)
452 TypeV* put(TypeK *key, TypeV *val) {
453 return putIfMatch(key, val, NO_MATCH_OLD);
458 @Interface: PutIfAbsent
460 Write_Success_Point | PutIfAbsent_Fail_Point
461 @Condition: !spec_table_contains(map, key)
463 COND_PutIfAbsentSucc :: __RET__ == NULL
466 void *_Old_Val = spec_table_get(map, key);
468 spec_table_put(map, key, value);
470 __COND_SAT__ ? __RET__ == NULL : equals_val(_Old_Val, __RET__)
473 TypeV* putIfAbsent(TypeK *key, TypeV *value) {
474 return putIfMatch(key, val, TOMBSTONE);
479 @Interface: RemoveAny
480 @Commit_point_set: Write_Success_Point
483 void *_Old_Val = spec_table_get(map, key);
484 spec_table_put(map, key, NULL);
486 equals_val(__RET__, _Old_Val)
489 TypeV* remove(TypeK *key) {
490 return putIfMatch(key, TOMBSTONE, NO_MATCH_OLD);
495 @Interface: RemoveIfMatch
497 Write_Success_Point | RemoveIfMatch_Fail_Point
499 equals_val(spec_table_get(map, key), val)
501 COND_RemoveIfMatchSucc :: __RET__ == true
505 spec_table_put(map, key, NULL);
507 __COND_SAT__ ? __RET__ : !__RET__
510 bool remove(TypeK *key, TypeV *val) {
511 slot *val_slot = val == NULL ? NULL : new slot(false, val);
512 return putIfMatch(key, TOMBSTONE, val) == val;
518 @Interface: ReplaceAny
523 void *_Old_Val = spec_table_get(map, key);
525 equals_val(__RET__, _Old_Val)
528 TypeV* replace(TypeK *key, TypeV *val) {
529 return putIfMatch(key, val, MATCH_ANY);
534 @Interface: ReplaceIfMatch
536 Write_Success_Point | ReplaceIfMatch_Fail_Point
538 equals_val(spec_table_get(map, key), oldval)
540 COND_ReplaceIfMatchSucc :: __RET__ == true
544 spec_table_put(map, key, newval);
546 __COND_SAT__ ? __RET__ : !__RET__
549 bool replace(TypeK *key, TypeV *oldval, TypeV *newval) {
550 return putIfMatch(key, newval, oldval) == oldval;
554 static CHM* get_chm(kvs_data* kvs) {
555 CHM *res = (CHM*) kvs->_data[0].load(memory_order_relaxed);
559 static int* get_hashes(kvs_data *kvs) {
560 return (int *) kvs->_data[1].load(memory_order_relaxed);
563 // Preserve happens-before semantics on newly inserted keys
564 static inline slot* key(kvs_data *kvs, int idx) {
565 MODEL_ASSERT (idx >= 0 && idx < kvs->_size);
566 // Corresponding to the volatile read in get_impl() and putIfMatch in
567 // Cliff Click's Java implementation
568 slot *res = (slot*) kvs->_data[idx * 2 + 2].load(memory_order_acquire);
573 The atomic operation in val() function is a "potential" commit point,
574 which means in some case it is a real commit point while it is not for
575 some other cases. This so happens because the val() function is such a
576 fundamental function that many internal operation will call. Our
577 strategy is that we label any potential commit points and check if they
578 really are the commit points later.
580 // Preserve happens-before semantics on newly inserted values
581 static inline slot* val(kvs_data *kvs, int idx) {
582 MODEL_ASSERT (idx >= 0 && idx < kvs->_size);
583 // Corresponding to the volatile read in get_impl() and putIfMatch in
584 // Cliff Click's Java implementation
585 slot *res = (slot*) kvs->_data[idx * 2 + 3].load(memory_order_acquire);
588 # This is a complicated potential commit point since many many functions are
590 @Potential_commit_point_define: true
591 @Label: Read_Val_Point
599 static int hash(slot *key_slot) {
600 MODEL_ASSERT(key_slot != NULL && key_slot->_ptr != NULL);
601 TypeK* key = (TypeK*) key_slot->_ptr;
602 int h = key->hashCode();
603 // Spread bits according to Cliff Click's code
604 h += (h << 15) ^ 0xffffcd7d;
608 h += (h << 2) + (h << 14);
609 return h ^ (h >> 16);
612 // Heuristic to decide if reprobed too many times.
613 // Be careful here: Running over the limit on a 'get' acts as a 'miss'; on a
614 // put it triggers a table resize. Several places MUST have exact agreement.
615 static int reprobe_limit(int len) {
616 return REPROBE_LIMIT + (len >> 2);
619 static inline bool is_prime(slot *val) {
620 return (val != NULL) && val->_prime;
623 // Check for key equality. Try direct pointer comparison first (fast
624 // negative teset) and then the full 'equals' call
625 static bool keyeq(slot *K, slot *key_slot, int *hashes, int hash,
627 // Caller should've checked this.
628 MODEL_ASSERT (K != NULL);
629 TypeK* key_ptr = (TypeK*) key_slot->_ptr;
632 ((hashes[hash] == 0 || hashes[hash] == fullhash) &&
634 key_ptr->equals(K->_ptr));
637 static bool valeq(slot *val_slot1, slot *val_slot2) {
638 MODEL_ASSERT (val_slot1 != NULL);
639 TypeK* ptr1 = (TypeV*) val_slot1->_ptr;
640 if (val_slot2 == NULL || ptr1 == NULL) return false;
641 return ptr1->equals(val_slot2->_ptr);
644 // Together with key() preserve the happens-before relationship on newly
646 static inline bool CAS_key(kvs_data *kvs, int idx, void *expected, void *desired) {
647 return kvs->_data[2 * idx + 2].compare_exchange_strong(expected,
648 desired, memory_order_release, memory_order_release);
652 Same as the val() function, we only label the CAS operation as the
653 potential commit point.
655 // Together with val() preserve the happens-before relationship on newly
657 static inline bool CAS_val(kvs_data *kvs, int idx, void *expected, void
659 bool res = kvs->_data[2 * idx + 3].compare_exchange_strong(expected,
660 desired, memory_order_release, memory_order_release);
662 # If it is a successful put instead of a copy or any other internal
663 # operantions, expected != NULL
665 @Potential_commit_point_define: res == true
666 @Label: Write_Val_Point
672 slot* get_impl(cliffc_hashtable *topmap, kvs_data *kvs, slot* key_slot, int
674 int len = kvs->_size;
675 CHM *chm = get_chm(kvs);
676 int *hashes = get_hashes(kvs);
678 int idx = fullhash & (len - 1);
681 slot *K = key(kvs, idx);
682 slot *V = val(kvs, idx);
685 @Commit_point_define: V == NULL
686 @Potential_commit_point_label: Read_Val_Point
687 @Label: Get_Success_Point_1
691 if (K == NULL) return NULL; // A miss
693 if (keyeq(K, key_slot, hashes, idx, fullhash)) {
694 // Key hit! Check if table-resize in progress
698 @Commit_point_define: true
699 @Potential_commit_point_label: Read_Val_Point
700 @Label: Get_Success_Point_2
703 return (V == TOMBSTONE) ? NULL : V; // Return this value
705 // Otherwise, finish the copy & retry in the new table
706 return get_impl(topmap, chm->copy_slot_and_check(topmap, kvs,
707 idx, key_slot), key_slot, fullhash);
710 if (++reprobe_cnt >= REPROBE_LIMIT ||
711 key_slot == TOMBSTONE) {
712 // Retry in new table
713 // Atomic read (acquire) can be here
714 kvs_data *newkvs = chm->_newkvs.load(memory_order_acquire);
717 @Commit_point_define_check: newkvs == NULL
718 @Label: Get_Success_Point_3
721 return newkvs == NULL ? NULL : get_impl(topmap,
722 topmap->help_copy(newkvs), key_slot, fullhash);
725 idx = (idx + 1) & (len - 1); // Reprobe by 1
729 // A wrapper of the essential function putIfMatch()
730 TypeV* putIfMatch(TypeK *key, TypeV *value, slot *old_val) {
731 // TODO: Should throw an exception rather return NULL
732 if (old_val == NULL) {
735 slot *key_slot = new slot(false, key);
737 slot *value_slot = new slot(false, value);
738 kvs_data *kvs = _kvs.load(memory_order_acquire);
739 slot *res = putIfMatch(this, kvs, key_slot, value_slot, old_val);
740 // Only when copy_slot() call putIfMatch() will it return NULL
741 MODEL_ASSERT (res != NULL);
742 MODEL_ASSERT (!is_prime(res));
743 return res == TOMBSTONE ? NULL : (TypeV*) res->_ptr;
747 Put, Remove, PutIfAbsent, etc will call this function. Return the old
748 value. If the returned value is equals to the expVal (or expVal is
749 NO_MATCH_OLD), then this function puts the val_slot to the table 'kvs'.
750 Only copy_slot will pass a NULL expVal, and putIfMatch only returns a
751 NULL if passed a NULL expVal.
753 static slot* putIfMatch(cliffc_hashtable *topmap, kvs_data *kvs, slot
754 *key_slot, slot *val_slot, slot *expVal) {
755 MODEL_ASSERT (val_slot != NULL);
756 MODEL_ASSERT (!is_prime(val_slot));
757 MODEL_ASSERT (!is_prime(expVal));
759 int fullhash = hash(key_slot);
760 int len = kvs->_size;
761 CHM *chm = get_chm(kvs);
762 int *hashes = get_hashes(kvs);
763 int idx = fullhash & (len - 1);
771 while (true) { // Spin till we get a key slot
774 if (K == NULL) { // Get a free slot
775 if (val_slot == TOMBSTONE) return val_slot;
776 // Claim the null key-slot
777 if (CAS_key(kvs, idx, NULL, key_slot)) {
778 chm->_slots.fetch_add(1, memory_order_relaxed); // Inc key-slots-used count
779 hashes[idx] = fullhash; // Memorize full hash
782 K = key(kvs, idx); // CAS failed, get updated value
783 MODEL_ASSERT (K != NULL);
786 // Key slot not null, there exists a Key here
787 if (keyeq(K, key_slot, hashes, idx, fullhash))
790 // Notice that the logic here should be consistent with that of get.
791 // The first predicate means too many reprobes means nothing in the
793 if (++reprobe_cnt >= reprobe_limit(len) ||
794 K == TOMBSTONE) { // Found a Tombstone key, no more keys
795 newkvs = chm->resize(topmap, kvs);
796 // Help along an existing copy
797 if (expVal != NULL) topmap->help_copy(newkvs);
798 return putIfMatch(topmap, newkvs, key_slot, val_slot, expVal);
801 idx = (idx + 1) & (len - 1); // Reprobe
802 } // End of spinning till we get a Key slot
804 if (val_slot == V) return V; // Fast cutout for no-change
806 // Here it tries to resize cause it doesn't want other threads to stop
807 // its progress (eagerly try to resize soon)
808 newkvs = chm->_newkvs.load(memory_order_acquire);
809 if (newkvs == NULL &&
810 ((V == NULL && chm->table_full(reprobe_cnt, len)) || is_prime(V)))
811 newkvs = chm->resize(topmap, kvs); // Force the copy to start
813 // Finish the copy and then put it in the new table
815 return putIfMatch(topmap, chm->copy_slot_and_check(topmap, kvs, idx,
816 expVal), key_slot, val_slot, expVal);
818 // Decided to update the existing table
820 MODEL_ASSERT (!is_prime(V));
822 if (expVal != NO_MATCH_OLD &&
824 (expVal != MATCH_ANY || V == TOMBSTONE || V == NULL) &&
825 !(V == NULL && expVal == TOMBSTONE) &&
826 (expVal == NULL || !valeq(expVal, V))) {
829 @Commit_point_define: expVal == TOMBSTONE
830 @Potential_commit_point_label: Read_Val_Point
831 @Label: PutIfAbsent_Fail_Point
832 # This is a check for the PutIfAbsent() when the value
838 @Commit_point_define: expVal != NULL && val_slot == TOMBSTONE
839 @Potential_commit_point_label: Read_Val_Point
840 @Label: RemoveIfMatch_Fail_Point
845 @Commit_point_define: !valeq(expVal, V)
846 @Potential_commit_point_label: Read_Val_Point
847 @Label: ReplaceIfMatch_Fail_Point
850 return V; // Do not update!
853 if (CAS_val(kvs, idx, V, val_slot)) {
856 # The only point where a successful put happens
857 @Commit_point_define: true
858 @Potential_commit_point_label: Write_Val_Point
859 @Label: Write_Success_Point
862 if (expVal != NULL) { // Not called by a table-copy
863 // CAS succeeded, should adjust size
864 // Both normal put's and table-copy calls putIfMatch, but
865 // table-copy does not increase the number of live K/V pairs
866 if ((V == NULL || V == TOMBSTONE) &&
867 val_slot != TOMBSTONE)
868 chm->_size.fetch_add(1, memory_order_relaxed);
869 if (!(V == NULL || V == TOMBSTONE) &&
870 val_slot == TOMBSTONE)
871 chm->_size.fetch_add(-1, memory_order_relaxed);
873 return (V == NULL && expVal != NULL) ? TOMBSTONE : V;
878 return putIfMatch(topmap, chm->copy_slot_and_check(topmap, kvs,
879 idx, expVal), key_slot, val_slot, expVal);
883 // Help along an existing table-resize. This is a fast cut-out wrapper.
884 kvs_data* help_copy(kvs_data *helper) {
885 kvs_data *topkvs = _kvs.load(memory_order_acquire);
886 CHM *topchm = get_chm(topkvs);
887 // No cpy in progress
888 if (topchm->_newkvs.load(memory_order_acquire) == NULL) return helper;
889 topchm->help_copy_impl(this, topkvs, false);