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