7 #error "This is an impl file and must not be included directly!"
10 #include <xenium/aligned_object.hpp>
11 #include <xenium/detail/port.hpp>
12 #include <xenium/reclamation/detail/perf_counter.hpp>
13 #include <xenium/reclamation/detail/thread_block_list.hpp>
20 #pragma warning(disable : 4324) // structure was padded due to alignment specifier
23 namespace xenium::reclamation {
25 struct stamp_it::thread_control_block :
26 aligned_object<thread_control_block, 1 << MarkBits>,
27 detail::thread_block_list<thread_control_block>::entry {
28 using concurrent_ptr = std::atomic<marked_ptr<thread_control_block, MarkBits>>;
33 std::atomic<stamp_t> stamp;
35 #ifdef WITH_PERF_COUNTER
36 performance_counters counters;
37 friend class thread_order_queue;
41 class stamp_it::thread_order_queue {
43 using marked_ptr = xenium::marked_ptr<thread_control_block, MarkBits>;
44 using concurrent_ptr = std::atomic<marked_ptr>;
46 thread_order_queue() {
47 head = new thread_control_block();
48 tail = new thread_control_block();
49 tail->next.store(head, std::memory_order_relaxed);
50 tail->stamp.store(StampInc, std::memory_order_relaxed);
51 head->prev.store(tail, std::memory_order_relaxed);
52 head->stamp.store(StampInc, std::memory_order_relaxed);
55 void push(thread_control_block* block) {
56 INC_PERF_CNT(block->counters.push_calls);
57 PERF_COUNTER(iterations, block->counters.push_iterations)
60 block->next.store(make_clean_marked(head, block->next), std::memory_order_release);
62 marked_ptr head_prev = head->prev.load(std::memory_order_relaxed);
68 assert((head_prev.mark() & DeleteMark) == 0 &&
"head must never be marked");
69 marked_ptr head_prev2 = head->prev.load(std::memory_order_relaxed);
70 if (head_prev != head_prev2) {
71 head_prev = head_prev2;
78 stamp = head->stamp.fetch_add(StampInc, std::memory_order_seq_cst);
79 auto pending_stamp = stamp - (StampInc - PendingPush);
80 assert((pending_stamp & PendingPush) && !(pending_stamp & NotInList));
85 block->stamp.store(pending_stamp, std::memory_order_release);
87 if (head->prev.load(std::memory_order_relaxed) != head_prev) {
91 my_prev = make_clean_marked(head_prev.get(), block->prev);
94 block->prev.store(my_prev, std::memory_order_release);
99 if (head->prev.compare_exchange_weak(
100 head_prev, make_marked(block, head_prev), std::memory_order_acq_rel, std::memory_order_relaxed)) {
108 block->stamp.store(stamp, std::memory_order_release);
112 auto link = my_prev->next.load(std::memory_order_acquire);
114 if (link.get() == block || ((link.mark() & DeleteMark) != 0) ||
115 block->prev.load(std::memory_order_relaxed) != my_prev) {
119 assert(link.get() != tail);
124 if (my_prev->next.compare_exchange_weak(
125 link, make_marked(block, link), std::memory_order_release, std::memory_order_acquire)) {
132 bool remove(marked_ptr block) {
133 INC_PERF_CNT(block->counters.remove_calls);
145 marked_ptr prev = set_mark_flag(block->prev, std::memory_order_acq_rel);
146 marked_ptr next = set_mark_flag(block->next, std::memory_order_relaxed);
148 bool fully_removed = remove_from_prev_list(prev, block, next);
149 if (!fully_removed) {
150 remove_from_next_list(prev, block, next);
153 auto stamp = block->stamp.load(std::memory_order_relaxed);
154 assert((stamp & (PendingPush | NotInList)) == 0);
156 block->stamp.store(stamp + NotInList, std::memory_order_relaxed);
158 bool wasTail = block->prev.load(std::memory_order_relaxed).get() == tail;
162 update_tail_stamp(stamp + StampInc);
168 void add_to_global_retired_nodes(deletable_object_with_stamp* chunk) { add_to_global_retired_nodes(chunk, chunk); }
170 void add_to_global_retired_nodes(deletable_object_with_stamp* first_chunk, deletable_object_with_stamp* last_chunk) {
171 assert(first_chunk !=
nullptr && last_chunk !=
nullptr);
172 auto* n = global_retired_nodes.load(std::memory_order_relaxed);
174 last_chunk->next_chunk = n;
176 }
while (!global_retired_nodes.compare_exchange_weak(
177 n, first_chunk, std::memory_order_release, std::memory_order_relaxed));
180 deletable_object_with_stamp* steal_global_retired_nodes() {
181 if (global_retired_nodes.load(std::memory_order_relaxed) !=
nullptr) {
183 return global_retired_nodes.exchange(
nullptr, std::memory_order_acquire);
188 stamp_t head_stamp() {
190 return head->stamp.load(std::memory_order_seq_cst);
192 stamp_t tail_stamp() {
194 return tail->stamp.load(std::memory_order_acquire);
197 thread_control_block* acquire_control_block() {
return global_thread_block_list.acquire_entry(); }
200 static const unsigned DeleteMark = 1;
201 static const unsigned TagInc = 2;
202 static const unsigned MarkMask = (1 << MarkBits) - 1;
204 static marked_ptr make_marked(thread_control_block* p,
const marked_ptr& mark) {
205 return marked_ptr(p, (mark.mark() + TagInc) & MarkMask);
208 static marked_ptr make_clean_marked(thread_control_block* p,
const concurrent_ptr& mark) {
209 auto m = mark.load(std::memory_order_relaxed);
210 return marked_ptr(p, (m.mark() + TagInc) & MarkMask & ~DeleteMark);
213 void update_tail_stamp(
size_t stamp) {
220 auto last = tail->next.load(std::memory_order_acquire);
222 auto last_prev = last->prev.load(std::memory_order_acquire);
223 auto last_stamp = last->stamp.load(std::memory_order_relaxed);
224 if (last_stamp > stamp && last_prev.get() == tail && tail->next.load(std::memory_order_relaxed) == last) {
225 assert((last_stamp & PendingPush) == 0);
226 assert((last_stamp & NotInList) == 0);
227 assert(last_stamp >= stamp);
228 if (last.get() != head) {
238 if (stamp < last_stamp - StampInc && head->prev.compare_exchange_strong(last_prev,
239 make_marked(last_prev.get(), last_prev),
240 std::memory_order_relaxed)) {
247 auto tail_stamp = tail->stamp.load(std::memory_order_relaxed);
248 while (tail_stamp < stamp) {
250 if (tail->stamp.compare_exchange_weak(tail_stamp, stamp, std::memory_order_release)) {
256 static bool remove_from_prev_list(marked_ptr& prev, marked_ptr b, marked_ptr& next) {
257 PERF_COUNTER(iterations, b->counters.remove_prev_iterations);
259 const auto my_stamp = b->stamp.load(std::memory_order_relaxed);
260 marked_ptr last =
nullptr;
265 if (next.get() == prev.get()) {
266 next = b->next.load(std::memory_order_relaxed);
270 auto prev_prev = prev->prev.load(std::memory_order_relaxed);
271 auto prev_stamp = prev->stamp.load(std::memory_order_relaxed);
274 if (prev_stamp > my_stamp ||
275 ((prev_stamp & NotInList) != 0))
280 if ((prev_prev.mark() & DeleteMark) != 0) {
281 if (!mark_next(prev, prev_stamp)) {
291 prev = prev->prev.load(std::memory_order_acquire);
302 auto next_prev = next->prev.load(std::memory_order_acquire);
304 auto next_stamp = next->stamp.load(std::memory_order_acquire);
306 if (next_prev != next->prev.load(std::memory_order_relaxed)) {
310 if (next_stamp < my_stamp) {
311 next = b->next.load(std::memory_order_relaxed);
321 if ((next_stamp & (NotInList | PendingPush)) != 0) {
322 if (last.get() !=
nullptr) {
327 next = next->next.load(std::memory_order_acquire);
332 if (remove_or_skip_marked_block(next, last, next_prev, next_stamp)) {
337 if (next_prev.get() != b.get()) {
338 save_next_as_last_and_move_next_to_next_prev(next_prev, next, last);
344 if (next->prev.compare_exchange_strong(
345 next_prev, make_marked(prev.get(), next_prev), std::memory_order_release, std::memory_order_relaxed)) {
353 static void remove_from_next_list(marked_ptr prev, marked_ptr removed, marked_ptr next) {
354 PERF_COUNTER(iterations, removed->counters.remove_next_iterations);
356 const auto my_stamp = removed->stamp.load(std::memory_order_relaxed);
358 marked_ptr last =
nullptr;
368 auto next_prev = next->prev.load(std::memory_order_acquire);
370 auto next_stamp = next->stamp.load(std::memory_order_acquire);
372 if (next_prev != next->prev.load(std::memory_order_relaxed)) {
377 if ((next_stamp & (NotInList | PendingPush)) != 0) {
378 if (last.get() !=
nullptr) {
383 next = next->next.load(std::memory_order_acquire);
389 auto prev_next = prev->next.load(std::memory_order_acquire);
390 auto prev_stamp = prev->stamp.load(std::memory_order_relaxed);
392 assert(prev.get() != removed.get() || prev_stamp > my_stamp);
395 if (prev_stamp > my_stamp || ((prev_stamp & NotInList) != 0)) {
401 if ((prev_next.mark() & DeleteMark) != 0) {
405 prev = prev->prev.load(std::memory_order_acquire);
409 if (next.get() == prev.get()) {
413 if (remove_or_skip_marked_block(next, last, next_prev, next_stamp)) {
418 if (next_prev.get() != prev.get()) {
419 save_next_as_last_and_move_next_to_next_prev(next_prev, next, last);
423 if (next_stamp <= my_stamp || prev_next.get() == next.get()) {
427 auto new_next = make_marked(next.get(), prev_next);
428 if (next->prev.load(std::memory_order_relaxed) == next_prev &&
430 prev->next.compare_exchange_weak(prev_next, new_next, std::memory_order_release, std::memory_order_relaxed)) {
431 if ((next->next.load(std::memory_order_relaxed).mark() & DeleteMark) == 0) {
440 remove_or_skip_marked_block(marked_ptr& next, marked_ptr& last, marked_ptr next_prev, stamp_t next_stamp) {
442 if ((next_prev.mark() & DeleteMark) != 0) {
443 if (last.get() !=
nullptr) {
445 assert((next.mark() & DeleteMark) == 0);
446 if (mark_next(next, next_stamp) && last->prev.load(std::memory_order_relaxed) == next) {
449 last->prev.compare_exchange_strong(
450 next, make_marked(next_prev.get(), next), std::memory_order_release, std::memory_order_relaxed);
456 next = next->next.load(std::memory_order_acquire);
464 static void save_next_as_last_and_move_next_to_next_prev(marked_ptr next_prev, marked_ptr& next, marked_ptr& last) {
466 size_t next_prev_stamp = next_prev->stamp.load(std::memory_order_acquire);
468 if (((next_prev_stamp & PendingPush) != 0) && next_prev == next->prev.load(std::memory_order_relaxed)) {
469 assert((next_prev_stamp & NotInList) == 0);
473 auto expected = next_prev_stamp;
474 const auto new_stamp = next_prev_stamp + (StampInc - PendingPush);
475 if (!next_prev->stamp.compare_exchange_strong(expected, new_stamp, std::memory_order_relaxed)) {
479 if (expected != new_stamp) {
490 static marked_ptr set_mark_flag(concurrent_ptr& ptr, std::memory_order order) {
491 auto link = ptr.load(std::memory_order_relaxed);
493 if (((link.mark() & DeleteMark) != 0) ||
494 ptr.compare_exchange_weak(
495 link, marked_ptr(link.get(), link.mark() | DeleteMark), order, std::memory_order_relaxed)) {
502 static bool mark_next(marked_ptr block,
size_t stamp) {
503 assert((stamp & (NotInList | PendingPush)) == 0);
505 auto link = block->next.load(std::memory_order_acquire);
509 while (block->stamp.load(std::memory_order_relaxed) == stamp) {
510 auto mark = link.mark();
511 if (((mark & DeleteMark) != 0) ||
513 block->next.compare_exchange_weak(
514 link, marked_ptr(link.get(), mark | DeleteMark), std::memory_order_relaxed, std::memory_order_acquire)) {
527 thread_control_block* head;
528 thread_control_block* tail;
530 alignas(64) std::atomic<deletable_object_with_stamp*> global_retired_nodes{
nullptr};
531 alignas(64) detail::thread_block_list<thread_control_block> global_thread_block_list{};
532 friend class stamp_it;
535 struct stamp_it::thread_data {
537 assert(region_entries == 0);
538 if (control_block ==
nullptr) {
542 control_block->abandon();
543 control_block =
nullptr;
546 process_local_nodes();
547 if (first_retired_node !=
nullptr) {
550 queue.add_to_global_retired_nodes(first_retired_node);
554 void enter_region() {
555 if (++region_entries == 1) {
556 ensure_has_control_block();
557 queue.push(control_block);
561 void leave_region() {
562 if (--region_entries == 0) {
563 auto wasLast = queue.remove(control_block);
566 process_global_nodes();
568 process_local_nodes();
569 if (number_of_retired_nodes > max_remaining_retired_nodes) {
570 queue.add_to_global_retired_nodes(first_retired_node);
571 first_retired_node =
nullptr;
572 prev_retired_node = &first_retired_node;
573 number_of_retired_nodes = 0;
579 void add_retired_node(deletable_object_with_stamp* p) {
580 p->stamp = queue.head_stamp();
581 *prev_retired_node = p;
582 prev_retired_node = &p->next;
584 ++number_of_retired_nodes;
585 if (number_of_retired_nodes > try_reclaim_threshold) {
586 process_local_nodes();
591 void ensure_has_control_block() {
592 if (control_block ==
nullptr) {
593 control_block = queue.acquire_control_block();
594 #ifdef WITH_PERF_COUNTER
595 control_block->counters = performance_counters{};
600 void process_local_nodes() {
601 auto tail_stamp = queue.tail_stamp();
603 auto* cur = first_retired_node;
604 for (deletable_object_with_stamp* next =
nullptr; cur !=
nullptr; cur = next) {
606 if (cur->stamp <= tail_stamp) {
614 first_retired_node = cur;
615 if (cur ==
nullptr) {
616 prev_retired_node = &first_retired_node;
618 number_of_retired_nodes -= cnt;
621 void process_global_nodes() {
622 auto tail_stamp = queue.tail_stamp();
623 auto* cur_chunk = queue.steal_global_retired_nodes();
625 if (first_retired_node !=
nullptr) {
626 first_retired_node->next_chunk = cur_chunk;
627 cur_chunk = first_retired_node;
628 first_retired_node =
nullptr;
629 prev_retired_node = &first_retired_node;
630 number_of_retired_nodes = 0;
632 if (cur_chunk ==
nullptr) {
636 stamp_t lowest_stamp;
637 auto process_chunk_nodes = [&tail_stamp, &lowest_stamp](deletable_object_with_stamp* chunk) {
639 while (cur !=
nullptr) {
640 if (cur->stamp <= tail_stamp) {
641 lowest_stamp = std::min(lowest_stamp, cur->stamp);
642 auto* next = cur->next;
653 lowest_stamp = std::numeric_limits<stamp_t>::max();
654 deletable_object_with_stamp* first_remaining_chunk =
nullptr;
655 deletable_object_with_stamp* last_remaining_chunk =
nullptr;
656 deletable_object_with_stamp** prev_remaining_chunk = &first_remaining_chunk;
657 while (cur_chunk !=
nullptr) {
658 auto* next_chunk = cur_chunk->next_chunk;
659 auto* remaining_chunk = process_chunk_nodes(cur_chunk);
660 if (remaining_chunk !=
nullptr) {
661 *prev_remaining_chunk = remaining_chunk;
662 last_remaining_chunk = remaining_chunk;
663 prev_remaining_chunk = &remaining_chunk->next_chunk;
665 cur_chunk = next_chunk;
668 *prev_remaining_chunk =
nullptr;
669 if (first_remaining_chunk !=
nullptr) {
670 auto new_tail_stamp = queue.tail_stamp();
671 if (lowest_stamp < new_tail_stamp) {
672 cur_chunk = first_remaining_chunk;
673 tail_stamp = new_tail_stamp;
676 assert(last_remaining_chunk !=
nullptr);
677 queue.add_to_global_retired_nodes(first_remaining_chunk, last_remaining_chunk);
685 static const std::size_t try_reclaim_threshold = 40;
689 static const std::size_t max_remaining_retired_nodes = 20;
691 thread_control_block* control_block =
nullptr;
692 unsigned region_entries = 0;
693 std::size_t number_of_retired_nodes = 0;
695 deletable_object_with_stamp* first_retired_node =
nullptr;
696 deletable_object_with_stamp** prev_retired_node = &first_retired_node;
698 friend class stamp_it;
699 ALLOCATION_COUNTER(stamp_it);
702 inline stamp_it::region_guard::region_guard() noexcept {
703 local_thread_data().enter_region();
706 inline stamp_it::region_guard::~region_guard()
708 local_thread_data().leave_region();
711 template <
class T,
class MarkedPtr>
712 stamp_it::guard_ptr<T, MarkedPtr>::guard_ptr(
const MarkedPtr& p) noexcept : base(p) {
714 local_thread_data().enter_region();
718 template <
class T,
class MarkedPtr>
719 stamp_it::guard_ptr<T, MarkedPtr>::guard_ptr(
const guard_ptr& p) noexcept : guard_ptr(MarkedPtr(p)) {}
721 template <
class T,
class MarkedPtr>
722 stamp_it::guard_ptr<T, MarkedPtr>::guard_ptr(guard_ptr&& p) noexcept : base(p.ptr) {
726 template <
class T,
class MarkedPtr>
727 typename stamp_it::guard_ptr<T, MarkedPtr>& stamp_it::guard_ptr<T, MarkedPtr>::operator=(
const guard_ptr& p) noexcept {
735 local_thread_data().enter_region();
741 template <
class T,
class MarkedPtr>
742 typename stamp_it::guard_ptr<T, MarkedPtr>& stamp_it::guard_ptr<T, MarkedPtr>::operator=(guard_ptr&& p) noexcept {
748 this->ptr = std::move(p.ptr);
754 template <
class T,
class MarkedPtr>
755 void stamp_it::guard_ptr<T, MarkedPtr>::acquire(
const concurrent_ptr<T>& p, std::memory_order order) noexcept {
756 if (p.load(std::memory_order_relaxed) ==
nullptr) {
762 local_thread_data().enter_region();
764 this->ptr = p.load(order);
766 local_thread_data().leave_region();
770 template <
class T,
class MarkedPtr>
771 bool stamp_it::guard_ptr<T, MarkedPtr>::acquire_if_equal(
const concurrent_ptr<T>& p,
772 const MarkedPtr& expected,
773 std::memory_order order) noexcept {
774 auto actual = p.load(std::memory_order_relaxed);
775 if (actual ==
nullptr || actual != expected) {
777 return actual == expected;
781 local_thread_data().enter_region();
783 this->ptr = p.load(order);
784 if (!this->ptr || this->ptr != expected) {
785 local_thread_data().leave_region();
789 return this->ptr == expected;
792 template <
class T,
class MarkedPtr>
793 void stamp_it::guard_ptr<T, MarkedPtr>::reset() noexcept {
795 local_thread_data().leave_region();
800 template <
class T,
class MarkedPtr>
801 void stamp_it::guard_ptr<T, MarkedPtr>::reclaim(Deleter d) noexcept {
802 this->ptr->set_deleter(std::move(d));
803 local_thread_data().add_retired_node(this->ptr.get());
807 inline stamp_it::thread_data& stamp_it::local_thread_data() {
809 static thread_local thread_data local_thread_data;
810 return local_thread_data;
814 __declspec(selectany) stamp_it::thread_order_queue stamp_it::queue;
816 inline stamp_it::thread_order_queue stamp_it::queue;
819 #ifdef WITH_PERF_COUNTER
820 inline stamp_it::performance_counters stamp_it::get_performance_counters() {
821 performance_counters result{};
823 queue.global_thread_block_list.begin(), queue.global_thread_block_list.end(), [&result](
const auto& block) {
824 result.push_calls += block.counters.push_calls;
825 result.push_iterations += block.counters.push_iterations;
826 result.remove_calls += block.counters.remove_calls;
827 result.remove_prev_iterations += block.counters.remove_prev_iterations;
828 result.remove_next_iterations += block.counters.remove_next_iterations;
835 #ifdef TRACK_ALLOCATIONS
836 inline void stamp_it::count_allocation() {
837 local_thread_data().allocation_counter.count_allocation();
840 inline void stamp_it::count_reclamation() {
841 local_thread_data().allocation_counter.count_reclamation();