summaryrefslogblamecommitdiffstats
path: root/malloc.c
blob: 4b574d6efc642c43dd882ab385786337967b0d37 (plain) (tree)
1
2
3
4
5
6
7






                                                                       







































































































































































































                                                                                      
/*
 * Copyright (c) Jon Santmyer.
 * This source file is released under the LGPL Version 3 as detailed in
 * the LICENSE file provided in the following repository:
 * https://git.jonsantmyer.com/heap/
 * */

#include "malloc.h"
#include <assert.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>

/**Allocate memory for a new bucket in the heap.
 * Automatically aligns new memory to BUCKET_ALIGN.
 * Instantiates a new free object spanning the entire bucket.
 * Then adds the new bucket to the heap cache.
 * If sbrk encounters an error, pass through errno and return NULL.
 * @param cache to allocate for.
 * @param objw width of the object the bucket is assumed to hold.
 * @return bucket created by this function.*/
bucket_t*
s_bucket_new(heap_cache_t *cache, size_t objw)
{
    DBGPRINT("malloc: ask for new bucket for cache %p with objw %i\n", cache, objw);
    size_t bucketw = BUCKET_WIDTH(objw);
    /* Assuming sbrk allocates memory for after the program break, make sure
     * we allocate enough for the requested bucket width and any adjustments
     * to ensure page alignment.*/
    uintptr_t pbrk = (uintptr_t)sbrk(0);
    uintptr_t brko = pbrk % BUCKET_ALIGN;
    uintptr_t nbrk = (uintptr_t)sbrk(bucketw + brko);
    if(errno != 0) return NULL; /* Fail on errno. */

    bucket_t *bucket = (bucket_t*)(nbrk + brko);
    DBGPRINT("malloc: got new bucket at %p\n", bucket);

    *bucket = (bucket_t){
        .next = NULL,
        .limit = (uintptr_t)bucket + bucketw,
    };
    /* Create a new object spanning the entire bucket. This new object will
     * be the first free.*/
    bucket_obj_t *obj0 = (bucket_obj_t*)bucket->objs;
    *obj0 = (bucket_obj_t) {
        .checksum = OBJ_CHECKSUM,
        .size_taken = bucketw - sizeof(bucket_t) - sizeof(bucket_obj_t)
    };
    bucket->firstfree = obj0;

    /* If the cache is empty, this will be the first and last bucket.*/
    if(cache->head == NULL) {
        cache->head = cache->tail = bucket;
        return bucket;
    }
    /* Add the bucket to the heap cache bucket list.*/
    cache->tail->next = bucket;
    cache->tail = bucket;
    return bucket;
}

/**Given an existing object and a size, split the object so that the original
 * has size w.
 * If the object is smaller than or is already the correct size, do nothing.
 * Space after the split is allocated to a new free object after the resized
 * original.
 * If the original == bucket->firstfree, the new object is assigned to 
 * that variable.
 * If the heap is corrupted, assert fail.
 * @param bucket object's parent bucket.
 * @param parent object to split.
 * @param w requested size for original object. */
void
s_obj_take_split(bucket_t *bucket, bucket_obj_t *parent, size_t w)
{
    DBGPRINT("malloc: requesting to split object %p (last %p) for size %i (was %i)\n",
           bucket, parent, w, parent->size_taken);

    /* Assert fail on corrupted heap.*/
    assert(OBJ_VALID(bucket));
    assert(OBJ_VALID(parent));
    assert(w >= OBJ_SIZE_MIN);

    size_t objw = OBJ_WIDTH(parent);
    if(w == objw) { /* Obj is already correct size, take and ignore.*/
        if(bucket->firstfree == parent)
            bucket->firstfree = OBJ_AFTER(parent);
        /* Ensure that firstfree does not exceed bucket limits.*/
        if((uintptr_t)bucket->firstfree > bucket->limit)
            bucket->firstfree = NULL;
        DBGPRINT("malloc: Object is already right size, ignoring\n");

        /* Mark object as taken.*/
        parent->size_taken = w | 1;
        return;
    }
    /* New object created by split will be the original width minus the new
     * width and metadata size.
     *
     * from:
     * [meta| --------objw-------- ]
     *
     * to:
     * [meta| -w- ][meta| -afterw- ]*/
    size_t afterw = objw - sizeof(bucket_obj_t) - w;

    parent->size_taken = w | 1;
    bucket_obj_t *obj = OBJ_AFTER(parent);
    DBGPRINT("malloc: New object created inplace at %p with size %i\n",
            obj, afterw);

    /* In-place allocate after obj.*/
    *obj = (bucket_obj_t){
        .checksum = OBJ_CHECKSUM,
        .size_taken = afterw,
    };
    if(bucket->firstfree == parent)
        bucket->firstfree = obj;
}

/* Look for a free object in the given bucket that closest matches size w.
 * Uses bucket->firstfree as a starting point to save checks against taken
 * objects.
 * If the heap is corrupted, assert fail.
 * If there are no good objects in this bucket, return NULL.
 * @param bucket to search in.
 * @param w to search for.
 * @return best-fitting object or NULL if none are found.
 * */
bucket_obj_t*
s_obj_bestfit(bucket_t *bucket, size_t w)
{
    assert(bucket != NULL);
    assert(OBJ_VALID(bucket));

    bucket_obj_t *best = NULL;
    bucket_obj_t *obj = bucket->firstfree;
    if(obj == NULL) return NULL;
    for(; (uintptr_t)obj < bucket->limit; obj = OBJ_AFTER(obj))
    {
        assert(OBJ_VALID(obj));
        if(OBJ_TAKEN(obj)) continue;
        size_t objw = OBJ_WIDTH(obj);
        if(objw < w) continue;
        if(best == NULL) best = obj;

        size_t bestw = OBJ_WIDTH(best);
        if(objw < bestw) best = obj;
    }
    
    DBGPRINT("malloc: Bucket %p returns best fit at %p for size %i ",
            bucket, best, w);
    if(best != NULL) { DBGPRINT("(size %i)\n", best->size_taken); }
    else { DBGPRINT("(is NULL)\n"); }

    return best;
}

/**Searching through the entire heap, or until an object is found, find a
 * free object that best fits the requested width and place the resulting
 * bucket in the passed pointer.
 * If the heap is corrupted, assert fail.
 * If no object is found in any of the buckets, return NULL.
 * @param cache heap to search through.
 * @param w object width to search for.
 * @param bucket pointer to bucket pointer owning the returned bucket.
 *        If no object is found, variable is untouched.
 * @return object that fits the given width.*/
bucket_obj_t*
s_bucket_bestfit(heap_cache_t *cache, size_t w, bucket_t **bucket)
{
    *bucket = cache->head;
    for(; *bucket != NULL; *bucket = (*bucket)->next)
    {
        bucket_obj_t *obj = s_obj_bestfit(*bucket, w);
        /* Object is already found, don't waste cycles searching through
         * the entire heap.*/
        if(obj != NULL) return obj;
    }
    return NULL;
}

void*
__malloc_impl(heap_cache_t *cache, size_t w)
{
    assert(cache != NULL);
    /* w == 0 is ub, just return NULL.*/
    if(w == 0) return NULL;
    /* Ensure that w is valid.*/
    w = REALW(w);

    DBGPRINT("malloc: Asking to malloc for size %i\n", w);

    bucket_t *bucket = NULL;
    bucket_obj_t *obj = s_bucket_bestfit(cache, w, &bucket);
    if(obj == NULL) {
        /* No bucket in the heap has an appropriate space.
         * Allocate a new bucket and take the first object.
         * If the bucket allocation fails, return -1 and pass errno through.*/
        bucket = s_bucket_new(cache, w);
        if(bucket == NULL) return (void*)(-1);
        obj = (bucket_obj_t*)bucket->objs;
    }
    /* Split the bucket to make the object bestfit.*/
    s_obj_take_split(bucket, obj, w);
    return obj->data;
}