summaryrefslogblamecommitdiffstats
path: root/malloc.h
blob: b5540d14e6513d154810e6597823484e3b69c8c5 (plain) (tree)















































































































































































                                                                                   
#ifndef _MALLOC_H
#define _MALLOC_H 1

/* A bucket-based malloc implementation for 2^n sized objects.
 * Allocates memory at pbrk for use in dynamic allocations, called "objects".
 * Each object is pre-empted by metadata, which stores a checksum, the width, 
 * and a bit to mark it as taken.
 *
 * Each alloc-able region is pre-empted by a "bucket", which stores metadata
 * about the bucket's state, the width, the first free object, and the next
 * bucket in the chain.
 *
 * Buckets are allocated when needed. The width of the new bucket is determined
 * by the object that needs the bucket.
 * Buckets are always page aligned, and the size is a multiple of a page width.
 *
 * Dependencies:
 *  stddef.h
 *  stdint.h
 *  errno.h
 *  assert.h
 *      assert
 *  string.h
 *      memset
 *      memcpy
 *  unistd.h
 *      sbrk
 *      brk
 *
 * with MALLOC_DBG:
 *  stdio.h
 *      printf*/

#include <stddef.h>
#include <stdint.h>

#define DBGPRINT(...)
#ifdef MALLOC_DBG
#include <stdio.h>
#undef DBGPRINT
#define DBGPRINT(...) printf(__VA_ARGS__)
#endif

/* Bucket checksum is a 64-bit value containing the string "BUCKET\0\0"*/
#define OBJ_CHECKSUM \
    (((uintmax_t)'B') | \
    ((uintmax_t)'U' << 8) | \
    ((uintmax_t)'C' << 16) | \
    ((uintmax_t)'K' << 24) | \
    ((uintmax_t)'E' << 32) | \
    ((uintmax_t)'T' << 40))

/* For an object to be valid, the object must have the correct checksum.*/
#define OBJ_VALID(obj) (obj->checksum == OBJ_CHECKSUM)

/* Object width is assumed to be a multiple of two. The first bit is reserved
 * for the taken flag.*/
#define OBJ_WIDTH(obj) (obj->size_taken & ~1)
#define OBJ_TAKEN(obj) (obj->size_taken & 1)

/* Gets the object struct after this object.
 * Since objects are allocated in-place, we can get the next object using
 * the object's size and it's pointer to the start of data.
 *
 * The same is true for getting the metadata for any given data pointer.
 * We take the pointer and subtract the size of the object struct from it
 * since the metadata is behind the actual data.*/
#define OBJ_AFTER(obj) \
    ((bucket_obj_t*)(((uintptr_t)obj->data) + OBJ_WIDTH(obj)))
#define OBJ_FROM_PTR(ptr) ((bucket_obj_t*)((uintptr_t)ptr - sizeof(bucket_obj_t)));

typedef struct bucket_obj
{
    uintmax_t checksum;
    size_t size_taken;
    char data[];
} bucket_obj_t;

#define LOG2(n) ((31) - __builtin_clz(n))
/* Buckets are always aligned to the page boundary for cache friendliness.*/
#define BUCKET_ALIGN (4096)
/* Buckets should always be a multiple of the page width, expanding with the
 * initial object size.*/
#define BUCKET_WIDTH(w) \
    (1UL << (12 + (w < BUCKET_ALIGN ? 0 : LOG2(w + 1) - 11)))

/* Bucket metadata is stored behind the object metadata.*/
typedef struct bucket
{
    size_t checksum;
    struct bucket *next;

    uintptr_t limit;
    bucket_obj_t *firstfree; /* Helpful for fast object allocations.*/
    char objs[];
} bucket_t;

/* The overall size of an object (data + metadata) should be 2*metadata.*/
#define OBJ_SIZE_MIN (sizeof(bucket_obj_t))
/* Data width is always a power of two.*/
#define REALW(n) \
    (n < OBJ_SIZE_MIN ? OBJ_SIZE_MIN : (1UL << (LOG2(n + 1))))

typedef struct heap_cache
{
    bucket_t *head;
    bucket_t *tail;
} heap_cache_t;

/**Given the heap cache and an object, find the bucket to which the object
 * belongs.
 * Since buckets are always page aligned, we can check each page boundary
 * for the checksum until we find it, then check the limit.
 * Since we also know the first bucket in the cache, we can also check if the
 * passed object is invalid and return NULL.
 * @param heap cache.
 * @param obj to find the bucket for.
 * @return bucket owning obj, or NULL if obj is invalid.*/
bucket_t *__bucket_from_obj(heap_cache_t *heap, bucket_obj_t *obj);

/**Allocate w bytes into the given heap.
 * If w == 0, returns NULL.
 * If w is not a power of 2, rounds to the nearest pow2.
 * If the heap is corrupted, assert fail.
 * If the program is OOM, errno is assumed to be set and returns (void*)-1
 * @param heap cache.
 * @param w n-bytes to allocate.
 * @return pointer to allocated memory.*/
void* __malloc_impl(heap_cache_t *heap, size_t w);

/**Allocate a contiguous array of nmemb members each w wide.
 * Newly allocated memory is zero'd.
 * If w == 0 || nmemb == 0, returns NULL
 * If w is not a power of 2, rounds to nearest pow2
 * If the heap is corrupted, assert fail
 * If the program is OON, ernno is assumed to be set and returns (void*)-1
 * @param heap cache.
 * @param nmemb number of members.
 * @param w number of bytes per member.
 * @return pointer to allocated memory.*/
void* __calloc_impl(heap_cache_t *heap, size_t nmemb, size_t w);

/**Resize the given allocated memory region to fit w.
 * If w is less than the original width, does nothing.
 * If w == 0, frees p
 * If the heap is corrupted, assert fail.
 * If the heap is OOM, errno is assumed to be set and returns (void*)-1
 * @param heap cache.
 * @param p pointer to reallocate.
 * @param w width to fit p to.
 * @return pointer to memory allocated to fit w.*/
void* __realloc_impl(heap_cache_t *heap, void *p, size_t w);

/**Resize the given allocated contiguous array to fit nmemb members of w width
 * objects.
 * Zeros the new members excluding already existing data.
 * If w * nmemb is less than the original width, does nothing.
 * If w == 0 || nmemb == 0, frees p
 * If the heap is corrupted, assert fail.
 * If the heap is OOM, errno is assumed to be set and returns (void*)-1
 * @param heap cache.
 * @param p pointer to reallocate.
 * @param nmemb number of members.
 * @param w width to fit p to.
 * @return pointer to memory allocated to fit w * nmemb.*/
void* __reallocarray_impl(heap_cache_t *heap, void *p, size_t nmemb, size_t w);

/**Free a pointer in the given heap.
 * If p == NULL, do nothing.
 * If p does not fall in the range of heap, assert fail.
 * If the heap is corrupted, assert fail.
 * @param heap cache.
 * @param p pointer to free.*/
void  __free_impl(heap_cache_t *heap, void *p);

#endif