From e9f0eba4b9666c033124daaf967b01e732e5b09c Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Fri, 5 Sep 2025 20:01:36 -0700 Subject: [PATCH] Change to C99 and add weak references --- CMakeLists.txt | 2 +- README.md | 6 +- include/refcount/allocator.h | 32 ++++---- include/refcount/refcount.h | 139 +++++++++++++++++++++++++++++++++-- src/allocator.c | 4 +- src/refcount.c | 122 +++++++++++++++++++++++++----- test/alloc.h | 6 +- test/test_refcount.c | 36 +++++++++ 8 files changed, 296 insertions(+), 51 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 26ea3f6..9054188 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.11) -set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD 99) project( refcount diff --git a/README.md b/README.md index 41eb284..3470fe1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # RefCount RefCount is a reference counting (thus the name) and garbage collection library -for C. It is currently WIP and unfinished. +written in standard C99. ### Building @@ -23,5 +23,5 @@ You can generate documentation with: doxygen ``` -For now, you can look in the `test/` directory for some examples of the various -features of RefCount in use. +You can look in the `test/` directory for some examples of the various features +of RefCount in use. diff --git a/include/refcount/allocator.h b/include/refcount/allocator.h index d850872..5645083 100644 --- a/include/refcount/allocator.h +++ b/include/refcount/allocator.h @@ -22,19 +22,19 @@ extern "C" { */ typedef struct RefcountAllocator { union { - void *(*malloc)(size_t size); //!< malloc implementation which takes no - //!< data. - void *(*malloc_with_data)( - size_t size, - void *user_data); //!< malloc implementation that takes a data - //!< argument. - }; + void *(*no_data)(size_t size); //!< malloc implementation which takes no + //!< data. + void *(*with_data)(size_t size, + void *user_data); //!< malloc implementation that + //!< takes a data argument. + } malloc; union { - void (*free)(void *ptr); //!< free implementation which takes no data. - void (*free_with_data)(void *ptr, - void *user_data); //!< free implementation that - //!< takes a data argument. - }; + void (*no_data)( + void *ptr); //!< free implementation which takes no data. + void (*with_data)(void *ptr, + void *user_data); //!< free implementation that + //!< takes a data argument. + } free; bool pass_data; void *user_data; } RefcountAllocator; @@ -50,9 +50,9 @@ extern const RefcountAllocator *refcount_global_allocator; static inline void *refcount_malloc(const RefcountAllocator *alloc, size_t size) { if (alloc->pass_data) { - return alloc->malloc_with_data(size, alloc->user_data); + return alloc->malloc.with_data(size, alloc->user_data); } - return alloc->malloc(size); + return alloc->malloc.no_data(size); } /** @@ -62,9 +62,9 @@ static inline void *refcount_malloc(const RefcountAllocator *alloc, */ static inline void refcount_free(const RefcountAllocator *alloc, void *ptr) { if (alloc->pass_data) { - alloc->free_with_data(ptr, alloc->user_data); + alloc->free.with_data(ptr, alloc->user_data); } else { - alloc->free(ptr); + alloc->free.no_data(ptr); } } diff --git a/include/refcount/refcount.h b/include/refcount/refcount.h index 8e898dc..d6e1b7d 100644 --- a/include/refcount/refcount.h +++ b/include/refcount/refcount.h @@ -8,6 +8,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -103,16 +104,28 @@ void refcount_context_destroy(RefcountContext *ctx); */ typedef struct RefcountEntry { bool is_static; //!< Whether the object is static. + RefcountList *weak_refs; //is_static; } @@ -141,12 +155,12 @@ static inline bool refcount_context_is_static(RefcountContext *ctx, void *obj) { * @param obj The object * @return The number of references the object has */ -static inline size_t refcount_context_num_refs(RefcountContext *ctx, +static inline size_t refcount_context_num_refs(const RefcountContext *ctx, void *obj) { - return REFCOUNT_OBJECT_ENTRY(ctx, obj)->ref_count; + return REFCOUNT_OBJECT_ENTRY(ctx, obj)->impl.counted.ref_count; } -void refcount_context_init_obj(RefcountContext *ctx, void *obj); +void refcount_context_init_obj(const RefcountContext *ctx, void *obj); bool refcount_context_init_static(RefcountContext *ctx, void *obj); @@ -163,7 +177,7 @@ static inline void refcount_context_deinit_static_as_callback(void *obj, refcount_context_deinit_static(ctx, obj); } -void *refcount_context_ref(RefcountContext *ctx, void *obj); +void *refcount_context_ref(const RefcountContext *ctx, void *obj); void *refcount_context_unref(RefcountContext *ctx, void *obj); @@ -180,6 +194,58 @@ void *refcount_context_float(RefcountContext *ctx, void *obj); ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx); +RefcountWeakref *refcount_context_make_weakref(const RefcountContext *ctx, + void *obj); + +void refcount_context_destroy_weakref(const RefcountContext *ctx, + RefcountWeakref *wr); + +/** + * Return weather the object referenced by a weak reference still exists. + * @param ctx The #RefcountContext + * @param wr The weak reference + * @return Weather the reference is still valid + */ +static inline bool refcount_context_weakref_is_valid(const RefcountContext *ctx, + RefcountWeakref *wr) { + return wr->data; +} + +void *refcount_context_ref_weakref(const RefcountContext *ctx, + const RefcountWeakref *wr); + +/** + * Convenience function that calls #refcount_context_ref_weakref followed by + * #refcount_context_destroy_weakref. + * @param ctx The #RefcountContext + * @param wr The weak reference + * @return The newly referenced object, or NULL if the object no longer exists + */ +static inline void *refcount_context_strengthen(const RefcountContext *ctx, + RefcountWeakref *wr) { + void *obj = refcount_context_ref_weakref(ctx, wr); + refcount_context_destroy_weakref(ctx, wr); + return obj; +} + +/** + * Create a weak reference for an object and then unref it. This is the same as + * calling #refcount_context_make_weakref followed by #refcount_context_unref. + * @param ctx The #RefcountContext + * @param obj The object to create a weak reference for then unref + * @return The newly created weak reference, or NULL if an allocation error + * occurred. In this case the object is not unrefed. + */ +static inline RefcountWeakref *refcount_context_weaken(RefcountContext *ctx, + void *obj) { + RefcountWeakref *wr = refcount_context_make_weakref(ctx, obj); + if (!wr) { + return NULL; + } + refcount_context_unref(ctx, obj); + return wr; +} + /** * Same as #refcount_context_is_static, but only operates on the global * context. @@ -273,6 +339,63 @@ static inline ptrdiff_t refcount_garbage_collect(void) { return refcount_context_garbage_collect(refcount_default_context); } +/** + * Same as #refcount_context_make_weakref, but operates on the global context. + * @param obj The object to reference + * @return The new weak reference, or NULL if an allocation error occurred + */ +static inline RefcountWeakref *refcount_make_weakref(void *obj) { + return refcount_context_make_weakref(refcount_default_context, obj); +} + +/** + * Same as #refcount_context_destroy_weakref, but operates on the global + * context. + * @param wr The weak reference + */ +static inline void refcount_destroy_weakref(RefcountWeakref *wr) { + refcount_context_destroy_weakref(refcount_default_context, wr); +} + +/** + * Same as #refcount_context_ref_weakref, but operates on the global context. + * @param wr The weak reference + * @return The newly referenced object, or NULL if the object no longer exists + */ +static inline void *refcount_ref_weakref(RefcountWeakref *wr) { + return refcount_context_ref_weakref(refcount_default_context, wr); +} + +/** + * Same as #refcount_context_weakref_is_valid, but operates on the global + * context. + * @param wr The weak reference + * @return Weather the reference is still valid + */ +static inline bool refcount_weakref_is_valid(RefcountWeakref *wr) { + return refcount_context_weakref_is_valid(refcount_default_context, wr); +} + +/** + * Same as #refcount_context_strengthen, but operates on the global context. + * @param wr The weak reference + * @return The newly referenced object, or NULL if the object no longer + * exists + */ +static inline void *refcount_strengthen(RefcountWeakref *wr) { + return refcount_context_strengthen(refcount_default_context, wr); +} + +/** + * Same as #refcount_context_weaken, but operates ojn the global context. + * @param obj The object to create a weak reference for then unref + * @return The newly created weak reference, or NULL if an allocation error + * occurred. In this case the object is not unrefed. + */ +static inline RefcountWeakref *refcount_weaken(void *obj) { + return refcount_context_weaken(refcount_default_context, obj); +} + #ifdef __cplusplus } #endif diff --git a/src/allocator.c b/src/allocator.c index cb1d732..1218620 100644 --- a/src/allocator.c +++ b/src/allocator.c @@ -10,8 +10,8 @@ * Initial value of #refcount_global_allocator. */ static const RefcountAllocator DEFAULT_ALLOCATOR = { - .malloc = malloc, - .free = free, + .malloc.no_data = malloc, + .free.no_data = free, .pass_data = false, .user_data = NULL, }; diff --git a/src/refcount.c b/src/refcount.c index 4a56698..108a947 100644 --- a/src/refcount.c +++ b/src/refcount.c @@ -94,11 +94,12 @@ void refcount_context_destroy(RefcountContext *ctx) { * @param ctx The #RefcountContext * @param obj The object to initialize */ -void refcount_context_init_obj(RefcountContext *ctx, void *obj) { +void refcount_context_init_obj(const RefcountContext *ctx, void *obj) { if (obj) { ENTRY->is_static = false; - ENTRY->gc_root = NULL; - ENTRY->ref_count = 0; + ENTRY->impl.counted.gc_root = NULL; + ENTRY->impl.counted.ref_count = 0; + ENTRY->weak_refs = NULL; } } @@ -115,7 +116,8 @@ bool refcount_context_init_static(RefcountContext *ctx, void *obj) { if (!ctx->static_objects) { return false; } - ENTRY->static_entry = ctx->static_objects; + ENTRY->impl.static_entry = ctx->static_objects; + ENTRY->weak_refs = NULL; return true; } @@ -134,6 +136,27 @@ static inline bool obj_held_refs(RefcountContext *ctx, void *obj, return true; } +/** + * Call back used from #clear_weak_references. + * @param wr_raw The #RefcountWeakref object + */ +static void clear_weak_reference_callback(void *wr_raw) { + RefcountWeakref *wr = wr_raw; + wr->entry = NULL; + wr->data = NULL; +} + +/** + * Clear all weak references of an object without freeing them. + * @param ctx The #RefcountContext + * @param obj The object to clear + */ +static inline void clear_weak_references(const RefcountContext *ctx, + void *obj) { + refcount_list_free_full(ENTRY->weak_refs, clear_weak_reference_callback, + &ctx->alloc); +} + /** * Unregister a static object in a context. * @param ctx The #RefcountContext @@ -146,8 +169,9 @@ bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) { if (!obj_held_refs(ctx, obj, &held_refs)) { return false; } + clear_weak_references(ctx, obj); ctx->static_objects = refcount_list_remove_full( - ctx->static_objects, ENTRY->static_entry, NULL, &ctx->alloc); + ctx->static_objects, ENTRY->impl.static_entry, NULL, &ctx->alloc); refcount_list_free_with_data_full( held_refs, refcount_context_unref_as_callback, ctx, &ctx->alloc); return true; @@ -163,11 +187,11 @@ bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) { * @param obj The object to reference * @return The input object */ -void *refcount_context_ref(RefcountContext *ctx, void *obj) { +void *refcount_context_ref(const RefcountContext *ctx, void *obj) { if (!obj) { return NULL; } else if (!ENTRY->is_static) { - ++ENTRY->ref_count; + ++ENTRY->impl.counted.ref_count; } return obj; } @@ -180,13 +204,13 @@ void *refcount_context_ref(RefcountContext *ctx, void *obj) { * @return True on success */ static bool track_gc_root(RefcountContext *ctx, void *obj) { - if (!ENTRY->gc_root) { + if (!ENTRY->impl.counted.gc_root) { ctx->gc_roots = refcount_list_push_full(ctx->gc_roots, obj, &ctx->alloc); if (!ctx->gc_roots) { return false; } - ENTRY->gc_root = ctx->gc_roots; + ENTRY->impl.counted.gc_root = ctx->gc_roots; } return true; } @@ -198,9 +222,9 @@ static bool track_gc_root(RefcountContext *ctx, void *obj) { * @param obj The object to untrack */ static void remove_gc_root(RefcountContext *ctx, void *obj) { - ctx->gc_roots = refcount_list_remove_full(ctx->gc_roots, ENTRY->gc_root, - NULL, &ctx->alloc); - ENTRY->gc_root = NULL; + ctx->gc_roots = refcount_list_remove_full( + ctx->gc_roots, ENTRY->impl.counted.gc_root, NULL, &ctx->alloc); + ENTRY->impl.counted.gc_root = NULL; } /** @@ -222,11 +246,11 @@ static void *unref_to_queue(RefcountContext *ctx, void *obj, return obj; } else if (obj == toplevel) { return NULL; - } else if (ENTRY->ref_count <= 1) { + } else if (ENTRY->impl.counted.ref_count <= 1) { *queue = refcount_list_push_full(*queue, obj, &ctx->alloc); return NULL; } - --ENTRY->ref_count; + --ENTRY->impl.counted.ref_count; track_gc_root(ctx, obj); return obj; } @@ -258,6 +282,7 @@ static void unref_to_queue_as_callback(void *obj, void *ctx_and_queue_raw) { * @param obj The object to destroy */ static inline void destroy_object(RefcountContext *ctx, void *obj) { + clear_weak_references(ctx, obj); remove_gc_root(ctx, obj); if (ctx->destroy_callback) { ctx->destroy_callback(obj, ctx->user_data); @@ -326,11 +351,11 @@ void *refcount_context_float(RefcountContext *ctx, void *obj) { return NULL; } else if (ENTRY->is_static) { return obj; - } else if (!ENTRY->ref_count) { + } else if (!ENTRY->impl.counted.ref_count) { BREAKPOINT(FLOAT, ctx, obj); return obj; } - if (--ENTRY->ref_count) { + if (--ENTRY->impl.counted.ref_count) { track_gc_root(ctx, obj); } else { remove_gc_root(ctx, obj); @@ -371,7 +396,8 @@ static bool free_roots_foreach(void *obj, void *ignored, void *user_data) { } struct ContextAndRootPtr *data = user_data; if (*data->root_ptr - && *data->root_ptr == REFCOUNT_OBJECT_ENTRY(data->ctx, obj)->gc_root) { + && *data->root_ptr + == REFCOUNT_OBJECT_ENTRY(data->ctx, obj)->impl.counted.gc_root) { *data->root_ptr = (*data->root_ptr)->next; data->did_update = true; } @@ -413,7 +439,7 @@ static ptrdiff_t check_gc_root(RefcountContext *ctx, RefcountList **root_ptr) { if (ht_has(counts, obj)) { count = HT_UUNSTUFF(ht_get(counts, obj)); } else { - count = REFCOUNT_OBJECT_ENTRY(ctx, obj)->ref_count; + count = REFCOUNT_OBJECT_ENTRY(ctx, obj)->impl.counted.ref_count; ++seen_objects; } queue = refcount_list_pop_full(queue, NULL, &ctx->alloc); @@ -460,3 +486,63 @@ ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) { } return total_cleared; } + +/** + * Create a new weak reference for an object. A weak reference will allow safe + * access to the referenced object without holding a reference. That is, the + * referenced object can be accessed until it's reference count falls to 0 and + * it is freed. After this, attempts to use the weak reference will just return + * NULL to indicate that the referenced object is no longer in existence. + * @param ctx The #RefcountContext + * @param obj The object for which to create a weak reference + * @return The newly created weak reference, or NULL if an allocated error + * occurred. + */ +RefcountWeakref *refcount_context_make_weakref(const RefcountContext *ctx, + void *obj) { + RefcountWeakref *wr = refcount_malloc(&ctx->alloc, sizeof(RefcountWeakref)); + if (!wr) { + return NULL; + } + RefcountList *new_weak_refs = + refcount_list_push_full(ENTRY->weak_refs, wr, &ctx->alloc); + if (!new_weak_refs) { + refcount_free(&ctx->alloc, wr); + return NULL; + } + ENTRY->weak_refs = new_weak_refs; + wr->entry = new_weak_refs; + wr->data = obj; + return wr; +} + +/** + * Destroy a weak reference. This has no effect on the reference count of the + * original object. + * @param ctx The #RefcountContext + * @param wr The weak reference + */ +void refcount_context_destroy_weakref(const RefcountContext *ctx, + RefcountWeakref *wr) { + if (wr->data) { + void *obj = wr->data; + ENTRY->weak_refs = refcount_list_remove_full( + ENTRY->weak_refs, wr->entry, NULL, &ctx->alloc); + } + refcount_free(&ctx->alloc, wr); +} + +/** + * Add a reference to an object referenced by a weak reference and return the + * object. If the referenced object no longer exists, return NULL. + * @param ctx The #RefcountContext + * @param wr The weak reference + * @return The newly referenced object, or NULL + */ +void *refcount_context_ref_weakref(const RefcountContext *ctx, + const RefcountWeakref *wr) { + if (!wr->data) { + return NULL; + } + return refcount_context_ref(ctx, wr->data); +} diff --git a/test/alloc.h b/test/alloc.h index afe9e18..5cd953e 100644 --- a/test/alloc.h +++ b/test/alloc.h @@ -8,7 +8,7 @@ #include #include -#if __has_attribute(unused) +#if defined(__has_attribute) && __has_attribute(unused) # define UNUSED __attribute__((unused)) #else # define UNUSED @@ -67,8 +67,8 @@ static UNUSED char *counting_strdup(const char *str) { } static UNUSED const RefcountAllocator COUNTING_ALLOCATOR = { - .malloc = counting_malloc, - .free = counting_free, + .malloc.no_data = counting_malloc, + .free.no_data = counting_free, }; #endif diff --git a/test/test_refcount.c b/test/test_refcount.c index 38b496b..559f616 100644 --- a/test/test_refcount.c +++ b/test/test_refcount.c @@ -113,6 +113,42 @@ int main(int argc, const char **argv) { assert(refcount_context_garbage_collect(c) == 26); + a = make_a(c, 0, "a"); + + RefcountWeakref *w = refcount_context_make_weakref(c, a); + assert(w); + assert(refcount_context_num_refs(c, a) == 0); + + b = refcount_context_strengthen(c, w); + assert(b); + assert(a == b); + assert(refcount_context_num_refs(c, a) == 1); + + refcount_context_ref(c, a); + assert(refcount_context_num_refs(c, a) == 2); + + RefcountWeakref *x = refcount_context_weaken(c, a); + w = refcount_context_make_weakref(c, a); + assert(refcount_context_num_refs(c, a) == 1); + assert(w != x); + refcount_context_destroy_weakref(c, x); + + refcount_context_ref(c, a); + assert(refcount_context_num_refs(c, a) == 2); + + x = refcount_context_weaken(c, a); + assert(refcount_context_num_refs(c, a) == 1); + assert(w != x); + assert(refcount_context_weakref_is_valid(c, w)); + assert(refcount_context_weakref_is_valid(c, x)); + + assert(!refcount_context_unref(c, a)); + assert(!refcount_context_weakref_is_valid(c, w)); + assert(!refcount_context_weakref_is_valid(c, x)); + + refcount_context_destroy_weakref(c, w); + refcount_context_destroy_weakref(c, x); + refcount_context_destroy(c); check_allocator_status();