#include "moar.h"

void * MVM_region_alloc(MVMThreadContext *tc, MVMRegionAlloc *al, size_t bytes) {
    char *result = NULL;

    /* Round up size to next multiple of 8, to ensure alignment. */
    bytes = (bytes + 7) & ~7;

    if (al->block != NULL && (al->block->alloc + bytes) < al->block->limit) {
        result = al->block->alloc;
        al->block->alloc += bytes;
    } else {
        /* No block, or block was full. Add another. */
        MVMRegionBlock *block = MVM_malloc(sizeof(MVMRegionBlock));
        size_t buffer_size = al->block == NULL
            ? MVM_REGIONALLOC_FIRST_MEMBLOCK_SIZE
            : MVM_REGIONALLOC_MEMBLOCK_SIZE;
        if (buffer_size < bytes)
            buffer_size = bytes;
        block->buffer = MVM_calloc(1, buffer_size);
        block->alloc  = block->buffer;
        block->limit  = block->buffer + buffer_size;
        block->prev   = al->block;
        al->block     = block;

        /* Now allocate out of it. */
        result = block->alloc;
        block->alloc += bytes;
    }
    return result;
}

void MVM_region_destroy(MVMThreadContext *tc, MVMRegionAlloc *alloc) {
    MVMRegionBlock *block = alloc->block;
    /* Free all of the allocated memory. */
    while (block) {
        MVMRegionBlock *prev = block->prev;
        MVM_free(block->buffer);
        MVM_free(block);
        block = prev;
    }
    alloc->block = NULL;
}

/* Link source region into target region, so they can be cleaned up as one */
void MVM_region_merge(MVMThreadContext *tc, MVMRegionAlloc *target, MVMRegionAlloc *source) {
    MVMRegionBlock *block = source->block;
    while (block != NULL) {
        MVMRegionBlock *prev = block->prev;
        block->prev = target->block->prev;
        target->block->prev = block;
        block = prev;
    }
    source->block = NULL;
}
