From f758bd4a4934ce3bd7931049e1ce61ddb40c367a Mon Sep 17 00:00:00 2001 From: Michael Clark Date: Mon, 26 Feb 2024 10:31:59 +1300 Subject: [PATCH] Add alignment block splitting to malloc_freelist The main allocator is modified to split blocks to align them. The fundamental algorithm is mostly unchanged although there is some additional logic to calculate alignment_slack in the case that the alignment is greater than the alignment of the found block. The alignment_slack is used to split the block with the surplus returned to the freelist. Some indent has been removed from the code for readability. aligned_free is now redundant but it is kept for compatibility. It is no longer necessary to unwrap an offset field in wrapper headers as the main allocation header block is aligned and free can be called directly on aligned allocations, so aligned_free now simply delegates to free(). The documentation is updated to reflect these changes. --- include/aligned_malloc.h | 16 +++-- src/aligned_malloc.c | 58 +---------------- src/malloc_freelist.c | 134 ++++++++++++++++++++++++--------------- 3 files changed, 93 insertions(+), 115 deletions(-) diff --git a/include/aligned_malloc.h b/include/aligned_malloc.h index 3aabb95..9da5537 100644 --- a/include/aligned_malloc.h +++ b/include/aligned_malloc.h @@ -16,8 +16,6 @@ extern "C" { * @brief Allocated aligned memory * * Allocate memory with at least alignment `align` and size `size` - * Memory which has been allocated with aligned_malloc() must be freed by calling - * aligned_free(). Calling free() will result in a panic or other negative effects. * * @param align Alignment of the memory block. * Alignment refers to the starting address of the memory block. @@ -59,10 +57,9 @@ void* aligned_malloc(size_t align, size_t size); /** Posix Memory Alignment Extension * * Generated aligned memory. This function forwards the request to aligned malloc. - * Allocated memory must be freed with aligned_free(). * - * @param memptr A pointer to the pointer which will store the aligned memory. The - * memory must be freed with aligned_free(). memptr must not be NULL. + * @param memptr A pointer to the pointer which will store the aligned memory. + * memptr must not be NULL. * @param alignment The target alignment for the memory. Must be a power of 2. * @param size The size of the allocation. Must be > 0. * @@ -77,8 +74,13 @@ int posix_memalign(void** memptr, size_t alignment, size_t size); * @brief Free aligned memory * * Free memory that was allocated using aligned_malloc(). - * This function *must not* be called on memory which was not allocated - * with aligned_malloc(). + * + * This function is kept for compatibility and it simply calls free(). + * The main allocator now splits blocks to align them making aligned_free + * redundant. It was previously necessary to unwrap an offset field in a + * wrapper header but now the regular header block is aligned so that free + * can be called directly on aligned allocations and the excess alignment + * slack is now added to the freelist instead of wasted. * * @param ptr Pointer to the aligned_memory() block that will be freed. */ diff --git a/src/aligned_malloc.c b/src/aligned_malloc.c index af1711a..3dde204 100644 --- a/src/aligned_malloc.c +++ b/src/aligned_malloc.c @@ -34,43 +34,6 @@ typedef uint16_t offset_t; #pragma mark - APIs - -/** - * We will call malloc with extra bytes for our header and the offset - * required to guarantee the desired alignment. - */ -void* aligned_malloc(size_t align, size_t size) -{ - void* ptr = NULL; - - // We want it to be a power of two since align_up operates on powers of two - assert((align & (align - 1)) == 0); - - if(align && size) - { - /* - * We know we have to fit an offset value - * We also allocate extra bytes to ensure we can meet the alignment - */ - size_t hdr_size = PTR_OFFSET_SZ + (align - 1); - void* base_ptr = malloc(size + hdr_size); - - if(base_ptr) - { - /* - * Add the offset size to malloc's pointer (we will always store that) - * Then align the resulting value to the target alignment - */ - ptr = (void*)align_up(((uintptr_t)base_ptr + PTR_OFFSET_SZ), align); - - // Calculate the offset and store it behind our aligned pointer - *((offset_t*)ptr - 1) = (offset_t)((uintptr_t)ptr - (uintptr_t)base_ptr); - - } // else NULL, could not malloc - } // else NULL, invalid arguments - - return ptr; -} - #if(defined(__ISO_C_VISIBLE) && __ISO_C_VISIBLE >= 2011) || \ (defined(__ISO_C_VISIBLE) && __STDC_VERSION >= 20112L) void* aligned_alloc(size_t align, size_t size) @@ -80,23 +43,6 @@ void* aligned_alloc(size_t align, size_t size) #endif /** - * aligned_free works like free(), but we work backwards from the returned - * pointer to find the correct offset and pointer location to return to free() - * Note that it is VERY BAD to call free() on an aligned_malloc() pointer. + * This function is kept for compatibility and it simply calls free(). */ -void aligned_free(void* ptr) -{ - assert(ptr); - - /* - * Walk backwards from the passed-in pointer to get the pointer offset - * We convert to an offset_t pointer and rely on pointer math to get the data - */ - offset_t offset = *((offset_t*)ptr - 1); - - /* - * Once we have the offset, we can get our original pointer and call free - */ - void* base_ptr = (void*)((uint8_t*)ptr - offset); - free(base_ptr); -} +void aligned_free(void* ptr) { free(ptr); } diff --git a/src/malloc_freelist.c b/src/malloc_freelist.c index 556fbbd..13814dc 100644 --- a/src/malloc_freelist.c +++ b/src/malloc_freelist.c @@ -5,6 +5,7 @@ #include #include +#include #include /// By default, the freelist is declared as static so that it cannot be accessed @@ -46,8 +47,8 @@ typedef struct */ #define ALLOC_HEADER_SZ offsetof(alloc_node_t, block) -// We are enforcing a minimum allocation size of 32B. -#define MIN_ALLOC_SZ ALLOC_HEADER_SZ + 32 +/* minimum allocation of one pointer */ +#define MIN_ALLOC_SZ (ALLOC_HEADER_SZ + sizeof(void*)) #pragma mark - Prototypes - @@ -128,78 +129,107 @@ __attribute__((weak)) void malloc_unlock() // Intentional no-op } -void* malloc(size_t size) +void *aligned_malloc(size_t align, size_t size) { - void* ptr = NULL; - alloc_node_t* found_block = NULL; + alloc_node_t* blk = NULL, *alloc_blk = NULL, *new_blk; + uintptr_t alignment_slack = 0; - if(size > 0) - { - // Align the pointer - size = align_up(size, sizeof(void*)); + // Return NULL pointer for zero size or invalid alignment + if (size == 0 || align == 0 || (align & (align - 1)) != 0) return NULL; - malloc_lock(); + // Make sure alignment is at least pointer width + if (align < sizeof(void*)) align = sizeof(void*); - // try to find a big enough block to alloc - list_for_each_entry(found_block, &free_list, node) - { - if(found_block->size >= size) - { - ptr = &found_block->block; - break; - } - } + // Align size to the pointer width + size = align_up(size, sizeof(void*)); - // we found something - if(ptr) - { - // Can we split the block? - if((found_block->size - size) >= MIN_ALLOC_SZ) - { - alloc_node_t* new_block = (alloc_node_t*)((uintptr_t)(&found_block->block) + size); - new_block->size = found_block->size - size - ALLOC_HEADER_SZ; - found_block->size = size; - list_insert(&new_block->node, &found_block->node, found_block->node.next); - } + malloc_lock(); - list_del(&found_block->node); + // try to find a big enough block with space for alignment + list_for_each_entry(blk, &free_list, node) + { + // calculate slack to align an unaligned block including space for + // an allocation header. slack will be zero for default alignment. + uintptr_t start = (uintptr_t)&blk->block; + uintptr_t end = align_up(start, align); + while (end - start != 0 && + end - start < ALLOC_HEADER_SZ) end += align; + alignment_slack = end - start; + + // break if the block is big enough + if (blk->size >= size + alignment_slack) + { + alloc_blk = blk; + break; } + } + if (!alloc_blk) { malloc_unlock(); + return NULL; + } - } // else NULL + // split block for alignment, if necessary, by subtracting the + // slack less the allocation header size and adding that to the + // freelist so that our block field is sufficiently aligned. + if (alignment_slack) { + uintptr_t start = (uintptr_t)&alloc_blk->block; + new_blk = (alloc_node_t*)(start + alignment_slack - ALLOC_HEADER_SZ); + new_blk->size = alloc_blk->size - alignment_slack; + alloc_blk->size = alignment_slack - ALLOC_HEADER_SZ; + list_add(&new_blk->node, &alloc_blk->node); + alloc_blk = new_blk; + } + + // split remainder of block if possible + if ((alloc_blk->size - size) >= MIN_ALLOC_SZ) + { + uintptr_t start = (uintptr_t)&alloc_blk->block; + new_blk = (alloc_node_t*)(start + size); + new_blk->size = alloc_blk->size - size - ALLOC_HEADER_SZ; + alloc_blk->size = size; + list_add(&new_blk->node, &alloc_blk->node); + } - return ptr; + list_del(&alloc_blk->node); + + malloc_unlock(); + + return &alloc_blk->block; +} + +void* malloc(size_t size) +{ + return aligned_malloc(sizeof(void*), size); } void free(void* ptr) { // Don't free a NULL pointer.. - if(ptr) - { - // we take the pointer and use container_of to get the corresponding alloc block - alloc_node_t* current_block = container_of(ptr, alloc_node_t, block); - alloc_node_t* free_block = NULL; + if(!ptr) return; - malloc_lock(); + // we take the pointer and use container_of to get the corresponding alloc block + alloc_node_t* current_block = container_of(ptr, alloc_node_t, block); + alloc_node_t* free_block = NULL; - // Let's put it back in the proper spot - list_for_each_entry(free_block, &free_list, node) + malloc_lock(); + + // Let's put it back in the proper spot + list_for_each_entry(free_block, &free_list, node) + { + if(free_block > current_block) { - if(free_block > current_block) - { - list_insert(¤t_block->node, free_block->node.prev, &free_block->node); - goto blockadded; - } + list_insert(¤t_block->node, free_block->node.prev, &free_block->node); + goto blockadded; } - list_add_tail(¤t_block->node, &free_list); + } + list_add_tail(¤t_block->node, &free_list); - blockadded: - // Let's see if we can combine any memory - defrag_free_list(); +blockadded: + // Let's see if we can combine any memory + defrag_free_list(); - malloc_unlock(); - } + malloc_unlock(); } void malloc_addblock(void* addr, size_t size)