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)