/* 
 * Fast, space-efficient allocation of fixed-size blocks.
 */

#include <stdio.h>
#include <stdlib.h>

#include "block_alloc.h"

#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif

struct allocator {
    size_t size;
    int align;
    int blocks_per_chunk;
    struct allocator *next;
    struct link *freelist;
    struct link *malloced_chunks;
};

struct link {
    struct link *next;
};

Allocator allocators = 0;

Allocator make_block_allocator(size_t size, int merge)
{
    Allocator a;
    static int alignments[] = {8, 4, 2, 1};
    int i, align;

    /* Discover the alignment needed.  We assume (eg) it can only need
       double alignment if it's a multiple of sizeof(double). */

    for(i=0; size % alignments[i] != 0; i++)
	;
    align = alignments[i];

    /* We need at least space for a link in each block, and alignment
       for a link. */
    
    if(size < sizeof(struct link))
	size = sizeof(struct link);
    if(align < sizeof(struct link))
	align = sizeof(struct link);

    size = ((size + align - 1) & ~(align - 1));

    /* See if we can re-use an existing allocator */

    if(merge)
	for(a = allocators; a; a = a->next)
	    if(a->size == size && a->align == align)
		return a;

    /* Create a new allocator */

    a = malloc(sizeof(struct allocator));
    if(!a)
    {
	fprintf(stderr, "Can't malloc block allocator\n");
	exit(1);
    }
    a->size = size;
    a->align = align;
    a->blocks_per_chunk = (8192 - max(align, sizeof(struct link))) / size;
    if(a->blocks_per_chunk == 0)
	a->blocks_per_chunk = 1;
    a->freelist = 0;
    a->malloced_chunks = 0;
    if(merge)
    {
	a->next = allocators;
	allocators = a;
    }
    else
	a->next = 0;

    return a;
}

void *block_alloc(Allocator a)
{
    void *mem;
    char *p;
    int i;

    if(!a->freelist)
    {
	/* Malloc some more memory */

	mem = malloc(max(a->align, sizeof(struct link)) +
		     a->blocks_per_chunk * a->size);
	if(!mem)
	{
	    fprintf(stderr, "Can't malloc block\n");
	    exit(1);
	}

	((struct link *)mem)->next = a->malloced_chunks;
	a->malloced_chunks = mem;

	/* Link blocks together to form freelist */

	a->freelist = (void*)(((char*)mem)
			      + max(a->align, sizeof(struct link)));

	for(i = 0, p = (char *)a->freelist;
	    i < a->blocks_per_chunk-1;
	    i++, p += a->size)
	    ((struct link *)p)->next = (struct link *)(p + a->size);

	((struct link *)p)->next = 0;

    }

    /* Return a block from the freelist */

    mem = a->freelist;
    a->freelist = a->freelist->next;

    return mem;
}

/* 
 * Free a block, returning it to the freelist.
 */

void block_free(Allocator a, void *mem)
{
    ((struct link *)mem)->next = a->freelist;
    a->freelist = mem;
}

/* 
 * Destroy an allocator, freeing all its blocks.
 */

void destroy_block_allocator(Allocator a)
{
    struct link *l, *next;

    for(l = a->malloced_chunks; l; l = next)
    {
	next = l->next;
	free(l);
    }

    free(a);
}
