Initial garbage colletor implementation
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.11)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
|
||||||
@ -7,13 +7,25 @@ project(
|
|||||||
VERSION 1.0
|
VERSION 1.0
|
||||||
LANGUAGES C)
|
LANGUAGES C)
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
ht
|
||||||
|
GIT_REPOSITORY https://git.zander.im/Zander671/ht.git
|
||||||
|
GIT_TAG 9a1b271cfdc8ab203f9d6aa6a83cc4523de422be)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(ht)
|
||||||
|
|
||||||
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
||||||
include(CTest)
|
include(CTest)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_compile_options(-fsanitize=address,leak,undefined)
|
||||||
|
add_link_options(-fsanitize=address,leak,undefined)
|
||||||
|
|
||||||
add_library(refcount src/allocator.c src/list.c src/refcount.c)
|
add_library(refcount src/allocator.c src/list.c src/refcount.c)
|
||||||
|
target_link_libraries(refcount PUBLIC ht)
|
||||||
target_include_directories(refcount PUBLIC include/)
|
target_include_directories(refcount PUBLIC include/)
|
||||||
target_compile_options(refcount PRIVATE -Wall)
|
target_compile_options(refcount PRIVATE -Wall -Wpedantic)
|
||||||
|
|
||||||
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
|
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
|
||||||
add_subdirectory(test/)
|
add_subdirectory(test/)
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
#ifndef INCLUDED_REFCOUNT_ALLOCATOR_H
|
#ifndef INCLUDED_REFCOUNT_ALLOCATOR_H
|
||||||
#define INCLUDED_REFCOUNT_ALLOCATOR_H
|
#define INCLUDED_REFCOUNT_ALLOCATOR_H
|
||||||
|
|
||||||
|
#include <ht.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@ -14,12 +16,27 @@ extern "C" {
|
|||||||
* A set of the three main C standard allocators. Each member function should
|
* 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,
|
* behave the same as its standard library counterpart. That is, for example,
|
||||||
* free should allow NULL as an input.
|
* 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 {
|
typedef struct RefcountAllocator {
|
||||||
void *(*malloc)(size_t); //!< malloc implementation for the allocator.
|
union {
|
||||||
void *(*realloc)(void *,
|
void *(*malloc)(size_t size); //!< malloc implementation which takes no
|
||||||
size_t); //!< realloc implementation for the allocator.
|
//!< data.
|
||||||
void (*free)(void *); //!< free implementation for the allocator.
|
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;
|
} RefcountAllocator;
|
||||||
|
|
||||||
extern const RefcountAllocator *refcount_global_allocator;
|
extern const RefcountAllocator *refcount_global_allocator;
|
||||||
@ -30,67 +47,29 @@ extern const RefcountAllocator *refcount_global_allocator;
|
|||||||
* @param size The number of bytes to allocate
|
* @param size The number of bytes to allocate
|
||||||
* @return The newly allocate pointer, or NULL if allocation failed
|
* @return The newly allocate pointer, or NULL if allocation failed
|
||||||
*/
|
*/
|
||||||
static inline void *refcount_allocator_malloc(const RefcountAllocator *alloc,
|
static inline void *refcount_malloc(const RefcountAllocator *alloc,
|
||||||
size_t size) {
|
size_t size) {
|
||||||
|
if (alloc->pass_data) {
|
||||||
|
return alloc->malloc_with_data(size, alloc->user_data);
|
||||||
|
}
|
||||||
return alloc->malloc(size);
|
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.
|
* Free memory using a #RefcountAllocator.
|
||||||
* @param alloc The allocator to use
|
* @param alloc The allocator to use
|
||||||
* @param ptr The pointer to free
|
* @param ptr The pointer to free
|
||||||
*/
|
*/
|
||||||
static inline void refcount_allocator_free(const RefcountAllocator *alloc,
|
static inline void refcount_free(const RefcountAllocator *alloc, void *ptr) {
|
||||||
void *ptr) {
|
if (alloc->pass_data) {
|
||||||
alloc->free(ptr);
|
alloc->free_with_data(ptr, alloc->user_data);
|
||||||
|
} else {
|
||||||
|
alloc->free(ptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void refcount_allocator_to_ht_allocator(const RefcountAllocator *src,
|
||||||
* Allocate memory using the global #RefcountAllocator.
|
HTAllocator *dest);
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#define INCLUDED_REFCOUNT_LIST_H
|
#define INCLUDED_REFCOUNT_LIST_H
|
||||||
|
|
||||||
#include <refcount/allocator.h>
|
#include <refcount/allocator.h>
|
||||||
|
#include <stdarg.h>
|
||||||
#include <stddef.h> // for NULL
|
#include <stddef.h> // for NULL
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@ -24,42 +25,130 @@ typedef struct RefcountList {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback to used to free list elements.
|
* Callback to used to free list elements.
|
||||||
|
* @param ptr The pointer to free
|
||||||
* @see refcount_list_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.
|
* Callback to used to free list elements.
|
||||||
* @see refcount_list_free_with_data
|
* @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.
|
* 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
|
* @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);
|
RefcountList *refcount_list_reverse(RefcountList *list);
|
||||||
|
|
||||||
void refcount_list_free(RefcountList *list,
|
void refcount_list_free_full(RefcountList *list,
|
||||||
refcount_list_destroy_callback_t callback);
|
refcount_list_destroy_callback_t callback,
|
||||||
|
const RefcountAllocator *alloc);
|
||||||
|
|
||||||
void refcount_list_free_with_data(RefcountList *list,
|
/**
|
||||||
refcount_list_foreach_callback_t callback,
|
* Free a #RefcountList.
|
||||||
void *data);
|
* @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.
|
* Return the first element in a #RefcountList.
|
||||||
* @param list The list
|
* @param list The list
|
||||||
* @return The first object in 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) {
|
if (!list) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -68,21 +157,171 @@ static inline void *refcount_list_peek(RefcountList *list) {
|
|||||||
|
|
||||||
RefcountList *refcount_list_drop(RefcountList *list, size_t n);
|
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,
|
RefcountList *
|
||||||
refcount_list_destroy_callback_t callback);
|
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_tail(RefcountList *list);
|
||||||
|
|
||||||
RefcountList *refcount_list_join(RefcountList *list1, RefcountList *list2);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#ifndef INCLUDED_REFCOUNT_REFCOUNT_H
|
#ifndef INCLUDED_REFCOUNT_REFCOUNT_H
|
||||||
#define INCLUDED_REFCOUNT_REFCOUNT_H
|
#define INCLUDED_REFCOUNT_REFCOUNT_H
|
||||||
|
|
||||||
|
#include <ht.h>
|
||||||
#include <refcount/list.h>
|
#include <refcount/list.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@ -14,15 +15,29 @@ extern "C" {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for listing all references held by the passed object. The function
|
* 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
|
* should take an object and return a #RefcountList of references held by that
|
||||||
* object.
|
* 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
|
* 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
|
* Context for reference counting a specific type of object. This should be
|
||||||
@ -33,9 +48,11 @@ typedef struct RefcountContext {
|
|||||||
refcount_held_refs_callback_t
|
refcount_held_refs_callback_t
|
||||||
held_refs_callback; //!< Callback to list an object's held references.
|
held_refs_callback; //!< Callback to list an object's held references.
|
||||||
refcount_destroy_callback_t
|
refcount_destroy_callback_t
|
||||||
destroy_callback; //!< Callback to free an object
|
destroy_callback; //!< Callback to free an object after its reference
|
||||||
//!< after its reference count
|
//!< count drops to 0.
|
||||||
//!< dropped 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
|
RefcountList *static_objects; //!< List of static objects registered with
|
||||||
//!< this context.
|
//!< this context.
|
||||||
@ -47,7 +64,6 @@ typedef struct RefcountContext {
|
|||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
REFCOUNT_OPER_DEINIT_STATIC,
|
REFCOUNT_OPER_DEINIT_STATIC,
|
||||||
REFCOUNT_OPER_UNREF,
|
|
||||||
REFCOUNT_OPER_FLOAT,
|
REFCOUNT_OPER_FLOAT,
|
||||||
} RefcountOperation;
|
} RefcountOperation;
|
||||||
|
|
||||||
@ -55,9 +71,12 @@ typedef enum {
|
|||||||
* Type for a function to be called when some kind of error occurs. It should
|
* 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
|
* take a #RefcountOperation, a #RefcountContext pointer, and the object on
|
||||||
* which the error occurred.
|
* 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 *,
|
typedef void (*refcount_debug_callback_t)(RefcountOperation oper,
|
||||||
void *);
|
RefcountContext *ctx, void *obj);
|
||||||
|
|
||||||
extern refcount_debug_callback_t refcount_debug_breakpoint;
|
extern refcount_debug_callback_t refcount_debug_breakpoint;
|
||||||
|
|
||||||
@ -66,7 +85,8 @@ extern RefcountContext *refcount_default_context;
|
|||||||
RefcountContext *
|
RefcountContext *
|
||||||
refcount_make_context(size_t entry_offset,
|
refcount_make_context(size_t entry_offset,
|
||||||
refcount_held_refs_callback_t held_refs_callback,
|
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);
|
void refcount_context_destroy(RefcountContext *ctx);
|
||||||
|
|
||||||
@ -100,7 +120,7 @@ typedef struct RefcountEntry {
|
|||||||
* @return A pointer to the objects #RefcountEntry
|
* @return A pointer to the objects #RefcountEntry
|
||||||
*/
|
*/
|
||||||
#define REFCOUNT_OBJECT_ENTRY(ctx, obj) \
|
#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
|
* 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_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
|
* 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);
|
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
|
* Same as #refcount_context_is_static, but only operates on the global
|
||||||
* context.
|
* context.
|
||||||
@ -242,6 +264,15 @@ static inline void *refcount_float(void *obj) {
|
|||||||
return refcount_context_float(refcount_default_context, 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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -9,14 +9,46 @@
|
|||||||
/**
|
/**
|
||||||
* Initial value of #refcount_global_allocator.
|
* Initial value of #refcount_global_allocator.
|
||||||
*/
|
*/
|
||||||
static const RefcountAllocator default_allocator = {
|
static const RefcountAllocator DEFAULT_ALLOCATOR = {
|
||||||
.malloc = malloc,
|
.malloc = malloc,
|
||||||
.realloc = realloc,
|
|
||||||
.free = free,
|
.free = free,
|
||||||
|
.pass_data = false,
|
||||||
|
.user_data = NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The global #RefcountAllocator used by other parts of RefCount. The default
|
* The global #RefcountAllocator used by other parts of RefCount. The default
|
||||||
* value just calls the C standard functions malloc, realloc, and free.
|
* value just calls the C standard functions malloc and free.
|
||||||
*/
|
*/
|
||||||
const RefcountAllocator *refcount_global_allocator = &default_allocator;
|
const RefcountAllocator *refcount_global_allocator = &DEFAULT_ALLOCATOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTAllocator malloc function that delegates to #refcount_malloc.
|
||||||
|
* @param size The number of bytes to allocate
|
||||||
|
* @parma alloc The #RefcountAllocator to use
|
||||||
|
* @return The result of #refcount_malloc
|
||||||
|
*/
|
||||||
|
static void *refcount_ht_malloc(size_t size, void *alloc) {
|
||||||
|
return refcount_malloc(alloc, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTAllocator free function that delegates to #refcount_free.
|
||||||
|
* @param ptr The pointer to free
|
||||||
|
* @parma alloc The #RefcountAllocator to use
|
||||||
|
*/
|
||||||
|
static void refcount_ht_free(void *ptr, void *alloc) {
|
||||||
|
refcount_free(alloc, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new HTAllocator that delegates to src.
|
||||||
|
* @param src The #RefcountAllocator to delegate to
|
||||||
|
* @param dest A pointer to a #HTAllocator to initialize
|
||||||
|
*/
|
||||||
|
void refcount_allocator_to_ht_allocator(const RefcountAllocator *src,
|
||||||
|
HTAllocator *dest) {
|
||||||
|
dest->malloc = refcount_ht_malloc;
|
||||||
|
dest->free = refcount_ht_free;
|
||||||
|
dest->user_data = (void *) src;
|
||||||
|
}
|
||||||
|
114
src/list.c
114
src/list.c
@ -4,31 +4,26 @@
|
|||||||
*/
|
*/
|
||||||
#include "refcount/list.h"
|
#include "refcount/list.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a #RefcountList from a number of elements.
|
* Like #refcount_list_build, but lets you specify the allocator to use and
|
||||||
*
|
* takes a va_list instead of variable arguments.
|
||||||
* > 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 count The number of elements given
|
||||||
* @param ... The elements from which to build the list
|
* @param alloc The allocator to use
|
||||||
|
* @param args The va_list to use to get the elements
|
||||||
* @return The built list, or NULL if a memory allocation error occurred
|
* @return The built list, or NULL if a memory allocation error occurred
|
||||||
*/
|
*/
|
||||||
RefcountList *refcount_list_build(int count, ...) {
|
RefcountList *refcount_list_build_full_va(int count,
|
||||||
va_list args;
|
const RefcountAllocator *alloc,
|
||||||
va_start(args, count);
|
va_list args) {
|
||||||
RefcountList *start = NULL;
|
RefcountList *start = NULL;
|
||||||
RefcountList *end;
|
RefcountList *end;
|
||||||
while (count--) {
|
while (count--) {
|
||||||
RefcountList *new_list = refcount_malloc(sizeof(RefcountList));
|
RefcountList *new_list = refcount_malloc(alloc, sizeof(RefcountList));
|
||||||
if (!new_list) {
|
if (!new_list) {
|
||||||
refcount_list_free(start, NULL);
|
refcount_list_free(start, NULL);
|
||||||
va_end(args);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
new_list->data = va_arg(args, void *);
|
new_list->data = va_arg(args, void *);
|
||||||
@ -43,28 +38,30 @@ RefcountList *refcount_list_build(int count, ...) {
|
|||||||
end = new_list;
|
end = new_list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
va_end(args);
|
|
||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new #RefcountList that is a copy of list. This does no allocation if
|
* Same as #refcount_list_copy, but lets you specify the allocator.
|
||||||
* the input list is NULL.
|
|
||||||
* @param list The list to copy
|
* @param list The list to copy
|
||||||
* @param callback The function to copy each element of the list, or NULL
|
* @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
|
||||||
|
* @param alloc The allocator to use
|
||||||
* @return The copy, or NULL if an allocation error occurred
|
* @return The copy, or NULL if an allocation error occurred
|
||||||
*/
|
*/
|
||||||
RefcountList *refcount_list_copy(RefcountList *list,
|
RefcountList *refcount_list_copy_full(const RefcountList *list,
|
||||||
refcount_list_copy_callback_t callback) {
|
refcount_list_copy_callback_t callback,
|
||||||
|
void *user_data,
|
||||||
|
const RefcountAllocator *alloc) {
|
||||||
RefcountList *start = NULL;
|
RefcountList *start = NULL;
|
||||||
RefcountList *end;
|
RefcountList *end;
|
||||||
while (list) {
|
while (list) {
|
||||||
RefcountList *new_end = refcount_malloc(sizeof(RefcountList));
|
RefcountList *new_end = refcount_malloc(alloc, sizeof(RefcountList));
|
||||||
if (!new_end) {
|
if (!new_end) {
|
||||||
refcount_list_free(start, NULL);
|
refcount_list_free_full(start, NULL, alloc);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
new_end->data = callback ? callback(list->data) : list->data;
|
new_end->data = callback ? callback(list->data, user_data) : list->data;
|
||||||
new_end->next = NULL;
|
new_end->next = NULL;
|
||||||
if (!start) {
|
if (!start) {
|
||||||
start = new_end;
|
start = new_end;
|
||||||
@ -101,16 +98,18 @@ RefcountList *refcount_list_reverse(RefcountList *list) {
|
|||||||
/**
|
/**
|
||||||
* Free a #RefcountList.
|
* Free a #RefcountList.
|
||||||
* @param list The list to free
|
* @param list The list to free
|
||||||
|
* @param alloc The allocator to use
|
||||||
* @param callback The function to call to free each member, or NULL
|
* @param callback The function to call to free each member, or NULL
|
||||||
*/
|
*/
|
||||||
void refcount_list_free(RefcountList *list,
|
void refcount_list_free_full(RefcountList *list,
|
||||||
refcount_list_destroy_callback_t callback) {
|
refcount_list_destroy_callback_t callback,
|
||||||
|
const RefcountAllocator *alloc) {
|
||||||
while (list) {
|
while (list) {
|
||||||
RefcountList *next = list->next;
|
RefcountList *next = list->next;
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(list->data);
|
callback(list->data);
|
||||||
}
|
}
|
||||||
refcount_free(list);
|
refcount_free(alloc, list);
|
||||||
list = next;
|
list = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,16 +119,17 @@ void refcount_list_free(RefcountList *list,
|
|||||||
* @param list The list to free
|
* @param list The list to free
|
||||||
* @param callback The function to call to free each member, or NULL
|
* @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
|
* @param data Extra data to pass to the second argument of the callback
|
||||||
|
* @param alloc The allocator to use
|
||||||
*/
|
*/
|
||||||
void refcount_list_free_with_data(RefcountList *list,
|
void refcount_list_free_with_data_full(
|
||||||
refcount_list_foreach_callback_t callback,
|
RefcountList *list, refcount_list_destroy_with_data_callback_t callback,
|
||||||
void *data) {
|
void *data, const RefcountAllocator *alloc) {
|
||||||
while (list) {
|
while (list) {
|
||||||
RefcountList *next = list->next;
|
RefcountList *next = list->next;
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(list->data, data);
|
callback(list->data, data);
|
||||||
}
|
}
|
||||||
refcount_free(list);
|
refcount_free(alloc, list);
|
||||||
list = next;
|
list = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ void refcount_list_free_with_data(RefcountList *list,
|
|||||||
* @param list The list
|
* @param list The list
|
||||||
* @return The length of the list
|
* @return The length of the list
|
||||||
*/
|
*/
|
||||||
size_t refcount_list_length(RefcountList *list) {
|
size_t refcount_list_length(const RefcountList *list) {
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
while (list) {
|
while (list) {
|
||||||
list = list->next;
|
list = list->next;
|
||||||
@ -171,41 +171,23 @@ RefcountList *refcount_list_drop(RefcountList *list, size_t n) {
|
|||||||
* @param n The index of the element
|
* @param n The index of the element
|
||||||
* @return The Nth element of the list
|
* @return The Nth element of the list
|
||||||
*/
|
*/
|
||||||
void *refcount_list_nth(RefcountList *list, size_t n) {
|
void *refcount_list_nth(const RefcountList *list, size_t n) {
|
||||||
RefcountList *link = refcount_list_drop(list, n);
|
RefcountList *link = refcount_list_drop((RefcountList *) list, n);
|
||||||
return link ? link->data : NULL;
|
return link ? link->data : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the first element from a #RefcountList. Note that you must save the
|
* The same as #refcount_list_remove, but lets you specify the allocator to use.
|
||||||
* 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 list The list
|
||||||
* @param link The link to remove, or NULL
|
* @param link The link to remove, or NULL
|
||||||
* @param callback The function to call to free the member, or NULL
|
* @param callback The function to call to free the member, or NULL
|
||||||
|
* @param alloc The allocator to use
|
||||||
* @return The new value of the list
|
* @return The new value of the list
|
||||||
*/
|
*/
|
||||||
RefcountList *refcount_list_remove(RefcountList *list, RefcountList *link,
|
RefcountList *
|
||||||
refcount_list_destroy_callback_t callback) {
|
refcount_list_remove_full(RefcountList *list, RefcountList *link,
|
||||||
|
refcount_list_destroy_callback_t callback,
|
||||||
|
const RefcountAllocator *alloc) {
|
||||||
if (!list || !link) {
|
if (!list || !link) {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@ -219,20 +201,22 @@ RefcountList *refcount_list_remove(RefcountList *list, RefcountList *link,
|
|||||||
if (callback) {
|
if (callback) {
|
||||||
callback(link->data);
|
callback(link->data);
|
||||||
}
|
}
|
||||||
refcount_free(link);
|
refcount_free(alloc, link);
|
||||||
return list == link ? rest : list;
|
return list == link ? rest : list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an element to the start of a #RefcountList. Note that you must save the
|
* The same as #refcount_list_push, but lets you specify the allocator to
|
||||||
* return value as it is the new value of the list.
|
* use.
|
||||||
* @param list The list
|
* @param list The list
|
||||||
* @param element The element to add
|
* @param element The element to add
|
||||||
|
* @param alloc The allocator to use
|
||||||
* @return The new value of the list, or NULL if allocation of the new node
|
* @return The new value of the list, or NULL if allocation of the new node
|
||||||
* failed
|
* failed
|
||||||
*/
|
*/
|
||||||
RefcountList *refcount_list_push(RefcountList *list, void *element) {
|
RefcountList *refcount_list_push_full(RefcountList *list, void *element,
|
||||||
RefcountList *new_head = refcount_malloc(sizeof(RefcountList));
|
const RefcountAllocator *alloc) {
|
||||||
|
RefcountList *new_head = refcount_malloc(alloc, sizeof(RefcountList));
|
||||||
if (!new_head) {
|
if (!new_head) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -283,15 +267,17 @@ RefcountList *refcount_list_join(RefcountList *list1, RefcountList *list2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an element to the end of a #RefcountList. Note that you must save the
|
* The same as #refcount_list_push_back, but lets you specify the allocator to
|
||||||
* return value as it is the new value of the list.
|
* use.
|
||||||
* @param list The list
|
* @param list The list
|
||||||
* @param element The element to add
|
* @param element The element to add
|
||||||
|
* @param alloc The allocator to use
|
||||||
* @return The new value of the list, or NULL if allocation of the new node
|
* @return The new value of the list, or NULL if allocation of the new node
|
||||||
* failed
|
* failed
|
||||||
*/
|
*/
|
||||||
RefcountList *refcount_list_push_back(RefcountList *list, void *element) {
|
RefcountList *refcount_list_push_back_full(RefcountList *list, void *element,
|
||||||
RefcountList *new_end = refcount_malloc(sizeof(RefcountList));
|
const RefcountAllocator *alloc) {
|
||||||
|
RefcountList *new_end = refcount_malloc(alloc, sizeof(RefcountList));
|
||||||
if (!new_end) {
|
if (!new_end) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
262
src/refcount.c
262
src/refcount.c
@ -45,14 +45,21 @@ RefcountContext *refcount_default_context = NULL;
|
|||||||
RefcountContext *
|
RefcountContext *
|
||||||
refcount_make_context(size_t entry_offset,
|
refcount_make_context(size_t entry_offset,
|
||||||
refcount_held_refs_callback_t held_refs_callback,
|
refcount_held_refs_callback_t held_refs_callback,
|
||||||
refcount_destroy_callback_t destroy_callback) {
|
refcount_destroy_callback_t destroy_callback,
|
||||||
RefcountContext *ctx = refcount_malloc(sizeof(RefcountContext));
|
void *user_data, const RefcountAllocator *alloc) {
|
||||||
|
if (!alloc) {
|
||||||
|
alloc = refcount_global_allocator;
|
||||||
|
}
|
||||||
|
RefcountContext *ctx = refcount_malloc(alloc, sizeof(RefcountContext));
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
ctx->entry_offset = entry_offset;
|
ctx->entry_offset = entry_offset;
|
||||||
ctx->held_refs_callback = held_refs_callback;
|
ctx->held_refs_callback = held_refs_callback;
|
||||||
ctx->destroy_callback = destroy_callback;
|
ctx->destroy_callback = destroy_callback;
|
||||||
|
ctx->user_data = user_data;
|
||||||
|
ctx->alloc = *alloc;
|
||||||
|
refcount_allocator_to_ht_allocator(&ctx->alloc, &ctx->ht_alloc);
|
||||||
|
|
||||||
ctx->static_objects = NULL;
|
ctx->static_objects = NULL;
|
||||||
ctx->gc_roots = NULL;
|
ctx->gc_roots = NULL;
|
||||||
@ -66,12 +73,13 @@ refcount_make_context(size_t entry_offset,
|
|||||||
* @param ctx The #RefcountContext
|
* @param ctx The #RefcountContext
|
||||||
*/
|
*/
|
||||||
void refcount_context_destroy(RefcountContext *ctx) {
|
void refcount_context_destroy(RefcountContext *ctx) {
|
||||||
refcount_list_free_with_data(
|
refcount_list_free_with_data_full(
|
||||||
ctx->static_objects, refcount_context_deinit_static_as_callback, ctx);
|
ctx->static_objects, refcount_context_deinit_static_as_callback, ctx,
|
||||||
|
&ctx->alloc);
|
||||||
|
|
||||||
// TODO process gc_roots
|
refcount_context_garbage_collect(ctx);
|
||||||
|
|
||||||
refcount_free(ctx);
|
refcount_free(&ctx->alloc, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,37 +93,65 @@ void refcount_context_destroy(RefcountContext *ctx) {
|
|||||||
* @param obj The object to initialize
|
* @param obj The object to initialize
|
||||||
*/
|
*/
|
||||||
void refcount_context_init_obj(RefcountContext *ctx, void *obj) {
|
void refcount_context_init_obj(RefcountContext *ctx, void *obj) {
|
||||||
ENTRY->is_static = false;
|
if (obj) {
|
||||||
ENTRY->gc_root = NULL;
|
ENTRY->is_static = false;
|
||||||
ENTRY->ref_count = 0;
|
ENTRY->gc_root = NULL;
|
||||||
|
ENTRY->ref_count = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a static object in a context.
|
* Register a static object in a context.
|
||||||
* @param ctx The #RefcountContext
|
* @param ctx The #RefcountContext
|
||||||
* @param obj The object to register
|
* @param obj The object to register
|
||||||
|
* @return True on success, false otherwise
|
||||||
*/
|
*/
|
||||||
void refcount_context_init_static(RefcountContext *ctx, void *obj) {
|
bool refcount_context_init_static(RefcountContext *ctx, void *obj) {
|
||||||
ENTRY->is_static = true;
|
ENTRY->is_static = true;
|
||||||
ctx->static_objects = refcount_list_push(ctx->static_objects, obj);
|
ctx->static_objects =
|
||||||
|
refcount_list_push_full(ctx->static_objects, obj, &ctx->alloc);
|
||||||
|
if (!ctx->static_objects) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
ENTRY->static_entry = ctx->static_objects;
|
ENTRY->static_entry = ctx->static_objects;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the references held by an object.
|
||||||
|
* @param ctx The #RefcountContext
|
||||||
|
* @param obj The object
|
||||||
|
* @param refs Where to store the refs
|
||||||
|
* @return True on success
|
||||||
|
*/
|
||||||
|
static inline bool obj_held_refs(RefcountContext *ctx, void *obj,
|
||||||
|
RefcountList **refs) {
|
||||||
|
if (ctx->held_refs_callback) {
|
||||||
|
return ctx->held_refs_callback(obj, refs, ctx->user_data);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregister a static object in a context.
|
* Unregister a static object in a context.
|
||||||
* @param ctx The #RefcountContext
|
* @param ctx The #RefcountContext
|
||||||
* @param obj The object to unregister
|
* @param obj The object to unregister
|
||||||
|
* @return True on success, false otherwise
|
||||||
*/
|
*/
|
||||||
void refcount_context_deinit_static(RefcountContext *ctx, void *obj) {
|
bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) {
|
||||||
if (refcount_context_is_static(ctx, obj)) {
|
if (refcount_context_is_static(ctx, obj)) {
|
||||||
ctx->static_objects = refcount_list_remove(ctx->static_objects,
|
RefcountList *held_refs = NULL;
|
||||||
ENTRY->static_entry, NULL);
|
if (!obj_held_refs(ctx, obj, &held_refs)) {
|
||||||
RefcountList *held_refs =
|
return false;
|
||||||
ctx->held_refs_callback ? ctx->held_refs_callback(obj) : NULL;
|
}
|
||||||
refcount_list_free_with_data(held_refs,
|
ctx->static_objects = refcount_list_remove_full(
|
||||||
refcount_context_unref_as_callback, ctx);
|
ctx->static_objects, ENTRY->static_entry, NULL, &ctx->alloc);
|
||||||
|
refcount_list_free_with_data_full(
|
||||||
|
held_refs, refcount_context_unref_as_callback, ctx, &ctx->alloc);
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
BREAKPOINT(DEINIT_STATIC, ctx, obj)
|
BREAKPOINT(DEINIT_STATIC, ctx, obj)
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +162,9 @@ void refcount_context_deinit_static(RefcountContext *ctx, void *obj) {
|
|||||||
* @return The input object
|
* @return The input object
|
||||||
*/
|
*/
|
||||||
void *refcount_context_ref(RefcountContext *ctx, void *obj) {
|
void *refcount_context_ref(RefcountContext *ctx, void *obj) {
|
||||||
if (!ENTRY->is_static) {
|
if (!obj) {
|
||||||
|
return NULL;
|
||||||
|
} else if (!ENTRY->is_static) {
|
||||||
++ENTRY->ref_count;
|
++ENTRY->ref_count;
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
@ -137,12 +175,18 @@ void *refcount_context_ref(RefcountContext *ctx, void *obj) {
|
|||||||
* already tracked object.
|
* already tracked object.
|
||||||
* @param ctx the #RefcountContext
|
* @param ctx the #RefcountContext
|
||||||
* @param obj The object to track
|
* @param obj The object to track
|
||||||
|
* @return True on success
|
||||||
*/
|
*/
|
||||||
static void track_gc_root(RefcountContext *ctx, void *obj) {
|
static bool track_gc_root(RefcountContext *ctx, void *obj) {
|
||||||
if (!ENTRY->gc_root) {
|
if (!ENTRY->gc_root) {
|
||||||
ctx->gc_roots = refcount_list_push(ctx->gc_roots, obj);
|
ctx->gc_roots =
|
||||||
|
refcount_list_push_full(ctx->gc_roots, obj, &ctx->alloc);
|
||||||
|
if (!ctx->gc_roots) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
ENTRY->gc_root = ctx->gc_roots;
|
ENTRY->gc_root = ctx->gc_roots;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,7 +196,8 @@ static void track_gc_root(RefcountContext *ctx, void *obj) {
|
|||||||
* @param obj The object to untrack
|
* @param obj The object to untrack
|
||||||
*/
|
*/
|
||||||
static void remove_gc_root(RefcountContext *ctx, void *obj) {
|
static void remove_gc_root(RefcountContext *ctx, void *obj) {
|
||||||
ctx->gc_roots = refcount_list_remove(ctx->gc_roots, ENTRY->gc_root, NULL);
|
ctx->gc_roots = refcount_list_remove_full(ctx->gc_roots, ENTRY->gc_root,
|
||||||
|
NULL, &ctx->alloc);
|
||||||
ENTRY->gc_root = NULL;
|
ENTRY->gc_root = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,14 +207,19 @@ static void remove_gc_root(RefcountContext *ctx, void *obj) {
|
|||||||
* @param ctx The #RefcountContext
|
* @param ctx The #RefcountContext
|
||||||
* @param obj The object to unref
|
* @param obj The object to unref
|
||||||
* @param queue A double pointer to a #RefcountList acting as a queue
|
* @param queue A double pointer to a #RefcountList acting as a queue
|
||||||
|
* @param toplevel The toplevel object that triggered this unref. It is ignored
|
||||||
|
* so that it is not freed twice in the specific case that an object with a
|
||||||
|
* floating ref and a reference cycle is freed
|
||||||
* @return NULL if the reference count fell to 0, the given object otherwise
|
* @return NULL if the reference count fell to 0, the given object otherwise
|
||||||
*/
|
*/
|
||||||
static void *unref_to_queue(RefcountContext *ctx, void *obj,
|
static void *unref_to_queue(RefcountContext *ctx, void *obj,
|
||||||
RefcountList **queue) {
|
RefcountList **queue, void *toplevel) {
|
||||||
if (ENTRY->is_static) {
|
if (ENTRY->is_static) {
|
||||||
return obj;
|
return obj;
|
||||||
|
} else if (obj == toplevel) {
|
||||||
|
return NULL;
|
||||||
} else if (ENTRY->ref_count <= 1) {
|
} else if (ENTRY->ref_count <= 1) {
|
||||||
*queue = refcount_list_push(*queue, obj);
|
*queue = refcount_list_push_full(*queue, obj, &ctx->alloc);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
--ENTRY->ref_count;
|
--ENTRY->ref_count;
|
||||||
@ -183,6 +233,7 @@ static void *unref_to_queue(RefcountContext *ctx, void *obj,
|
|||||||
*/
|
*/
|
||||||
struct ContextAndQueue {
|
struct ContextAndQueue {
|
||||||
RefcountContext *ctx; //!< The context.
|
RefcountContext *ctx; //!< The context.
|
||||||
|
void *toplevel; //!< Toplevel object that triggered the unref.
|
||||||
RefcountList **queue; //!< The queue.
|
RefcountList **queue; //!< The queue.
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -193,27 +244,49 @@ struct ContextAndQueue {
|
|||||||
*/
|
*/
|
||||||
static void unref_to_queue_as_callback(void *obj, void *ctx_and_queue_raw) {
|
static void unref_to_queue_as_callback(void *obj, void *ctx_and_queue_raw) {
|
||||||
struct ContextAndQueue *ctx_and_queue = 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);
|
unref_to_queue(ctx_and_queue->ctx, obj, ctx_and_queue->queue,
|
||||||
|
ctx_and_queue->toplevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy an object by calling it's destructor.
|
||||||
|
* @param ctx The #RefcountContext
|
||||||
|
* @param obj The object to destroy
|
||||||
|
*/
|
||||||
|
static inline void destroy_object(RefcountContext *ctx, void *obj) {
|
||||||
|
remove_gc_root(ctx, obj);
|
||||||
|
if (ctx->destroy_callback) {
|
||||||
|
ctx->destroy_callback(obj, ctx->user_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Continually release held references and object held in a queue.
|
* Continually release held references and object held in a queue.
|
||||||
* @param ctx The #RefcountContext
|
* @param ctx The #RefcountContext
|
||||||
* @param queue The queue
|
* @param queue The queue
|
||||||
|
* @param toplevel Toplevel object that triggered the unref
|
||||||
*/
|
*/
|
||||||
static void process_unref_queue(RefcountContext *ctx, RefcountList *queue) {
|
static void process_unref_queue(RefcountContext *ctx, RefcountList *queue,
|
||||||
struct ContextAndQueue ctx_and_queue = {.ctx = ctx, .queue = &queue};
|
void *toplevel) {
|
||||||
|
struct ContextAndQueue ctx_and_queue = {
|
||||||
|
.ctx = ctx, .queue = &queue, .toplevel = toplevel};
|
||||||
while (queue) {
|
while (queue) {
|
||||||
void *cur = refcount_list_peek(queue);
|
void *cur = refcount_list_peek(queue);
|
||||||
queue = refcount_list_pop(queue, NULL);
|
RefcountList *held_refs = NULL;
|
||||||
RefcountList *held_refs =
|
queue = refcount_list_pop_full(queue, NULL, &ctx->alloc);
|
||||||
ctx->held_refs_callback ? ctx->held_refs_callback(cur) : NULL;
|
if (!cur) {
|
||||||
refcount_list_free_with_data(held_refs, unref_to_queue_as_callback,
|
continue;
|
||||||
&ctx_and_queue);
|
|
||||||
remove_gc_root(ctx, cur);
|
|
||||||
if (ctx->destroy_callback) {
|
|
||||||
ctx->destroy_callback(cur);
|
|
||||||
}
|
}
|
||||||
|
if (obj_held_refs(ctx, cur, &held_refs)) {
|
||||||
|
// I don't really know how else to handle this as I can't think of a
|
||||||
|
// good way to undo all the unrefs that have already been processed,
|
||||||
|
// so we can't really make this atomic without going over all
|
||||||
|
// objects twice.
|
||||||
|
refcount_list_free_with_data_full(held_refs,
|
||||||
|
unref_to_queue_as_callback,
|
||||||
|
&ctx_and_queue, &ctx->alloc);
|
||||||
|
}
|
||||||
|
destroy_object(ctx, cur);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,10 +299,13 @@ static void process_unref_queue(RefcountContext *ctx, RefcountList *queue) {
|
|||||||
* object
|
* object
|
||||||
*/
|
*/
|
||||||
void *refcount_context_unref(RefcountContext *ctx, void *obj) {
|
void *refcount_context_unref(RefcountContext *ctx, void *obj) {
|
||||||
|
if (!obj) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
RefcountList *queue = NULL;
|
RefcountList *queue = NULL;
|
||||||
obj = unref_to_queue(ctx, obj, &queue);
|
void *retval = unref_to_queue(ctx, obj, &queue, NULL);
|
||||||
process_unref_queue(ctx, queue);
|
process_unref_queue(ctx, queue, obj);
|
||||||
return obj;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -242,10 +318,13 @@ void *refcount_context_unref(RefcountContext *ctx, void *obj) {
|
|||||||
* @return The input object
|
* @return The input object
|
||||||
*/
|
*/
|
||||||
void *refcount_context_float(RefcountContext *ctx, void *obj) {
|
void *refcount_context_float(RefcountContext *ctx, void *obj) {
|
||||||
if (ENTRY->is_static) {
|
if (!obj) {
|
||||||
|
return NULL;
|
||||||
|
} else if (ENTRY->is_static) {
|
||||||
return obj;
|
return obj;
|
||||||
} else if (!ENTRY->ref_count) {
|
} else if (!ENTRY->ref_count) {
|
||||||
BREAKPOINT(FLOAT, ctx, obj);
|
BREAKPOINT(FLOAT, ctx, obj);
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
if (--ENTRY->ref_count) {
|
if (--ENTRY->ref_count) {
|
||||||
track_gc_root(ctx, obj);
|
track_gc_root(ctx, obj);
|
||||||
@ -254,3 +333,108 @@ void *refcount_context_float(RefcountContext *ctx, void *obj) {
|
|||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of hash table functions used in #gc_check_root.
|
||||||
|
*/
|
||||||
|
static const HTTableFunctions ROOT_COUNTS_FNS = {
|
||||||
|
.hash = ht_intptr_hash_callback,
|
||||||
|
.equal = ht_intptr_equal_callback,
|
||||||
|
.destroy_key = NULL,
|
||||||
|
.destroy_value = NULL,
|
||||||
|
.user_data = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ContextAndRootPtr {
|
||||||
|
RefcountContext *ctx;
|
||||||
|
RefcountList **root_ptr;
|
||||||
|
bool did_update;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool free_roots_foreach(void *obj, void *ignored, void *user_data) {
|
||||||
|
struct ContextAndRootPtr *data = user_data;
|
||||||
|
if (*data->root_ptr == REFCOUNT_OBJECT_ENTRY(data->ctx, obj)->gc_root) {
|
||||||
|
*data->root_ptr = (*data->root_ptr)->next;
|
||||||
|
data->did_update = true;
|
||||||
|
}
|
||||||
|
destroy_object(data->ctx, obj);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the root pointed to by the double pointer root_ptr. After the call,
|
||||||
|
* root_ptr is set to the next root to be checked.
|
||||||
|
* @param ctx The context
|
||||||
|
* @param root_ptr Double pointer to one of the GC roots
|
||||||
|
* @return True on success
|
||||||
|
*/
|
||||||
|
static bool check_gc_root(RefcountContext *ctx, RefcountList **root_ptr) {
|
||||||
|
HTTable *counts = ht_new(&ROOT_COUNTS_FNS, &ctx->ht_alloc, NULL);
|
||||||
|
if (!counts) {
|
||||||
|
*root_ptr = (*root_ptr)->next;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RefcountList *root = *root_ptr;
|
||||||
|
RefcountList *queue = NULL;
|
||||||
|
if (!obj_held_refs(ctx, root->data, &queue)) {
|
||||||
|
ht_free(counts);
|
||||||
|
*root_ptr = (*root_ptr)->next;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t seen_objects = 0;
|
||||||
|
size_t clear_objects = 0;
|
||||||
|
// ignore allocation errors until I decide how to deal with them (never)
|
||||||
|
while (queue) {
|
||||||
|
void *obj = queue->data;
|
||||||
|
if (!obj || refcount_context_is_static(ctx, obj)) {
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
uintptr_t count;
|
||||||
|
if (ht_has(counts, obj)) {
|
||||||
|
count = HT_UUNSTUFF(ht_get(counts, obj));
|
||||||
|
} else {
|
||||||
|
count = REFCOUNT_OBJECT_ENTRY(ctx, obj)->ref_count;
|
||||||
|
++seen_objects;
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
|
ht_insert(counts, obj, HT_STUFF(--count));
|
||||||
|
if (count == 0) {
|
||||||
|
++clear_objects;
|
||||||
|
}
|
||||||
|
obj_held_refs(ctx, root->data, &queue);
|
||||||
|
}
|
||||||
|
next:
|
||||||
|
queue = refcount_list_pop_full(queue, NULL, &ctx->alloc);
|
||||||
|
}
|
||||||
|
if (seen_objects == clear_objects) {
|
||||||
|
struct ContextAndRootPtr data = {
|
||||||
|
.ctx = ctx, .root_ptr = root_ptr, .did_update = false};
|
||||||
|
ht_foreach(counts, free_roots_foreach, &data);
|
||||||
|
if (!data.did_update) {
|
||||||
|
*root_ptr = (*root_ptr)->next;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*root_ptr = (*root_ptr)->next;
|
||||||
|
}
|
||||||
|
ht_free(counts);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the garbage collector on a context.
|
||||||
|
* @param cts The #RefcountContext
|
||||||
|
* @return False if an error occurred, true otherwise
|
||||||
|
*/
|
||||||
|
bool refcount_context_garbage_collect(RefcountContext *ctx) {
|
||||||
|
if (!ctx->held_refs_callback) {
|
||||||
|
// no loops possible
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
RefcountList *root = ctx->gc_roots;
|
||||||
|
while (root) {
|
||||||
|
if (!check_gc_root(ctx, &root)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
link_libraries(refcount)
|
link_libraries(refcount)
|
||||||
add_compile_options(-Wall)
|
add_compile_options(-Wall -Wpedantic)
|
||||||
|
|
||||||
add_executable(test_list test_list.c)
|
add_executable(test_list test_list.c)
|
||||||
add_test(NAME list COMMAND test_list)
|
add_test(NAME list COMMAND test_list)
|
||||||
|
@ -68,7 +68,6 @@ static UNUSED char *counting_strdup(const char *str) {
|
|||||||
|
|
||||||
static UNUSED const RefcountAllocator COUNTING_ALLOCATOR = {
|
static UNUSED const RefcountAllocator COUNTING_ALLOCATOR = {
|
||||||
.malloc = counting_malloc,
|
.malloc = counting_malloc,
|
||||||
.realloc = counting_realloc,
|
|
||||||
.free = counting_free,
|
.free = counting_free,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,7 +4,11 @@
|
|||||||
#include <refcount/list.h>
|
#include <refcount/list.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
int main() {
|
void *counting_strdup_callback(const void *str, void *ignored) {
|
||||||
|
return counting_strdup(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char **argv) {
|
||||||
refcount_global_allocator = &COUNTING_ALLOCATOR;
|
refcount_global_allocator = &COUNTING_ALLOCATOR;
|
||||||
|
|
||||||
assert(refcount_list_length(NULL) == 0);
|
assert(refcount_list_length(NULL) == 0);
|
||||||
@ -77,7 +81,7 @@ int main() {
|
|||||||
assert(strcmp(refcount_list_nth(l, 3), "str4") == 0);
|
assert(strcmp(refcount_list_nth(l, 3), "str4") == 0);
|
||||||
assert(strcmp(refcount_list_nth(l, 4), "str5") == 0);
|
assert(strcmp(refcount_list_nth(l, 4), "str5") == 0);
|
||||||
|
|
||||||
RefcountList *l2 = refcount_list_copy(l, (void *) counting_strdup);
|
RefcountList *l2 = refcount_list_copy(l, counting_strdup_callback, NULL);
|
||||||
|
|
||||||
refcount_list_free(l, counting_free);
|
refcount_list_free(l, counting_free);
|
||||||
|
|
||||||
|
@ -7,49 +7,78 @@ typedef struct A {
|
|||||||
int num;
|
int num;
|
||||||
RefcountEntry refcount;
|
RefcountEntry refcount;
|
||||||
char *str;
|
char *str;
|
||||||
|
struct A *next;
|
||||||
} A;
|
} A;
|
||||||
|
|
||||||
A *make_a(RefcountContext *c, int n, const char *s) {
|
A *make_a(RefcountContext *c, int n, const char *s) {
|
||||||
A *a = counting_malloc(sizeof(A));
|
A *a = counting_malloc(sizeof(A));
|
||||||
a->num = n;
|
a->num = n;
|
||||||
a->str = counting_strdup(s);
|
a->str = counting_strdup(s);
|
||||||
|
a->next = NULL;
|
||||||
refcount_context_init_obj(c, a);
|
refcount_context_init_obj(c, a);
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy_callback(void *a_raw) {
|
bool held_refs_callback(void *a_raw, RefcountList **out, void *ignored) {
|
||||||
|
A *a = a_raw;
|
||||||
|
if (a->next) {
|
||||||
|
*out = refcount_list_push_full(*out, a->next, &COUNTING_ALLOCATOR);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_callback(void *a_raw, void *ignored) {
|
||||||
A *a = a_raw;
|
A *a = a_raw;
|
||||||
counting_free(a->str);
|
counting_free(a->str);
|
||||||
counting_free(a);
|
counting_free(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main(int argc, const char **argv) {
|
||||||
refcount_global_allocator = &COUNTING_ALLOCATOR;
|
|
||||||
|
|
||||||
RefcountContext *c =
|
RefcountContext *c =
|
||||||
refcount_make_context(offsetof(A, refcount), NULL, destroy_callback);
|
refcount_make_context(offsetof(A, refcount), held_refs_callback,
|
||||||
|
destroy_callback, NULL, &COUNTING_ALLOCATOR);
|
||||||
|
|
||||||
A *a = make_a(c, 10, "Hello world\n");
|
A *a = make_a(c, 10, "Hello world\n");
|
||||||
assert(!refcount_context_is_static(c, a));
|
assert(!refcount_context_is_static(c, a));
|
||||||
assert(refcount_context_num_refs(c, a) == 0);
|
assert(refcount_context_num_refs(c, a) == 0);
|
||||||
|
|
||||||
refcount_context_ref(c, a);
|
a = refcount_context_ref(c, a);
|
||||||
assert(refcount_context_num_refs(c, a) == 1);
|
assert(refcount_context_num_refs(c, a) == 1);
|
||||||
|
|
||||||
refcount_context_ref(c, a);
|
a = refcount_context_ref(c, a);
|
||||||
assert(refcount_context_num_refs(c, a) == 2);
|
assert(refcount_context_num_refs(c, a) == 2);
|
||||||
|
|
||||||
refcount_context_unref(c, a);
|
a = refcount_context_unref(c, a);
|
||||||
assert(refcount_context_num_refs(c, a) == 1);
|
assert(refcount_context_num_refs(c, a) == 1);
|
||||||
|
|
||||||
refcount_context_float(c, a);
|
a = refcount_context_float(c, a);
|
||||||
|
assert(a);
|
||||||
assert(refcount_context_num_refs(c, a) == 0);
|
assert(refcount_context_num_refs(c, a) == 0);
|
||||||
|
|
||||||
refcount_context_ref(c, a);
|
refcount_context_ref(c, a);
|
||||||
assert(refcount_context_num_refs(c, a) == 1);
|
assert(refcount_context_num_refs(c, a) == 1);
|
||||||
|
|
||||||
|
a = refcount_context_unref(c, a);
|
||||||
|
assert(!a);
|
||||||
|
|
||||||
|
a = make_a(c, 10, "Hello World\n");
|
||||||
|
A *b = make_a(c, 42, "The answer!");
|
||||||
|
a->next = refcount_context_ref(c, b);
|
||||||
|
assert(refcount_context_num_refs(c, a->next) == 1);
|
||||||
|
assert(refcount_context_num_refs(c, a) == 0);
|
||||||
|
|
||||||
refcount_context_unref(c, a);
|
refcount_context_unref(c, a);
|
||||||
|
|
||||||
|
a = make_a(c, 'a', "a");
|
||||||
|
a->next = refcount_context_ref(c, a);
|
||||||
|
assert(refcount_context_num_refs(c, a) == 1);
|
||||||
|
|
||||||
|
refcount_context_ref(c, a);
|
||||||
|
refcount_context_unref(c, a);
|
||||||
|
assert(refcount_context_num_refs(c, a) == 1);
|
||||||
|
|
||||||
|
refcount_context_garbage_collect(c);
|
||||||
|
|
||||||
refcount_context_destroy(c);
|
refcount_context_destroy(c);
|
||||||
|
|
||||||
check_allocator_status();
|
check_allocator_status();
|
||||||
|
Reference in New Issue
Block a user