Initial garbage colletor implementation

This commit is contained in:
2025-08-30 21:21:02 -07:00
parent 80e3f1a916
commit d78cf29765
11 changed files with 709 additions and 214 deletions

View File

@ -4,6 +4,8 @@
#ifndef INCLUDED_REFCOUNT_ALLOCATOR_H
#define INCLUDED_REFCOUNT_ALLOCATOR_H
#include <ht.h>
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
@ -14,12 +16,27 @@ extern "C" {
* A set of the three main C standard allocators. Each member function should
* behave the same as its standard library counterpart. That is, for example,
* free should allow NULL as an input.
*
* To allow e.g. keeping track of allocations in different contexts, each
* function also takes a second user_data pointer argument.
*/
typedef struct RefcountAllocator {
void *(*malloc)(size_t); //!< malloc implementation for the allocator.
void *(*realloc)(void *,
size_t); //!< realloc implementation for the allocator.
void (*free)(void *); //!< free implementation for the allocator.
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.
};
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.
};
bool pass_data;
void *user_data;
} RefcountAllocator;
extern const RefcountAllocator *refcount_global_allocator;
@ -30,67 +47,29 @@ extern const RefcountAllocator *refcount_global_allocator;
* @param size The number of bytes to allocate
* @return The newly allocate pointer, or NULL if allocation failed
*/
static inline void *refcount_allocator_malloc(const RefcountAllocator *alloc,
size_t size) {
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(size);
}
/**
* Reallocate memory using a #RefcountAllocator.
* @param alloc The allocator to use
* @param old_ptr The pointer to reallocate
* @param size The number of bytes to allocate
* @return The resized pointer, a newly allocated pointer, or NULL if
* allocation failed
*/
static inline void *refcount_allocator_realloc(const RefcountAllocator *alloc,
void *old_ptr, size_t size) {
return alloc->realloc(old_ptr, size);
}
/**
* Free memory using a #RefcountAllocator.
* @param alloc The allocator to use
* @param ptr The pointer to free
*/
static inline void refcount_allocator_free(const RefcountAllocator *alloc,
void *ptr) {
alloc->free(ptr);
static inline void refcount_free(const RefcountAllocator *alloc, void *ptr) {
if (alloc->pass_data) {
alloc->free_with_data(ptr, alloc->user_data);
} else {
alloc->free(ptr);
}
}
/**
* Allocate memory using the global #RefcountAllocator.
* @param size The number of bytes to allocate
* @return The newly allocate pointer, or NULL if allocation failed
*
* @see refcount_allocator_malloc
*/
static inline void *refcount_malloc(size_t size) {
return refcount_allocator_malloc(refcount_global_allocator, size);
}
/**
* Reallocate memory using the global #RefcountAllocator.
* @param old_ptr The pointer to reallocate
* @param size The number of bytes to allocate
* @return The resized pointer, a newly allocated pointer, or NULL if
* allocation failed
*
* @see refcount_allocator_realloc
*/
static inline void *refcount_realloc(void *old_ptr, size_t size) {
return refcount_allocator_realloc(refcount_global_allocator, old_ptr, size);
}
/**
* Free memory using the global #RefcountAllocator.
* @param ptr The pointer to free
*
* @see refcount_allocator_free
*/
static inline void refcount_free(void *ptr) {
refcount_allocator_free(refcount_global_allocator, ptr);
}
void refcount_allocator_to_ht_allocator(const RefcountAllocator *src,
HTAllocator *dest);
#ifdef __cplusplus
}

View File

@ -5,6 +5,7 @@
#define INCLUDED_REFCOUNT_LIST_H
#include <refcount/allocator.h>
#include <stdarg.h>
#include <stddef.h> // for NULL
#ifdef __cplusplus
@ -24,42 +25,130 @@ typedef struct RefcountList {
/**
* Callback to used to free list elements.
* @param ptr The pointer to free
* @see refcount_list_free
*/
typedef void (*refcount_list_destroy_callback_t)(void *);
typedef void (*refcount_list_destroy_callback_t)(void *ptr);
/**
* Callback to used when looping over the list with extra data.
* @see refcount_list_free_with_data
* Callback to used to free list elements.
* @param ptr The pointer to free
* @param user_data Arbitrary user specified data
* @see refcount_list_free
*/
typedef void (*refcount_list_foreach_callback_t)(void *, void *);
typedef void (*refcount_list_destroy_with_data_callback_t)(void *ptr,
void *user_data);
/**
* Callback used when copying elements from one list to another.
* @param ptr The element to copy
* @param user_data Arbitrary user specified data
* @return The new copy, or NULL if an error occurred
* @see refcount_list_copy
*/
typedef void *(*refcount_list_copy_callback_t)(void *);
typedef void *(*refcount_list_copy_callback_t)(const void *ptr,
void *user_data);
RefcountList *refcount_list_build(int count, ...);
RefcountList *refcount_list_build_full_va(int count,
const RefcountAllocator *alloc,
va_list args);
RefcountList *refcount_list_copy(RefcountList *list,
refcount_list_copy_callback_t callback);
/**
* Like #refcount_list_build, but lets you specify the allocator to use.
* @param count The number of elements given
* @param alloc The allocator to use
* @param ... The elements from which to build the list
* @return The built list, or NULL if a memory allocation error occurred
*/
static inline RefcountList *
refcount_list_build_full(int count, const RefcountAllocator *alloc, ...) {
va_list args;
va_start(args, alloc);
RefcountList *retval = refcount_list_build_full_va(count, alloc, args);
va_end(args);
return retval;
}
/**
* Build a #RefcountList from a number of elements.
*
* > that this will return NULL if count is 0 or if a memory allocation error
* > occurred. However, as no allocation is preformed is count is 0, these
* > errors should be mutually exclusive.
*
* @param count The number of elements given
* @param ... The elements from which to build the list
* @return The built list, or NULL if a memory allocation error occurred
*/
static inline RefcountList *refcount_list_build(int count, ...) {
va_list args;
va_start(args, count);
RefcountList *retval =
refcount_list_build_full_va(count, refcount_global_allocator, args);
va_end(args);
return retval;
}
RefcountList *refcount_list_copy_full(const RefcountList *list,
refcount_list_copy_callback_t callback,
void *user_data,
const RefcountAllocator *alloc);
/**
* Return a new #RefcountList that is a copy of list. This does no allocation if
* the input list is NULL.
* @param list The list to copy
* @param callback The function to copy each element of the list, or NULL
* @param user_data Extra data to be passed to the copy callback
* @return The copy, or NULL if an allocation error occurred
*/
static inline RefcountList *
refcount_list_copy(const RefcountList *list,
refcount_list_copy_callback_t callback, void *user_data) {
return refcount_list_copy_full(list, callback, user_data,
refcount_global_allocator);
}
RefcountList *refcount_list_reverse(RefcountList *list);
void refcount_list_free(RefcountList *list,
refcount_list_destroy_callback_t callback);
void refcount_list_free_full(RefcountList *list,
refcount_list_destroy_callback_t callback,
const RefcountAllocator *alloc);
void refcount_list_free_with_data(RefcountList *list,
refcount_list_foreach_callback_t callback,
void *data);
/**
* Free a #RefcountList.
* @param list The list to free
* @param callback The function to call to free each member, or NULL
*/
static inline void
refcount_list_free(RefcountList *list,
refcount_list_destroy_callback_t callback) {
refcount_list_free_full(list, callback, refcount_global_allocator);
}
size_t refcount_list_length(RefcountList *list);
void refcount_list_free_with_data_full(
RefcountList *list, refcount_list_destroy_with_data_callback_t callback,
void *data, const RefcountAllocator *alloc);
/**
* Free a #RefcountList.
* @param list The list to free
* @param callback The function to call to free each member, or NULL
* @param data Extra data to pass to the second argument of the callback
*/
static inline void refcount_list_free_with_data(
RefcountList *list, refcount_list_destroy_with_data_callback_t callback,
void *data) {
refcount_list_free_with_data_full(list, callback, data,
refcount_global_allocator);
}
size_t refcount_list_length(const RefcountList *list);
/**
* Return the first element in a #RefcountList.
* @param list The list
* @return The first object in the list
*/
static inline void *refcount_list_peek(RefcountList *list) {
static inline void *refcount_list_peek(const RefcountList *list) {
if (!list) {
return NULL;
}
@ -68,21 +157,171 @@ static inline void *refcount_list_peek(RefcountList *list) {
RefcountList *refcount_list_drop(RefcountList *list, size_t n);
void *refcount_list_nth(RefcountList *list, size_t n);
void *refcount_list_nth(const RefcountList *list, size_t n);
RefcountList *refcount_list_pop(RefcountList *list,
refcount_list_destroy_callback_t callback);
RefcountList *
refcount_list_remove_full(RefcountList *list, RefcountList *link,
refcount_list_destroy_callback_t callback,
const RefcountAllocator *alloc);
RefcountList *refcount_list_remove(RefcountList *list, RefcountList *link,
refcount_list_destroy_callback_t callback);
/**
* Remove a link from a #RefcountList. Note that you must save the return
* value as it is the new value of the list.
*
* If the given list or link is NULL, this does nothing. It is undefined
* behavior if link is not in list.
*
* @param list The list
* @param link The link to remove, or NULL
* @param callback The function to call to free the member, or NULL
* @return The new value of the list
*/
static inline RefcountList *
refcount_list_remove(RefcountList *list, RefcountList *link,
refcount_list_destroy_callback_t callback) {
return refcount_list_remove_full(list, link, callback,
refcount_global_allocator);
}
RefcountList *refcount_list_push(RefcountList *list, void *element);
/**
* Same as #refcount_list_remove_with_data, but lets you specify the allocator
* to use.
* @param list The list
* @param callback The function to call to free the member, or NULL
* @param link The link to remove, or NULL
* @param user_data Extra data to pass to the callback
* @param alloc The allocator to use
* @return The new value of the list
*/
static inline RefcountList *refcount_list_remove_with_data_full(
RefcountList *list, RefcountList *link,
refcount_list_destroy_with_data_callback_t callback, void *user_data,
const RefcountAllocator *alloc) {
if (list && link && callback) {
callback(link->data, user_data);
}
return refcount_list_remove_full(list, link, NULL, alloc);
}
/**
* Same as #refcount_list_remove except that the callback takes an additional
* argument. This also does nothing if either of list or link is NULL.
* @param list The list
* @param callback The function to call to free the member, or NULL
* @param link The link to remove, or NULL
* @param user_data Extra data to pass to the callback
* @return The new value of the list
*/
static inline RefcountList *refcount_list_remove_with_data(
RefcountList *list, RefcountList *link,
refcount_list_destroy_with_data_callback_t callback, void *user_data) {
return refcount_list_remove_with_data_full(list, link, callback, user_data,
refcount_global_allocator);
}
/**
* Same as #refcount_list_pop, but lets you specify the allocator to use.
*
* @param list The list
* @param callback The function to call to free the removed, or NULL
* @param alloc The allocator to use
* @return The new value of the list
*
* @see refcount_list_remove
*/
static inline RefcountList *
refcount_list_pop_full(RefcountList *list,
refcount_list_destroy_callback_t callback,
const RefcountAllocator *alloc) {
return refcount_list_remove_full(list, list, callback, alloc);
}
/**
* Remove the first element from a #RefcountList. Note that you must save the
* return value as it is the new value of the list. If the list is a sub-list,
* the state of the larger list is undefined after this call.
*
* @param list The list
* @param callback The function to call to free the removed, or NULL
* @return The new value of the list
*
* @see refcount_list_remove
*/
static inline RefcountList *
refcount_list_pop(RefcountList *list,
refcount_list_destroy_callback_t callback) {
return refcount_list_pop_full(list, callback, refcount_global_allocator);
}
/**
* Same as #refcount_list_pop_with_data, but lets specify the allocator to use.
* @param list The list
* @param callback The function to call to free the member, or NULL
* @param user_data Extra data to pass to the callback
* @param alloc The allocator to use
* @return The new value of the list
*/
static inline RefcountList *refcount_list_pop_with_data_full(
RefcountList *list, refcount_list_destroy_with_data_callback_t callback,
void *user_data, const RefcountAllocator *alloc) {
if (list && callback) {
callback(list->data, user_data);
}
return refcount_list_pop_full(list, NULL, alloc);
}
/**
* Same as #refcount_list_pop except that the callback takes an additional
* argument.
* @param list The list
* @param callback The function to call to free the member, or NULL
* @param user_data Extra data to pass to the callback
* @return The new value of the list
*/
static inline RefcountList *
refcount_list_pop_with_data(RefcountList *list,
refcount_list_destroy_with_data_callback_t callback,
void *user_data) {
return refcount_list_pop_with_data_full(list, callback, user_data,
refcount_global_allocator);
}
RefcountList *refcount_list_push_full(RefcountList *list, void *element,
const RefcountAllocator *alloc);
/**
* Add an element to the start of a #RefcountList. Note that you must save the
* return value as it is the new value of the list.
* @param list The list
* @param element The element to add
* @return The new value of the list, or NULL if allocation of the new node
* failed
*/
static inline RefcountList *refcount_list_push(RefcountList *list,
void *element) {
return refcount_list_push_full(list, element, refcount_global_allocator);
}
RefcountList *refcount_list_tail(RefcountList *list);
RefcountList *refcount_list_join(RefcountList *list1, RefcountList *list2);
RefcountList *refcount_list_push_back(RefcountList *list, void *element);
RefcountList *refcount_list_push_back_full(RefcountList *list, void *element,
const RefcountAllocator *alloc);
/**
* Add an element to the end of a #RefcountList. Note that you must save the
* return value as it is the new value of the list.
* @param list The list
* @param element The element to add
* @return The new value of the list, or NULL if allocation of the new node
* failed
*/
static inline RefcountList *refcount_list_push_back(RefcountList *list,
void *element) {
return refcount_list_push_back_full(list, element,
refcount_global_allocator);
}
#ifdef __cplusplus
}

View File

@ -4,6 +4,7 @@
#ifndef INCLUDED_REFCOUNT_REFCOUNT_H
#define INCLUDED_REFCOUNT_REFCOUNT_H
#include <ht.h>
#include <refcount/list.h>
#include <stdbool.h>
#include <stdint.h>
@ -14,15 +15,29 @@ extern "C" {
/**
* Callback for listing all references held by the passed object. The function
* should take an objects and return a #RefcountList of references held by that
* object.
* should take an object and return a #RefcountList of references held by that
* object. The second parameter is user supplied data.
*
* > The list in the refs parameter will probably not be empty, so it is
* > important that you update it, not replace it. If an errors occurs, you
* > mustn't free anything that you did not add to the list yourself.
*
* @param obj The object
* @param refs A pointer to a #RefcountList that should be updated with the held
* references.
* @param user_data User supplied data pointer
* @return True if the operation was successful, false otherwise
*/
typedef RefcountList *(*refcount_held_refs_callback_t)(void *);
typedef bool (*refcount_held_refs_callback_t)(void *obj, RefcountList **refs,
void *user_data);
/**
* Callback for freeing an object after its reference count drops to 0. It is
* safe the free the object from this callback.
* safe the free the object from this callback. The second parameter is user
* supplied data.
* @param obj The object to free
* @param user_data User supplied data pointer
*/
typedef void (*refcount_destroy_callback_t)(void *);
typedef void (*refcount_destroy_callback_t)(void *obj, void *user_data);
/**
* Context for reference counting a specific type of object. This should be
@ -33,9 +48,11 @@ typedef struct RefcountContext {
refcount_held_refs_callback_t
held_refs_callback; //!< Callback to list an object's held references.
refcount_destroy_callback_t
destroy_callback; //!< Callback to free an object
//!< after its reference count
//!< dropped to 0.
destroy_callback; //!< Callback to free an object after its reference
//!< count drops to 0.
void *user_data; //<! User data to pass to other callbacks.
RefcountAllocator alloc; //<! Allocator to use for this context.
HTAllocator ht_alloc; //<! ht version of the context's allocator.
RefcountList *static_objects; //!< List of static objects registered with
//!< this context.
@ -47,7 +64,6 @@ typedef struct RefcountContext {
*/
typedef enum {
REFCOUNT_OPER_DEINIT_STATIC,
REFCOUNT_OPER_UNREF,
REFCOUNT_OPER_FLOAT,
} RefcountOperation;
@ -55,9 +71,12 @@ typedef enum {
* Type for a function to be called when some kind of error occurs. It should
* take a #RefcountOperation, a #RefcountContext pointer, and the object on
* which the error occurred.
* @param oper The operation that failed
* @param ctx The context the operation was performed on
* @param obj The object that caused the failure
*/
typedef void (*refcount_debug_callback_t)(RefcountOperation, RefcountContext *,
void *);
typedef void (*refcount_debug_callback_t)(RefcountOperation oper,
RefcountContext *ctx, void *obj);
extern refcount_debug_callback_t refcount_debug_breakpoint;
@ -66,7 +85,8 @@ extern RefcountContext *refcount_default_context;
RefcountContext *
refcount_make_context(size_t entry_offset,
refcount_held_refs_callback_t held_refs_callback,
refcount_destroy_callback_t destroy_callback);
refcount_destroy_callback_t destroy_callback,
void *user_data, const RefcountAllocator *alloc);
void refcount_context_destroy(RefcountContext *ctx);
@ -100,7 +120,7 @@ typedef struct RefcountEntry {
* @return A pointer to the objects #RefcountEntry
*/
#define REFCOUNT_OBJECT_ENTRY(ctx, obj) \
((RefcountEntry *) (((void *) (obj)) + (ctx)->entry_offset))
((RefcountEntry *) (((char *) (obj)) + (ctx)->entry_offset))
/**
* Test whether an object is static or not. The object must have been
@ -128,9 +148,9 @@ static inline size_t refcount_context_num_refs(RefcountContext *ctx,
void refcount_context_init_obj(RefcountContext *ctx, void *obj);
void refcount_context_init_static(RefcountContext *ctx, void *obj);
bool refcount_context_init_static(RefcountContext *ctx, void *obj);
void refcount_context_deinit_static(RefcountContext *ctx, void *obj);
bool refcount_context_deinit_static(RefcountContext *ctx, void *obj);
/**
* Unregister a static object in a context. This is suitable to be passed as a
@ -158,6 +178,8 @@ 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);
/**
* Same as #refcount_context_is_static, but only operates on the global
* context.
@ -242,6 +264,15 @@ static inline void *refcount_float(void *obj) {
return refcount_context_float(refcount_default_context, obj);
}
/**
* Same as #refcount_context_garbage_collect, but only operates on the globa
* context.
* @return False if an error occured, true otherwise
*/
static inline bool refcount_garbage_collect(void) {
return refcount_context_garbage_collect(refcount_default_context);
}
#ifdef __cplusplus
}
#endif