// utf-8

/*	Copyright (C) 1998-2003 Günter Woigk
	kio@little-bat.de

	This file is free software

 	This program is distributed in the hope that it will be useful,
 	but WITHOUT ANY WARRANTY; without even the implied warranty of
 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

 	Permission to use, copy, modify, distribute, and sell this software and
 	its documentation for any purpose is hereby granted without fee, provided
 	that the above copyright notice appear in all copies and that both that
 	copyright notice and this permission notice appear in supporting
 	documentation, and that the name of the copyright holder not be used
 	in advertising or publicity pertaining to distribution of the software
 	without specific, written prior permission.  The copyright holder makes no
 	representations about the suitability of this software for any purpose.
 	It is provided "as is" without express or implied warranty.

 	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 	PERFORMANCE OF THIS SOFTWARE.


	kio's fast memory manager
	-------------------------

	Simply include this file in your project
	and new() and delete() are automatically used by the linker !
	for historical reason this file is named 'malloc', though it 
	does not replace the C malloc but the C++ new/delete methods

  + Fast
  + Serves memory requests of any size
  + Uses Temp Mem (not application heap space) ((MacOS))
  + Returned memory addresses are aligned to a multiple of 4
  +	Debugging support
  +	Statistics
	
  - Initial memory requirement for tables:	approx. 16*(16*33+333) = 14 kB
  - Chunk size for Temp Mem allocations:	approx. 2^(16-1+2+1) = 256 kB	(64 kB .. 1 MB dep. on settings)
  - Administrative overhead per request:	20 bytes
  - Another 0 to 3 bytes wasted to align addresses to n*4
  - Depending on the new/delete behaviour of the application, up to 50% of the allocated memory is wasted in unused gaps


	The following switches can be defined in "malloc.h" ((options.h))

	USE_KIOS_MALLOC		enable kio's malloc replacement (default: on)
	LOG					log level: 0 or 3
	SAFE				level of error checking: 0 ... 3
	MALLOC_TEMPMEM		enable allocation in TempMem (default: on)
	MALLOC_FREELISTS	number of freelists: 14 ... 18 (default: 16)
	MALLOC_OWNER		add field for owner id to every memory header (default: off)
	MALLOC_STATISTICS	count requests etc. (default: off)


	programmed:															apr.98		kio
	quality check:														4.-5.mai.98	kio
	thread support: Mem.owner and logging of cross-deletion				03.aug.98	kio
	thread support: StartDispatcher() in InitMalloc()					05.aug.98	kio
	thread support: XMem.size, XMem.owner, twsp.xblocks and twsp.xsize	07.aug.98	kio
	malloc statistics													07.aug.98	kio
	rework of allocation of blocks in external memory					10.aug.98	kio
	quality check:														10.aug.98	kio
	last freed xmem block buffered to avoid repeated xfree/xalloc		22.aug.98	kio
	port to linux														01.jan.99  	kio
	string support (auto-deletion via master pointers)					20.feb.99	kio
	adapted to new log macros											11.may.99	kio
	added new[] and delete[] ((after finding a bug in the qt-lib))		03.jun.99	kio
	cleaned up behaviour at recursive calls resulting from logging		25.sep.1999	kio
	removed string support and dependencies to kio.h					01.jan.2001 kio
	now allocating lists via NewPtr and xmem too (option, MacOS)		01.jan.2001 kio
	List::resize benutzt jetzt auch am Mac wieder xalloc()				05.aug.2001 kio
	

to do:
	o improve out-of-memory handling in List::resize()
	o sort MD's in freelists somehow to improve freeing up XMem
*/



#define		LOG		0		// default log level (0 or 3)
#define		SAFE	3		// default safety level (0...3)

#include	"malloc.h"		// defs, options, etc.


/* ----	if not compiled with all my standard headers -------------------------------
		disable all my funny macros
*/
#ifndef _kio_h_
	#include <stdlib.h>
	#include <size_t.h>
	#define no_index
	typedef unsigned char uchar;
	typedef char* ptr;
	typedef uchar* uptr;
	typedef unsigned long ulong;
	typedef const char* cstr;
	inline	void XXXLogIn	(cstr, ...) 	{ }
	inline	void XXXLogOut	( )			 	{ }
	inline	void XXXLogOut	(cstr, ...) 	{ }
	inline	void XXXXLogIn	(cstr, ...) 	{ }
	inline	void XXXXLogOut	( ) 			{ }
	inline	void XXXXLogLine(cstr, ...) 	{ }
	inline	void XXXXLog	(cstr, ...) 	{ }
	inline	void XTRAP		(bool) 			{ }
	inline	void XXXTRAP	(bool) 			{ }
	inline	void XXTRAP		(bool) 			{ }
	inline	void OMEM		(void*) 		{ }
	inline	void Log		( ) 			{ }
	inline	void Log		( cstr, ... )	{ }
#endif



/* ====	To Be Or Not To Be =========================================================
*/
#if USE_KIOS_MALLOC



/* ----	Logging --------------------------------------------------------------------

		Logging only occurs on log level 3 and 4.
		3 - General hard debugging
		4 - Debugging malloc.cp itself. -> DO NOT USE, RECURSION WILL KILL YOU!

	 	The check macros like TRAP() log the message and display an alert (on some 
		systems). this may cause recursive calls to the memory module. if this 
		happens, the system may crash. since the system is probably crashed anyway 
		and these macros never return this danger may be acceptable. if your program
		is instable and frequently crashes you should use a log method which does not
		allocate memory, so that you can see the final error message.
*/
#if LOG>3
	#undef  LOG
	#define LOG 3
#endif



/* ----	Out of bounds tests ----------------------------------------------------
*/
#if XXSAFE				// add unused memory around the requested memory block
	#define	XXMEM	256	// ((must be multiple of 4))
#else
	#define	XXMEM	0
#endif



/* ----	Trap recursive logging -------------------------------------------------

	Recursively calling of new() or delete() will crash the memory management.
	there is only one possibility for doing so: the LOG and SAFE macros.

	Calling Log() may in return allocate memory. this is done explicitly
	by the XXXLog() type macros and implicitely by TRAP(), OMEM(), etc.

	There are 3 possibilities when a recursive call can occur:

1.	The memory system is 'dirty', that is, some inner state is in progress.
	the system is likely to crash when this happens. therefore no logging
	to level 0 or 1 is allowed throughout the module (except as noted above)
	if logging may request memory.

2. 	The system is still clean but we already have loaded some values into
	local variables. then the recursive call is o.k. but resuming as usual
	after logging the message will crash me. At this state only log&abort
	is acceptable. 

3. 	The system is clean and we do not hold information in local variables.
	in this case only infinite recursion of Log() calling must be prevented.

	->	recursive logging is suppressed by a flag: if recursion is detected, 
		the message is not logged. 
		Passing parameters to MyLog() must not recursively allocate memory.
*/
static int f = -1;			// -1 = free
#define T cstr 
#define P const void*

void MyLog ( )					{ if (!++f) Log(); 		--f; }
void MyLog ( T t )				{ if (!++f) Log(t);		--f; }
void MyLog ( T t, int n )		{ if (!++f) Log(t,n);	--f; }
void MyLog ( T t, int n, P p )	{ if (!++f) Log(t,n,p);	--f; }
void MyLog ( T t, P p )			{ if (!++f) Log(t,p); 	--f; }

#undef T
#undef P
#define Log	MyLog



/* ----	How many freelists ---------------------------------------------------

	Free memory is managed by lists which each contain memory of a fixed size range.	
	
	The guaranteed minimum block size of memory in list[0] is 4 = sizeof(struct Mem)
	which allows satisfying any request for 0 bytes.

	The guaranteed blocksize is doubled for each list, 
	so the guaranteed minimum block size for list[i] is 2^(i+2)
	
	MALLOC_FREELISTS = number of lists. Recommended values range from 14 to 18,
	which result in a maximum size of requests which will be served by the last list 
	from  2^(14-1+2)  to  2^(18-1+2)   =   32kB to 512kB
	
	Requests for larger blocks of memory are served with external memory directly.
*/
#ifndef MALLOC_FREELISTS
	#if MALLOC_TEMPMEM || !defined(_MACINTOSH)
		#define	MALLOC_FREELISTS	16
	#else
		#define	MALLOC_FREELISTS	14
	#endif
#endif



/* ----	Store owner ID with each block? ---------------------------------------
		Optionally store a owner ID with each allocated block.
		This may help with statistics, quota or purging lots of memory in one go.
*/
#if MALLOC_OWNER
	static long	malloc_owner	 = 0;	// owner id for all subsequent allocations
#endif



/* ----	Statistics ------------------------------------------------------------
*/
#if MALLOC_STATISTICS
	long	malloc_xrequests	= 0;	// total number of xmem requests (internal and external)
	long	malloc_xmemsize 	= 0;	// memory currently allocated externally
	long	malloc_xmemblocks	= 0;	// number of blocks currently allocated externally

	long	malloc_requests		= 0;	// total number of malloc requests
	long	malloc_size			= 0;	// memory currently allocated internally
	long	malloc_blocks		= 0;	// number of blocks currently allocated externally

	#define	STATISTICS_XCOUNT()		malloc_xrequests++
	#define	STATISTICS_XALLOC(N)	malloc_xmemblocks++,	malloc_xmemsize+=N
	#define	STATISTICS_XFREE(N)		malloc_xmemblocks--,	malloc_xmemsize-=N

	#define	STATISTICS_COUNT()		malloc_requests++
	#define	STATISTICS_ALLOC(N)		malloc_blocks++,		malloc_size+=N
	#define	STATISTICS_FREE(N)		malloc_blocks--,		malloc_size-=N	
#else
	#define	STATISTICS_COUNT()
	#define	STATISTICS_XCOUNT()
	#define	STATISTICS_ALLOC(N)
	#define	STATISTICS_XALLOC(N)
	#define	STATISTICS_FREE(N)
	#define	STATISTICS_XFREE(N)
#endif



/*****************************************************************************
		allocating external memory
*****************************************************************************/


/* ----	header block for externally allocated memory -------------------------
		any block of memory, which is allocated from the operating system,
		is preceeded by a descriptive and security header.
		if MALLOC_STATISTICS is enabled, the size of the memory block is stored too.
*/
struct XMem
{
	#if defined(_MACINTOSH) && MALLOC_TEMPMEM
		Handle	handle;
	#endif
    #if MALLOC_STATISTICS || XXMEM
		size_t	size;
    #endif
    #if XXMEM
		char	buffer[XXMEM];
    #endif
		uchar	data[no_index];
};


/* ----	alloc() and free() code ---------------------------------------------
*/
#if defined(_UNIX)
	#define ALLOC(N)	malloc(N)
	#define	FREE(P)		free (P)

#elif defined(_MACINTOSH) && !MALLOC_TEMPMEM
	#define	ALLOC(N)	NewPtr(N)
	#define	FREE(P)		DisposPtr((char*)(P))

#elif defined(_MACINTOSH) && MALLOC_TEMPMEM
	inline XMem* ALLOC(size_t n)
	{
		OSErr err;
		XMem*  xmem = NULL;
		Handle h    = TempNewHandle ( n, &err );
		if (h) 
		{
			XXXTRAP(err);
			TempHLock ( h, &err );
			XXXTRAP(err);
	  		xmem = *(XMem**)h;
			xmem->handle = h;
		}
		return xmem;
	}

	inline void	FREE ( XMem* P ) 
	{
		OSErr err;
		TempDisposeHandle(P->handle,&err); 
	}

#else
	#error
#endif



/* ----	allocate external memory ----------------------------
		returns address or NULL
*/
static uptr xalloc ( size_t size )
{
	XXXXLogIn("xalloc(%lu)",size);

	STATISTICS_XCOUNT();

	XXXTRAP(sizeof(XMem)&3);
	XXXTRAP(size&3);

	XMem* xmem = (XMem*)ALLOC(size+XXMEM+sizeof(XMem));
	if (xmem)
	{
		#if MALLOC_STATISTICS || XXMEM
			xmem->size = size;
		#endif

		STATISTICS_XALLOC(size+XXMEM+sizeof(XMem));

		#if XXMEM
			memset(xmem->buffer,   0xe5,XXMEM);
			memset(xmem->data+size,0xe7,XXMEM);
		#endif

		XXXXLogOut("-> $%08X", size, xmem->data);
		XXXTRAP((long)(xmem->data)&3);
		return xmem->data;
	}

	XXXXLogOut("failed!");
	return NULL;
}


/* ----	dispose off external memory -------------------------
		free memory which has been allocated with xalloc()
*/
static void xfree ( void* ptr )
{	
	XXXXLogIn ( "xfree($%08X)", ptr);

	XXXTRAP(ptr==0);
	XXXTRAP((long)ptr&3);

	XMem* xmem = ((XMem*)ptr)-1;

	STATISTICS_XFREE(xmem->size+XXMEM+sizeof(XMem));

	#if XXMEM
		ulong *p,*e;
		for ( p=(ulong*)xmem->buffer, e=(ulong*)xmem->data; p<e; p++ ) 
			TRAP(*p!=0xe5e5e5e5 );
		for ( p=(ulong*)(xmem->data+xmem->size), e=p+XXMEM/sizeof(ulong); p<e; p++ ) 
			TRAP(*p!=0xe7e7e7e7 );
	#endif

	FREE(xmem);

	XXXXLogOut("");
}



/*****************************************************
		internal memory management
*****************************************************/


/* ====	allocated memory block =============================
		any memory block returned to the application is preceeded by a 
		backpointer ( German English: "Rückpointer", therefore the 'r' ) 
		which leads to it's associated memory description block. 
		memory is allocated inside a larger chunk of memory which was
		retrieved from the operating system.
*/
struct Mem
{
	struct MD*	rptr;			// backpointer to memory descriptor
	char		data[no_index];
};


/* ====	descriptor for allocated memory block ===================
		the descriptor keeps track of the address and size of it's associated
		memory block and, optionally, of it's owner.
		all MDs form a cyclic, double linked list, which goes through all 
		descriptors of memory blocks allocated within the same externally
		allocated memory block. the linked list is sorted by ascending addresses,
		so that melting of adjacent free blocks is easy. the list is cyclic; that
		means, the last and first block are linked too. if there is only one block 
		inside an externally allocated block (that is: prev==next==this) and 
		this block is free, this externally allocated memory block can be released.
*/
struct MD
{
	MD*			prev;		// link to lower-address neighbour
	MD*			next;		// link to higher-address neighbour
	Mem*		addr;		// pointer to my own memory block (struct Mem)
	ulong		size;		// my memory block size (total)
#if MALLOC_OWNER
	long		owner;		// ID of owner
#endif

private:
	void		move	( MD* ziel ) const;	// MOVE this  -->  ziel

public:
	inline		MD		( )		 			{ }
	inline		MD		( const MD& q )		{ q.move(this); }	// DOES NOT COPY BUT MOVE !!!
	inline void	operator= ( const MD& q )	{ q.move(this); }	// DOES NOT COPY BUT MOVE !!!

	inline void	Melt	( );					// melt this and this->next  -->  this;  this->next becomes void
	inline void	rMelt	( );					// melt this and this->prev  -->  this;  this->prev becomes void
	inline void	Split	( long sz, MD& s2 );	// split this  -->  this and s2
};


/* ----	move this to another position -------------------------
		target becomes valid and must be put in an appropriate list!
		source becomes void and must be removed from it's list!
*/
void MD::move ( MD* target ) const
{
	XXXXLogIn("MD::move()");

// if target==this then we would move a void source
// this causes problems because we would pull the neighbourhood links to this void target!
	if (target==this) goto x;	

// security
	XXTRAP ( addr->rptr != this );

// copy data members
	target->addr = addr;
	target->size = size;
#if MALLOC_OWNER
	target->owner = owner;
#endif

// pull backpointer
	target->addr->rptr = target;

// neighbourhood linking
	if (this!=next)
	{	// there are neighbours
		target->prev = prev;
		target->next = next;
		target->next->prev = target;
		target->prev->next = target;
	}
	else
	{	// i'm alone...
		target->next = target->prev = target;
	}

x:	XXXXLogOut("");
}


/* ----	melt this and this->next ----------------------------
		this->next becomes void and must be removed from it's list!
		since all Mem blocks in an xalloc'ed XMem block are linked in a ring list,
		Melt() must not be called with the last Mem block of the XMem block !!
*/
void MD::Melt ( ) 
{
	XXXXLogIn("MD::Melt()");

// debugging security tests
	XXTRAP ( next==this );		
	XXTRAP ( next->addr<=addr );
	XXTRAP ( addr->rptr != this );
	XXTRAP ( next->addr->rptr != next );

// clear rptr (for security)
	next->addr->rptr = NULL;		

// melt
	size += next->size;			
	next = next->next;
	next->prev = this;

	XXXXLogOut("");
}


/* ----	melt this and this->prev ----------------------------
		this->prev becomes void and must be removed from it's list!
		since all Mem blocks in an xalloc'ed XMem block are linked in a ring list,
		rMelt() must not be called with the first Mem block of the XMem block !!
*/
void MD::rMelt ( ) 
{
	XXXXLogIn("MD::rMelt()");

// debugging security tests
	XXTRAP ( prev==this );		
	XXTRAP ( prev->addr>=addr );
	XXTRAP ( addr->rptr != this );
	XXTRAP ( prev->addr->rptr != prev );

// clear rptr (for security)
	addr->rptr = NULL;			

// melt
	addr = prev->addr;			
	addr->rptr = this;	

	size += prev->size;
	prev = prev->prev;
	prev->next = this;

	XXXXLogOut("");
}


/* ----	split this into this and s2 -----------------------------
		sz is the requested size for this (total)
		s2 becomes valid and must be put in an appropriate list!
*/
void MD::Split ( long sz, MD& s2 )
{
	XXXXLogIn("MD::Split()");

// debugging security tests
	XXTRAP ( addr->rptr != this );
	XXTRAP ( size < sz+sizeof(Mem) );
	XXTRAP ( sz&3 );

// split
	s2.addr = (Mem*)(((uptr)addr)+sz);
	s2.size = size-sz;
	s2.next = next;
	s2.prev = this;
	s2.addr->rptr = &s2;
	s2.next->prev = &s2;
	
	next = &s2;
	size = sz;

	XXXXLogOut("");
}


/* ====	lists of memory description blocks =====================
		while MDs are linked in accordance to the location of their associated 
		memory block, they are stored in array-type lists, which reflect
		their state (allocated or free) and their size (if they are free).
		the list struct simply consists of a static body, describing the size and 
		the filling level of the array, and a dynamically allocated array for MDs.
		there is one list for all allocated memory blocks and typically 16 lists
		for free memory blocks. 
*/
struct List
{
	MD*		list;				// address of the array of MDs
	long	size;				// size of the array
	long	cnt;				// current allocation inside this array

private:
	void	resize	( );		// increase size of array

public:
			List	( )			: list(NULL),size(0),cnt(0) {}
	const 	MD&Top	( )			{ return list[cnt-1];	}	// no check !
	const 	MD&Pop	( )			{ return list[--cnt];	}	// no check !
	void	Drop	( )			{ cnt--;				}	// no check !
	MD&		New		( )			{ if (cnt==size) resize(); return list[cnt++]; }
	void	Push	( MD& m )	{ if (cnt==size) resize(); list[cnt++] = m; }
};

#if defined(_MACINTOSH) && 0
	#define	NewList(N)		(MD*)NewPtr((N)*sizeof(MD))
	#define	DeleteList(P)	if(P==NULL) {} else DisposPtr((ptr)P)
#else
	#define	NewList(N)		(MD*)xalloc((N)*sizeof(MD))
	#define	DeleteList(P)	if(P==NULL) {} else xfree(P)
#endif


/* ----	increase size of array for MDs ----------------------------
*/
void List::resize ( )
{	MD *newlist,*q,*z,*e;

	long sz = cnt +cnt/4 +32;

	XXXXLogIn ( "List::resize()  -->  %i", sz );

	newlist = NewList(sz);
	OMEM(newlist);						// omem error	***to be improved***
	
	z = newlist;
	q = list;
	e = q+cnt;

	while ( q<e ) *z++ = *q++;			// MOVE (!!) members
	DeleteList ( list );

	list = newlist;
	size = sz;

	XXXXLogOut("");
}



/*****************************************************
			memory management
******************************************************

speicher:	vor jeden block wird ein rückpointer auf den zugehörigen
			verwaltungsblock eingetragen

listen:		ein array für die belegten blöcke
			mehrere arrays für freie blöcke, jeweils für einen bestimmten größenbereich
		
einträge:	prev	linker nachbar im speicher	(für melting)
			next	rechter nachbar im speicher	(für melting)
			addr	eigene adresse (-> rptr)		
			size	eigene blocklänge (incl. rptr)
			owner	tid of owner (threaded application)

			speicherblöcke über einer bestimmten größe werden extern alloziert.
*/

// calculate the guaranteed minimum size of any free block in freelist[N]
#define	Guaranteed(N)	(sizeof(Mem)<<N)

// guaranteed block size (brutto) in last freelist
const ulong	max_size	= Guaranteed(MALLOC_FREELISTS-1);

// size of external memory block allocated by the internal memory manager
const ulong	xmem_size	= max_size*2;


// ===== the variables ===========================================
// note: it is _required_ that static variables are cleared by the
//       start-up code; else freelist[] must be cleared explicitely !
static List		usedlist;					// list of used blocks
static List		freelist[MALLOC_FREELISTS];	// lists of free blocks, one list per size range
static void*	lastfreemem= NULL;			// empty, unused block of external memory


#if MALLOC_STATISTICS
	long MallocUsedBlocks()
	{
		return usedlist.cnt;
	}
	long MallocFreeBlocks(int n)
	{
		return freelist[n%MALLOC_FREELISTS].cnt;
	}
#endif


#if XXSAFE
	static void XXCHECKLIST(const List& list)
	{
		XXXXLog("+");
		TRAP(list.cnt<0);
		TRAP(list.size<list.cnt);
		
		MD* p = list.list;
		for ( int i=list.cnt; i>0; i-- )
		{
			TRAP(p->addr->rptr != p );
			TRAP(p->next->prev != p );
			TRAP(p->prev->next != p );
			p++;
		}
	}

	static void XXCHECK ( )
	{
		XXXXLogIn("XXCHECK()");
		XXCHECKLIST(usedlist);
		for (int i=0; i<MALLOC_FREELISTS; i++) XXCHECKLIST(freelist[i]);

		#if XXMEM && XXXSAFE
			MD* p = usedlist.list;
			for ( int i=usedlist.cnt; i>0; i-- )
			{
				XXXXLog(".");
				char *a,*e;
				for ( a=p->addr->data,e=a+XXMEM; a<e; a+=sizeof(long) ) 
					TRAP(*(ulong*)a!=0xe1e1e1e1);
				for ( e=((char*)p->addr)+p->size, a=e-XXMEM; a<e; a+=sizeof(long) ) 
					TRAP(*(ulong*)a!=0xe3e3e3e3);
			}
		#endif

		XXXXLogOut("");
	}
#else
	#define	XXCHECK()
#endif


/* ----	UP: get index of appropriate freelist for retrieving a free block --------------------
		no overflow check!
		this is the block N with:	Guaranteed(N)>=size  &&  Guaranteed(N-1)<size
									except for very small sizes which all go into freelist[0]
*/
inline int ChunkIdx ( size_t size )
{	int a,e,m;
	a  =  -1;
	e  =  MALLOC_FREELISTS-1;
	do { m = (a+e)/2; if ( Guaranteed(m) < size ) a=m; else e=m; } while (a+1<e);

	XXTRAP ( e>=MALLOC_FREELISTS || e<0 );		// major error	(index out of range)
	XXTRAP ( size>(Guaranteed(e)) );			// major error	(guaranteed size is too small)
	XXXTRAP( e>0 && !(size>Guaranteed(e-1)) );	// minor error	(guaranteed size is bigger than required)

	return e;
}

/* ----	UP: get index of appropriate freelist for storing a free block --------------------
		no overflow check!
		this is the block N with:	Guaranteed(N)<=size  &&  Guaranteed(N+1)>size
									except for very big sizes which all go into the last freelist
*/
static int FreeChunkIdx ( size_t size )
{	int a,e,m;
	a  =  0;
	e  =  MALLOC_FREELISTS;

	do 
	{	m = (a+e)/2;
		if ( Guaranteed(m) <= size ) a=m; else e=m; 
	} while (a+1<e);

	XXTRAP ( a>=MALLOC_FREELISTS || a<0 );						// major error	(index out of range)
	XXTRAP ( size<Guaranteed(a) );								// major error	(guarantee size is too small)
	XXXTRAP( a+1<MALLOC_FREELISTS && size>=Guaranteed(a+1) );	// minor error	(guarantee size is bigger than required)

	return a;
}


/* ====	allocate memory =======================================
		returns address
		returns NULL if out of memory
*/
void* operator new ( size_t size )
{	MD	m;
	int	idx;
	
	XXXLogIn ( "new(%i)", size );
	XXCHECK();	
	STATISTICS_COUNT();

	XTRAP((long)size<0);
	size = ( size+sizeof(Mem)+2*XXMEM+3 ) & ~3;

// allocate externally ?
	if ( size >= max_size ) 
	{	/*	if the required total size exceeds the guaranteed size of the last freelist,
			we allocate an external memory block just large enough to hold the required 
			block and put the block into it. this way we need no extra testing when releasing 
			it: delete() will free the block and find that the enclosing external memory 
			block is now empty and will release it, as it would do when it released the last
			small block allocated inside an external memory block.
		*/
		if ( (m.addr = (Mem*)xalloc(size)) == NULL ) 
		{
			XXXLogOut("OUT OF MEMORY => return NULL");
			return NULL;	// out of memory
		}
		m.size = size;
		m.next = m.prev = &m;
		m.addr->rptr=&m;
	}

// allocate internally
	else
	{
	// get appropriate free list
		idx = ChunkIdx ( size );

	// list empty? -> try next
		while ( idx<MALLOC_FREELISTS && freelist[idx].cnt==0 ) idx++;
	
	// all lists empty? -> allocate external memory
		if (idx>=MALLOC_FREELISTS)
		{
			if (lastfreemem)
			{
				m.addr = (Mem*)lastfreemem;
				lastfreemem=0;
			}
			else
			{
				if ( (m.addr = (Mem*)xalloc(xmem_size)) == NULL ) 
				{
					XXXLogOut("OUT OF MEMORY => return NULL");
					return NULL;	// out of memory
				}
			}
			m.size = xmem_size;
			m.next = m.prev = &m;
			m.addr->rptr=&m;
		}
	
	// get and remove an entry from freelist
		else 
		{
			m = freelist[idx].Pop();
		}
		
		XXTRAP ( m.size<size );
		XXTRAP ( m.addr->rptr != &m);

	// split block if possible
		if ( m.size >= size+sizeof(Mem) )
		{
			idx = FreeChunkIdx(m.size-size);
			m.Split ( size, freelist[idx].New() );
		}
	}
	
// optional stuff
	#if MALLOC_OWNER
		m.owner = malloc_owner;
	#endif

	STATISTICS_ALLOC ( m.size-sizeof(Mem)-2*XXMEM );

	#if XXMEM
		memset ( m.addr->data,                0xe1, XXMEM );
		memset ( ((uptr)m.addr)+m.size-XXMEM, 0xe3, XXMEM );
	#endif

// put MD into used list
	usedlist.Push(m);

	XXCHECK();
	XXXLogOut("-> $%08X", m.addr->data+XXMEM);

// return address
	return m.addr->data+XXMEM;
}



/* ====	release memory =====================================
		may be called with NULL pointer
*/
void operator delete ( void* mem )
{	
	XXXLogIn ( "delete($%08X)", mem );
	XXCHECK();
	
	if (mem!=NULL)
	{
	// get MD and remove it from usedlist
		MD* md = ((Mem*)(((uptr)mem)-XXMEM))[-1].rptr;
		XTRAP ( md<usedlist.list || md>=usedlist.list+usedlist.cnt );	// out of list
		XTRAP ( usedlist.list+(md-usedlist.list)!=md );					// misaligned
		XTRAP ( md->addr->data+XXMEM != mem );							// final security check

		MD m = *md;					// Move (!!) it into the local MD m
		*md = usedlist.Pop();		// fill up the gap in the usedlist

	// optional stuff
		STATISTICS_FREE(m.size-sizeof(Mem)-2*XXMEM);

		#if XXMEM
			char *a,*e;
			for ( a=m.addr->data,e=a+XXMEM; a<e; a+=sizeof(long) ) 
				TRAP(*(ulong*)a!=0xe1e1e1e1);
			for ( e=((char*)m.addr)+m.size, a=e-XXMEM; a<e; a+=sizeof(long) ) 
				TRAP(*(ulong*)a!=0xe3e3e3e3);
		#endif
		
	// melt with right neighbour
		md = m.next;
		if  (	( md<usedlist.list || md>=usedlist.list+usedlist.cnt ) 	// not in the usedlist ?
			&& md->addr>m.addr 					// not last Mem inside XMem and not m itself ?
		     )
		{	XXTRAP ( md->addr->rptr != md );
			int idx = FreeChunkIdx(md->size);
			m.Melt();
			*md = freelist[idx].Pop();
		}

	// melt with left neighbour
		md = m.prev;
		if  ( 	( md<usedlist.list || md>=usedlist.list+usedlist.cnt ) 	// not in the usedlist ?
			&& md->addr<m.addr 					// not first Mem inside XMem and not m itself ?
		     )
		{	XXTRAP ( md->addr->rptr != md );
			int idx = FreeChunkIdx(md->size);
			m.rMelt();
			*md = freelist[idx].Pop();
		}

	// external memory completely released?
		if (m.next==&m) 
		{
			if (lastfreemem||m.size!=xmem_size)	
				xfree(m.addr); 
			else
				lastfreemem=m.addr;
		}

	// else put into appropriate free list	
		else
		{
			freelist[FreeChunkIdx(m.size)].Push(m);
			XXCHECK();
		}
	}

	XXXLogOut("");
}


#if !metrowerks
void operator delete[] ( void* mem )
{	
	XXXLogLine ( "delete[]" );
	delete (char*)mem;
}


void* operator new[] (size_t size)
{
	XXXLogLine("new[]");
	return new char(size);
}
#endif



/* ================================================================
		additional stuff: Walking used mem
================================================================ */

#if unix
	static bool segfaultok;
	#define	SEGFAULTOK(FLAG)	segfaultok = FLAG
#elif defined(_MACINTOSH)
	#define	SEGFAULTOK(FLAG)
#else
	#error
#endif


/* ----	up: get MD for memory address -------------------------------
*/
static MD* GetMD ( void* mem )
{
	XXCHECK();

	if ( mem==NULL ) return NULL;

	SEGFAULTOK(true);
	MD* md = ((Mem*)(((uptr)mem)-XXMEM))[-1].rptr;
	SEGFAULTOK(false);

	if ( md<usedlist.list || md>=usedlist.list+usedlist.cnt ) return NULL;	// out of list
	if ( usedlist.list+(md-usedlist.list)!=md ) return NULL;				// misaligned
	if ( md->addr->data+XXMEM != mem ) return NULL;							// final check
	return md;
}


bool IsMem ( void* mem )
{
	return GetMD(mem)!=NULL;
}


void* NextMem ( void* mem )
{
	XXCHECK();

	MD* md;
	SEGFAULTOK(true);
	if (mem!=NULL)	md = ((Mem*)(((uptr)mem)-XXMEM))[-1].rptr  +1;
	else			md = usedlist.list;
	SEGFAULTOK(false);

	if ( md<usedlist.list || md>=usedlist.list+usedlist.cnt ) return NULL;	// out of list
	//if ( usedlist.list+(md-usedlist.list)!=md ) return NULL;				// misaligned
	//if ( md->addr->data+XXMEM != mem ) return NULL;						// final check

	return usedlist.list[md-usedlist.list].addr->data+XXMEM;
}



/* ================================================================
		additional stuff: owner handling
================================================================ */

#if MALLOC_OWNER

void SetOwner ( long id )
{
	malloc_owner = id;
}

void SetOwner ( void* mem, long id )
{
	XXTRAP( !IsMem(mem) );
	((Mem*)(((uptr)mem)-XXMEM))[-1].rptr->owner = id;
}

long GetOwner ( void* mem )
{
	XXTRAP( !IsMem(mem) );
	return ((Mem*)(((uptr)mem)-XXMEM))[-1].rptr->owner;
}

#endif



/* ==== Don't use lib, supply stubs =====================================
*/
#else

void*	NextMem		( void*  ) 			{ return NULL;  }
bool	IsMem		( void*  ) 			{ return false; }

#if MALLOC_OWNER 
void	SetOwner	( long )			{ }
void	SetOwner	( void*, long )		{ }
long	GetOwner	( void* )			{ return 0; }
#endif

#endif



