3#include <ttak/mem/epoch.h>
9#if defined(CWIST_PLATFORM_HOSTED)
47static pthread_mutex_t g_reg_lock = PTHREAD_MUTEX_INITIALIZER;
48static cwist_reg_entry_t *g_reg_slots = NULL;
49static size_t g_reg_cap = 0;
50static size_t g_reg_count = 0;
52static size_t cwist_reg_slot(
const void *ptr,
size_t mask) {
53 uint64_t h = (uint64_t)((uintptr_t)ptr >> 4) * UINT64_C(0x9E3779B97F4A7C15);
54 return (
size_t)(h >> 32) & mask;
58static cwist_reg_entry_t *cwist_reg_find_locked(
const void *ptr) {
59 if (g_reg_count == 0)
return NULL;
60 size_t mask = g_reg_cap - 1;
61 for (
size_t i = cwist_reg_slot(ptr, mask);; i = (i + 1) & mask) {
62 if (!g_reg_slots[i].ptr)
return NULL;
63 if (g_reg_slots[i].ptr == ptr)
return &g_reg_slots[i];
68static void cwist_reg_remove_locked(cwist_reg_entry_t *e) {
69 size_t mask = g_reg_cap - 1;
70 size_t hole = (size_t)(e - g_reg_slots);
71 for (
size_t j = (hole + 1) & mask; g_reg_slots[j].ptr; j = (j + 1) & mask) {
72 size_t home = cwist_reg_slot(g_reg_slots[j].ptr, mask);
73 if (((j - home) & mask) >= ((j - hole) & mask)) {
74 g_reg_slots[hole] = g_reg_slots[j];
78 g_reg_slots[hole] = (cwist_reg_entry_t){0};
83static bool cwist_reg_reserve_locked(
void) {
84 if ((g_reg_count + 1) * 2 <= g_reg_cap)
return true;
85 size_t new_cap = g_reg_cap ? g_reg_cap * 2 : 64;
86 cwist_reg_entry_t *slots = (cwist_reg_entry_t *)calloc(new_cap,
sizeof(*slots));
87 if (!slots)
return false;
88 size_t mask = new_cap - 1;
89 for (
size_t i = 0; i < g_reg_cap; i++) {
90 if (!g_reg_slots[i].ptr)
continue;
91 size_t j = cwist_reg_slot(g_reg_slots[i].ptr, mask);
92 while (slots[j].ptr) j = (j + 1) & mask;
93 slots[j] = g_reg_slots[i];
114 if (!ptr)
return false;
115 pthread_mutex_lock(&g_reg_lock);
116 cwist_reg_entry_t *e = cwist_reg_find_locked(ptr);
118 pthread_mutex_unlock(&g_reg_lock);
122 cwist_reg_remove_locked(e);
123 pthread_mutex_unlock(&g_reg_lock);
126 ttak_mem_node_t *node = ttak_mem_tree_find_node(&owner->
impl.tree, ptr);
127 if (node) ttak_mem_tree_remove(&owner->
impl.tree, node);
146 pthread_mutex_lock(&g_reg_lock);
147 cwist_reg_entry_t *e = NULL;
148 for (
size_t i = 0; i < g_reg_cap; i++) {
149 if (g_reg_slots[i].ptr && g_reg_slots[i].owner == gc) {
155 pthread_mutex_unlock(&g_reg_lock);
159 cwist_reg_remove_locked(e);
160 pthread_mutex_unlock(&g_reg_lock);
162 ttak_mem_node_t *node = ttak_mem_tree_find_node(&gc->
impl.tree, ptr);
163 if (node) ttak_mem_tree_remove(&gc->
impl.tree, node);
176 ttak_epoch_gc_init(&gc->
impl);
179 ttak_epoch_gc_manual_rotate(&gc->
impl, manual_rotation);
195 ttak_epoch_gc_destroy(&gc->
impl);
205 ttak_epoch_gc_rotate(&gc->
impl);
215 while (!atomic_compare_exchange_weak(&gc->
auto_rotated, &gc_status, enabled));
264 pthread_mutex_lock(&g_reg_lock);
265 cwist_reg_entry_t *e = cwist_reg_find_locked(ptr);
267 if (e->owner == gc) {
270 pthread_mutex_unlock(&g_reg_lock);
277 cwist_reg_remove_locked(e);
278 pthread_mutex_unlock(&g_reg_lock);
280 ttak_mem_node_t *node = ttak_mem_tree_find_node(&prev->
impl.tree, ptr);
281 if (node) ttak_mem_tree_remove(&prev->
impl.tree, node);
283 pthread_mutex_lock(&g_reg_lock);
284 if (cwist_reg_find_locked(ptr)) {
287 pthread_mutex_unlock(&g_reg_lock);
291 bool tracked = cwist_reg_reserve_locked();
293 size_t mask = g_reg_cap - 1;
294 size_t i = cwist_reg_slot(ptr, mask);
295 while (g_reg_slots[i].ptr) i = (i + 1) & mask;
296 g_reg_slots[i] = (cwist_reg_entry_t){.ptr = ptr, .owner = gc};
299 pthread_mutex_unlock(&g_reg_lock);
305 if (!tracked)
return;
306 ttak_epoch_gc_register(&gc->
impl, ptr, size);
341static void cwist_ebr_free_cb(
void *ptr) {
352 ttak_epoch_retire(ptr, cwist_ebr_free_cb);
359 ttak_epoch_reclaim();
368 atomic_store_explicit(guard,
false, memory_order_relaxed);
377 if (!guard)
return false;
378 bool expected =
false;
379 return atomic_compare_exchange_strong_explicit(guard, &expected,
true, memory_order_acq_rel,
380 memory_order_relaxed);
405 _Atomic
bool enabled;
407} cwist_full_gc_guard_t;
414static cwist_full_gc_guard_t *g_full_gc_guard = NULL;
423#if !defined(CWIST_PLATFORM_HOSTED)
424 static cwist_full_gc_guard_t s_wasm_guard;
425 atomic_init(&s_wasm_guard.enabled,
false);
426 atomic_init(&s_wasm_guard.locked,
false);
427 g_full_gc_guard = &s_wasm_guard;
429 long page_size = sysconf(_SC_PAGESIZE);
430 if (page_size <= 0) page_size = 4096;
432 mmap(NULL, (
size_t)page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
433 if (page == MAP_FAILED)
return;
434 cwist_full_gc_guard_t *guard = (cwist_full_gc_guard_t *)page;
435 atomic_init(&guard->enabled,
false);
436 atomic_init(&guard->locked,
false);
437 g_full_gc_guard = guard;
443static pthread_once_t g_full_gc_once = PTHREAD_ONCE_INIT;
446static void cwist_full_gc_lazy_init(
void) {
454static cwist_gc_t *cwist_full_gc_instance(
void) {
455 pthread_once(&g_full_gc_once, cwist_full_gc_lazy_init);
471static pthread_mutex_t g_full_gc_claim_mu = PTHREAD_MUTEX_INITIALIZER;
484 if (!g_full_gc_guard)
return;
489 if (atomic_load_explicit(&g_full_gc_guard->locked, memory_order_acquire))
return;
491 pthread_mutex_lock(&g_full_gc_claim_mu);
492 if (atomic_load_explicit(&g_full_gc_guard->locked, memory_order_acquire)) {
494 pthread_mutex_unlock(&g_full_gc_claim_mu);
500 atomic_store_explicit(&g_full_gc_guard->enabled, enable, memory_order_relaxed);
501 atomic_store_explicit(&g_full_gc_guard->locked,
true, memory_order_release);
502#if defined(CWIST_PLATFORM_HOSTED)
503 long page_size = sysconf(_SC_PAGESIZE);
504 mprotect(g_full_gc_guard, (
size_t)(page_size > 0 ? page_size : 4096), PROT_READ);
506 pthread_mutex_unlock(&g_full_gc_claim_mu);
516 if (!g_full_gc_guard)
return false;
517 return atomic_load_explicit(&g_full_gc_guard->enabled, memory_order_relaxed);
525 if (!g_full_gc_guard)
return false;
526 return atomic_load_explicit(&g_full_gc_guard->locked, memory_order_acquire);
534 return g_full_gc_guard;
561#define CWIST_GC_PENDING_MIN_CAP 16
564#define CWIST_GC_PENDING_KEEP_CAP 1024
570static inline size_t cwist_gc_pending_slot(
const void *ptr,
size_t mask) {
571 uint64_t h = (uint64_t)((uintptr_t)ptr >> 4) * UINT64_C(0x9E3779B97F4A7C15);
572 return (
size_t)(h >> 32) & mask;
576static bool cwist_gc_pending_grow(cwist_gc_pending_t *pending) {
578 void **slots = (
void **)calloc(new_cap,
sizeof(
void *));
579 if (!slots)
return false;
580 size_t mask = new_cap - 1;
581 for (
size_t i = 0; i < pending->cap; i++) {
582 void *key = pending->slots[i];
584 size_t j = cwist_gc_pending_slot(key, mask);
585 while (slots[j]) j = (j + 1) & mask;
588 free(pending->slots);
589 pending->slots = slots;
590 pending->cap = new_cap;
604static void cwist_gc_pending_flush(cwist_gc_pending_t *pending) {
605 if (!pending)
return;
606 void **slots = pending->slots;
607 size_t cap = pending->cap;
608 size_t count = pending->count;
611 pending->slots = NULL;
615 for (
size_t i = 0; i < cap && count > 0; i++) {
616 if (!slots[i])
continue;
623 pending->slots = slots;
643#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L)
644#define CWIST_GC_PENDING_TLS 1
646#elif defined(__GNUC__) || defined(__clang__)
647#define CWIST_GC_PENDING_TLS 1
655static void cwist_gc_pending_destroy(
void *arg) {
656 cwist_gc_pending_t *pending = (cwist_gc_pending_t *)arg;
657 if (!pending)
return;
658#ifdef CWIST_GC_PENDING_TLS
664 cwist_gc_pending_flush(pending);
665 free(pending->slots);
672static void cwist_gc_pending_key_init(
void) {
683static cwist_gc_pending_t *cwist_gc_pending_get(
void) {
684#ifdef CWIST_GC_PENDING_TLS
687 pthread_once(&g_pending_key_once, cwist_gc_pending_key_init);
690 pending = (cwist_gc_pending_t *)
calloc(1,
sizeof(*pending));
693#ifdef CWIST_GC_PENDING_TLS
709 cwist_gc_pending_t *pending = cwist_gc_pending_get();
710 if (!pending)
return;
713 if ((pending->count + 1) * 2 > pending->cap && !cwist_gc_pending_grow(pending))
return;
714 size_t mask = pending->cap - 1;
715 size_t i = cwist_gc_pending_slot(ptr,
mask);
716 while (pending->slots[
i]) {
717 if (pending->slots[
i] == ptr)
return;
720 pending->slots[
i] = ptr;
735 if (!ptr)
return false;
736 cwist_gc_pending_t *pending = cwist_gc_pending_get();
737 if (!pending || pending->count == 0)
return false;
738 size_t mask = pending->cap - 1;
739 size_t i = cwist_gc_pending_slot(ptr,
mask);
740 while (pending->slots[
i] != ptr) {
741 if (!pending->slots[
i])
return false;
749 for (
size_t j = (
i + 1) &
mask; pending->slots[
j];
j = (
j + 1) &
mask) {
750 size_t home = cwist_gc_pending_slot(pending->slots[
j],
mask);
752 pending->slots[
hole] = pending->slots[
j];
777 cwist_gc_pending_flush(cwist_gc_pending_get());
785 cwist_gc_pending_t *pending = cwist_gc_pending_get();
786 return pending ? pending->count : 0;
806typedef struct cwist_conn_owner {
819static cwist_conn_entry_t *g_conn_slots =
NULL;
820static size_t g_conn_cap = 0;
821static size_t g_conn_count = 0;
822static _Atomic
bool g_conn_hooks_registered =
false;
824static size_t cwist_conn_slot(
const void *handle,
size_t mask) {
829 x *= 0xff51afd7ed558ccdULL;
831 return (
size_t)
x &
mask;
835static size_t cwist_conn_find_locked(
const void *handle) {
836 if (g_conn_count == 0)
return SIZE_MAX;
837 size_t mask = g_conn_cap - 1;
838 for (
size_t i = cwist_conn_slot(handle,
mask);;
i = (
i + 1) &
mask) {
839 if (g_conn_slots[
i].handle == handle)
return i;
840 if (!g_conn_slots[
i].handle)
return SIZE_MAX;
845static void cwist_conn_remove_at_locked(
size_t i) {
846 size_t mask = g_conn_cap - 1;
848 for (
size_t j = (
i + 1) &
mask; g_conn_slots[
j].handle;
j = (
j + 1) &
mask) {
849 size_t home = cwist_conn_slot(g_conn_slots[
j].handle,
mask);
851 g_conn_slots[
hole] = g_conn_slots[
j];
855 g_conn_slots[
hole] = (cwist_conn_entry_t){0};
860static bool cwist_conn_reserve_locked(
void) {
861 if ((g_conn_count + 1) * 2 <= g_conn_cap)
return true;
862 size_t new_cap = g_conn_cap ? g_conn_cap * 2 : 64;
863 cwist_conn_entry_t *slots = (cwist_conn_entry_t *)
calloc(
new_cap,
sizeof(*slots));
864 if (!slots)
return false;
866 for (
size_t i = 0;
i < g_conn_cap;
i++) {
867 if (!g_conn_slots[
i].handle)
continue;
868 size_t j = cwist_conn_slot(g_conn_slots[
i].handle,
mask);
869 while (slots[
j].handle)
j = (
j + 1) &
mask;
870 slots[
j] = g_conn_slots[
i];
873 g_conn_slots = slots;
890 for (
size_t i = 0;
i < g_conn_cap;
i++) {
891 if (g_conn_slots[
i].handle && (!owner || g_conn_slots[
i].owner == owner))
want++;
897 cwist_conn_entry_t
one;
898 cwist_conn_entry_t *heap = (cwist_conn_entry_t *)
calloc(
want,
sizeof(*heap));
899 cwist_conn_entry_t *
batch = heap ? heap : &
one;
902 for (
size_t i = 0;
i < g_conn_cap &&
n <
room;) {
903 if (g_conn_slots[
i].handle && (!owner || g_conn_slots[
i].owner == owner)) {
905 cwist_conn_remove_at_locked(
i);
916static void cwist_conn_registry_atexit(
void) {
917 cwist_conn_close_owned(
NULL);
924static void cwist_conn_atfork_prepare(
void) {
928static void cwist_conn_atfork_parent(
void) {
932static void cwist_conn_atfork_child(
void) {
933 if (g_conn_slots)
memset(g_conn_slots, 0, g_conn_cap *
sizeof(*g_conn_slots));
939static void cwist_conn_owner_destroy(
void *arg) {
942 cwist_conn_close_owned(owner);
946static void cwist_conn_key_init(
void) {
954 if (owner || !
create)
return owner;
957 if (!owner)
return NULL;
967 atexit(cwist_conn_registry_atexit);
968 pthread_atfork(cwist_conn_atfork_prepare, cwist_conn_atfork_parent, cwist_conn_atfork_child);
985 if (!handle || !close_fn)
return;
991 size_t i = cwist_conn_find_locked(handle);
993 g_conn_slots[
i].close_fn = close_fn;
994 g_conn_slots[
i].owner = owner;
995 }
else if (cwist_conn_reserve_locked()) {
996 size_t mask = g_conn_cap - 1;
997 size_t j = cwist_conn_slot(handle,
mask);
998 while (g_conn_slots[
j].handle)
j = (
j + 1) &
mask;
999 g_conn_slots[
j] = (cwist_conn_entry_t){.handle = handle, .close_fn = close_fn, .owner = owner};
1011 if (!handle)
return false;
1013 size_t i = cwist_conn_find_locked(handle);
1014 if (
i !=
SIZE_MAX) cwist_conn_remove_at_locked(
i);
1028 if (!owner)
return false;
1030 size_t i = cwist_conn_find_locked(handle);
1031 if (
i !=
SIZE_MAX) g_conn_slots[
i].owner = owner;
1043 if (owner) cwist_conn_close_owned(owner);
1053 cwist_conn_close_owned(
NULL);
1062 if (!owner)
return 0;
1065 for (
size_t i = 0;
i < g_conn_cap;
i++) {
1066 if (g_conn_slots[
i].handle && g_conn_slots[
i].owner == owner)
n++;
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
__attribute__((constructor))
Map the guard page before main() runs, so cwist_full_gc_enabled() – called on every cwist_alloc()/cwi...
void cwist_full_gc(bool enable)
Claim full-GC mode permanently, exactly once, then harden the toggle.
ttak_epoch_gc_t * cwist_gc_raw(cwist_gc_t *gc)
Expose the underlying libttak epoch GC structure for advanced integrations.
#define CWIST_GC_PENDING_KEEP_CAP
void cwist_gc_auto_rotate(cwist_gc_t *gc, bool enabled)
Enable auto-rotation of epoch GC.
void cwist_conn_registry_sweep_all(void)
Close every tracked handle, whichever thread owns it.
bool cwist_conn_registry_untrack(void *handle)
Remove a handle from the registry, whichever thread owns it.
void cwist_ebr_free(void *ptr)
Defer release of a block until every thread that might still be reading it (inside ttak_epoch_enter()...
#define CWIST_GC_PENDING_MIN_CAP
bool cwist_gc_scope_disown(void *ptr)
Alias of cwist_gc_scope_untrack(): drop tracking without retiring.
void cwist_reg_ptr(cwist_gc_t *gc, void *ptr)
Register a pointer with zero-size accounting metadata.
bool cwist_full_gc_enabled(void)
Query whether full-GC mode was enabled by the (single) cwist_full_gc() claim.
struct cwist_conn_owner cwist_conn_owner_t
bool cwist_gc_reg_untrack(void *ptr)
Detach ptr from whichever GC context tracks it, without freeing.
void cwist_release_guard_init(cwist_release_guard_t *guard)
Reset a release guard to its unclaimed state.
bool cwist_conn_registry_adopt(void *handle)
Make the calling thread the owner of a tracked handle.
bool cwist_gc_auto_rotated(cwist_gc_t *gc)
Return if epoch GC is auto-rotated. @ param gc GC context to get status.
void cwist_gc_shutdown(cwist_gc_t *gc)
Destroy the wrapped libttak GC state when it has been initialized.
bool cwist_release_guard_acquire(cwist_release_guard_t *guard)
Claim a release guard.
void cwist_gc_rotate(cwist_gc_t *gc)
Advance the current epoch and reclaim retired nodes when eligible.
void cwist_gc_scope_flush(void)
Retire every pending block this thread is still tracking.
size_t cwist_conn_registry_pending_count(void)
Count the tracked handles the calling thread owns.
void cwist_gc_pipeline_tick(void)
Advance the EBR epoch and run any callbacks that are now safe.
void cwist_conn_registry_flush(void)
Close every handle the calling thread owns, right now.
bool cwist_gc_scope_untrack(void *ptr)
Stop tracking a block without retiring it.
bool cwist_full_gc_locked(void)
Query whether the full-GC toggle has already been claimed and locked.
void cwist_reg_ptr_sized(cwist_gc_t *gc, void *ptr, size_t size)
Register a pointer and its approximate size with the epoch GC.
void cwist_conn_registry_track(void *handle, cwist_conn_close_fn close_fn)
Track a non-memory resource for the full-GC exit sweep.
void * cwist_ebr_alloc(size_t size)
Allocate a block that will later be released through cwist_ebr_free().
size_t cwist_gc_scope_pending_count(void)
Count this thread's not-yet-retired tracked blocks.
void cwist_gc_scope_track(void *ptr)
Track a live cwist_alloc() block so full-GC can retire it later.
void * cwist_full_gc_guard_page(void)
Expose the hardened full-GC guard page for introspection/tests.
Epoch-based garbage collection wrapper.
void(* cwist_conn_close_fn)(void *handle)
Full-GC's connection registry: thread/process-exit sweep for resources that aren't plain cwist_alloc(...
atomic_bool cwist_release_guard_t
One-shot guard ensuring a shared teardown path runs exactly once.
High-level wrapper over libttak's epoch GC.
_Atomic bool auto_rotated
Check if GC should scan and reclaim objects.
bool initialized
Check if CWIST GC has initialized.