/*
 * allocation routines.
 */
#include <stdlib.h>

#define	PAGE_SIZE	4096
#define	PAGE_MASK	(PAGE_SIZE-1)

#define	PAGE_SLOP	0

#define	MEM_PAGE	(PAGE_SIZE - sizeof(struct page_hdr) - PAGE_SLOP)

typedef	union	object	{
	union	object	*next;
	char	value[sizeof(long)];
	long	pading;
} OB;

#define	OBJTOPAGE(op)	(&((PA *)((unsigned long)(op) & ~PAGE_MASK))->ph)

#define	PHTOPAGE(ph)	((ph)->page)

#define	ROUND(x, y)	(((unsigned long)(x) + (y)-1) & ~((y)-1))

typedef	struct	page_hdr {
	struct	page_hdr *next;
	struct	page_hdr *prev;
	struct	page	*page;
	OB	*free;
	int	nfree;
	int	maxfree;
	long	osize;
	size_t	nblks;
} PH;

typedef	struct	page	{
	PH	ph;
	union	{
		char	mem[MEM_PAGE];
		OB	obj[1];
	}u;
}PA;

#define	HASH_SIZ	32

#define	SHASH(x)	(((x)>>4) % HASH_SIZ)

typedef	struct	{
	PH	*page;
	int	hlen;
	int	pfree;
} PAH;

static	PAH	*alloced_pages;
static	PAH	*alloced_pages_HASH_SIZ;
static	PA	*free_pages;
int	pages_alloced;
int	pages_free;
static	int	done_alloc_pages;

/*
 * this variable controls what the likely maximum memory requirement is
 * in pages. If we allocate more than this number of pages, then the
 * allocation scheme will slow down a bit. It is not a hard limit!
 * by default the maxium is ~4Mb which should be enough for most
 * applications.
 */
int	max_mem_size = 1000;

#if PAGE_SLOP != 0
#define	END_PTR(pa, nblks)	((PA *)((char *)(pa) + (nblks) * PAGE_SIZE))
#else
#define	END_PTR(pa, nblks)	((PA *)(pa) + (nblks))
#endif

#define	BLOCK_END(pa) 		END_PTR(pa, pa->ph.nblks)

#define	MAXHASH_FREE		6

static	PH	*alloc_page(size_t, PAH *);
static	PA	*get_space(size_t);
static	PA	*get_pages(size_t, PAH *);
static	void	free_page(PH *);
static	void	free_pap_pages(PAH *);
static	int	do_alloc_pages(void);

#define	MIN_ALLOCSIZ	64

void	*
m_get(register size_t size)
{
	register PH	*php;
	OB	*ob;
	PAH	*pap;

	if(!done_alloc_pages && !do_alloc_pages())
		return( (void *)0);
	/*
	 * round up the size to a number of longs
	 * Assume that MIN_ALLOCSIZ is a multiple of the round up value.
	 */
	if(size < MIN_ALLOCSIZ)
		size = MIN_ALLOCSIZ;
	else
		size = ROUND(size, sizeof(OB) * 4);

	pap = &alloced_pages[SHASH(size)];
	for(php = pap->page ; php ; php = php->next)
		if(php->osize == size && php->nfree > 0)
			break;
	if(php == 0){
		if( (php = alloc_page(size, pap)) == 0)
			return( (void *)0);
	}
	if(php->nfree == php->maxfree)
		pap->pfree -= php->nblks;
	ob = php->free;
	php->free = ob->next;
	--php->nfree;
	return( (void *)ob->value);
}

#ifdef	MDUMP
mdump()
{
	PH	*php;
	PA	*pa;
	PAH	*pap;
	int	i, j;
	int	oc = 0;
	extern	int	printf();

	for(i = 0, pap = alloced_pages ; i < HASH_SIZ ; i++, pap++){
		for(j = 0, php = pap->page ; php ; php = php->next)
			j++;
		printf("bucket %d length %d free len = %d\r\n", i, j, pap->pfree);
		if(j != pap->hlen){
			printf("bad length %d %d\r\n", j, pap->hlen);
		}
		for(j = 0, php = pap->page ; php ; php = php->next, j++){
			printf("\tblock %d = %x page = %x\r\n", j,php, php->page);
		printf("\t\tnfree = %d maxfree = %d osize = %d nblks = %d\r\n",
			php->nfree, php->maxfree, php->osize, php->nblks);
			oc += php->maxfree - php->nfree;
		}
	}
	for(i = 0, pa = free_pages ; pa ; pa = pa->ph.page, i++){
		printf("freeblock %d at %x\r\n", i, pa);
		php = &pa->ph;
		printf("\tnblks = %d tblock_end = %x\r\n",
					php->nblks, BLOCK_END(pa));
	}
	printf("pages_alloced = %d pages_free = %d objs alloced = %d\r\n",
				pages_alloced, pages_free, oc);
}
#endif

void
m_free(void *mem)
{
	register PH	*php;
	register OB	*ob;

	ob = (OB *)mem;
	php = OBJTOPAGE(ob);

#ifdef	CHECK_MFREE
	{ PH	*tphp; PAH *pap; OB *tstob; int n; int i; PA *tpap;

	for(i = 0, pap = alloced_pages ; i < HASH_SIZ ; i++, pap++)
		for(n = 0, tphp = pap->page; tphp ; tphp = tphp->next, n++)
			if(php == tphp)
				goto out;
out:;
	if(tphp == 0 || tphp->nfree >= tphp->maxfree || tphp->nfree < 0 ||
		n > pap->hlen || (n == pap->hlen && tphp->next) ){
		/*
		 * check failed
		 */
		abort();
	}
	tpap = php->page;
	if(php != &tpap->ph)
		abort();
	for(n = 0, tstob = tphp->free ; n < tphp->maxfree && tstob ;
						 n++, tstob = tstob->next)
		if(tstob == ob || OBJTOPAGE(tstob) != tphp ||
				&tpap->u.obj[tstob - tpap->u.obj] != tstob)
			break;
	if(tstob != 0 || n != tphp->nfree){
		/*
		 * already on the free list. Or free list is broken
		 */
		abort();
	}
	}
#endif
	ob->next = php->free;
	php->free = ob;
	php->nfree++;
	if(php->nfree == php->maxfree){
		PAH	*pap = &alloced_pages[SHASH(php->osize)];
		if( (pap->pfree += php->nblks) > MAXHASH_FREE * 3)
			free_pap_pages(pap);
	}
}

void
m_purge()
{
	register PAH	*pap;

	if(!done_alloc_pages)
		return;
	for(pap = alloced_pages ; pap < alloced_pages_HASH_SIZ ; pap++)
		free_pap_pages(pap);
}
		
static	PH *
alloc_page(size_t size, PAH *pap)
{
	register PH	*php;
	register OB	*ob;
	PA	*pa;
	register size_t	nobjs, osize;
	size_t	npages;

	osize = size / sizeof(OB);

	if(size <= MEM_PAGE){
		npages = 1;
		nobjs = (MEM_PAGE/sizeof(OB)) / osize;
	}
	else {
		npages = ((size - MEM_PAGE+PAGE_SIZE-1)/ PAGE_SIZE) + 1;
		nobjs = 1;
	}
	pa = get_pages(npages, pap);

	if(pa == 0)
		return( (PH *)0);

	php = &pa->ph;
	php->page = pa;
	php->osize = size;
	php->nfree = php->maxfree = nobjs;
	php->free = 0;
	for(ob = pa->u.obj ; nobjs; nobjs--, ob += osize){
		ob->next = php->free;
		php->free = ob;
	}
	php->prev = 0;
	if(pap->page)
		pap->page->prev = php;
	php->next = pap->page;
	pap->page = php;
	pap->hlen++;
	pap->pfree += php->nblks;
	return(php);
}

/*
 * Now uses best fit algorithm
 */
static	PA *
get_pages(size_t npages, register PAH *pap)
{
	register PA	*pa, *opa, *npa;
	int	freed = 0;
	int	hasfreed;
	int	maxhash;
	PA	*spa, *sopa;
	size_t	spages;

/*
	if(pap->pfree > MAXHASH_FREE)
		free_pap_pages(pap);
*/
	for(;;){
		if(pages_free >= npages){
			spa = 0;
			spages = 0;	/* only for lint */
			for(opa = 0, pa = free_pages ; pa ;
						opa = pa, pa = pa->ph.page){
				if(pa->ph.nblks < npages)
					continue;
				if(pa->ph.nblks > npages){
					/*
					 * A potential candidate, see if
					 * it is the best.
					 */
					if(spa == 0 || pa->ph.nblks < spages){
						spages = pa->ph.nblks;
						spa = pa;
						sopa = opa;
					}
					continue;
				}
				pages_free -= npages;
				npa = pa->ph.page;
				if(opa == 0)
					free_pages = npa;
				else
					opa->ph.page = npa;
				return(pa);
			}
			/*
			 * IF spa != 0 then we have found the best fit for
			 * this size
			 */
			if( (pa = spa) != 0){
				pages_free -= npages;
				npa = END_PTR(pa, npages);
				npa->ph.page = pa->ph.page;
				npa->ph.nblks = pa->ph.nblks - npages;
				pa->ph.nblks = npages;
				if(sopa == 0)
					free_pages = npa;
				else
					sopa->ph.page = npa;
				return(pa);
			}
		}
		if(!freed){
			hasfreed = 0;
			if(pages_alloced){
				maxhash = max_mem_size / pages_alloced;
				for(pap = alloced_pages ;
					pap < alloced_pages_HASH_SIZ ; pap++)
					if(pap->pfree > maxhash){
						free_pap_pages(pap);
						hasfreed++;
					}
			}
			freed++;
			if(hasfreed)
				continue;
		}
		if(freed == 2)
			return(0);

		if( (pa = get_space(npages)) != 0){
			pa->ph.nblks = npages;
			pages_alloced += npages;
			return(pa);
		}
		hasfreed = 0;
		for(pap = alloced_pages ; pap < alloced_pages_HASH_SIZ ;pap++)
			if(pap->pfree){
				free_pap_pages(pap);
				hasfreed++;
			}
		if(!hasfreed)
			return(0);
		freed++;
	}
}

static	void
free_pap_pages(register PAH *pap)
{
	register PH	*php, *nphp = 0;

	for(php = pap->page ; php ; php = nphp){
		nphp = php->next;
		if(php->nfree == php->maxfree){
			if(php->prev == 0)
				pap->page = nphp;
			else
				php->prev->next = nphp;
			if(nphp != 0)
				nphp->prev = php->prev;
			pap->hlen--;
			pap->pfree -= php->nblks;
			free_page(php);
		}
	}
}

static	void
free_page(php)
PH	*php;
{
	register PA	*pa = php->page;
	register PA	*tpa, *xpa;

	pages_free += php->nblks;

	for(xpa = 0, tpa = free_pages ; tpa && pa > tpa ; tpa = tpa->ph.page)
		xpa = tpa;
	/*
	 * join at top of list
	 */
	if(BLOCK_END(pa) == tpa){
		pa->ph.nblks += tpa->ph.nblks;
		pa->ph.page = tpa->ph.page;
	}
	else
		pa->ph.page = tpa;

	if(xpa == 0)
		free_pages = pa;
	else {
		/*
		 * join at bottom
		 */
		if(BLOCK_END(xpa) == pa){
			xpa->ph.nblks += pa->ph.nblks;
			xpa->ph.page = pa->ph.page;
		}
		else
			xpa->ph.page = pa;
	}
}

extern	void	*sbrk(int);

static	int
do_alloc_pages(void)
{
	register char	*cur_mem;
	register size_t	i;

	done_alloc_pages = 1;

	cur_mem = (char *)sbrk(0);
	i = (char *)ROUND(cur_mem, PAGE_SIZE) - cur_mem;
	if(i < sizeof(PAH) * HASH_SIZ)
		i += PAGE_SIZE;
	if(sbrk(i) == (void *)-1)
		return(0);
	alloced_pages = (PAH *)(void *)cur_mem;
	alloced_pages_HASH_SIZ = alloced_pages + HASH_SIZ;
	i = sizeof(PAH) * HASH_SIZ;
	while(i--)
		*cur_mem++ = 0;
	return(1);
}

static	PA *
get_space(size_t size)
{
	register void	*cur_mem;

	cur_mem = sbrk(PAGE_SIZE * size);
	if(cur_mem == (void *)-1)
		return( (PA *)0);
	if( (int)cur_mem % PAGE_SIZE)
		return( (PA *)0);
	return( (PA *)cur_mem);
}


syntax highlighted by Code2HTML, v. 0.9.1