/* memory allocation routines
 *
 * Adapted from alloc routine in K&R; memory statistics and interrupt
 * protection added for use with net package.
 */

#include <stdio.h>

#if (defined(MSC) || defined(__TURBOC__))
# include <alloc.h>
#else
# ifdef M_XENIX
#  include <prototypes.h>
# else
extern char *sbrk();
extern int brk();
# endif
#endif

#include "global.h"
#include "alloc.h"

static long grabbedbytes = 0;
static unsigned memfail = 0;
static HEADER base = {&base,1};
HEADER PTR allocp = &base;
int need_disable = 0;

/* Define ALLOCDEBUG for allocation recording. Note: works only with
 * small memory model
 */
#ifdef	ALLOCDEBUG
#define NALLOC	150
/* Map of allocated memory chunks */
struct alloc {
	char *addr;
	unsigned size;
	long pc;
};
struct alloc alloc[NALLOC];
#endif

/* Allocate block of 'nb' bytes */
#ifdef MSDOS
void *
#else
char *
#endif
malloc(nb)
unsigned nb;
{
	HEADER PTR morecore();
	register HEADER PTR p, PTR q;
	register unsigned nu;
#ifdef	ALLOCDEBUG
	struct alloc *ap;
	unsigned short *ptr;
#endif
#ifndef NODISABLE
	char i_state;
	
	if (need_disable)
		i_state = disable();
#endif
	/* Round up to full block, incl hdr */
	nu = (nb + 2 * sizeof(HEADER) - 1) / sizeof(HEADER);
	if ((q = allocp) == NULLHDR){
		base.s.ptr = allocp = q = &base;
		base.s.size = 1;
	}
	for (p = q->s.ptr; ; q = p, p = p->s.ptr){
		if (p->s.size >= nu){
			/* This chunk is at least as large as we need */
			if (p->s.size == nu){
				/* Perfect fit; remove from list */
				q->s.ptr = p->s.ptr;
			} else {
				/* Carve out piece from end of entry */
				p->s.size -= nu;
				p += p->s.size;
				p->s.size = nu;
			}
			allocp = q;
			p->s.ptr = p;	/* for auditing */
#ifdef	ALLOCDEBUG
			for(ap = alloc;ap < &alloc[NALLOC];ap++){
				if(ap->addr == NULLCHAR){
					ap->addr = (char *)(p+1);
					ap->size = nb;
					ptr = &nb;
#ifdef	LARGECODE
					ap->pc = (long)ptr[-1] << 16 +
						(long)ptr[-2];
#else
					ap->pc = (long)ptr[-1];
#endif
					break;
				}
			}
#endif
#ifndef NODISABLE
			if (need_disable)
				restore(i_state);
#endif
			return (char *)(p + 1);
		}
		if (p == allocp && (p = morecore(nu)) == NULLHDR){
			memfail++;
#ifndef NODISABLE
			if (need_disable)
				restore(i_state);
#endif
			return NULLCHAR;
		}
	}
}

/* Put memory block back on heap */
void
free(blk)
#ifdef MSDOS
void *blk;
#else
char *blk;
#endif
{
	register HEADER PTR p, PTR q;
	unsigned short *ptr;
#ifdef	ALLOCDEBUG
	struct alloc *ap;
#endif
#ifndef NODISABLE
	char i_state;
#endif

	if (blk == NULL)
		return;

#ifndef NODISABLE
	if (need_disable)
		i_state = disable();
#endif

#ifdef	ALLOCDEBUG
	for(ap = alloc;ap < &alloc[NALLOC];ap++){
		if(ap->addr == blk){
			ap->addr = NULLCHAR;
			break;
		}
	}
#endif
	p = (HEADER PTR)blk - 1;
	/* Audit check */
	if(p->s.ptr != p){
		ptr = (unsigned short *)&blk;
		printf("WARNING!! freeing garbage (0x%lx) pc = 0x%04x %04x\n",
			ptr2long(blk),ptr[-1],ptr[-2]);
		fflush(stdout);
#ifndef NODISABLE
		if (need_disable)
			restore(i_state);
#endif
#ifdef	Z80
		printf("SP = 0x%04x\n",getsp());
		for(;;) ;
#else
		return;
#endif
	}
	/* Search the free list looking for the right place to insert */
	for(q = allocp; !(p > q && p < q->s.ptr); q = q->s.ptr){
		/* Highest address on circular list? */
		if(q >= q->s.ptr && (p > q || p < q->s.ptr))
			break;
	}
	if(p + p->s.size == q->s.ptr){
		/* Combine with front of this entry */
		p->s.size += q->s.ptr->s.size;
		p->s.ptr = q->s.ptr->s.ptr;
	} else {
		/* Link to front of this entry */
		p->s.ptr = q->s.ptr;
	}
	if(q + q->s.size == p){
		/* Combine with end of this entry */
		q->s.size += p->s.size;
		q->s.ptr = p->s.ptr;
	} else {
		/* Link to end of this entry */
		q->s.ptr = p;
	}
	allocp = q;
#ifndef NODISABLE
	if (need_disable)
		restore(i_state);
#endif
}

/* Move existing block to new area */
#ifdef MSDOS
void *
#else
char *
#endif
realloc(area,size)
#ifdef MSDOS
void *area;
#else
char *area;
#endif
unsigned size;
{
	unsigned osize;
	HEADER PTR hp;
	char *cp;
#ifndef NODISABLE
	/* Make sure nobody else comes in and takes it */
	char i_state;
	
	if (need_disable)
		i_state = disable();
#endif

	hp = (HEADER PTR)area - 1;
	osize = (hp->s.size -1) * sizeof(HEADER);

	free(area);
	if((cp = malloc(size)) != NULLCHAR && cp != area)
		memcpy((char *)cp,(char *)area,size>osize? osize : size);
#ifndef NODISABLE
	if (need_disable)
		restore(i_state);
#endif
	return cp;
}

/* Allocate block of cleared memory */
#ifdef MSDOS
void *
#else
char *
#endif
calloc(nelem,size)
unsigned nelem; /* Number of elements */
unsigned size;	/* Size of each element */
{
	register unsigned i;
	register char *cp;
#ifdef	ALLOCDEBUG
	struct alloc *ap;
	short *ptr;
#endif
	i = nelem * size;
	if((cp = malloc(i)) != NULLCHAR)
		memset(cp,0,i);
#ifdef	ALLOCDEBUG
	if(cp == NULLCHAR)
		return cp;
	/* Adjust caller field to indicate real caller, not calloc() */
	ptr = (short *)&nelem;
	for(ap = alloc; ap < &alloc[NALLOC];ap++)
		if(ap->addr == cp)
			ap->pc = (long)ptr[-1];
#endif
	return cp;
}

/* Get more memory from the system and put it on the heap */
HEADER PTR
morecore(nu)
unsigned nu;
{
	register char *cp;
	register HEADER PTR up;

	/* adjust requests for small amounts upward to MIN_ALLOC */
	/* when LARGEDATA, make sure we have one unit extra */
	if (nu < (MIN_ALLOC / sizeof(HEADER)))
		nu = MIN_ALLOC / sizeof(HEADER)
#ifdef	LARGEDATA
		+ 1;
	else
		nu++
#endif
		;
	if ((int)(cp = sbrk(nu * sizeof(HEADER))) == -1)
		return NULLHDR;
	grabbedbytes += nu * sizeof(HEADER);
#ifdef	LARGEDATA
	/* most C runtimes don't like pointers with offset 0! */
	if ((FP_OFF(cp) % sizeof(HEADER)) == 0){
		cp += 4;		/* so move it off the para mark */
		nu--;			/* which costs us one unit */
	}
#endif
	up = (HEADER PTR)cp;
	up->s.size = nu;
	up->s.ptr = up; /* satisfy audit */
	free((char *)(up + 1));
	return allocp;
}

/* Grab as much memory as possible from the system and put it on the heap */
void
grabcore()
{
	char *cp;
	register HEADER PTR up;
	register unsigned long size;
#if (defined(MSC) || defined(__TURBOC__))
	char redzone[4096];
#endif

	/* Find out where the break is */
	cp = sbrk(0);
	/* Now try to push it as high as possible */
#if (defined(MSC) || defined(__TURBOC__))
	redzone[0] = size = 0;
	while(sbrk(256) != (char *)(-1)) {
		size += 256;
# ifdef LARGEDATA
		if (size > 32000)
			break;
# endif
	}
#else
	for(size=256; size < 65535-256;size += 256){
		if(brk((char *) (cp + size)) == -1){
			size -= 256;
			break;
		}
	}
#endif
	grabbedbytes += size;
#ifdef	LARGEDATA
	/* most C runtimes don't like pointers with offset 0! */
	if ((FP_OFF(cp) % sizeof(HEADER)) == 0){
		cp += 4;		/* so move it off the para mark */
		size -= 4;
	}
#endif
	up = (HEADER PTR)cp;
	up->s.size = size / sizeof(HEADER);
	up->s.ptr = up; /* satisfy audit */
	free((char *)(up + 1));
}

#ifndef NOMEMSTAT
/* Print free list map */
memstat(argc,argv)
int argc;
char *argv[];
{
	HEADER PTR p;
	unsigned long totalfree;

	totalfree = 0;
	p = &base;
	do {
		totalfree += (unsigned long) p->s.size * sizeof(HEADER);
		if (argc != 1){
			printf("%*lx %lu",2*sizeof(p),ptr2long(p),
				(unsigned long) p->s.size * sizeof(HEADER));
			if(p == allocp)
				printf(" <-- allocp\n");
			else
				printf("\n");
		}
		p = p->s.ptr;
	} while(p != &base);
	printf("Total: %lu bytes grabbed, %lu bytes free\n",
		grabbedbytes,totalfree);
#ifdef	ALLOCDEBUG
	if (argc != 1) {
		struct alloc *ap;
		printf("addr      size      caller\n");
		for(ap = alloc;ap < &alloc[NALLOC];ap++){
			if(ap->addr != NULLCHAR)
				printf("0x%-8lx%-10d  %08lx\n",
					ptr2long(ap->addr),ap->size,ap->pc);
		}
	}
#endif
	printf("malloc fails %u\n",memfail);
	fflush(stdout);
	return 0;
}
#endif
