Update documentation
This commit is contained in:
		| @ -178,7 +178,7 @@ static inline void refcount_context_unref_as_callback(void *obj, void *ctx) { | |||||||
| } | } | ||||||
| void *refcount_context_float(RefcountContext *ctx, void *obj); | 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 |  * 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 |  * Same as #refcount_context_garbage_collect, but only operates on the globa | ||||||
|  * context. |  * 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); |     return refcount_context_garbage_collect(refcount_default_context); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ const RefcountAllocator *refcount_global_allocator = &DEFAULT_ALLOCATOR; | |||||||
| /** | /** | ||||||
|  * HTAllocator malloc function that delegates to #refcount_malloc. |  * HTAllocator malloc function that delegates to #refcount_malloc. | ||||||
|  * @param size The number of bytes to allocate |  * @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 |  * @return The result of #refcount_malloc | ||||||
|  */ |  */ | ||||||
| static void *refcount_ht_malloc(size_t size, void *alloc) { | 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. |  * HTAllocator free function that delegates to #refcount_free. | ||||||
|  * @param ptr The pointer to 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) { | static void refcount_ht_free(void *ptr, void *alloc) { | ||||||
|     refcount_free(alloc, ptr); |     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. |  * Create a new HTAllocator that delegates to src. | ||||||
|  * @param src The #RefcountAllocator to delegate to |  * @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, | void refcount_allocator_to_ht_allocator(const RefcountAllocator *src, | ||||||
|                                         HTAllocator *dest) { |                                         HTAllocator *dest) { | ||||||
|  | |||||||
| @ -40,6 +40,8 @@ RefcountContext *refcount_default_context = NULL; | |||||||
|  * object |  * object | ||||||
|  * @param destroy_callback A function to be called when an objects reference |  * @param destroy_callback A function to be called when an objects reference | ||||||
|  * count drops to zero |  * 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 |  * @return The new context, or NULL in the case of an error | ||||||
|  */ |  */ | ||||||
| RefcountContext * | RefcountContext * | ||||||
| @ -214,7 +216,9 @@ static void remove_gc_root(RefcountContext *ctx, void *obj) { | |||||||
|  */ |  */ | ||||||
| static void *unref_to_queue(RefcountContext *ctx, void *obj, | static void *unref_to_queue(RefcountContext *ctx, void *obj, | ||||||
|                             RefcountList **queue, void *toplevel) { |                             RefcountList **queue, void *toplevel) { | ||||||
|     if (ENTRY->is_static) { |     if (!obj) { | ||||||
|  |         return NULL; | ||||||
|  |     } else if (ENTRY->is_static) { | ||||||
|         return obj; |         return obj; | ||||||
|     } else if (obj == toplevel) { |     } else if (obj == toplevel) { | ||||||
|         return NULL; |         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 = { | static const HTTableFunctions ROOT_COUNTS_FNS = { | ||||||
|     .hash = ht_intptr_hash_callback, |     .hash = ht_intptr_hash_callback, | ||||||
| @ -345,15 +349,29 @@ static const HTTableFunctions ROOT_COUNTS_FNS = { | |||||||
|     .user_data = NULL, |     .user_data = NULL, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Holds data for #free_roots_foreach, used in #check_gc_root. | ||||||
|  |  */ | ||||||
| struct ContextAndRootPtr { | struct ContextAndRootPtr { | ||||||
|     RefcountContext *ctx; |     RefcountContext *ctx; //!< The context. | ||||||
|     RefcountList **root_ptr; |     RefcountList **root_ptr; //!< Double pointer to the root. | ||||||
|     bool did_update; |     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) { | static bool free_roots_foreach(void *obj, void *ignored, void *user_data) { | ||||||
|  |     if (!obj) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|     struct ContextAndRootPtr *data = user_data; |     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->root_ptr = (*data->root_ptr)->next; | ||||||
|         data->did_update = true; |         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. |  * root_ptr is set to the next root to be checked. | ||||||
|  * @param ctx The context |  * @param ctx The context | ||||||
|  * @param root_ptr Double pointer to one of the GC roots |  * @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); |     HTTable *counts = ht_new(&ROOT_COUNTS_FNS, &ctx->ht_alloc, NULL); | ||||||
|     if (!counts) { |     if (!counts) { | ||||||
|         *root_ptr = (*root_ptr)->next; |         *root_ptr = (*root_ptr)->next; | ||||||
|         return false; |         return -1; | ||||||
|     } |     } | ||||||
|     RefcountList *root = *root_ptr; |     RefcountList *root = *root_ptr; | ||||||
|     RefcountList *queue = NULL; |     RefcountList *queue = NULL; | ||||||
|     if (!obj_held_refs(ctx, root->data, &queue)) { |     if (!obj_held_refs(ctx, root->data, &queue)) { | ||||||
|         ht_free(counts); |         ht_free(counts); | ||||||
|         *root_ptr = (*root_ptr)->next; |         *root_ptr = (*root_ptr)->next; | ||||||
|         return false; |         return -1; | ||||||
|     } |     } | ||||||
|     size_t seen_objects = 0; |     size_t seen_objects = 0; | ||||||
|     size_t clear_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) { |     while (queue) { | ||||||
|         void *obj = queue->data; |         void *obj = queue->data; | ||||||
|         if (!obj || refcount_context_is_static(ctx, obj)) { |         if (!obj || refcount_context_is_static(ctx, obj)) { | ||||||
|             goto next; |             queue = refcount_list_pop_full(queue, NULL, &ctx->alloc); | ||||||
|  |             continue; | ||||||
|         } |         } | ||||||
|         uintptr_t count; |         uintptr_t count; | ||||||
|         if (ht_has(counts, obj)) { |         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; |             count = REFCOUNT_OBJECT_ENTRY(ctx, obj)->ref_count; | ||||||
|             ++seen_objects; |             ++seen_objects; | ||||||
|         } |         } | ||||||
|  |         queue = refcount_list_pop_full(queue, NULL, &ctx->alloc); | ||||||
|         if (count > 0) { |         if (count > 0) { | ||||||
|             ht_insert(counts, obj, HT_STUFF(--count)); |             ht_insert(counts, obj, HT_STUFF(--count)); | ||||||
|             if (count == 0) { |             if (count == 0) { | ||||||
|                 ++clear_objects; |                 ++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) { |     if (seen_objects == clear_objects) { | ||||||
|         struct ContextAndRootPtr data = { |         struct ContextAndRootPtr data = { | ||||||
| @ -417,24 +436,27 @@ static bool check_gc_root(RefcountContext *ctx, RefcountList **root_ptr) { | |||||||
|         *root_ptr = (*root_ptr)->next; |         *root_ptr = (*root_ptr)->next; | ||||||
|     } |     } | ||||||
|     ht_free(counts); |     ht_free(counts); | ||||||
|     return true; |     return seen_objects == clear_objects ? seen_objects : 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Run the garbage collector on a context. |  * Run the garbage collector on a context. | ||||||
|  * @param cts The #RefcountContext |  * @param ctx The #RefcountContext | ||||||
|  * @return False if an error occurred, true otherwise |  * @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) { |     if (!ctx->held_refs_callback) { | ||||||
|         // no loops possible |         // no loops possible | ||||||
|         return true; |         return 0; | ||||||
|     } |     } | ||||||
|  |     ptrdiff_t total_cleared = 0; | ||||||
|     RefcountList *root = ctx->gc_roots; |     RefcountList *root = ctx->gc_roots; | ||||||
|     while (root) { |     while (root) { | ||||||
|         if (!check_gc_root(ctx, &root)) { |         ptrdiff_t res = check_gc_root(ctx, &root); | ||||||
|             return false; |         if (res < 0) { | ||||||
|  |             return -1; | ||||||
|         } |         } | ||||||
|  |         total_cleared += res; | ||||||
|     } |     } | ||||||
|     return true; |     return total_cleared; | ||||||
| } | } | ||||||
|  | |||||||
| @ -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) { | bool held_refs_callback(void *a_raw, RefcountList **out, void *ignored) { | ||||||
|     A *a = a_raw; |     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; |     return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -73,11 +71,47 @@ int main(int argc, const char **argv) { | |||||||
|     a->next = refcount_context_ref(c, a); |     a->next = refcount_context_ref(c, a); | ||||||
|     assert(refcount_context_num_refs(c, a) == 1); |     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_ref(c, a); | ||||||
|     refcount_context_unref(c, a); |     refcount_context_unref(c, a); | ||||||
|     assert(refcount_context_num_refs(c, a) == 1); |     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); |     refcount_context_destroy(c); | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user