Skip to content

Commit

Permalink
Add alignment block splitting to malloc_freelist
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
michaeljclark committed Feb 25, 2024
1 parent 4ab8b7a commit f758bd4
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 115 deletions.
16 changes: 9 additions & 7 deletions include/aligned_malloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
*
Expand All @@ -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.
*/
Expand Down
58 changes: 2 additions & 56 deletions src/aligned_malloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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); }
134 changes: 82 additions & 52 deletions src/malloc_freelist.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <linkedlist/ll.h>
#include <malloc.h>
#include <aligned_malloc.h>
#include <stdint.h>

/// By default, the freelist is declared as static so that it cannot be accessed
Expand Down Expand Up @@ -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 -

Expand Down Expand Up @@ -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(&current_block->node, free_block->node.prev, &free_block->node);
goto blockadded;
}
list_insert(&current_block->node, free_block->node.prev, &free_block->node);
goto blockadded;
}
list_add_tail(&current_block->node, &free_list);
}
list_add_tail(&current_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)
Expand Down

0 comments on commit f758bd4

Please sign in to comment.