#ifdef USE_SMALLOC
/*

		Satoria's Malloc Package 1.2
*/

#include <stdio.h>
#include <memory.h>
#include "config.h"
#include "externs.h"

#define MTYPE unsigned int

#define SMALL_BLOCK_MAX_BYTES	32
#define SMALL_CHUNK_SIZE	0x4000
#define CHUNK_SIZE		0x40000

#define SINT sizeof(int)
#define SMALL_BLOCK_MAX (SMALL_BLOCK_MAX_BYTES/SINT)

#define PREV_BLOCK	0x80000000
#define THIS_BLOCK	0x40000000
#define MASK		0x0FFFFFFF

typedef unsigned int u;

/*
	SMALL BLOCK data structures
*/

static u *sfltable[SMALL_BLOCK_MAX];	/* freed list */
static u *next_unused;
static u unused_size;			/* until we need a new chunk */

/*
	LARGE BLOCK data structures
*/

static u *free_list;
static u *start_next_block;

/*
	STATISTICS
*/

static int small_count[SMALL_BLOCK_MAX];
static int small_total[SMALL_BLOCK_MAX];

typedef struct { unsigned counter; unsigned long size; } stat;

#ifndef SLOW_STATISTICS
#define COUNT(a,b)
#define START(a)
#else
#define COUNT(a,b) 			\
	{				\
		a.size+=(b);		\
		if ((b)<0)		\
			--a.counter;	\
		else			\
			++a.counter;	\
	}
#define START(a) a.size=0; a.counter=0;
#endif

#ifdef DEBUG
int debugmalloc = 1;	/* Only used when debuging malloc() */
#else
int debugmalloc = 0;
#endif

/********************************************************/
/*  SMALL BLOCK HANDLER					*/
/********************************************************/

char *large_malloc();
static void large_free();

#define ptr_to_smallblock_size_field(p)	(p)
#define next_free_smallblock(p)	((u **) (p+1))

stat	small_alloc_stat,		/* amount allocated */
	small_free_stat,		/* amount unused */
	small_total_stat,		/* total ever requested */
	small_chunk_stat;		/* small chunks allocated */

void *malloc(sz)
MTYPE sz;
{
	u *temp,i,size;

	if (sz == 0) {
		fprintf(stderr, "Malloc size 0.\n");
		return NULL;
	}

	if (sz>SMALL_BLOCK_MAX_BYTES)
		return large_malloc((u) sz,0);

/* Compute the index into the small block table:
	index		block size
	  0		  1-4 bytes
	  1		  5-8 bytes
	  2		  9-12 bytes
*/

	i = (sz - 1) >> 2;

/* Compute the actual size of the small block in ints,
   including the one int overhead for the "header"
*/

	size = (sz + 3) >> 2;			/* block size in ints */
	++size;					/* one int overhead */

/* Update statistics of small block allocations */

	COUNT(small_alloc_stat,size << 2);
	COUNT(small_total_stat,size << 2);

	small_count[i] += 1;			/* update statistics */
	small_total[i] += 1;

/* Is the free list for this size of block non-empty? */

	if (sfltable[i]) {

/* Update the free list stats */

		COUNT(small_free_stat, -(int) (size << 2));

		temp = sfltable[i];
		sfltable[i] = * (u **) (temp+1);

		return (char *) (temp+1);
	}

/* Free list was empty.  So allocate from the small-block chunk. */

	if (unused_size<size) {

/* There's not enough room in this chunk.  Blow it off (wasting the
   space at the end of the chunk), and get a new one. */

		next_unused = (u *) large_malloc(SMALL_CHUNK_SIZE,1);
		if (next_unused == 0)
			return 0;

		COUNT(small_chunk_stat, SMALL_CHUNK_SIZE+SINT);
		unused_size = SMALL_CHUNK_SIZE / SINT;
	}

	temp = (u *) next_free_smallblock(next_unused);

	*ptr_to_smallblock_size_field(next_unused) = size;
	next_unused += size;
	unused_size -= size;

	return (char *) temp;
}

void free(ptr)
char *ptr;
{
	u *block;
	u i;

/* Find the header for this block */

	block = (u *) ptr;
	block -= 1;

/* Test the size of this block to see if it's large or small */

	if ( (*ptr_to_smallblock_size_field(block) & MASK)
		 > SMALL_BLOCK_MAX + 1) {
			large_free(ptr);
			return;
	}

/* Get index for this size of block.  There's no & MASK because
   small block headers only have the size
*/
	i = *block - 2;

/* Update the statistics */

	COUNT(small_alloc_stat, - (int) ((i+2) << 2));
	COUNT(small_free_stat, (i+2) << 2);

	small_count[i] -= 1;

	*next_free_smallblock(block) = sfltable[i];
	sfltable[i] = block;
}

/************************************************/
/*	LARGE BLOCK HANDLER			*/
/************************************************/

#define BEST_FIT	0
#define FIRST_FIT	1
#define HYBRID		2
int fit_style = BEST_FIT;

#define ptr_to_largeblock_size_field(p)			(p)
#define next_free_largeblock(p)				(*((u **) (p+1)))
#define previous_free_largeblock(p)			(*((u **) (p+2)))
#define next_large_block(p)				(p + (*(p) & MASK))
#define previous_largeblock(p) 				(p - (*(p-1)))
#define is_previous_free(p)				(!(*p & PREV_BLOCK))
#define is_next_free(p)			(!(*next_large_block(p) & THIS_BLOCK))

#ifdef DEBUG
static void show_block(ptr)
u *ptr;
{
	fprintf(stderr, "MALLOC: [%c%d: %d]\n",(*ptr & THIS_BLOCK ? '+' : '-'),
		(int) ptr, *ptr & MASK);
}

void show_free_list()
{
	u *p;
	p = free_list;
	while (p) {
		show_block(p);
		p = next_free_largeblock(p);
	}
}
#endif

stat large_free_stat;
stat large_free_chain;

void remove_from_free_list(ptr)
u *ptr;
{
	COUNT(large_free_stat, - (int) ((*ptr & MASK) << 2));

	if (previous_free_largeblock(ptr))
		next_free_largeblock(previous_free_largeblock(ptr))
			= next_free_largeblock(ptr);
	else
		free_list
			= next_free_largeblock(ptr);

	if (next_free_largeblock(ptr))
		previous_free_largeblock(next_free_largeblock(ptr))
			= previous_free_largeblock(ptr);
}

void add_to_free_list(ptr)
u *ptr;
{
	COUNT(large_free_stat, (*ptr & MASK) << 2);

	if (free_list && previous_free_largeblock(free_list))
		fprintf(stderr, "ERROR: Free list consistency error.\n");

	next_free_largeblock(ptr) = free_list;
	if (free_list)
		previous_free_largeblock(free_list) = ptr;
	previous_free_largeblock(ptr) = 0;
	free_list = ptr;
}

/* build a properly annotated unallocated block */
static void build_block(ptr, size)
u *ptr,size;
{

/* Set the header to:  (old value of PREV_BLOCK) | !THIS_BLOCK | size */

	*(ptr) = (*ptr & PREV_BLOCK) | size;

/* Set the footer (immediately before the next blocks' header) to the size */

	*(ptr+size-1) = size;

/* Clear PREV_BLOCK on the next block */

	*(ptr+size) &= (MASK | THIS_BLOCK);
}

static void mark_block(ptr)
u *ptr;
{
	*ptr |= THIS_BLOCK;
	*next_large_block(ptr) |= PREV_BLOCK;
}


/*
	mod by Lars Pensj| (??):
		It is system dependent how sbrk() aligns data, so we simply
		use brk() to insure that we have enough.
*/
stat sbrk_stat;
static char *esbrk(size)
u size;
{
	extern char *sbrk();
	extern int brk();
	static char *current_break;

	if (current_break == 0)
		current_break = sbrk(0);
	if (brk(current_break + size) == -1)
		return 0;
	COUNT(sbrk_stat,size);
	current_break += size;
	return current_break - size;
}

stat large_alloc_stat;
stat large_total_stat;
char *large_malloc(size, force_more)
u size;
int force_more;
{
	u best_size, real_size;
	u *first, *best, *ptr;

/* Round the block size up to a multiple of 4, then divide by four, and add
   1, to get the actual block size in ints, including room for the header
*/
	size = (size + 7) >> 2;

	first = best = 0;
	best_size = MASK;

/* If we are being forced to allocate a big block, ignore the fit attempts */

	if (force_more)
		ptr = 0;
	else {
		ptr = free_list;
#ifdef SLOW_STATISTICS
		large_free_chain.counter++;
#endif
	}

/* run through the free list, looking for a perfect, first, or best fit */

	while (ptr) {
		u tempsize;
#ifdef SLOW_STATISTICS
		large_free_chain.size++;
#endif
						/* Perfect fit? */
		tempsize = *ptr & MASK;
		if (tempsize == size) {
			best = first = ptr;
			break;		/* always accept perfect fit */
		}

/* If allocating this block for this malloc call would result in a hole
   that's smaller than the minimum large block size, that is, smaller
   than than SMALL_BLOCK_MAX, then it'd never get allocated.  So refuse
   those cases.
*/
#ifndef WASTE_SMALL
		if (tempsize >= size + SMALL_BLOCK_MAX + 1) {
#else
		if (tempsize >= size) {
#endif
			if (!first) {
				first = ptr;
				if (fit_style == FIRST_FIT)
/* If we're using first fit, just take this one! */
					break;
			}

/* Find out how well this one fits */

			tempsize -= size;
			if (tempsize>0 && tempsize<=best_size) {
				best = ptr;
				best_size = tempsize;
			}
		}

		ptr = next_free_largeblock(ptr);

	} /* end while */

	if (fit_style==BEST_FIT)
		ptr = best;
	else
		ptr = first;	/* Hybrid says use first if no perfect */

	if (!ptr) {

/* There was no match, or we're forced, so allocate more memory */

		u chunk_size, block_size;

		block_size = size*SINT;

		if (force_more || (block_size>CHUNK_SIZE))
			chunk_size = block_size;
		else
			chunk_size = CHUNK_SIZE;

		if (!start_next_block) {

/* This is the first allocation */

			start_next_block = (u *) esbrk(SINT);
			if (!start_next_block)
				return 0;

/* The first block header must mark the previous block as used */

			*(start_next_block) = PREV_BLOCK;

/* Initialize statistics */

			START(large_alloc_stat)
			START(large_total_stat)
			START(large_free_stat)
			START(sbrk_stat)
			START(small_alloc_stat)
			START(small_free_stat)
			START(small_total_stat)
			START(small_chunk_stat)
			START(large_free_chain)
		}

		ptr = (u *) esbrk(chunk_size);
		if (ptr == 0)
			return 0;

/* Old block at end of memory had an extra trailing word. Overwrite it. */

		ptr -= 1;

		block_size = chunk_size / SINT;

/* Configure the header information for this new bit. */

		build_block(ptr,block_size);

/* Set up the bogus header at the end of memory */

		*next_large_block(ptr)=THIS_BLOCK;

/* Stick this new block in the free list, so it's exactly like a real
   free block, so it looks the same as one found in the free list
*/
		add_to_free_list(ptr);
	}

/* Ok, we got the free block of appropriate size from somewhere */

	remove_from_free_list(ptr);
	real_size = *ptr & MASK;

	if (real_size != size) {
		/* split block pointed to by ptr into two blocks */
		build_block(ptr+size, real_size-size);
		add_to_free_list(ptr+size);
		build_block(ptr, size);
	}

	mark_block(ptr);

/* Update statistics */

	COUNT(large_alloc_stat, size << 2);
	COUNT(large_total_stat, size << 2);

	return (char *) (ptr + 1);
}

static void large_free(ptr)
char *ptr;
{
	u size, *p;

/* Find the header again */

	p = (u *) ptr;
	p-=1;

/* Update statistics */

	size = *p & MASK;
	COUNT(large_alloc_stat, - (int) (size << 2));

/* Since this is about to become a hole, check memory before and after
   for holes.  If they are holes, merge them with this one.
*/
	if (is_next_free(p)) {
		remove_from_free_list(next_large_block(p));
		size += (*next_large_block(p) & MASK);
		*p = (*p & PREV_BLOCK) | size;
	}

	if (is_previous_free(p)) {
		remove_from_free_list(previous_largeblock(p));
		size += (*previous_largeblock(p) & MASK);
		p = previous_largeblock(p);
	}

	build_block(p, size);
	add_to_free_list(p);
}

void *realloc(p, size)
char *p; unsigned size;
{
	unsigned *q,old_size;
	char *t;
	q = ((unsigned *) p)-1;
	old_size = ((*q & MASK) -1) * SINT;

	if (old_size >= size)
		return p;

	t = malloc(size);
	if (t == 0) return (char *) 0;

	memcpy(t, p, old_size);
	free(p);
	return t;
}

int resort_free_list() { return 0; }

#define d(str,stat) p(player, tprintf(str,stat.counter,stat.size))
#define p notify
#ifdef SLOW_STATISTICS
void dump_malloc_data(player)
    dbref player;
{
p(player, "TOTAL ALLOCATIONS  (all requests ever made)" );
p(player, " Type               Count      Space (bytes)" );
d(" large blocks:  %9d      %12ld", large_total_stat);
d(" small blocks:  %9d      %12ld", small_total_stat);
d(" sbrk requests:  %8d        %10ld (a)",sbrk_stat);
if (large_free_chain.counter)
p(player, tprintf(" large mallocs: %9d      average search length: %7.2f",
		  large_free_chain.counter,
		  (double) large_free_chain.size/large_free_chain.counter));
p(player, "CURRENT USAGE");
p(player, " Type               Count      Space (bytes)" );
d(" large blocks:   %8d        %10ld (b)", large_alloc_stat);
d(" large holes:    %8d        %10ld (c)", large_free_stat);
d(" small chunks:   %8d        %10ld (d)", small_chunk_stat);
d(" small blocks:   %8d        %10ld (e)", small_alloc_stat);
d(" small holes:    %8d        %10ld (f)", small_free_stat);
p(player, tprintf(" unused from current chunk       %10d (g)",
		  unused_size<<2));
p(player, "NOTES");
p(player, "  Small blocks are stored in small chunks, which are allocated as");
p(player,"large blocks.  Therefore, the total large blocks allocated (b) plus");
p(player,"the large free blocks (c) should equal total storage from sbrk (a).");
p(player,"Similarly, (e) + (f) + (g) equals (d).  The total amount of storage");
p(player,"wasted is (c) + (f) + (g); the amount allocated is (b) - (f) - (g).");
}
#endif
#undef p
#undef d

/*
	Modified by Lars Pensj| (??)
	calloc() is provided because some stdio packages uses it.
*/
void *calloc(nelem, sizel)
unsigned nelem, sizel;
{
	char *p;

	if (nelem == 0 || sizel == 0)
		return 0;
	p = malloc(nelem * sizel);
	if (p == 0)
		return 0;
	(void) memset(p, 0, nelem * sizel);
	return p;
}

/*
 * Functions below can be used to debug malloc.
 */

#ifdef DEBUG

unsigned int memused() { return sbrk_stat.size; }

/*
 * Verify that the free list is correct. The upper limit compared to
 * is very machine dependant.
 */
verify_sfltable(player)
    dbref player;
{
    u *p;
    int i, j;
    extern int end;

    if (!debugmalloc)
	return;
    if (unused_size > SMALL_CHUNK_SIZE / SINT) {
      fprintf(stderr, "ERROR: free list.  SMALL_CHUNK/SINT < unused");
      notify(player, "ERROR: free list.  SMALL_CHUNK/SINT < unused");
    }
    for (i=0; i < SMALL_BLOCK_MAX; i++) {
	for (j=0, p = sfltable[i]; p; p = * (u **) (p + 1), j++) {
	    if (p < (u *)&end || p > (u *) 0xfffff) {
		fprintf(stderr, "ERROR: free list. pointer out of range.");
		notify(player, "ERROR: free list. pointer out of range.");
	    }
	    if (*p - 2 != i) {
		fprintf(stderr, "ERROR: free list. pointer corrupt.");
		notify(player, "ERROR: free list. pointer corrupt.");
	    }
	}
	if (p >= next_unused && p < next_unused + unused_size) {
		notify(player, "ERROR: free list.  pointer in bad place.");
		fprintf(stderr, "ERROR: free list.  pointer in bad place.");
	}
    }
    p = free_list;
    while (p) {
	if (p >= next_unused && p < next_unused + unused_size) {
		notify(player, "ERROR: free list.  pointer in bad place.");
		fprintf(stderr, "ERROR: free list.  pointer in bad place.");
	}
	p = next_free_largeblock(p);
    }
}

verify_free(ptr)
    u *ptr;
{
    u *p;
    int i, j;

    if (!debugmalloc)
	return;
    for (i=0; i < SMALL_BLOCK_MAX; i++) {
	for (j=0, p = sfltable[i]; p; p = * (u **) (p + 1), j++) {
	    if (*p - 2 != i)
		fprintf(stderr, "ERROR: error in free list");
	    if (ptr >= p && ptr < p + *p)
		fprintf(stderr, "ERROR: error in free list");
	    if (p >= ptr && p < ptr + *ptr)
		fprintf(stderr, "ERROR: error in free list");
	    if (p >= next_unused && p < next_unused + unused_size)
		fprintf(stderr, "ERROR: error in free list");
	}
    }

    p = free_list;
    while (p) {
	if (ptr >= p && ptr < p + (*p & MASK))
	    fprintf(stderr, "ERROR: error in free list");
	if (p >= ptr && p < ptr + (*ptr & MASK))
	    fprintf(stderr, "ERROR: error in free list");
	if (p >= next_unused && p < next_unused + unused_size)
	    fprintf(stderr, "ERROR: error in free list");
	p = next_free_largeblock(p);
    }
    if (ptr >= next_unused && ptr < next_unused + unused_size)
	fprintf(stderr, "ERROR: error in free list");
}
#endif /* DEBUG */
#endif /* USE_SMALLOC */