diff options
-rw-r--r-- | include/zix/allocator.h | 62 | ||||
-rw-r--r-- | src/.clang-tidy | 17 | ||||
-rw-r--r-- | src/allocator.c | 44 | ||||
-rw-r--r-- | src/btree.c | 26 | ||||
-rw-r--r-- | src/zix_config.h | 54 | ||||
-rw-r--r-- | test/allocator_test.c | 9 | ||||
-rw-r--r-- | test/failing_allocator.c | 24 |
7 files changed, 219 insertions, 17 deletions
diff --git a/include/zix/allocator.h b/include/zix/allocator.h index 809067d..f1737be 100644 --- a/include/zix/allocator.h +++ b/include/zix/allocator.h @@ -23,10 +23,10 @@ extern "C" { A memory allocator. This object-like structure provides an interface like the standard C - functions malloc(), calloc(), realloc(), and free(). It contains function - pointers that differ from their standard counterparts by taking a context - parameter (a pointer to this struct), which allows the user to implement - custom stateful allocators. + functions malloc(), calloc(), realloc(), free(), and aligned_alloc(). It + contains function pointers that differ from their standard counterparts by + taking a context parameter (a pointer to this struct), which allows the user + to implement custom stateful allocators. */ typedef struct ZixAllocatorImpl ZixAllocator; @@ -72,12 +72,37 @@ typedef void (*ZixFreeFunc)( // ZixAllocator* ZIX_NULLABLE allocator, void* ZIX_NULLABLE ptr); +/** + General aligned_alloc-like memory deallocation function. + + This works like the standard C aligned_alloc(), except has an additional + handle parameter for implementing stateful allocators without static data. +*/ +typedef void* ZIX_ALLOCATED (*ZixAlignedAllocFunc)( // + ZixAllocator* ZIX_NULLABLE allocator, + size_t alignment, + size_t size); + +/** + General aligned memory deallocation function. + + This works like the standard C free(), but must be used to free memory + allocated with the aligned_alloc() method of the allocator. This allows + portability to systems (like Windows) that can not use the same free function + in these cases. +*/ +typedef void (*ZixAlignedFreeFunc)( // + ZixAllocator* ZIX_NULLABLE allocator, + void* ZIX_NULLABLE ptr); + /// Definition of ZixAllocator struct ZixAllocatorImpl { - ZixMallocFunc ZIX_NONNULL malloc; - ZixCallocFunc ZIX_NONNULL calloc; - ZixReallocFunc ZIX_NONNULL realloc; - ZixFreeFunc ZIX_NONNULL free; + ZixMallocFunc ZIX_NONNULL malloc; + ZixCallocFunc ZIX_NONNULL calloc; + ZixReallocFunc ZIX_NONNULL realloc; + ZixFreeFunc ZIX_NONNULL free; + ZixAlignedAllocFunc ZIX_NONNULL aligned_alloc; + ZixAlignedFreeFunc ZIX_NONNULL aligned_free; }; /// Return the default allocator which simply uses the system allocator @@ -126,6 +151,27 @@ zix_free(ZixAllocator* const ZIX_NULLABLE allocator, actual->free(actual, ptr); } +/// Convenience wrapper that defers to the system allocator if allocator is null +static inline void* ZIX_ALLOCATED +zix_aligned_alloc(ZixAllocator* const ZIX_NULLABLE allocator, + const size_t alignment, + const size_t size) +{ + ZixAllocator* const actual = allocator ? allocator : zix_default_allocator(); + + return actual->aligned_alloc(actual, alignment, size); +} + +/// Convenience wrapper that defers to the system allocator if allocator is null +static inline void +zix_aligned_free(ZixAllocator* const ZIX_NULLABLE allocator, + void* const ZIX_NULLABLE ptr) +{ + ZixAllocator* const actual = allocator ? allocator : zix_default_allocator(); + + actual->aligned_free(actual, ptr); +} + /** @} @} diff --git a/src/.clang-tidy b/src/.clang-tidy new file mode 100644 index 0000000..aa4ea77 --- /dev/null +++ b/src/.clang-tidy @@ -0,0 +1,17 @@ +Checks: > + *, + -*-magic-numbers, + -*-uppercase-literal-suffix, + -altera-struct-pack-align, + -bugprone-reserved-identifier, + -cert-dcl37-c, + -cert-dcl51-cpp, + -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, + -hicpp-multiway-paths-covered, + -llvm-header-guard, + -llvmlibc-*, + -misc-no-recursion, + -readability-function-cognitive-complexity, +WarningsAsErrors: '*' +HeaderFilterRegex: '.*' +FormatStyle: file diff --git a/src/allocator.c b/src/allocator.c index c998463..bedef47 100644 --- a/src/allocator.c +++ b/src/allocator.c @@ -1,7 +1,18 @@ // Copyright 2011-2021 David Robillard <d@drobilla.net> // SPDX-License-Identifier: ISC +#define _POSIX_C_SOURCE 200809L + +#include "zix_config.h" + #include "zix/allocator.h" +#include "zix/attributes.h" + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN 1 +# include <malloc.h> +# include <windows.h> +#endif #include <stdlib.h> @@ -39,6 +50,37 @@ zix_default_free(ZixAllocator* const allocator, void* const ptr) free(ptr); } +ZIX_MALLOC_FUNC +static void* +zix_default_aligned_alloc(ZixAllocator* const allocator, + const size_t alignment, + const size_t size) +{ + (void)allocator; + +#if defined(_WIN32) + return _aligned_malloc(size, alignment); +#elif USE_POSIX_MEMALIGN + void* ptr = NULL; + const int ret = posix_memalign(&ptr, alignment, size); + return ret ? NULL : ptr; +#else +# error No aligned memory allocation available +#endif +} + +static void +zix_default_aligned_free(ZixAllocator* const allocator, void* const ptr) +{ + (void)allocator; + +#if defined(_WIN32) + _aligned_free(ptr); +#else + free(ptr); +#endif +} + ZixAllocator* zix_default_allocator(void) { @@ -47,6 +89,8 @@ zix_default_allocator(void) zix_default_calloc, zix_default_realloc, zix_default_free, + zix_default_aligned_alloc, + zix_default_aligned_free, }; return &default_allocator; diff --git a/src/btree.c b/src/btree.c index 869b98a..9bb0904 100644 --- a/src/btree.c +++ b/src/btree.c @@ -50,6 +50,7 @@ struct ZixBTreeNodeImpl { #if ((defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112l) || \ (defined(__cplusplus) && __cplusplus >= 201103L)) +static_assert(sizeof(ZixBTree) <= ZIX_BTREE_PAGE_SIZE, ""); static_assert(sizeof(ZixBTreeNode) <= ZIX_BTREE_PAGE_SIZE, ""); static_assert(sizeof(ZixBTreeNode) >= ZIX_BTREE_PAGE_SIZE - 2u * sizeof(ZixBTreeNode*), @@ -66,8 +67,8 @@ zix_btree_node_new(ZixAllocator* const allocator, const bool leaf) ZIX_BTREE_PAGE_SIZE - 2u * sizeof(ZixBTreeNode*)); #endif - ZixBTreeNode* const node = - (ZixBTreeNode*)zix_malloc(allocator, sizeof(ZixBTreeNode)); + ZixBTreeNode* const node = (ZixBTreeNode*)zix_aligned_alloc( + allocator, ZIX_BTREE_PAGE_SIZE, ZIX_BTREE_PAGE_SIZE); if (node) { node->is_leaf = leaf; @@ -91,15 +92,22 @@ zix_btree_new(ZixAllocator* const allocator, const ZixComparator cmp, const void* const cmp_data) { +#if !((defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112l) || \ + (defined(__cplusplus) && __cplusplus >= 201103L)) + assert(sizeof(ZixBTree) <= ZIX_BTREE_PAGE_SIZE); +#endif + assert(cmp); - ZixBTree* const t = (ZixBTree*)zix_malloc(allocator, sizeof(ZixBTree)); + ZixBTree* const t = (ZixBTree*)zix_aligned_alloc( + allocator, ZIX_BTREE_PAGE_SIZE, ZIX_BTREE_PAGE_SIZE); + if (!t) { return NULL; } if (!(t->root = zix_btree_node_new(allocator, true))) { - zix_free(allocator, t); + zix_aligned_free(allocator, t); return NULL; } @@ -121,7 +129,7 @@ zix_btree_free_children(ZixBTree* const t, for (ZixShort i = 0; i < n->n_vals + 1u; ++i) { zix_btree_free_children( t, zix_btree_child(n, i), destroy, destroy_user_data); - zix_free(t->allocator, zix_btree_child(n, i)); + zix_aligned_free(t->allocator, zix_btree_child(n, i)); } } @@ -145,8 +153,8 @@ zix_btree_free(ZixBTree* const t, { if (t) { zix_btree_clear(t, destroy, destroy_user_data); - zix_free(t->allocator, t->root); - zix_free(t->allocator, t); + zix_aligned_free(t->allocator, t->root); + zix_aligned_free(t->allocator, t); } } @@ -618,10 +626,10 @@ zix_btree_merge(ZixBTree* const t, ZixBTreeNode* const n, const unsigned i) // Root is now empty, replace it with its only child assert(n == t->root); t->root = lhs; - zix_free(t->allocator, n); + zix_aligned_free(t->allocator, n); } - zix_free(t->allocator, rhs); + zix_aligned_free(t->allocator, rhs); return lhs; } diff --git a/src/zix_config.h b/src/zix_config.h new file mode 100644 index 0000000..832b50f --- /dev/null +++ b/src/zix_config.h @@ -0,0 +1,54 @@ +// Copyright 2021 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +/* + Configuration header that defines reasonable defaults at compile time. + + This allows compile-time configuration from the command line (typically via + the build system) while still allowing the source to be built without any + configuration. The build system can define ZIX_NO_DEFAULT_CONFIG to disable + defaults, in which case it must define things like HAVE_FEATURE to enable + features. The design here ensures that compiler warnings or + include-what-you-use will catch any mistakes. +*/ + +#ifndef ZIX_CONFIG_H +#define ZIX_CONFIG_H + +#if !defined(ZIX_NO_DEFAULT_CONFIG) + +// We need unistd.h to check _POSIX_VERSION +# ifndef ZIX_NO_POSIX +# ifdef __has_include +# if __has_include(<unistd.h>) +# include <unistd.h> +# endif +# elif defined(__unix__) +# include <unistd.h> +# endif +# endif + +// POSIX.1-2001: posix_memalign() +# ifndef HAVE_POSIX_MEMALIGN +# if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L +# define HAVE_POSIX_MEMALIGN +# endif +# endif + +#endif // !defined(ZIX_NO_DEFAULT_CONFIG) + +/* + Make corresponding USE_FEATURE defines based on the HAVE_FEATURE defines from + above or the command line. The code checks for these using #if (not #ifdef), + so there will be an undefined warning if it checks for an unknown feature, + and this header is always required by any code that checks for features, even + if the build system defines them all. +*/ + +#ifdef HAVE_POSIX_MEMALIGN +# define USE_POSIX_MEMALIGN 1 +#else +# define USE_POSIX_MEMALIGN 0 +#endif + +#endif // ZIX_CONFIG_H diff --git a/test/allocator_test.c b/test/allocator_test.c index a6087e1..5275ce8 100644 --- a/test/allocator_test.c +++ b/test/allocator_test.c @@ -6,6 +6,7 @@ #include "zix/allocator.h" #include <assert.h> +#include <stdint.h> static void test_allocator(void) @@ -40,6 +41,14 @@ test_allocator(void) assert(realloced[6] == 6); assert(realloced[7] == 7); + char* const aligned = (char*)zix_aligned_alloc(allocator, 4096, 4096); + assert((uintptr_t)aligned % 4096 == 0); + aligned[0] = 0; + aligned[3] = 3; + assert(aligned[0] == 0); + assert(aligned[3] == 3); + + zix_aligned_free(allocator, aligned); zix_free(allocator, realloced); zix_free(allocator, malloced); } diff --git a/test/failing_allocator.c b/test/failing_allocator.c index eda762b..684a8ec 100644 --- a/test/failing_allocator.c +++ b/test/failing_allocator.c @@ -66,6 +66,28 @@ zix_failing_free(ZixAllocator* const allocator, void* const ptr) base->free(base, ptr); } +ZIX_MALLOC_FUNC +static void* +zix_failing_aligned_alloc(ZixAllocator* const allocator, + const size_t alignment, + const size_t size) +{ + ZixFailingAllocator* const state = (ZixFailingAllocator*)allocator; + ZixAllocator* const base = zix_default_allocator(); + + return attempt(state) ? base->aligned_alloc(base, alignment, size) : NULL; +} + +static void +zix_failing_aligned_free(ZixAllocator* const allocator, void* const ptr) +{ + (void)allocator; + + ZixAllocator* const base = zix_default_allocator(); + + base->aligned_free(base, ptr); +} + ZIX_CONST_FUNC ZixFailingAllocator zix_failing_allocator(void) @@ -76,6 +98,8 @@ zix_failing_allocator(void) zix_failing_calloc, zix_failing_realloc, zix_failing_free, + zix_failing_aligned_alloc, + zix_failing_aligned_free, }, 0, SIZE_MAX, |