Initial commit

This commit is contained in:
2025-08-28 04:59:23 -07:00
commit 5ac6aaf900
14 changed files with 4983 additions and 0 deletions

256
src/refcount.c Normal file
View File

@ -0,0 +1,256 @@
/**
* @file
* Reference counting subroutines.
*/
#include "refcount/refcount.h"
#include "refcount/allocator.h"
#include <stdbool.h>
#include <stdlib.h>
/**
* Callback to be executed when a program error occurred. A sane to default is
* to print a message and then abort. This is not used to report normal
* recoverable errors, but rather to report fatal logic errors.
*/
refcount_debug_callback_t refcount_debug_breakpoint = NULL;
/**
* Call the breakpoint handler if one is installed.
* @param oper The failing operation
* @param ctx The #RefcountContext
* @param obj The object on which the operation tried to act
*/
#define BREAKPOINT(oper, ctx, obj) \
if (refcount_debug_breakpoint) { \
refcount_debug_breakpoint(REFCOUNT_OPER_##oper, (ctx), (obj)); \
}
/**
* Default context to use for functions that do not take a context. You must
* initialize this before use.
*/
RefcountContext *refcount_default_context = NULL;
/**
* Create a new context.
* @param entry_offset The offset to the #RefcountEntry member
* @param held_refs_callback A function that will list all references held by an
* object
* @param destroy_callback A function to be called when an objects reference
* count drops to zero
* @return The new context, or NULL in the case of an error
*/
RefcountContext *
refcount_make_context(size_t entry_offset,
refcount_held_refs_callback_t held_refs_callback,
refcount_destroy_callback_t destroy_callback) {
RefcountContext *ctx = refcount_malloc(sizeof(RefcountContext));
if (!ctx) {
return NULL;
}
ctx->entry_offset = entry_offset;
ctx->held_refs_callback = held_refs_callback;
ctx->destroy_callback = destroy_callback;
ctx->static_objects = NULL;
ctx->gc_roots = NULL;
return ctx;
}
/**
* Cleanup a #RefcountContext and free any associated resources. This also
* causes all remaining references, both static and non-static, to be dropped
* and the context's destroy callback to be called on each object.
* @param ctx The #RefcountContext
*/
void refcount_context_destroy(RefcountContext *ctx) {
refcount_list_free_with_data(
ctx->static_objects, refcount_context_deinit_static_as_callback, ctx);
// TODO process gc_roots
refcount_free(ctx);
}
/**
* Internal magic macro.
*/
#define ENTRY (REFCOUNT_OBJECT_ENTRY(ctx, obj))
/**
* Initialize the #RefcountEntry member of an object.
* @param ctx The #RefcountContext
* @param obj The object to initialize
*/
void refcount_context_init_obj(RefcountContext *ctx, void *obj) {
ENTRY->is_static = false;
ENTRY->gc_root = NULL;
ENTRY->ref_count = 0;
}
/**
* Register a static object in a context.
* @param ctx The #RefcountContext
* @param obj The object to register
*/
void refcount_context_init_static(RefcountContext *ctx, void *obj) {
ENTRY->is_static = true;
ctx->static_objects = refcount_list_push(ctx->static_objects, obj);
ENTRY->static_entry = ctx->static_objects;
}
/**
* Unregister a static object in a context.
* @param ctx The #RefcountContext
* @param obj The object to unregister
*/
void refcount_context_deinit_static(RefcountContext *ctx, void *obj) {
if (refcount_context_is_static(ctx, obj)) {
ctx->static_objects = refcount_list_remove(ctx->static_objects,
ENTRY->static_entry, NULL);
RefcountList *held_refs =
ctx->held_refs_callback ? ctx->held_refs_callback(obj) : NULL;
refcount_list_free_with_data(held_refs,
refcount_context_unref_as_callback, ctx);
} else {
BREAKPOINT(DEINIT_STATIC, ctx, obj)
}
}
/**
* Increment the reference count of an object.
* @param ctx The #RefcountContext
* @param obj The object to reference
* @return The input object
*/
void *refcount_context_ref(RefcountContext *ctx, void *obj) {
if (!ENTRY->is_static) {
++ENTRY->ref_count;
}
return obj;
}
/**
* Track an object as a GC root in a context. It is safe to call this on an
* already tracked object.
* @param ctx the #RefcountContext
* @param obj The object to track
*/
static void track_gc_root(RefcountContext *ctx, void *obj) {
if (!ENTRY->gc_root) {
ctx->gc_roots = refcount_list_push(ctx->gc_roots, obj);
ENTRY->gc_root = ctx->gc_roots;
}
}
/**
* Remove an object from the GV root list of a context. It is safe to call this
* on an object that is not currently tracked.
* @param ctx The #RefcountContext
* @param obj The object to untrack
*/
static void remove_gc_root(RefcountContext *ctx, void *obj) {
ctx->gc_roots = refcount_list_remove(ctx->gc_roots, ENTRY->gc_root, NULL);
ENTRY->gc_root = NULL;
}
/**
* Decrement the reference count of an object. If the reference count dropped to
* zero, add it to the queue. It is safe to call this on a static object.
* @param ctx The #RefcountContext
* @param obj The object to unref
* @param queue A double pointer to a #RefcountList acting as a queue
* @return NULL if the reference count fell to 0, the given object otherwise
*/
static void *unref_to_queue(RefcountContext *ctx, void *obj,
RefcountList **queue) {
if (ENTRY->is_static) {
return obj;
} else if (ENTRY->ref_count <= 1) {
*queue = refcount_list_push(*queue, obj);
return NULL;
}
--ENTRY->ref_count;
track_gc_root(ctx, obj);
return obj;
}
/**
* A pair of a context and double pointer to a queue. This is for internal use.
* @see unref_to_queue_as_callback
*/
struct ContextAndQueue {
RefcountContext *ctx; //!< The context.
RefcountList **queue; //!< The queue.
};
/**
* Unref an object using a combined context and queue parameter.
* @param obj The object
* @param ctx_and_queue_raw The context and queue
*/
static void unref_to_queue_as_callback(void *obj, void *ctx_and_queue_raw) {
struct ContextAndQueue *ctx_and_queue = ctx_and_queue_raw;
unref_to_queue(ctx_and_queue->ctx, obj, ctx_and_queue->queue);
}
/**
* Continually release held references and object held in a queue.
* @param ctx The #RefcountContext
* @param queue The queue
*/
static void process_unref_queue(RefcountContext *ctx, RefcountList *queue) {
struct ContextAndQueue ctx_and_queue = {.ctx = ctx, .queue = &queue};
while (queue) {
void *cur = refcount_list_peek(queue);
queue = refcount_list_pop(queue, NULL);
RefcountList *held_refs =
ctx->held_refs_callback ? ctx->held_refs_callback(cur) : NULL;
refcount_list_free_with_data(held_refs, unref_to_queue_as_callback,
&ctx_and_queue);
remove_gc_root(ctx, cur);
if (ctx->destroy_callback) {
ctx->destroy_callback(cur);
}
}
}
/**
* Decrement the reference count of a object. It is safe to call this on a
* static object.
* @param ctx The #RefcountContext
* @param obj The object
* @return NULL if the object's reference counter fell to 0, otherwise the
* object
*/
void *refcount_context_unref(RefcountContext *ctx, void *obj) {
RefcountList *queue = NULL;
obj = unref_to_queue(ctx, obj, &queue);
process_unref_queue(ctx, queue);
return obj;
}
/**
* Float a reference of an object. That is, decrement it's refrerence count, but
* don't free it if the count drops to 0. It is safe to call this on a static
* object. It is an error to call this on an object that already has a floating
* reference.
* @param ctx The #RefcountContext
* @param obj The object
* @return The input object
*/
void *refcount_context_float(RefcountContext *ctx, void *obj) {
if (ENTRY->is_static) {
return obj;
} else if (!ENTRY->ref_count) {
BREAKPOINT(FLOAT, ctx, obj);
}
if (--ENTRY->ref_count) {
track_gc_root(ctx, obj);
} else {
remove_gc_root(ctx, obj);
}
return obj;
}