/*
 * mempool.c - Pooled memory allocation, similar to GNU obstacks.
 *
 * Copyright (c) 1993, 1994  Colin Plumb.  All rights reserved.
 * For licensing and other legal details, see the file legal.c.
 */
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>	/* For malloc() & free() */

#include "mempool.h"

/*
 * The memory pool allocation functions
 *
 * These are based on a linked list of memory blocks, usually of uniform
 * size.  New memory is allocated from the tail of the current block,
 * until that is inadequate, then a new block is allocated.
 * The entire pool can be freed at once by calling memPoolFree().
 */

struct PoolBuf {
	struct PoolBuf *next;
	unsigned size;
	/* Data follows */
};

/* A global pool for miscellaneous stuff. */
struct MemPool MiscPool = { 0, 0, 0, 1024, 0, 0, 0};


/* Page size to use */
#ifdef MSDOS
#define ALLOC_UNIT 1024
#else
#define ALLOC_UNIT 4096
#endif

static struct MemPool EmptyPool = { 0, 0, 0, ALLOC_UNIT, 0 , 0, 0};

/*
 * Initialize the pool for first use
 */
void
memPoolInit(struct MemPool *pool)
{
	*pool = EmptyPool;
}

/* Set the pool's purge function */
void
memPoolSetPurge(struct MemPool *pool, int (*purge)(void *), void *arg)
{
	pool->purge = purge;
	pool->purgearg = arg;
}

/*
 * Free all the memory in the pool
 */
void
memPoolEmpty(struct MemPool *pool)
{
	struct PoolBuf *buf;

	while ((buf = pool->head) != 0) {
		pool->head = buf->next;
		free(buf);
	}
	pool->freespace = 0;
	pool->totalsize = 0;
}


/*
 * Restore a pool to a marked position, freeing subsequently allocated
 * memory.
 */
void
memPoolCutBack(struct MemPool *pool, struct MemPool const *cutback)
{
	struct PoolBuf *buf;

	assert(pool);
	assert(cutback);

	assert(pool->totalsize >= cutback->totalsize);

	while((buf = pool->head) != cutback->head) {
		pool->head = buf->next;
		free(buf);
	}
	*pool = *cutback;
}


/*
 * Structure and string allocations.
 * To make alingment work, strings and other objects
 * are allocated from different pools.
 */

/*
 * Allocate a chunk of memory for a structure.  Alignment is assumed to be
 * a power of 2.  It could be generalized, if that ever becomes relevant.
 * Note that alignment is to the beginning of an allocated chunk, which
 * is guaranteed by ANSI to be as aligned as can possibly matter.
 */
void *
memPoolAlloc(struct MemPool *pool, unsigned len, unsigned alignment)
{
	char *p;
	unsigned t;

	/* Where to allocate next object */
	p = pool->freeptr;
	/* How far it is from the beginning of the chunk. */
	t = p - (char *)pool->head;
	/* How much to round up freeptr to make alignment */
	t = -t & --alignment;

	/* Okay, does it fit? */
	if (pool->freespace >= len+t) {
		pool->freespace -= len+t;
		p += t;
		pool->freeptr = p + len;
		return p;
	}

	/* It does not fit in the current chunk.  Go for a bigger chunk. */

	/* First, figure out how much to skip at the beginning of the chunk */
	alignment &= -(unsigned)sizeof(struct PoolBuf);
	alignment += sizeof(struct PoolBuf);
	/* Then, figure out a chunk size that will fit */
	t = pool->chunksize;
	assert(t);
	while (len + alignment > t)
		t *= 2;
	while ((p = malloc(t)) == 0) {
		/* If that didn't work, try purging or smaller allocations */
		if (!pool->purge || !pool->purge(pool->purgearg)) {
			t /= 2;
			if (len + alignment > t)
				fputs("Out of memory!\n", stderr);
				exit (1);	/* Failed */
		}
	}

	/* Update the various pointers. */
	pool->totalsize += t;
	((struct PoolBuf *)p)->next = pool->head;
	((struct PoolBuf *)p)->size = t;
	pool->head = (struct PoolBuf *)p;
	pool->freespace = t - len - alignment;
	p += alignment;
	pool->freeptr = p + len;

	return p;
}

#ifdef DEADCODE
/*
 * Store a string in the table, returning a pointer to the string.
 */
char const *
memPoolStore(struct MemPool *pool, char const *str)
{
	unsigned len = strlen(str) + 1;
	char *p;

	p = memPoolAlloc(pool, len, 1);
	if (p)
		memcpy(p, str, len);

	return p;
}
#endif
