From 4efdcc97ae3abbbfc3eb974a7880ff59522b379f Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Sun, 31 Aug 2025 00:34:08 -0700 Subject: [PATCH] Update documentation --- include/refcount/refcount.h | 6 ++-- src/allocator.c | 6 ++-- src/refcount.c | 68 ++++++++++++++++++++++++------------- test/test_refcount.c | 42 ++++++++++++++++++++--- 4 files changed, 89 insertions(+), 33 deletions(-) diff --git a/include/refcount/refcount.h b/include/refcount/refcount.h index 88b840f..8e898dc 100644 --- a/include/refcount/refcount.h +++ b/include/refcount/refcount.h @@ -178,7 +178,7 @@ static inline void refcount_context_unref_as_callback(void *obj, void *ctx) { } void *refcount_context_float(RefcountContext *ctx, void *obj); -bool refcount_context_garbage_collect(RefcountContext *ctx); +ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx); /** * Same as #refcount_context_is_static, but only operates on the global @@ -267,9 +267,9 @@ static inline void *refcount_float(void *obj) { /** * Same as #refcount_context_garbage_collect, but only operates on the globa * context. - * @return False if an error occured, true otherwise + * @return The number of object's freed, or -1 if an error occurred */ -static inline bool refcount_garbage_collect(void) { +static inline ptrdiff_t refcount_garbage_collect(void) { return refcount_context_garbage_collect(refcount_default_context); } diff --git a/src/allocator.c b/src/allocator.c index 844f130..cb1d732 100644 --- a/src/allocator.c +++ b/src/allocator.c @@ -25,7 +25,7 @@ const RefcountAllocator *refcount_global_allocator = &DEFAULT_ALLOCATOR; /** * HTAllocator malloc function that delegates to #refcount_malloc. * @param size The number of bytes to allocate - * @parma alloc The #RefcountAllocator to use + * @param alloc The #RefcountAllocator to use * @return The result of #refcount_malloc */ static void *refcount_ht_malloc(size_t size, void *alloc) { @@ -35,7 +35,7 @@ static void *refcount_ht_malloc(size_t size, void *alloc) { /** * HTAllocator free function that delegates to #refcount_free. * @param ptr The pointer to free - * @parma alloc The #RefcountAllocator to use + * @param alloc The #RefcountAllocator to use */ static void refcount_ht_free(void *ptr, void *alloc) { refcount_free(alloc, ptr); @@ -44,7 +44,7 @@ static void refcount_ht_free(void *ptr, void *alloc) { /** * Create a new HTAllocator that delegates to src. * @param src The #RefcountAllocator to delegate to - * @param dest A pointer to a #HTAllocator to initialize + * @param dest A pointer to a HTAllocator to initialize */ void refcount_allocator_to_ht_allocator(const RefcountAllocator *src, HTAllocator *dest) { diff --git a/src/refcount.c b/src/refcount.c index d4a662d..4a56698 100644 --- a/src/refcount.c +++ b/src/refcount.c @@ -40,6 +40,8 @@ RefcountContext *refcount_default_context = NULL; * object * @param destroy_callback A function to be called when an objects reference * count drops to zero + * @param user_data Extra data to pass to the callbacks + * @param alloc The #RefcountAllocator to use for all internal allocations * @return The new context, or NULL in the case of an error */ RefcountContext * @@ -214,7 +216,9 @@ static void remove_gc_root(RefcountContext *ctx, void *obj) { */ static void *unref_to_queue(RefcountContext *ctx, void *obj, RefcountList **queue, void *toplevel) { - if (ENTRY->is_static) { + if (!obj) { + return NULL; + } else if (ENTRY->is_static) { return obj; } else if (obj == toplevel) { return NULL; @@ -335,7 +339,7 @@ void *refcount_context_float(RefcountContext *ctx, void *obj) { } /** - * Set of hash table functions used in #gc_check_root. + * Set of hash table functions used in #check_gc_root. */ static const HTTableFunctions ROOT_COUNTS_FNS = { .hash = ht_intptr_hash_callback, @@ -345,15 +349,29 @@ static const HTTableFunctions ROOT_COUNTS_FNS = { .user_data = NULL, }; +/** + * Holds data for #free_roots_foreach, used in #check_gc_root. + */ struct ContextAndRootPtr { - RefcountContext *ctx; - RefcountList **root_ptr; - bool did_update; + RefcountContext *ctx; //!< The context. + RefcountList **root_ptr; //!< Double pointer to the root. + bool did_update; //!< Weather or not *root_ptr was changed. }; +/** + * Foreach function to free roots from a hash table. Used in #check_gc_root. + * @param obj The object to free + * @param ignored Ignored + * @param user_data A #ContextAndRootPtr + * @return Always false + */ static bool free_roots_foreach(void *obj, void *ignored, void *user_data) { + if (!obj) { + return false; + } struct ContextAndRootPtr *data = user_data; - if (*data->root_ptr == REFCOUNT_OBJECT_ENTRY(data->ctx, obj)->gc_root) { + if (*data->root_ptr + && *data->root_ptr == REFCOUNT_OBJECT_ENTRY(data->ctx, obj)->gc_root) { *data->root_ptr = (*data->root_ptr)->next; data->did_update = true; } @@ -366,28 +384,30 @@ static bool free_roots_foreach(void *obj, void *ignored, void *user_data) { * root_ptr is set to the next root to be checked. * @param ctx The context * @param root_ptr Double pointer to one of the GC roots - * @return True on success + * @return The number of object's freed, or -1 if an error happened */ -static bool check_gc_root(RefcountContext *ctx, RefcountList **root_ptr) { +static ptrdiff_t check_gc_root(RefcountContext *ctx, RefcountList **root_ptr) { HTTable *counts = ht_new(&ROOT_COUNTS_FNS, &ctx->ht_alloc, NULL); if (!counts) { *root_ptr = (*root_ptr)->next; - return false; + return -1; } RefcountList *root = *root_ptr; RefcountList *queue = NULL; if (!obj_held_refs(ctx, root->data, &queue)) { ht_free(counts); *root_ptr = (*root_ptr)->next; - return false; + return -1; } size_t seen_objects = 0; size_t clear_objects = 0; - // ignore allocation errors until I decide how to deal with them (never) + // ignore allocation errors until I decide how to deal with them (in the far + // future) while (queue) { void *obj = queue->data; if (!obj || refcount_context_is_static(ctx, obj)) { - goto next; + queue = refcount_list_pop_full(queue, NULL, &ctx->alloc); + continue; } uintptr_t count; if (ht_has(counts, obj)) { @@ -396,15 +416,14 @@ static bool check_gc_root(RefcountContext *ctx, RefcountList **root_ptr) { count = REFCOUNT_OBJECT_ENTRY(ctx, obj)->ref_count; ++seen_objects; } + queue = refcount_list_pop_full(queue, NULL, &ctx->alloc); if (count > 0) { ht_insert(counts, obj, HT_STUFF(--count)); if (count == 0) { ++clear_objects; } - obj_held_refs(ctx, root->data, &queue); + obj_held_refs(ctx, obj, &queue); } - next: - queue = refcount_list_pop_full(queue, NULL, &ctx->alloc); } if (seen_objects == clear_objects) { struct ContextAndRootPtr data = { @@ -417,24 +436,27 @@ static bool check_gc_root(RefcountContext *ctx, RefcountList **root_ptr) { *root_ptr = (*root_ptr)->next; } ht_free(counts); - return true; + return seen_objects == clear_objects ? seen_objects : 0; } /** * Run the garbage collector on a context. - * @param cts The #RefcountContext - * @return False if an error occurred, true otherwise + * @param ctx The #RefcountContext + * @return The number of object's freed, or -1 if an error occurred */ -bool refcount_context_garbage_collect(RefcountContext *ctx) { +ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) { if (!ctx->held_refs_callback) { // no loops possible - return true; + return 0; } + ptrdiff_t total_cleared = 0; RefcountList *root = ctx->gc_roots; while (root) { - if (!check_gc_root(ctx, &root)) { - return false; + ptrdiff_t res = check_gc_root(ctx, &root); + if (res < 0) { + return -1; } + total_cleared += res; } - return true; + return total_cleared; } diff --git a/test/test_refcount.c b/test/test_refcount.c index c79d6d3..38b496b 100644 --- a/test/test_refcount.c +++ b/test/test_refcount.c @@ -21,9 +21,7 @@ A *make_a(RefcountContext *c, int n, const char *s) { bool held_refs_callback(void *a_raw, RefcountList **out, void *ignored) { A *a = a_raw; - if (a->next) { - *out = refcount_list_push_full(*out, a->next, &COUNTING_ALLOCATOR); - } + *out = refcount_list_push_full(*out, a->next, &COUNTING_ALLOCATOR); return true; } @@ -73,11 +71,47 @@ int main(int argc, const char **argv) { a->next = refcount_context_ref(c, a); assert(refcount_context_num_refs(c, a) == 1); + refcount_context_unref(c, a); + + a = NULL; + for (char i = 'a'; i <= 'z'; ++i) { + A *na = make_a(c, i, ""); + na->next = refcount_context_ref(c, a); + a = na; + } + assert(refcount_context_num_refs(c, a) == 0); + + refcount_context_unref(c, a); + + A *first = NULL; + a = NULL; + for (char i = 'a'; i <= 'z'; ++i) { + A *na = make_a(c, i, ""); + if (!first) { + first = na; + } + na->next = refcount_context_ref(c, a); + a = na; + } + assert(refcount_context_num_refs(c, a) == 0); + first->next = refcount_context_ref(c, a); + a = first; + assert(refcount_context_num_refs(c, a) == 1); + refcount_context_ref(c, a); refcount_context_unref(c, a); assert(refcount_context_num_refs(c, a) == 1); - refcount_context_garbage_collect(c); + a = NULL; + for (char i = 'a'; i <= 'z'; ++i) { + A *na = make_a(c, i, ""); + na->next = refcount_context_ref(c, a); + a = na; + } + assert(refcount_context_num_refs(c, a) == 0); + refcount_context_unref(c, a); + + assert(refcount_context_garbage_collect(c) == 26); refcount_context_destroy(c);