/*  Author: Mark Moraes <moraes@csri.toronto.edu> */

/*LINTLIBRARY*/

/* #define PH_TRACE 1 */ /* In case you need to see all your allocations and frees */
#if defined( PH_TRACE )
  #define DEBUG      /* In case you need to turn on some more debug checks to track down a corruptor. Need to have PH_TRACE on to make sense  */
#endif

#include "mem_defs.h"
#include "mem_globals.c"


//#define INLINE_WORKS
#if defined( INLINE_WORKS )
#define INLINE inline
#else
#define INLINE
#endif

/* RCSID("$Id: mem_malloc.c,v 1.2 2004/06/24 20:17:58 peter Exp $") */

/* PH: Everything is renamed as this library only manages the shared memory - it does not replace malloc */
extern void* shm_mem_malloc( size_t nbytes );
extern void  shm_mem_free( void* cp );
extern void* shm_mem_realloc( void* cp, size_t nbytes );
extern void* shm_mem_calloc( size_t nelem, size_t elsize );

/*
 * GETBIN, UNLINK, LINK and CARVE are free-list maintenance macros used in
 * several places.  A free-list is a doubly-linked list of non-contiguous
 * blocks, marked by boundary tags that indicate the size.
 */

/* GETBIN returns a number such that i <= _malloc_binmax[bin] */
#if defined( PH_MACRO_ONLY )
#define GETBIN(i)   \
	(((i) <= _malloc_binmax[3]) ? \
		(((i) <= _malloc_binmax[1]) ? \
			(((i) <= _malloc_binmax[0]) ? 0 : 1) \
		: \
			(((i) <= _malloc_binmax[2]) ? 2 : 3) \
		) \
	: \
		(((i) <= _malloc_binmax[5]) ? \
			(((i) <= _malloc_binmax[4]) ? 4 : 5) \
		: \
			(((i) <= _malloc_binmax[6]) ? 6 : 7) \
		) \
	)
#else
#define GETBIN(i) _shm_mem_getbin(i)
static INLINE int  _shm_mem_getbin( size_t i )
{
  return (((i) <= _malloc_binmax[3]) ?
		(((i) <= _malloc_binmax[1]) ?
			(((i) <= _malloc_binmax[0]) ? 0 : 1)
		:
			(((i) <= _malloc_binmax[2]) ? 2 : 3)
		)
	:
		(((i) <= _malloc_binmax[5]) ?
			(((i) <= _malloc_binmax[4]) ? 4 : 5)
		:
			(((i) <= _malloc_binmax[6]) ? 6 : 7)
		)
	);
}
#endif

/* UNLINK removes the block 'ep' from the free list 'epbin' */
#if defined( PH_MACRO_ONLY )
#define UNLINK(ep, epbin) \
	{ \
		REGISTER Word *epnext = NEXT(ep); \
		if (ep == epnext) { \
			SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[epbin] ) = NULL; \
			if (SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin) == epbin) \
				while (! SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin)] ) && \
				       SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin) < MAXBINS-1) \
					SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin)++; \
		} else { \
			REGISTER Word *epprev = PREV(ep); \
			NEXT(epprev) = epnext; \
			PREV(epnext) = epprev; \
			if (ep == SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[epbin] )) \
				SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[epbin] ) = epprev; \
		} \
	}
#else
#define UNLINK(ep, epbin) _shm_mem_unlink(ep, epbin)
static INLINE void _shm_mem_unlink( Word* ep, int epbin )
{
	REGISTER Word *epnext = NEXT(ep);
	if (ep == epnext) {
		SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[epbin] ) = NULL;
		if (SERVER_STATIC_VARIABLE( mem_global,_malloc_firstbin ) == epbin)
			while (! SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[SERVER_STATIC_VARIABLE( mem_global,_malloc_firstbin )] ) &&
			       SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin ) < MAXBINS-1)
				SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin )++;
	} else {
		REGISTER Word *epprev = PREV(ep);
		NEXT(epprev) = epnext;
		PREV(epnext) = epprev;
		if (ep == SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[epbin] ))
			SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[epbin] ) = epprev;
	}
}
#endif

/*
 * LINK adds the block 'ep' (epsize words) to the free list 'epbin',
 * immediately after the block pointed to by that bin's rover.
 */
#if defined( PH_MACRO_ONLY )
#define LINK(ep, epsize, epbin) \
	{ \
		REGISTER Word *epprev; \
		REGISTER Word *eprover = SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[epbin] ); \
		\
		if (eprover == NULL) { \
			SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[epbin] ) = eprover = epprev = ep; \
			if (SERVER_STATIC_VARIABLE( mem_global,_malloc_firstbin ) > epbin) \
				SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin ) = epbin; \
		} else { \
			CHECKFREEPTR(eprover, "while checking rover"); \
			epprev = PREV(eprover); \
		} \
		NEXT(ep) = eprover; \
		PREV(eprover) = ep; \
		NEXT(epprev) = ep; \
		PREV(ep) = epprev; /* PREV(eprover) */\
		SIZEFIELD(ep) = SIZEFIELD(ep-epsize+1) = FREEMASK(epsize); \
	}
#else
#define LINK(a,b,c) _shm_mem_link(a,b,c);
static INLINE void _shm_mem_link( Word* ep, size_t epsize, int epbin )
	{
		REGISTER Word *epprev;
		REGISTER Word *eprover = SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[epbin] );
		
		if (eprover == NULL) {
			SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[epbin] ) = eprover = epprev = ep;
			if (SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin ) > epbin)
				SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin ) = epbin;
		} else { 
			CHECKFREEPTR(eprover, "while checking rover"); 
			epprev = PREV(eprover); 
		} 
		NEXT(ep) = eprover; 
		PREV(eprover) = ep; 
		NEXT(epprev) = ep; 
		PREV(ep) = epprev; /* PREV(eprover) */
		SIZEFIELD(ep) = SIZEFIELD(ep-epsize+1) = FREEMASK(epsize); 
	}
#endif

#if defined( PH_MACRO_ONLY )
#define CARVE(ep, epsize, epbin, reqsize) \
	 { \
		REGISTER size_t eprest = epsize - reqsize; \
		int newepbin; \
		\
		if (eprest >= _malloc_minchunk) { \
			newepbin = GETBIN(eprest); \
			if (newepbin != epbin) { \
				UNLINK(ep, epbin); \
				LINK(ep, eprest, newepbin); \
			} else { \
				SIZEFIELD(ep) = SIZEFIELD(ep-eprest+1) \
					= FREEMASK(eprest); \
			} \
		} else { \
			/* alloc the entire block */ \
			UNLINK(ep, epbin); \
			reqsize = epsize; \
		} \
	 }
#else
#define CARVE(ep, epsize, epbin, reqsize) _shm_mem_carve(ep, epsize, epbin, &(reqsize));
static INLINE void _shm_mem_carve( Word* ep, size_t epsize, int epbin, size_t* reqsizep)
{
	REGISTER size_t eprest = epsize - *reqsizep;
	int newepbin;

	if (eprest >= _malloc_minchunk) {
		newepbin = GETBIN(eprest);
		if (newepbin != epbin) {
			UNLINK(ep, epbin);
			LINK(ep, eprest, newepbin);
		} else {
			SIZEFIELD(ep) = SIZEFIELD(ep-eprest+1)
				= FREEMASK(eprest);
		}
	} else {
		/* alloc the entire block */
		UNLINK(ep, epbin);
		*reqsizep = epsize;
	}
}
#endif

static int
grabhunk( size_t nwords)
{
	univptr_t cp;
	size_t morecore;
	Word *ptr;
	size_t sbrkwords;
	size_t blksize;
	static char *spare;
	static int nspare;
	
	/* 
	 *  two words for fake boundary tags for the entire block, and one
	 *  for the next ptr of the block.
	 */
#define EXCESS 3
	sbrkwords = (size_t) (((nwords + EXCESS) / _malloc_sbrkunits + 1) *
	 _malloc_sbrkunits);
	morecore = sbrkwords * sizeof(Word) + SBRKEXTRA;
	if ((cp = (* _malloc_memfunc)(morecore)) == (univptr_t) -1)
		return(0);
	/* 
	 * Should first GUARANTEE that what sbrk returns is aligned to
	 * Word boundaries - see align.h. Unfortunately, to guarantee
	 * that the pointer returned by sbrk is aligned on a word
	 * boundary, we must ask for sizeof(Word) -1 extra bytes, since
	 * we have no guarantee what other sbrk'ed blocks exist. (Sun
	 * sbrk always returns an aligned value, that is another story!)
	 * We use spare and nspare to keep track of the bytes wasted, so
	 * that we can try and reuse them later. If no other sbrk()s are
	 * called, then nspare rotates through the values 3, 2, 1, 0,
	 * and the first branch of the if() is always taken.
	 */
	if ((spare + nspare) == (char *) cp) {
		ptr = (Word *) SBRKALIGN(spare);
		morecore += nspare;
		sbrkwords = morecore / sizeof(Word);
	} else {
		ptr = (Word *) SBRKALIGN(cp);
		morecore -= (char *) ptr - (char *) cp;
	}
	spare = (char *) (ptr + sbrkwords);
	nspare = (morecore - sbrkwords * sizeof(Word));
	PRTRACE(sprintf(_malloc_statsbuf, "sbrk %lu\n",
			(ulong) sbrkwords*sizeof(Word)));
	
	/*
	 * If the new chunk adjoins _malloc_hiword, then _malloc_hiword
	 * need not be a fake boundary tag any longer, (its a real one) and
	 * the higher end of the block we sbrk'ed is the fake tag.  So we
	 * tag it appropriately, make the start of the block point to the
	 * old _malloc_hiword, and free it.  If we aren't next to
	 * _malloc_hiword, then someone else sbrk'ed in between, so we
	 * can't coalesce over the boundary anyway, in which case we just
	 * change _malloc_hiword to be in the new sbrk'ed block without
	 * damaging the old one. And we free the block.
	 */
	if (ptr != SERVER_STATIC_VARIABLE( mem_global,_malloc_hiword ) + 1 || SERVER_STATIC_VARIABLE( mem_global, _malloc_mem ) == NULL) {
		/* Non-contiguous sbrk'ed block, or first sbrk we've done. */
		/* 
		 * First push this block on the stack of non-contiguous blocks
		 * we've sbrked. !! For real paranoia, we'd also check
		 * _malloc_mem...
		 */
		REGISTER Word *tmp = SERVER_STATIC_VARIABLE( mem_global, _malloc_mem );

		SERVER_STATIC_VARIABLE( mem_global, _malloc_mem ) = ptr;
		ptr->next = tmp;
		ptr++;
		sbrkwords--;
		
		SERVER_STATIC_VARIABLE( mem_global,_malloc_hiword ) = ptr;
		if (SERVER_STATIC_VARIABLE( mem_global,_malloc_loword ) == NULL || SERVER_STATIC_VARIABLE( mem_global,_malloc_loword ) > ptr) {
			/* First time - set lower bound. */
			PRTRACE(sprintf(_malloc_statsbuf, "heapstart 0x%lx\n",
					(ulong) ptr));
			SERVER_STATIC_VARIABLE( mem_global,_malloc_loword ) = ptr;
		}
		/*
		 * Fake boundary tags to indicate the ends of an arena.
		 * Since they are marked as allocated, no attempt will be
		 * made to coalesce before or after them.
		 */
		SIZEFIELD(ptr) = ALLOCED | sbrkwords;
		SERVER_STATIC_VARIABLE( mem_global,_malloc_hiword ) += sbrkwords - 1;
		PRTRACE(sprintf(_malloc_statsbuf, "heapend 0x%lx\n",
				(ulong) SERVER_STATIC_VARIABLE( mem_global,_malloc_hiword )));
		SIZEFIELD(SERVER_STATIC_VARIABLE( mem_global,_malloc_hiword )) = ALLOCED | sbrkwords;
		
		/* * Subtract 2 for the special arena end tags. */
		sbrkwords -= 2;
		ptr++;
		DMEMSET(ptr + FREEHEADERWORDS, sbrkwords - FREE_OVERHEAD);
		ptr = SERVER_STATIC_VARIABLE( mem_global,_malloc_hiword ) - 1;
		SERVER_STATIC_VARIABLE( mem_global, _malloc_lastbin ) = GETBIN(sbrkwords);
		LINK(ptr, sbrkwords, SERVER_STATIC_VARIABLE( mem_global, _malloc_lastbin ))
		SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[SERVER_STATIC_VARIABLE( mem_global, _malloc_lastbin )] ) = ptr;
		while (SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[ SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin )] ) == NULL &&
		       SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin ) < MAXBINS-1)
			SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin )++;
		return(1);
	}
	/*
	 * If we get here, then the sbrked chunk is contiguous, so we fake
	 * up the boundary tags and size to look like an allocated block
	 * and then call free()
	 */
	ptr--;
	blksize = SIZE(ptr) + sbrkwords;
	SIZEFIELD(ptr) = ALLOCMASK(sbrkwords);
	SERVER_STATIC_VARIABLE( mem_global,_malloc_hiword ) += sbrkwords;
	SIZEFIELD(SERVER_STATIC_VARIABLE( mem_global,_malloc_hiword )-1) = SIZEFIELD(ptr);
	/* Update special arena end tags of the memory chunk */
	SIZEFIELD(SERVER_STATIC_VARIABLE( mem_global,_malloc_hiword )) = ALLOCMASK(blksize);
	SIZEFIELD(SERVER_STATIC_VARIABLE( mem_global,_malloc_hiword ) - blksize + 1) = ALLOCMASK(blksize);
	SET_REALSIZE(ptr, (sbrkwords - ALLOC_OVERHEAD) * sizeof(Word));

	/* PH: Adding this PRTRACE so that we have matching malloc/free for simplicity */
        PRTRACE(sprintf(_malloc_statsbuf, "+ %lu %lu 0x%lx\n", (ulong) (sbrkwords - ALLOC_OVERHEAD)*sizeof(Word),
		(ulong) (sbrkwords - ALLOC_OVERHEAD)*sizeof(Word),
		(ulong) (ptr + HEADERWORDS)));
	
	shm_mem_free((univptr_t) (ptr + HEADERWORDS));
	return(1);
}

univptr_t
shm_mem_malloc( size_t nbytes )
{
	REGISTER Word *start, *search = NULL;
	REGISTER Word *p;
	REGISTER size_t required;
	REGISTER size_t searchsize;
	int bin;

#ifdef SVID_MALLOC_0
	/* SVID requires that malloc(0) return NULL, ick! */
	if (nbytes == 0) {
		errno = EINVAL;
		return(NULL);
	}
#endif /* SVID_MALLOC_0 */

	required = ALLOC_OVERHEAD + (nbytes + sizeof(Word) - 1) / sizeof(Word);
	if (required < (size_t) _malloc_minchunk)
		required = _malloc_minchunk;

	searchsize = 0;
	bin = GETBIN(required);
	if (bin < SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin))
		bin = SERVER_STATIC_VARIABLE( mem_global, _malloc_firstbin);
	/* typically, we expect to execute this loop only once */
	while (searchsize < required && bin < MAXBINS) {
		if ((search = SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[bin++] )) == NULL) {
			continue;
		}
		if (search == SERVER_STATIC_VARIABLE( mem_global,_malloc_hiword ) - 1) {
			/* avoid final "wilderness" block */
			CHECKFREEPTR(search, "while checking \"wilderness\" in malloc()");
			search = NEXT(search);
		}
		start = search;
		do {
			CHECKFREEPTR(search, "while searching in malloc()");
			searchsize = FREESIZE(search);
			if (searchsize >= required) {
				break;
			} else {
				search = NEXT(search);
			}
		} while (search != start);
	}

	if (searchsize < required) {
		if (grabhunk(required) == 0) {
			errno = ENOMEM;
			return(NULL);
		}
		/*
		 * We made sure in grabhunk() or free() that
		 * _malloc_rovers[lastbin] is pointing to the newly sbrked
		 * (and freed) block.
		 */
		bin = SERVER_STATIC_VARIABLE( mem_global, _malloc_lastbin );
		search = SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[bin] );
		searchsize = FREESIZE(search);
	} else if (bin > 0) {
		bin--;
	}
	CARVE(search, searchsize, bin, required);
	p = search - searchsize + 1;
	SIZEFIELD(p) = SIZEFIELD(p + required - 1) = ALLOCMASK(required);
	PRTRACE(sprintf(_malloc_statsbuf, "+ %lu %lu 0x%lx\n", (ulong) nbytes,
			(ulong) (required - ALLOC_OVERHEAD) * sizeof(Word),
			(ulong) (p + HEADERWORDS)));
	COUNTSIZE(required);
	SET_REALSIZE(p, nbytes);
	return((univptr_t) (p + HEADERWORDS));
}



void
shm_mem_free( univptr_t cp )
{
	/* 
	 * This is where the boundary tags come into their own. The
	 * boundary tag guarantees a constant time insert with full
	 * coalescing (the time varies slightly for the four case possible,
	 * but still, essentially a very fast free.
	 */
	/*
	 *  P0 is the block being freed. P1 is the pointer to the block
	 *  before the block being freed, and P2 is the block after it.
	 *  We can either coalesce with P1, P2, both, or neither
	 */
	REGISTER Word *p0, *p1, *p2;
	REGISTER size_t sizep0;
	int bin, oldbin = -1;

	if (cp == NULL)
		return;
		
	p0 = (Word *) cp;
	p0 -= HEADERWORDS;

	CHECKALLOCPTR(p0, "passed to free()");
	/* With debugging, the CHECKALLOCPTR would have already aborted */
	if (TAG(p0) == FREE) { 
		errno = EINVAL;
		*(int*)0x0 = 78; /* PH: Death trap multiple frees */
		return;
	}

	/*
	 * clear the entire block that used to be p0's, just in case
	 * someone tries to refer to it or anything in it again.  We leave
	 * the end tags alone for now - we'll smash them individually
	 * depending on the way p0 merges with p1 and/or p2.
	 */
	sizep0 = SIZE(p0);
	DMEMSET(p0 + FREEHEADERWORDS, sizep0 - FREE_OVERHEAD);
	PRTRACE(sprintf(_malloc_statsbuf, "- %lu 0x%lx\n",
			(ulong) (sizep0 - ALLOC_OVERHEAD) * sizeof(Word),
			(ulong) (p0 + HEADERWORDS)));

	p1 = p0 - 1;
	/*
	 * p0 now points to the end of the block -- we start treating it as
	 * a free block
	 */
	p0 += sizep0 - 1;
	p2 = p0 + 1;

	/*
	 * We can't match the SIZEFIELDs of p1/p2 with p1/p2 + SIZE(p1/p2)
	 * -1 because they might be a fake tag to indicate the bounds of
	 * the arena. Further, we should only check p1 if p0-1 is not the
	 * _malloc_loword or an arena bound - else p1 is probably not a
	 * valid pointer. If tag p0-1 is allocated, then it could be an
	 * arena bound.
	 */

	if (TAG(p2) == FREE) {
		/*
		 * Aha - block p2 (physically after p0) is free.  Merging
		 * p0 with p2 merely means increasing p2's size to
		 * incorporate p0 - no other pointer shuffling needed.
		 * We'll move it to the right free-list later, if necessary.
		 */
		p2 += FREESIZE(p2) - 1;
		oldbin = GETBIN(FREESIZE(p2));
		CHECKFREEPTR(p2, "while checking p2 in free()");
		sizep0 += FREESIZE(p2);
		SIZEFIELD(p2- sizep0 + 1) = SIZEFIELD(p2) = FREEMASK(sizep0);
		/*  Smash p0's old end tag and p2's old start tag. */
		DMEMSET(p0 - FREETRAILERWORDS + 1, FREETRAILERWORDS + FREEHEADERWORDS);
		p0 = p2; /* p0 just vanished - became part of p2 */
	}
	if (TAG(p1) == FREE) {
		/*
		 * The block p1 (physically precedes p0 in memory) is free.
		 * We grow p0 backward to absorb p1 and delete p1 from its
		 * free list, since it no longer exists.
		 */
		CHECKFREEPTR(p1, "while checking p1 in free()");
		sizep0 += FREESIZE(p1);
		bin = GETBIN(FREESIZE(p1));
		UNLINK(p1, bin);
		SIZEFIELD(p0 - sizep0 + 1) = SIZEFIELD(p0) = FREEMASK(sizep0);
		/*
		 * smash the free list pointers in p1 (SIZE, NEXT, PREV) to
		 * make sure no one refers to them again. We cannot smash
		 * the start boundary tag because it becomes the start tag
		 * for the new block.  Also trash p0's start tag.
		 */
		DMEMSET(p1 - FREETRAILERWORDS + 1, FREETRAILERWORDS + FREEHEADERWORDS);
	}
	bin = GETBIN(sizep0);
	if (oldbin != bin) {
		/*
		 * If we're here, it means block P0 needs to be inserted in
		 * the correct free list, either because it didn't merge
		 * with anything, or because it merged with p1 so we
		 * deleted p1, or it merged with p2 and grew out p2's
		 * existing free-list.
		 */
		if (oldbin >= 0) {
			/* merged with P2, still in P2's free-list */
			UNLINK(p0, oldbin);
		}
		LINK(p0, sizep0, bin);
		SERVER_STATIC_VARIABLE( mem_global, _malloc_lastbin ) = bin;
		SERVER_STATIC_VARIABLE( mem_global, _malloc_rovers[bin] ) = p0;
	}

	CHECKHEAP();
	return;
}


/*
 *  WARNING: This realloc() IS *NOT* upwards compatible with the
 *  convention that the last freed block since the last malloc may be
 *  realloced. Allegedly, this was because the old free() didn't
 *  coalesce blocks, and reallocing a freed block would perform the
 *  compaction. Yuk!
 */
univptr_t
shm_mem_realloc( univptr_t cp, size_t nbytes )
{
	REGISTER Word *p0 = (Word *) cp;
	REGISTER Word *p1;
	univptr_t tmp;
	REGISTER size_t required;
	REGISTER size_t sizep0;
	int bin;

	if (p0 == NULL)
		return(shm_mem_malloc(nbytes));

	if (nbytes == 0) {
		shm_mem_free(cp);
		return(NULL);
	}
	
	required = ALLOC_OVERHEAD + (nbytes + sizeof(Word) - 1) /
		sizeof(Word);
	if (required < (size_t) _malloc_minchunk)
		required = _malloc_minchunk;

	p0 -= HEADERWORDS;
	
	CHECKALLOCPTR(p0, "passed to realloc()");
	/* With debugging, the CHECKALLOCPTR would have already aborted */
	if (TAG(p0) == FREE) { 
		errno = EINVAL;
		return(NULL);
	}
	sizep0 = SIZE(p0);
	if (sizep0 >= required) {
		/* Shrinking the block */
		size_t after = sizep0 - required;
		
		SET_REALSIZE(p0, nbytes);
		if (after < _malloc_minchunk) {
			/*
			 * Not enough to free what's left so we return the
			 * block intact - print no-op for neatness in
			 * trace output.
			 */
			PRTRACE(strcpy(_malloc_statsbuf, "no-op\n"));
			return(cp);
		}
		SIZEFIELD(p0+required-1) = SIZEFIELD(p0) = ALLOCMASK(required);
		p0 += required;
		/*
		 * We free what's after the block - mark it alloced and
		 * throw it to free() to figure out whether to merge it
		 * with what follows...
		 */
		SIZEFIELD(p0 + after - 1) = SIZEFIELD(p0) = ALLOCMASK(after);
		SET_REALSIZE(p0, (after - ALLOC_OVERHEAD) * sizeof(Word));
		shm_mem_free((univptr_t) (p0 + HEADERWORDS));
		return(cp);
	}
		
	/*
	 * If we get here, then we are growing the block p0 to something
	 * bigger.
	 */
	p1 = p0 + sizep0;
	required -= sizep0;
	if (TAG(p1) != FREE || FREESIZE(p1) < required) {
		/* Have to do it the hard way: block after us cannot be used */
		tmp = shm_mem_malloc(nbytes);
		if (tmp != NULL) {
			MEMCPY(tmp, cp, ((SIZE(p0) - ALLOC_OVERHEAD)));
			shm_mem_free(cp);
		}
		return(tmp);
	}
	/*
	 * block after us is free and big enough to provide the required
	 * space, so we grow into that block.
	 */
	p1 += FREESIZE(p1) - 1;
	CHECKFREEPTR(p1, "while checking p1 in realloc()");
	bin = GETBIN(FREESIZE(p1));
	CARVE(p1, FREESIZE(p1), bin, required);
	sizep0 += required;
	SIZEFIELD(p0) = SIZEFIELD(p0+sizep0-1) = ALLOCMASK(sizep0);
	SET_REALSIZE(p0, nbytes);
	PRTRACE(sprintf(_malloc_statsbuf, "++ %lu %lu 0x%lx\n",
			(ulong) nbytes,
			(ulong) (sizep0-ALLOC_OVERHEAD)*sizeof(Word),
			(ulong) cp));
	CHECKHEAP();
	return(cp);
}



/*
 *  !! Given what we know about alignment, we should be able to do better
 *  than memset and set words. Hopefully memset has been tuned.
 */
univptr_t
shm_mem_calloc( size_t nelem, size_t elsize)
{
	REGISTER size_t nbytes = nelem * elsize;
	REGISTER univptr_t cp = shm_mem_malloc(nbytes);

	if (cp)
		(void) memset((univptr_t) cp, 0, (memsize_t) nbytes);
	return(cp);
}

#if 0
/* PH: DON'T NEED THIS */
/*
 *  Why would anyone want this.... ?
 */
void
cfree( univptr_t cp )
{
	free(cp);
}
#endif

#if defined( DEBUG )
  #include "mem_debug_dumpheap.c"
  #include "mem_debug_verify.c"
  #include "mem_debug_botch.c"
#endif
