/*
 * malloc
 *
 *	C B Weinstock and W A Wulf, "Quick Fit: An Efficient Storage Algorithm for Heap Storage Allocation",
 *		SIGPLAN Notices, 23(10), 144-148 (1988).
 *	A Iyengar, "Scalability of Dynamic Storage Allocation Algorithms", IEEE Proceedings Frontiers of
 *		Massively Parallel Computing, 223-232 (1996).
 *	Yi Feng and Emery D Berger, "A Locality-Improving Dynamic Memory Allocator",
 *		ACM Proceedings of the 2005 workshop on Memory system performance, 68-77 (2005).
 */
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"

typedef union Header Header;
typedef struct Qlist Qlist;
typedef struct Region Region;
typedef struct BigAlloc BigAlloc;

enum{
	Bpw=	8*sizeof(uint32_t),
	Busy= 	(uint32_t)1<<(Bpw-1),		/* set in Header.s.size of allocated blocks */
	Poisoning=	1,	/* or DBGFLG */	/* DEBUG: poison block */
};

/*
 * Memory is allocated in units of Header,
 * which also provides the basic alignment.
 * Every usable block is at least 2 units: the header
 * and at least one unit of data. The block heading
 * the misc list has size 0 and will never be allocated.
 *
 * Yi Feng and Berger eliminate the object headers for
 * quick fit items, since they are never coalesced,
 * but we retain headers to track ownership.
 */
union Header
{
	struct {
		uint32_t	size;		/* size in units including Header, and Busy flag */
		uint32_t	tag;		/* only for debugging: set malloctag */
		Header*	next;		/* free list */
	} s;
	uint8_t	_align[8];
};

enum{
	Unitsz=	sizeof(Header),		/* must be sizeof(Header) */
	MinUnits=	2,
	Align=	Unitsz,		/* minimum alignment */
};

/* application pointers to and from headers */
#define	D2H(p)	((Header*)(p)-1)
#define	H2D(h)	((void*)((h)+1))

#define	XX(P,N)	iprint("[%d]: %#p -> %#p\n", (N), (P), (P)->s.next)
#undef XX
#define	XX(P,N)

struct Qlist
{
	Header*	first;

	uint32_t	nalloc;
};

struct Region
{
	Region*	down;	/* run as a stack */
	Header*	start;
	Header*	tail;	/* next available, if any */
	Header*	end;
	uint32_t	size;		/* remaining space, in Unitsz */
	char*	base;
	char*	limit;
};

/*
 * big mallocs out of basealloc
 */
enum
{
	BHashSize=	67,	/* prime */
	NBig=	512,
	BigThreshold=	8*1024*1024,
};

struct BigAlloc
{
	uintptr_t	va;
	uintptr_t	nbytes;
	uintptr_t	tag;
	BigAlloc*	next;
};

static struct {
	Lock;
	BigAlloc*	free;
	BigAlloc	pool[NBig];	/* could qalloc them */

	BigAlloc*	set[BHashSize];
} bighash;

#define	BIGHASH(p)		((uintptr_t)p%BHashSize)
#define	isbigalloc(p)	((uintptr_t)(p)>=KSEG2 && (uintptr_t)(p)<KSEG1)

/* TO DO: fewer buckets for larger blocks */
#define	NUNITS(n)	(HOWMANY(n, Unitsz) + 1)
#define	NQUICK		((4096/Unitsz)+1)

static	Qlist	quicklist[NQUICK+1];
static	Header	misclist;
static	Header	*rover;	/* through misc list over all Regions */
static	Region	*tail;		/* current tail Region */
static	int	morecore(unsigned);
//static	void	coalesce(Region*);	// not used
static	void	freemisc(Header*);
static	BigAlloc*	bigfind(void*);
static	void	bignote(void*, usize, uintptr_t);
static	void*	bigalloc(usize, usize, uintptr_t);
static	void	bigfree(void*);

static void	(*memprof)(void*, uint32_t, usize, int) = nil;

enum
{
	QSmalign = 0,
	QSmalignquick,
	QSmalignrover,
	QSmalignfront,
	QSmalignback,
	QSmaligntail,
	QSmalignnottail,
	QSmalloc,
	QSmallocrover,
	QSmalloctail,
	QSfree,
	QSfreetail,
	QSfreequick,
	QSfreenext,
	QSfreeprev,
	QSsplitquick,
	QSsplitmisc,
	QSmax
};

static	char*	qstatname[QSmax] = {
[QSmalign]		"malign",
[QSmalignquick]	"malignquick",
[QSmalignrover]	"malignrover",
[QSmalignfront]	"malignfront",
[QSmalignback]	"malignback",
[QSmaligntail]	"maligntail",
[QSmalignnottail]	"malignnottail",
[QSmalloc]		"malloc",
[QSmallocrover]	"mallocrover",
[QSmalloctail]	"malloctail",
[QSfree]		"free",
[QSfreetail]	"freetail",
[QSfreequick]	"freequick",
[QSfreenext]	"freenext",
[QSfreeprev]	"freeprev",
[QSsplitquick]	"split quick",
[QSsplitmisc]	"split misc",
};

static	void*	qalloc(usize, uintptr_t);
//static	void	qfreeinternal(Header*); // not defined
static	int	qstats[QSmax];

static	void*	bigalloc(usize, usize, uintptr_t);
static	void	bigfree(void*);

static	Lock		mainlock;

/* misc lock (TO DO: per misclist, and Region/Reap) */
#define	MLOCK		ilock(&mainlock)
#define	MUNLOCK	iunlock(&mainlock)

#define ISPOWEROF2(x)	(/*((x) != 0) && */!((x) & ((x)-1)))
#define ALIGNHDR(h, a)	(Header*)((((uintptr_t)(h))+((a)-1)) & ~((a)-1))

static Header*
tailalloc(Region *r, int n)
{
	Header *p;

	qstats[QSmalloctail]++;
	p = r->tail;
	if(n == 0)
		panic("tailalloc size");
	if(r->size < n)
		panic("tailalloc");
	r->size -= n;
	r->tail += n;
	p->s.size = Busy | n;
	return p;
}

static int
freetail(Header *h)
{
	if(h+h->s.size == tail->tail) {	/* worthwhile locking? */
		if(h+h->s.size == tail->tail){
			/* block before tail */
			tail->tail = h;
			tail->size += h->s.size;
			qstats[QSfreetail]++;
			return 1;
		}
	}
	return 0;
}

static void
freequick(Header *t)
{
	Qlist *ql;

	ql = &quicklist[t->s.size];
	t->s.next = ql->first;
	ql->first = t;
	qstats[QSfreequick]++;
}

int
malloclocked(void)
{
	if(canlock(&mainlock)){
		unlock(&mainlock);
		return 0;
	}
	return 1;
}

static void
showchain(Header *p)
{
	Header *q;

	iprint("chain %#p:\n", p);
	q = p;
	do{
		iprint("%#p [%#ux %#ux] -> %#p\n", q, q->s.size, q->s.tag, q->s.next);
	}while((q = q->s.next) != nil && q->s.next != p);
}

static void
badchain(char *tag, char *why, Header *p, Header *q, Header *r, void *h, Header *split)
{
	if(0)showchain(&misclist);
	if(split != nil)
		panic("badchain %s: %s: %#p %#ux %#ux -> %#p %#ux %#ux [%#p -| %#p]%s %#p %#ux %#ux\n", tag, why,
			q, q->s.size, q->s.tag,
			r, r->s.size, r->s.tag,
			p, h, " split", split, split->s.size, split->s.tag);
	else
		panic("badchain %s: %s: %#p %#ux %#ux -> %#p %#ux %#ux [%#p -| %#p]\n", tag, why,
			q, q->s.size, q->s.tag,
			r, r->s.size, r->s.tag,
			p, h);
}

static Header*
contained(Header *b, Header *chain)
{
	Header *q;
	int i;

	i = 0;
	q = chain;
	do{
		if(q->s.next == nil)
			panic("nil contained");
		if(q->s.next == b)
			return q;
	}while((q = q->s.next) != chain && ++i < 100000);
	if(i >= 100000)
		jehanne_print("long check chain\n");
	return 0;
}

static void
checkchain(Header *p, char *tag, void *h, Header *split)
{
	Header *q, *r;
	int i;

	q = p;
	i = 0;
	do{
		if(q->s.next == nil)
			badchain(tag, "nil next", p, q, q, h, split);
		if((r = q->s.next)->s.size & Busy)
			badchain(tag, "busy", p, q, r, h, split);
		if(split && r == h)
			badchain(tag, "mentioned", p, q, r, h, split);
	}while((q = q->s.next) != p && ++i < 100000);
	if(i >= 100000)
		jehanne_print("long chain\n");
}

static void*
qallocalign(usize nbytes, uintptr_t align, long offset, usize span, uintptr_t pc)
{
	Qlist *qlist;
	uintptr_t aligned;
	Header **pp, *p, *q, *r;
	uint32_t n, nunits, alunits, maxunits, gap;

	if(nbytes == 0 || offset != 0 || span != 0)
		return nil;

	if(!ISPOWEROF2(align))
		panic("qallocalign");

	if(align <= Align)
		return qalloc(nbytes, pc);

	qstats[QSmalign]++;
	nunits = NUNITS(nbytes);
	if(nunits <= NQUICK){
		/*
		 * Look for a conveniently aligned block
		 * on one of the quicklists.
		 */
		MLOCK;
		qlist = &quicklist[nunits];
		for(pp = &qlist->first; (p = *pp) != nil; pp = &p->s.next){
			if(ALIGNED(p+1, align)){
				*pp = p->s.next;
				p->s.size |= Busy;
				MUNLOCK;
				p->s.tag = pc;
				qstats[QSmalignquick]++;
				return H2D(p);
			}
		}
		MUNLOCK;
	}

	alunits = HOWMANY(align, Unitsz);
	maxunits = nunits + alunits + MinUnits;
	MLOCK;
	if(maxunits > tail->size) {
		/* hard way */
		q = rover;
		do {
			p = q->s.next;
			aligned = ALIGNED(p+1, align);
			if(aligned && p->s.size >= nunits || p->s.size >= maxunits){

				/*
				 * This block is big enough
				 */
				qstats[QSmalignrover]++;

				/*
				 * Leave behind any runt in front of the alignment;
				 * it will be coalesced if the aligned memory is released.
				 */
				if(!aligned){
					r = p;
					p = ALIGNHDR(p+1, align) - 1;
					if(!ALIGNED(p+1, align))
						panic("qmallocalign");
					n = p - r;
					p->s.size = r->s.size - n;
					if(r->s.size <= 1 || n <= 1)
						panic("qallocalign size");
					p->s.size = n;
					p->s.next = r->s.next;
					q = r;
					qstats[QSmalignfront]++;
				}

				q->s.next = p->s.next;
				XX(q,1);
				rover = q;

				/*
				 * Leave behind any residue after the aligned block.
				 */
				if(p->s.size > nunits+MinUnits){
					r = p + nunits;
					r->s.size = p->s.size - nunits;
					r->s.next = q->s.next;
					q->s.next = r;
					XX(q,2);
					qstats[QSmalignback]++;
					p->s.size = nunits;
				}

				p->s.size |= Busy;
				p->s.next = nil;
				MUNLOCK;

				p->s.tag = pc;
				return H2D(p);
			}
		} while((q = p) != rover);

		/* grow tail */
		if(!morecore(maxunits+MinUnits)){
			MUNLOCK;
			return nil;
		}
	}

	q = tail->tail + 1;
	p = ALIGNHDR(q, align);
	gap = p - q;
	if(gap == 0){
		p = tailalloc(tail, nunits);
		if(!ALIGNED(p+1, align))
			panic("qmallocalign 2");
		qstats[QSmaligntail]++;
	}else{
		if(gap < MinUnits)
			gap += alunits;
		if(tail->size < nunits+gap)
			panic("qallocalign: miscalculation tail=%ud maxunits=%ud nunits=%ud gap=%ud", tail->size, maxunits, nunits, gap);
		/*
		 * Save the residue before the aligned allocation
		 * and free it after the tail pointer has been bumped
		 * for the main allocation.
		 */
		r = tailalloc(tail, gap);
		p = tailalloc(tail, nunits);
		if(!ALIGNED(p+1, align))
			panic("qmallocalign 3");
		qstats[QSmalignnottail]++;
		freemisc(r);	/* put on misc list to allow combining if this block is freed */
	}
	p->s.next = nil;
	MUNLOCK;

	p->s.tag = pc;
	return H2D(p);
}

static void*
qalloc(usize nbytes, uintptr_t pc)
{
	Qlist *qlist;
	Header *p, *q, *t;
	uint32_t nunits, u;
	int split;

	if(nbytes == 0)
		return nil;

	split = 0;
	nunits = NUNITS(nbytes);
	for(u = nunits; u <= NQUICK; u++){
		MLOCK;
		qlist = &quicklist[u];
		if((p = qlist->first) != nil){
			qlist->first = p->s.next;
			qlist->nalloc++;
			if(p->s.size >= nunits+2*MinUnits){	/* don't make blocks pointlessly small */
				t = p;
				t->s.size -= nunits;
				p += t->s.size;
				p->s.size = nunits;
				freequick(t);
				split = 1;
			}
			p->s.next = nil;
			p->s.size |= Busy;
			MUNLOCK;
			p->s.tag = pc;
			if(split)
				qstats[QSsplitquick]++;
			return H2D(p);
		}
		MUNLOCK;
	}

	MLOCK;
	if(nunits > tail->size) {
		/* hard way */
		q = rover;
		checkchain(q, "qalloc-1", rover, 0);
		do {
			p = q->s.next;
			if(p->s.size & Busy)
				panic("qalloc: busy: %#p %#ux ~ %#ux\n", p, p->s.size, p->s.tag);
			if(p->s.next->s.size & Busy)
				panic("qalloc+: busy");
			if(p->s.next->s.next->s.size & Busy)
				panic("qalloc++: busy");
			if(p->s.size >= nunits) {
				if(p->s.size > nunits+MinUnits) {	/* split block; return tail */
					p->s.size -= nunits;
					p += p->s.size;
					p->s.size = nunits;
					split = 1;
					if(contained(p, rover))
						badchain("qalloc-3", "contained", rover, q, q->s.next, p, q->s.next);
					qstats[QSsplitmisc]++;
				}else{
					q->s.next = p->s.next;
					XX(q,3);
				}
				rover = q;
				qstats[QSmallocrover]++;
				if(contained(p, rover))
					badchain("qalloc-4", "contained", rover, q, p, p, nil);
				checkchain(q, "qalloc-2", p, split? q->s.next: nil);
				p->s.size |= Busy;
				MUNLOCK;
				p->s.tag = pc;
				return H2D(p);
			}
		} while((q = p) != rover);

		/* grow tail */
		if(!morecore(nunits)){
			MUNLOCK;
			return nil;
		}
	}
	p = tailalloc(tail, nunits);
	MUNLOCK;
	p->s.tag = pc;
	return H2D(p);
}

static void
freemisc(Header *p)
{
	Header *q, *x;

	p->s.size &= ~Busy;
	q = rover;
	checkchain(q, "qfree-1", p, 0);
	for(; !(p > q && p < q->s.next); q = q->s.next)
		if(q >= q->s.next && (p > q || p < q->s.next)){	/* put freed block at start or end of arena */
			iprint("q==%#p %#ux %#p p==%#p %#ux\n", q, q->s.size, q->s.next, p, p->s.size);
			break;
		}
	if((x = contained(p, &misclist)) != nil)
		badchain("qfree-1a", "contained", x, q, p, p, nil);
	if(p+p->s.size == q->s.next) {
		p->s.size += q->s.next->s.size;
		p->s.next = q->s.next->s.next;
		XX(p,4);
		qstats[QSfreenext]++;
	}else{
		p->s.next = q->s.next;
		XX(p,5);
	}
	if(q+q->s.size == p) {
		q->s.size += p->s.size;
		q->s.next = p->s.next;
		XX(q,6);
		qstats[QSfreeprev]++;
		if(contained(p, &misclist))
			badchain("qfree-1b", "contained", rover, q, p, p, nil);
	}else{
		q->s.next = p;
		XX(q,7);
	}
	checkchain(q, "qfree-2", p, 0);
	rover = q;
}

uint32_t
jehanne_msize(void* ap)
{
	BigAlloc *b;
	Header *h;
	uint32_t nunits;

	if(ap == nil)
		return 0;

	if(isbigalloc(ap)){
		b = bigfind(ap);
		if(b != nil)
			return b->nbytes;
	}
	h = D2H(ap);
	nunits = h->s.size & ~Busy;
	if((h->s.size & Busy) == 0 || nunits == 0)
		panic("msize: corrupt allocation arena");

	return (nunits - 1) * Unitsz;
}

static void
mallocreadfmt(char* s, char* e)
{
	char *p;
	Header *q;
	int i, n;
	Qlist *qlist;
	Region *r;
	uintmem t, u;

	p = s;
	MLOCK;
	t = 0;
	n = 0;
	u = 0;
	for(r = tail; r != nil; r = r->down){
		p = jehanne_seprint(p, e, "reg%d: %#p %#p %#p : %#p %#p\n", n, r, r->base, r->limit, r->start, r->tail);
		t += r->limit - r->base;
		u += (r->tail - r->start)*Unitsz;
		n++;
	}
	MUNLOCK;
	p = jehanne_seprint(p, e, "%P kernel malloc %P used %d regions\n", t, u, n);
	p = jehanne_seprint(p, e, "0/0 kernel draw\n"); // keep scripts happy

	t = 0;
	for(i = 0; i <= NQUICK; i++) {
		n = 0;
		MLOCK;
		qlist = &quicklist[i];
		for(q = qlist->first; q != nil; q = q->s.next)
			n++;
		MUNLOCK;

		if(n != 0)
			p = jehanne_seprint(p, e, "q%d %ud %ud %ud\n", i, n, n*i*Unitsz, qlist->nalloc);
		t += n * i*Unitsz;
	}
	p = jehanne_seprint(p, e, "quick: %P bytes total\n", t);

	MLOCK;
	if((q = rover) != nil){
		i = t = 0;
		do {
			t += q->s.size;
			i++;
//			p = jehanne_seprint(p, e, "m%d\t%#p\n", q->s.size, q);
		} while((q = q->s.next) != rover);

		p = jehanne_seprint(p, e, "rover: %d blocks %P bytes total\n",
			i, t*Unitsz);
	}
	MUNLOCK;

	for(i = 0; i < nelem(qstats); i++)
		if(qstats[i] != 0)
			p = jehanne_seprint(p, e, "%s: %ud\n", qstatname[i], qstats[i]);
	USED(p);
}

long
mallocreadsummary(Chan* _1, void *a, long n, long offset)
{
	char *alloc;

	alloc = jehanne_malloc(READSTR);
	if(waserror()){
		jehanne_free(alloc);
		nexterror();
	}
	mallocreadfmt(alloc, alloc+READSTR);
	n = readstr(offset, a, n, alloc);
	poperror();
	jehanne_free(alloc);

	return n;
}

#if 0
static void
coalesce(Region *r)
{
	Header *p, *q;

	ilock(&r->lk);
	/* TO DO: need to re-establish the local free list */
	for(p = r->start; p != r->tail;){
		if((p->s.size & Busy) == 0){
			if((q = p->s.next) != nil && (q->s.size & Busy) == 0)
				p->s.size += q->s.size;
		}else
			p += p->s.size & ~Busy;
	}
	iunlock(&r->lk);
}
#endif

/*
 * big allocations use basealloc
 */
static BigAlloc*
bigfind(void *p)
{
	BigAlloc *b, **l;

	/* quick check without lock is fine: p can't be added meanwhile */
	l = &bighash.set[BIGHASH(p)];
	if(*l == nil)
		return nil;
	ilock(&bighash);
	for(; (b = *l) != nil; l = &b->next)
		if(b->va == (uintptr_t)p)
			break;
	iunlock(&bighash);
	return b;
}

static void
bignote(void *p, usize nbytes, uintptr_t pc)
{
	BigAlloc *b, **l;

	ilock(&bighash);
	b = bighash.free;
	if(b == nil)
		panic("bigstore: no free structures");
	bighash.free = b->next;
	b->va = (uintptr_t)p;
	b->nbytes = nbytes;
	b->tag = pc;
	l = &bighash.set[BIGHASH(p)];
	b->next = *l;
	*l = b;
	iunlock(&bighash);
}

static void*
bigalloc(usize nbytes, usize align, uintptr_t pc)
{
	void *p;

	p = basealloc(nbytes, align, &nbytes);
	if(p == nil)
		return p;
	bignote(p, nbytes, pc);
	return p;
}

static void
bigfree(void *p)
{
	BigAlloc **l, *b;
	uintmem used;

	if(p == nil)
		return;
	ilock(&bighash);
	l = &bighash.set[BIGHASH(p)];
	for(; (b = *l) != nil; l = &b->next)
		if(b->va == (uintptr_t)p){
			used = b->nbytes;
			*l = b->next;
			b->next = bighash.free;
			bighash.free = b;
			iunlock(&bighash);
			basefree(p, used);
			return;
		}
	iunlock(&bighash);
	panic("bigfree");
}

typedef struct Rov Rov;
struct Rov{
	uint32_t	tag;
	uint32_t	size;
};
static Rov rovers[2048];

void
mallocsummary(void)
{
	Header *q;
	int i, n, t;
	Qlist *qlist;

	t = 0;
	for(i = 0; i <= NQUICK; i++) {
		n = 0;
		MLOCK;
		qlist = &quicklist[i];
		for(q = qlist->first; q != nil; q = q->s.next){
			if(q->s.size != i)
				DBG("q%d\t%#p\t%ud\n", i, q, q->s.size);
			n++;
		}
		MUNLOCK;

		t += n * i*Unitsz;
	}
	jehanne_print("quick: %ud bytes total\n", t);

	MLOCK;
	if((q = rover) != nil){
		i = t = 0;
		do {
			t += q->s.size;
			if(i < nelem(rovers)){
				rovers[i].tag = q->s.tag;
				rovers[i].size = q->s.size;
			}
			i++;
		} while((q = q->s.next) != rover);
	}
	MUNLOCK;

	if(i != 0){
		jehanne_print("rover: %d blocks %ud bytes total\n",
			i, t*Unitsz);
		while(--i >= 0)
			if(i < nelem(rovers) && rovers[i].size != 0)
				jehanne_print("R%d: %#8.8ux %ud\n", i, rovers[i].tag, rovers[i].size);
	}

	for(i = 0; i < nelem(qstats); i++){
		if(qstats[i] == 0)
			continue;
		jehanne_print("%s: %ud\n", qstatname[i], qstats[i]);
	}
}

void
jehanne_free(void* ap)
{
	Header *h;
	BigAlloc *b;
	usize nunits;

	if(ap == nil)
		return;
	qstats[QSfree]++;
	if(isbigalloc(ap)){
		b = bigfind(ap);
		if(b != nil){
			bigfree(ap);
			return;
		}
	}
	h = D2H(ap);
	nunits = h->s.size;
	if((nunits & Busy) == 0)
		panic("free: already free %#p: freed %#p tag %#ux", ap, getcallerpc(), h->s.tag);
	nunits &= ~Busy;
	h->s.size = nunits;
	if(nunits < MinUnits)
		panic("free: empty block: corrupt allocation arena");
	if(memprof != nil)
		memprof(ap, h->s.tag, (nunits-1)*Unitsz, -1);
	if(Poisoning)
		jehanne_memset(h+1, 0xAA, (nunits-1)*Unitsz);
	MLOCK;
	if(!freetail(h)){
		if(nunits > NQUICK){
			freemisc(h);
		}else
			freequick(h);
	}
	MUNLOCK;
}

void*
qmalloc(uint32_t size)
{
	void* v;

	qstats[QSmalloc]++;
if(size > 1536*1024)jehanne_print("qmalloc %lud %#p\n", size, getcallerpc());
	if(size >= BigThreshold)
		v = bigalloc(size, 0, getcallerpc());
	else
		v = qalloc(size, getcallerpc());
	return v;
}

void*
jehanne_malloc(uint32_t size)
{
	void* v;

	qstats[QSmalloc]++;
if(size > 1536*1024)jehanne_print("malloc %lud %#p\n", size, getcallerpc());
	if(size >= BigThreshold)
		v = bigalloc(size, 0, getcallerpc());
	else
		v = qalloc(size, getcallerpc());
	if(v != nil)
		jehanne_memset(v, 0, size);
	return v;
}

void*
jehanne_mallocz(uint32_t size, int clr)
{
	void *v;

	qstats[QSmalloc]++;
if(size > 1900*1024)jehanne_print("mallocz %lud %#p\n", size, getcallerpc());
	if(size >= BigThreshold)
		v = bigalloc(size, 0, getcallerpc());
	else
		v = qalloc(size, getcallerpc());
	if(v == nil)
		return nil;
	if(clr)
		jehanne_memset(v, 0, size);
	return v;
}

void*
jehanne_mallocalign(uint32_t nbytes, uint32_t align, long offset, uint32_t span)
{
	void *v;

	qstats[QSmalloc]++;
	if(span != 0 && align <= span){
		if(nbytes > span)
			return nil;
		align = span;
		span = 0;
	}
	if(align <= Align)
		return jehanne_mallocz(nbytes, 1);

if(nbytes > 1900*1024)jehanne_print("mallocalign %lud %lud %#p\n", nbytes, align, getcallerpc());

	if(nbytes >= BigThreshold)
		v = bigalloc(nbytes, align, getcallerpc());
	else
		v = qallocalign(nbytes, align,offset, span, getcallerpc());
	if(v != nil){
		if(align && (uintptr_t)v & (align-1))
			panic("mallocalign %#p %#lux", v, align);
		jehanne_memset(v, 0, nbytes);	/* leave it to caller? */
	}
	return v;
}

void*
sqmalloc(uint32_t size)
{
	void *v;

	while((v = jehanne_malloc(size)) == nil)
		tsleep(&up->sleep, return0, 0, 100);
	jehanne_setmalloctag(v, getcallerpc());

	return v;
}

void*
smalloc(uint32_t size)
{
	void *v;

	while((v = jehanne_malloc(size)) == nil)
		tsleep(&up->sleep, return0, 0, 100);
	jehanne_setmalloctag(v, getcallerpc());
	jehanne_memset(v, 0, size);

	return v;
}

void*
jehanne_realloc(void* ap, uint32_t size)
{
	void *v;
	Header *h;
	BigAlloc *b;
	uint32_t osize;
	uint32_t nunits, ounits;
	int delta;
	Region *t;

	/*
	 * Easy stuff:
	 * free and return nil if size is 0
	 * (implementation-defined behaviour);
	 * behave like malloc if ap is nil;
	 * check for arena corruption;
	 * do nothing if units are the same.
	 */
	if(size == 0){
		jehanne_free(ap);
		return nil;
	}
	if(ap == nil){
		v = jehanne_malloc(size);
		if(v != nil)
			jehanne_setmalloctag(v, getcallerpc());
		return v;
	}

	if(!isbigalloc(ap) || (b = bigfind(ap)) == nil){
		h = D2H(ap);
		ounits = h->s.size & ~Busy;
		if((h->s.size & Busy) == 0 || ounits == 0)
			panic("realloc: corrupt allocation arena");

		if((nunits = NUNITS(size)) == ounits)
			return ap;

		/*
		 * Slightly harder:
		 * if this allocation abuts the tail of a region, try to adjust the tail
		 */
		MLOCK;
		for(t = tail; t != nil; t = t->down){
			if(t->tail != nil && h+ounits == t->tail){
				delta = nunits-ounits;
				if(delta < 0 || t->size >= delta){
					h->s.size = nunits | Busy;
					t->size -= delta;
					t->tail += delta;
					MUNLOCK;
					return ap;
				}
			}
		}
		MUNLOCK;
		osize = (ounits-1)*Unitsz;
	}else
		osize = b->nbytes;

	/*
	 * Too hard (or can't be bothered):
	 * allocate, copy and free.
	 * The original block must be unchanged on failure.
	 */
	if((v = jehanne_malloc(size)) != nil){
		jehanne_setmalloctag(v, getcallerpc());
		if(size < osize)
			osize = size;
		jehanne_memmove(v, ap, osize);
		jehanne_free(ap);
	}

	return v;
}

void
jehanne_setmalloctag(void *a, uint32_t tag)
{
	Header *h;
	BigAlloc *b;

	if(isbigalloc(a) && (b = bigfind(a)) != nil){
		b->tag = tag;
		if(memprof != nil)
			memprof(a, tag, b->nbytes, 2);
		return;
	}
	h = D2H(a);
	if((h->s.size & Busy) == 0)
		panic("setmalloctag free %#p %#lux [%#ux %#ux] %#p", a, tag, h->s.size, h->s.tag, getcallerpc());
	h->s.tag = tag;
	if(memprof != nil)
		memprof(a, tag, (h->s.size-1)*Unitsz, 2);
}

uint32_t
jehanne_getmalloctag(void *a)
{
	BigAlloc *b;

	if(a == nil)
		return 0;
	if(isbigalloc(a) && (b = bigfind(a)) != nil)
		return b->tag;
	return D2H(a)->s.tag;
}

void
mallocinit(void)
{
	BigAlloc *p, *pe;

	if(tail != nil)
		return;

	rover = &misclist;
	rover->s.next = rover;
	pe = &bighash.pool[nelem(bighash.pool)-1];
	bighash.free = p = bighash.pool;
	for(; p < pe; p++)
		p->next = p+1;
	p->next = nil;
	if(!morecore(BigThreshold/Unitsz))
		panic("mallocinit");
	jehanne_print("base %#p bound %#p nunits %lud\n", tail->start, tail->end, tail->end - tail->start);
}

/*
 * get some space from basealloc
 */
static int
morecore(uint32_t nunits)
{
	usize nbytes;
	char *p;
	Region *r;

	if(nunits < NUNITS(256*KiB))
		nunits = NUNITS(256*KiB);
	nbytes = nunits*Unitsz + sizeof(Region) + Unitsz;
	p = basealloc(nbytes, Align, &nbytes);
	if(p == nil)
		return 0;
	/* build a new region if current one can't be extended */
	if((r = tail) == nil || p != r->limit){
		r = (Region*)p;
		r->base = p;
		r->start= ALIGNHDR(r+1, Align);
		r->tail = r->start;
		r->down = tail;
		tail = r;
	}
	r->limit = p+nbytes;
	r->end = r->start + (r->limit - (char*)r->start)/Unitsz;
	r->size = r->end - r->tail;
	return 1;
}

void
setmemprof(void (*f)(void*, uint32_t, usize, int))
{
	memprof = f;
}

/*
 * Mstate :: base  (Used|Free Size)*
 */
void
snapmemarena(void)
{
	/* just the available parts */
}
