Initial commit
This commit is contained in:
22
src/allocator.c
Normal file
22
src/allocator.c
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @file
|
||||
* Replaceable memory allocator.
|
||||
*/
|
||||
#include "refcount/allocator.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* Initial value of #refcount_global_allocator.
|
||||
*/
|
||||
static const RefcountAllocator default_allocator = {
|
||||
.malloc = malloc,
|
||||
.realloc = realloc,
|
||||
.free = free,
|
||||
};
|
||||
|
||||
/**
|
||||
* The global #RefcountAllocator used by other parts of RefCount. The default
|
||||
* value just calls the C standard functions malloc, realloc, and free.
|
||||
*/
|
||||
const RefcountAllocator *refcount_global_allocator = &default_allocator;
|
302
src/list.c
Normal file
302
src/list.c
Normal file
@ -0,0 +1,302 @@
|
||||
/**
|
||||
* @file
|
||||
* Doubly linked list implementation.
|
||||
*/
|
||||
#include "refcount/list.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
RefcountList *refcount_list_build(int count, ...) {
|
||||
va_list args;
|
||||
va_start(args, count);
|
||||
RefcountList *start = NULL;
|
||||
RefcountList *end;
|
||||
while (count--) {
|
||||
RefcountList *new_list = refcount_malloc(sizeof(RefcountList));
|
||||
if (!new_list) {
|
||||
refcount_list_free(start, NULL);
|
||||
va_end(args);
|
||||
return NULL;
|
||||
}
|
||||
new_list->data = va_arg(args, void *);
|
||||
new_list->next = NULL;
|
||||
if (!start) {
|
||||
start = new_list;
|
||||
end = new_list;
|
||||
new_list->prev = NULL;
|
||||
} else {
|
||||
end->next = new_list;
|
||||
new_list->prev = end;
|
||||
end = new_list;
|
||||
}
|
||||
}
|
||||
va_end(args);
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return The copy, or NULL if an allocation error occurred
|
||||
*/
|
||||
RefcountList *refcount_list_copy(RefcountList *list,
|
||||
refcount_list_copy_callback_t callback) {
|
||||
RefcountList *start = NULL;
|
||||
RefcountList *end;
|
||||
while (list) {
|
||||
RefcountList *new_end = refcount_malloc(sizeof(RefcountList));
|
||||
if (!new_end) {
|
||||
refcount_list_free(start, NULL);
|
||||
return NULL;
|
||||
}
|
||||
new_end->data = callback ? callback(list->data) : list->data;
|
||||
new_end->next = NULL;
|
||||
if (!start) {
|
||||
start = new_end;
|
||||
end = new_end;
|
||||
new_end->prev = NULL;
|
||||
} else {
|
||||
end->next = new_end;
|
||||
new_end->prev = end;
|
||||
end = new_end;
|
||||
}
|
||||
list = list->next;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse a #RefcountList and return it. This mutates the input list.
|
||||
* @param list The list to reverse destructively
|
||||
* @return The reversed list
|
||||
*/
|
||||
RefcountList *refcount_list_reverse(RefcountList *list) {
|
||||
while (list) {
|
||||
RefcountList *next = list->next;
|
||||
list->next = list->prev;
|
||||
list->prev = next;
|
||||
if (!next) {
|
||||
return list;
|
||||
}
|
||||
list = next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a #RefcountList.
|
||||
* @param list The list to free
|
||||
* @param callback The function to call to free each member, or NULL
|
||||
*/
|
||||
void refcount_list_free(RefcountList *list,
|
||||
refcount_list_destroy_callback_t callback) {
|
||||
while (list) {
|
||||
RefcountList *next = list->next;
|
||||
if (callback) {
|
||||
callback(list->data);
|
||||
}
|
||||
refcount_free(list);
|
||||
list = next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
void refcount_list_free_with_data(RefcountList *list,
|
||||
refcount_list_foreach_callback_t callback,
|
||||
void *data) {
|
||||
while (list) {
|
||||
RefcountList *next = list->next;
|
||||
if (callback) {
|
||||
callback(list->data, data);
|
||||
}
|
||||
refcount_free(list);
|
||||
list = next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of a #RefcountList. This will hang if the list is circular.
|
||||
* @param list The list
|
||||
* @return The length of the list
|
||||
*/
|
||||
size_t refcount_list_length(RefcountList *list) {
|
||||
size_t len = 0;
|
||||
while (list) {
|
||||
list = list->next;
|
||||
++len;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sub-list starting at the Nth link. That is, drop the first n
|
||||
* links. If n is greater than the length of the list, return NULL.
|
||||
* @param list The list
|
||||
* @param n The number of elements to drop
|
||||
* @return The Nth link of the list
|
||||
*/
|
||||
RefcountList *refcount_list_drop(RefcountList *list, size_t n) {
|
||||
for (size_t i = 0; list; ++i, list = list->next) {
|
||||
if (i == n) {
|
||||
return list;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Nth element of a #RefcountList. If n is greater than the length of
|
||||
* the list, return NULL.
|
||||
* @param list The list
|
||||
* @param n The index of the element
|
||||
* @return The Nth element of the list
|
||||
*/
|
||||
void *refcount_list_nth(RefcountList *list, size_t n) {
|
||||
RefcountList *link = refcount_list_drop(list, n);
|
||||
return link ? link->data : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
RefcountList *refcount_list_pop(RefcountList *list,
|
||||
refcount_list_destroy_callback_t callback) {
|
||||
return refcount_list_remove(list, list, 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
|
||||
*/
|
||||
RefcountList *refcount_list_remove(RefcountList *list, RefcountList *link,
|
||||
refcount_list_destroy_callback_t callback) {
|
||||
if (!list || !link) {
|
||||
return list;
|
||||
}
|
||||
RefcountList *rest = link->next;
|
||||
if (rest) {
|
||||
rest->prev = link->prev;
|
||||
}
|
||||
if (link->prev) {
|
||||
link->prev->next = rest;
|
||||
}
|
||||
if (callback) {
|
||||
callback(link->data);
|
||||
}
|
||||
refcount_free(link);
|
||||
return list == link ? rest : list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
RefcountList *refcount_list_push(RefcountList *list, void *element) {
|
||||
RefcountList *new_head = refcount_malloc(sizeof(RefcountList));
|
||||
if (!new_head) {
|
||||
return NULL;
|
||||
}
|
||||
new_head->data = element;
|
||||
new_head->next = list;
|
||||
new_head->prev = NULL;
|
||||
if (list) {
|
||||
list->prev = new_head;
|
||||
}
|
||||
return new_head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last link in a #RefcountList.
|
||||
* @param list The list
|
||||
* @return The last link in the list, or NULL if the list is empty
|
||||
*/
|
||||
RefcountList *refcount_list_tail(RefcountList *list) {
|
||||
if (!list) {
|
||||
return NULL;
|
||||
}
|
||||
RefcountList *end = list;
|
||||
while (end && end->next) {
|
||||
end = end->next;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join list1 and list2. This mutates both lists. If list2 is part of another
|
||||
* list, it will be removed from that list!
|
||||
* @param list1 The first list
|
||||
* @param list2 The second list
|
||||
* @return The joined list
|
||||
*/
|
||||
RefcountList *refcount_list_join(RefcountList *list1, RefcountList *list2) {
|
||||
RefcountList *end1 = refcount_list_tail(list1);
|
||||
if (end1) {
|
||||
end1->next = list2;
|
||||
}
|
||||
if (list2) {
|
||||
if (list2->prev) {
|
||||
list2->prev = NULL;
|
||||
}
|
||||
list2->prev = end1;
|
||||
}
|
||||
return list1 ? list1 : list2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
RefcountList *refcount_list_push_back(RefcountList *list, void *element) {
|
||||
RefcountList *new_end = refcount_malloc(sizeof(RefcountList));
|
||||
if (!new_end) {
|
||||
return NULL;
|
||||
}
|
||||
new_end->data = element;
|
||||
new_end->next = NULL;
|
||||
new_end->prev = NULL;
|
||||
return refcount_list_join(list, new_end);
|
||||
}
|
256
src/refcount.c
Normal file
256
src/refcount.c
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user