/* ==========================================================================
 * libarena/src/pool.c - Custom Memory Allocator Interface
 * --------------------------------------------------------------------------
 * Copyright (c) 2006  William Ahern
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the
 * following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 * ==========================================================================
 */
#include <stdio.h>
#include <stdlib.h>	/* size_t */

#ifdef _WIN32
typedef unsigned int uint32_t;
#else
#include <inttypes.h>	/* uint32_t */
#endif

#include <stdarg.h>	/* va_list */
#include <stddef.h>	/* offsetof */

#include <string.h>	/* memcpy(3) memmove(3) */

#include <assert.h>

#include "align.h"
#include "proto.h"
#include "pool.h"
#include "rbits.h"
#include "util.h"
#include "queue.h"

#ifndef MIN
#define MIN(a, b)	(((a) < (b))? (a) : (b))
#endif

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


static const struct pool_bucket_options pool_bucket_defaults[] = {
	{ 32,   32 },
	{ 128,  32 },
	{ 512,  32 },
	{ 4096,  8 },
	{ 0,     0 },
};

const struct pool_options pool_defaults = {
	ARENA_SYSTEM_ALIGNMENT,
	pool_bucket_defaults,
};


struct pool_chunk {
	SLIST_ENTRY(pool_chunk) sle;

	unsigned char bytes[];
}; /* struct pool_chunk */


struct pool_block {
	SLIST_ENTRY(pool_block) sle;

	size_t nbytes;

	unsigned char *bytep;

	unsigned char bytes[];
}; /* struct pool_block */


static const struct pool_bucket {
	size_t bucketlen;
	size_t nperblock;

	size_t bytep_offset;
	size_t chunk_size;

	SLIST_HEAD(, pool_chunk) chunks;

	CIRCLEQ_ENTRY(pool_bucket) cqe;
} pool_bucket_initializer;


static const struct pool {
	struct arena_prototype interface;		/* Exportable interface */

	const struct arena_prototype *allocator;	/* Internal "core" allocator */

	size_t alignment;				/* Default alignment */

	SLIST_HEAD(, pool_block) blocks;

	size_t nbuckets;
	CIRCLEQ_HEAD(, pool_bucket) buckets;

	/*
	 * Must provide the range of indexes that pool_hibit() returns,
	 * currently 0 to 31.
	 */
	struct pool_bucket *bucket_index[sizeof(uint32_t) * CHAR_BIT];
} pool_initializer;


/*
 * Returns the highest bit "index" of the passed unsigned integer.
 *
 * Examples:
 *
 * 	1     -> 0  (00000000000000000000000000000001)
 * 	                                            ^ 0th
 *
 * 	65536 -> 15 (00000000000000001111111111111111)
 * 	                             ^ 15th
 *
 * 	65536 -> 16 (00000000000000010000000000000000)
 * 	                            ^ 16th
 */
static inline int pool_hibit(uint32_t n) {
	register int i	= (n & 0xffff0000)? 16 : 0;

	if ((n >>= i) & 0xff00)
		i |= 8, n >>= 8;

	if (n & 0xf0)
		i |= 4, n >>= 4;

	if (n & 0x0c)
		i |= 2, n >>= 2;

	return (i | (n >> 1));
} /* pool_hibit() */


/*
 * Allocate a new memory block and push it onto the block stack. Upon
 * returning block->bytep is suitably aligned and there is sufficient space
 * from block->bytep to provide at least `len' bytes of usable memory.
 */
static struct pool_block *pool_block_push(struct pool *P, size_t len) {
	struct pool_block *blk	= 0;
	/* We must satisfy both data and data structure alignment needs. */
	size_t align	= MAX(ARENA_SYSTEM_ALIGNMENT,P->alignment);
	size_t offset	= ARENA_BOUNDARY_OFFSETOF(offsetof(struct pool_block, bytes),align);

	if (!(blk = P->allocator->malloc(P->allocator,offsetof(struct pool_block, bytes) + offset + len,align)))
		return 0;

	blk->nbytes	= offset + len;
	blk->bytep	= blk->bytes + offset;

	SLIST_INSERT_HEAD(&P->blocks,blk,sle);

	return blk;
} /* pool_block_push() */


static inline int pool_bucket_indexof(struct pool_bucket *b) {
	return pool_hibit(b->bucketlen);
} /* pool_bucket_indexof() */


/*
 * Update the pool bucket lookup index at the slot derived from the
 * provided bucket. Then fill any lower slots.
 */
static void pool_bucket_reindex(struct pool *P, struct pool_bucket *b) {
	int i	= pool_bucket_indexof(b);

	if (!P->bucket_index[i]
	||  b->bucketlen < P->bucket_index[i]->bucketlen) {
		P->bucket_index[i]	= b;

		for (i--; i >= 0 && !P->bucket_index[i]; i--) {
			P->bucket_index[i]	= b;
		}
	}

	return /* void */;
} /* pool_bucket_reindex() */


/*
 * Insert the provided bucket in-order into the pool buckets list.
 */
static void pool_bucket_insert(struct pool *P, struct pool_bucket *b) {
	struct { struct pool_bucket *this; } i;

	/* Try to jump into the list, otherwise begin at the [high] end. */
	if (!(i.this = P->bucket_index[pool_bucket_indexof(b)]))
		i.this	= CIRCLEQ_LAST(&P->buckets);

	while (i.this != CIRCLEQ_END(&P->buckets)) {
		if (b->bucketlen >= i.this->bucketlen)
			break;

		i.this	= CIRCLEQ_PREV(i.this,cqe);
	}

	if (i.this != CIRCLEQ_END(&P->buckets))
		CIRCLEQ_INSERT_AFTER(&P->buckets,i.this,b,cqe);
	else
		CIRCLEQ_INSERT_HEAD(&P->buckets,b,cqe);

	P->nbuckets++;

	return /* void */;
} /* pool_bucket_insert() */


/*
 * Calculate the bytep offset required for maximum guaranteed alignment (the
 * alignment specified in the options) and the actual size to allocate per
 * chunk so chunk arrays are properly aligned.
 */
static void pool_bucket_analyze(struct pool *P, struct pool_bucket *b) {
	size_t align_data	= P->alignment;
	size_t align_struct	= MAX(ARENA_SYSTEM_ALIGNMENT,P->alignment);
	size_t rbits		= rbits_len(b->bucketlen);
	size_t offset		= rbits + ARENA_BOUNDARY_OFFSETOF(rbits + offsetof(struct pool_chunk, bytes),align_data);

#if 0
	printf("align_data: %d\n",(int)align_data);
	printf("align_struct: %d\n",(int)align_struct);
	printf("rbits: %d\n",(int)rbits);
	printf("offsetof(c,bytes): %d\n",(int)offsetof(__typeof__(struct pool_chunk),bytes));
	printf("offset: %d\n",(int)offset);
#endif

	b->bytep_offset	= offset;
	b->chunk_size	= offsetof(struct pool_chunk, bytes) + offset + b->bucketlen;
	b->chunk_size	+= ARENA_BOUNDARY_OFFSETOF(b->chunk_size,align_struct);

	return /* void */;
} /* pool_bucket_analyze() */


static struct pool_bucket *pool_bucket_add(struct pool *P, const struct pool_bucket_options *opts) {
	struct pool_block *blk;
	struct pool_bucket *b;

	if (!(blk = pool_block_push(P,sizeof *b)))
		return 0;

	b		= (void *)blk->bytep;
	blk->bytep	+= sizeof *b;

	b->bucketlen	= opts->bucketlen;
	b->nperblock	= (ARENA_DEBUG)? 1 : opts->nperblock;

	SLIST_INIT(&b->chunks);

	pool_bucket_analyze(P,b);

	pool_bucket_insert(P,b);

	pool_bucket_reindex(P,b);

	return b;
} /* pool_bucket_add() */


/*
 * For a given size find the lowest bucket which can fulfill an allocation
 * of that size. If no bucket is found, try to add a new one.
 */
static struct pool_bucket *pool_bucket_find(struct pool *P, size_t len, int tryhard) {
	struct pool_bucket_options opts;
	struct pool_bucket *b;

	if ((b = P->bucket_index[pool_hibit(len)])) {
		while (b != CIRCLEQ_END(&P->buckets) && b->bucketlen < len)
			b	= CIRCLEQ_NEXT(b,cqe);

		if (b != CIRCLEQ_END(&P->buckets))
			return b;
	}

	if (!tryhard)
		return 0;

	opts.bucketlen	= len;	
	opts.nperblock	= 1;

	return pool_bucket_add(P,&opts);
} /* pool_bucket_find() */


static struct pool_chunk *pool_bucket_grow(struct pool *P, struct pool_bucket *b) {
	struct pool_block *blk;
	struct { struct pool_chunk *pos; struct pool_chunk *end; } c;

	if (!(blk = pool_block_push(P,b->chunk_size * b->nperblock)))
		return 0;

	c.pos	= (void *)(blk->bytep);
	c.end	= (void *)(blk->bytep + (b->nperblock * b->chunk_size));

	while (c.pos < c.end) {
		SLIST_INSERT_HEAD(&b->chunks,c.pos,sle);

		c.pos	= (void *)(((unsigned char *)c.pos) + b->chunk_size);
	}

	blk->bytep	+= b->chunk_size * b->nperblock;

	return SLIST_FIRST(&b->chunks);
} /* pool_bucket_grow() */


static unsigned char *pool_recover(struct pool *P, struct pool_bucket **b, struct pool_chunk **c, unsigned char *q) {
	unsigned char *p;
	size_t bucketlen;

	bucketlen	= rbits_get(q - 1,&p);
	*c		= (void *)(p - offsetof(struct pool_chunk, bytes));

	assert(*b = P->bucket_index[pool_hibit(bucketlen)]);

	while (*b != CIRCLEQ_END(&P->buckets) && (*b)->bucketlen != bucketlen)
		*b	= CIRCLEQ_NEXT((*b),cqe);

	assert (*b != CIRCLEQ_END(&P->buckets));

	return p;
} /* pool_recover() */


void *pool_get(struct pool *P, size_t len, size_t align) {
	size_t bucketlen	= len;
	struct pool_bucket *b;
	struct pool_chunk *c;
	unsigned char *p;

	if (align == 0)
		align		= P->alignment;
	else if (align > P->alignment)
		bucketlen	+= align - P->alignment;

	if (!(b = pool_bucket_find(P,len,1)))
		return 0;

	if (!(c = SLIST_FIRST(&b->chunks))
	&&  !(c = pool_bucket_grow(P,b)))
		return 0;

	SLIST_REMOVE_HEAD(&b->chunks,sle);

	p	= &c->bytes[rbits_ptroffset(&c->bytes[0],b->bucketlen,align)];

	(void)rbits_put(&c->bytes[0],p - &c->bytes[0],b->bucketlen,0);

	return p;
} /* pool_get() */


void *pool_realloc(struct pool *P, void *q, size_t dstlen, size_t align) {
	struct pool_chunk *c;
	struct pool_bucket *b, *bx;
	unsigned char *p;
	size_t srcoff, dstoff;

	if (align == 0)
		align	= P->alignment;

	/*
	 * Make things easy on ourselves.
	 */
	if (dstlen == 0) {
		pool_put(P,q);

		return 0;
	}

	if (q == 0)
		return pool_get(P, dstlen, align);

	p	= pool_recover(P,&b,&c,q);

	if (align > P->alignment)
		bx	= pool_bucket_find(P,dstlen + (align - P->alignment),1);
	else
		bx	= pool_bucket_find(P,dstlen,1);

	if (!bx) {
		return 0;
	} else if (bx != b) {	/* Check if (bx->bucketlen > b->bucketlen)? */
		srcoff	= (unsigned char *)q - p;

		if (!(q = pool_get(P,dstlen,align)))
			return 0;

		(void)memcpy(q, p + srcoff, MIN(dstlen, (size_t)(&c->bytes[b->chunk_size - offsetof(struct pool_chunk, bytes)] - p)));

		SLIST_INSERT_HEAD(&b->chunks,c,sle);
	} else {
		srcoff	= (unsigned char *)q - p;
		dstoff	= rbits_ptroffset(p,b->bucketlen,align);

		/* If previous boundary is greater than current, then the
		 * previous is aligned for the new one (given power of 2
		 * arithmetic). If not, we need to shift the contents since
		 * the reverse relationship isn't necessarily true.
		 */
		if (dstoff > srcoff) {
			q	= memmove(p + dstoff, p + srcoff, MIN(dstlen, (size_t)(&c->bytes[b->chunk_size - offsetof(struct pool_chunk, bytes)] - (p + srcoff))));
		} else
			q	= p + srcoff;
	}

	return q;
} /* pool_realloc() */


void pool_put(struct pool *P, void *q) {
	unsigned char *p;
	struct pool_chunk *c;
	struct pool_bucket *b;

	if (q == 0)
		return /* void */;

	p	= pool_recover(P,&b,&c,q);

	SLIST_INSERT_HEAD(&b->chunks,c,sle);

	return /* void */;
} /* pool_put() */


static char pool_name[]	= __FILE__;

const char *pool_instanceof(struct pool *P) {
	return &pool_name[0];
} /* pool_instanceof() */


struct pool *pool_import(const struct arena_prototype *ap) {
	return (ap->instanceof(ap) == &pool_name[0])? (struct pool *)ap : 0;
} /* pool_import() */


const char *pool_strerror(struct pool *P) {
	return P->allocator->strerror(P->allocator);
} /* pool_strerror() */


void pool_clearerr(struct pool *P) {
	(P->allocator->clearerr)(P->allocator);

	return /* void */;
} /* pool_clearerr() */


const struct arena_prototype *pool_export(struct pool *P) {
	if (!P->interface.malloc) {
		P->interface.malloc	= (void *(*)(const struct arena_prototype *, size_t, size_t))&pool_get;
		P->interface.realloc	= (void *(*)(const struct arena_prototype *, void *, size_t, size_t))&pool_realloc;
		P->interface.free	= (void (*)(const struct arena_prototype *, void *))&pool_put;
		P->interface.instanceof	= (const char *(*)(const struct arena_prototype *))&pool_instanceof;
		P->interface.strerror	= (const char *(*)(const struct arena_prototype *))&pool_strerror;
		P->interface.clearerr	= (void (*)(const struct arena_prototype *))&pool_clearerr;
	}

	return &P->interface;
} /* pool_export() */


POOL *pool_open(const struct pool_options *opts, const struct arena_prototype *m) {
	struct pool *P	= 0;
	int i;

	if (!opts)
		opts	= &pool_defaults;

	if (!m)
		m	= ARENA_STDLIB;

	if (!(P = m->malloc(m,sizeof *P,0)))
		return 0;

	*P		= pool_initializer;
	P->allocator	= m;
	P->alignment	= opts->alignment;

	SLIST_INIT(&P->blocks);
	CIRCLEQ_INIT(&P->buckets);

	for (i = 0; opts->buckets[i].bucketlen > 0; i++) {
		if (!pool_bucket_add(P,&opts->buckets[i]))
			goto fail;
	}

	return P;
fail:
	pool_close(P);

	return 0;
} /* pool_open() */


void pool_close(POOL *P) {
	struct pool_block *b;

	/*
	 * Release everything in reverse order. Block list is a LIFO.
	 */
	if (P) {
		while ((b = SLIST_FIRST(&P->blocks))) {
			SLIST_REMOVE_HEAD(&P->blocks,sle);

			P->allocator->free(P->allocator,b);
		}

		P->allocator->free(P->allocator,P);
	}

	return /* void */;
} /* pool_close() */


char *pool_strdup(struct pool *P, const char *src) {
	return arena_util_strdup(pool_export(P),src);
} /* pool_strdup() */


char *pool_strndup(struct pool *P, const char *src, size_t n) {
	return arena_util_strndup(pool_export(P),src,n);
} /* pool_strndup() */


void *pool_memdup(struct pool *P, const void *p, size_t n) {
	return arena_util_memdup(pool_export(P),p,n);
} /* pool_memdup() */


int pool_vasprintf(struct pool *P, char **dstp, const char *fmt, va_list ap) {
	return arena_util_vasprintf(pool_export(P),dstp,fmt,ap);
} /* pool_vasprintf() */


int pool_asprintf(struct pool *P, char **dstp, const char *fmt, ...) {
	va_list ap;
	int n;

	va_start(ap,fmt);

	n	= arena_util_vasprintf(pool_export(P),dstp,fmt,ap);

	va_end(ap);

	return n;
} /* pool_asprintf() */


char *pool_vsprintf(struct pool *P, const char *fmt, va_list ap) {
	return arena_util_vsprintf(pool_export(P),fmt,ap);
} /* pool_vsprintf() */


char *pool_sprintf(struct pool *P, const char *fmt, ...) {
	va_list ap;
	char *s;

	va_start(ap,fmt);

	s	= arena_util_vsprintf(pool_export(P),fmt,ap);

	va_end(ap);

	return s;
} /* pool_sprintf() */


#if POOL_MAIN

#include <stdio.h>
#include <err.h>
#include <string.h>

int main(int argc, char *argv[]) {
	struct pool_options opts = pool_defaults;
	POOL *p;
	struct pool_bucket *b;
	int i;

	/*return printf("%d\n",rbits_ptroffset((unsigned char *)16,23,16));*/

	opts.alignment = (argc > 1)? atoi(argv[1]) : ARENA_SYSTEM_ALIGNMENT;

	if (!(p = pool_open(&opts,0)))
		err(1,"pool_open");

	CIRCLEQ_FOREACH(b,&p->buckets,cqe) {
		printf("bucket: %d\n",(int)b->bucketlen);
		printf("bytep_offset: %d\n",(int)b->bytep_offset);
		printf("chunk_size: %d\n",(int)b->chunk_size);
		printf("\n");
	}

	for (i = 0; i < 1024; i++) {
		void *ptr	= pool_get(p,12,16);

		strcpy(ptr,"abcdefghijk");

		pool_put(p,ptr);
	}

	pool_close(p);

	return 0;
}

#endif /* POOL_MAIN */


syntax highlighted by Code2HTML, v. 0.9.1