// Copyright 2021 David Robillard // SPDX-License-Identifier: ISC #include "zix/bump_allocator.h" #include "zix/allocator.h" #include "zix/attributes.h" #include #include #include #include static const size_t min_alignment = sizeof(uintmax_t); ZIX_PURE_FUNC static size_t round_up_multiple(const size_t number, const size_t factor) { assert(factor); // Factor must be non-zero assert((factor & (factor - 1U)) == 0U); // Factor must be a power of two return (number + factor - 1U) & ~(factor - 1U); } ZIX_MALLOC_FUNC static void* zix_bump_malloc(ZixAllocator* const allocator, const size_t size) { ZixBumpAllocator* const state = (ZixBumpAllocator*)allocator; assert((uintptr_t)((char*)state->buffer + state->top) % min_alignment == 0U); /* For C malloc(), the result is guaranteed to be aligned for any variable. What that means is deliberately vague to accommodate diverse platforms, but sizeof(uintmax_t) is more than enough on all the common ones. */ const size_t real_size = round_up_multiple(size, min_alignment); if (state->top + real_size > state->capacity) { return NULL; } state->last = state->top; state->top += real_size; return (void*)((char*)state->buffer + state->last); } ZIX_MALLOC_FUNC static void* zix_bump_calloc(ZixAllocator* const allocator, const size_t nmemb, const size_t size) { const size_t total_size = nmemb * size; void* const ptr = zix_bump_malloc(allocator, total_size); if (ptr) { memset(ptr, 0, total_size); } return ptr; } static void* zix_bump_realloc(ZixAllocator* const allocator, void* const ptr, const size_t size) { ZixBumpAllocator* const state = (ZixBumpAllocator*)allocator; if ((char*)ptr != (char*)state->buffer + state->last) { return NULL; } const size_t new_top = state->last + size; if (new_top > state->capacity) { return NULL; } state->top = new_top; return ptr; } static void zix_bump_free(ZixAllocator* const allocator, void* const ptr) { ZixBumpAllocator* const state = (ZixBumpAllocator*)allocator; if (ptr == (char*)state->buffer + state->last) { state->top = state->last; // Reclaim the space of the last allocation } } ZIX_MALLOC_FUNC static void* zix_bump_aligned_alloc(ZixAllocator* const allocator, const size_t alignment, const size_t size) { ZixBumpAllocator* const state = (ZixBumpAllocator*)allocator; const size_t old_last = state->last; const size_t old_top = state->top; assert(alignment >= min_alignment); assert(size % alignment == 0U); /* First, calculate how much we need to offset the top to achieve this alignment. Note that it's not the offset that needs to be aligned (since the buffer may not be), but the final address, so we do the calculation with "global" addresses and calculate the offset from that. */ const char* top_ptr = (char*)state->buffer + state->top; const uintptr_t top_addr = (uintptr_t)top_ptr; const uintptr_t aligned_top_addr = round_up_multiple(top_addr, alignment); const uintptr_t offset = aligned_top_addr - top_addr; if (state->top + offset > state->capacity) { return NULL; } /* Then, adjust the top to be aligned and try to allocate. If that fails, reset everything to how it was before this call. */ state->top += offset; void* const ptr = zix_bump_malloc(allocator, size); if (!ptr) { state->last = old_last; state->top = old_top; return NULL; } return ptr; } static void zix_bump_aligned_free(ZixAllocator* const allocator, void* const ptr) { zix_bump_free(allocator, ptr); } ZIX_CONST_FUNC ZixBumpAllocator zix_bump_allocator(const size_t capacity, void* buffer) { const size_t aligned_top = (uintptr_t)buffer % min_alignment; ZixBumpAllocator bump_allocator = { { zix_bump_malloc, zix_bump_calloc, zix_bump_realloc, zix_bump_free, zix_bump_aligned_alloc, zix_bump_aligned_free, }, buffer, aligned_top, aligned_top, capacity, }; return bump_allocator; }