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_with_data(ptr, alloc->user_data); | ||||||
|  |     } else { | ||||||
|         alloc->free(ptr); |         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; | ||||||
|     } |     } | ||||||
|  | |||||||
							
								
								
									
										256
									
								
								src/refcount.c
									
									
									
									
									
								
							
							
						
						
									
										256
									
								
								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) { | ||||||
|  |     if (obj) { | ||||||
|         ENTRY->is_static = false; |         ENTRY->is_static = false; | ||||||
|         ENTRY->gc_root = NULL; |         ENTRY->gc_root = NULL; | ||||||
|         ENTRY->ref_count = 0; |         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