/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <Types.h>
#include <Files.h>
#include <Gestalt.h>
#include <Processes.h>

#include "TypesAndSwitches.h"
#include "MemoryTracker.h"
#include "prthread.h"


/*
 * These should all be runtime options.
 */
#define	LOG_ALL_OPEN_SETS		1
//#define	LOG_SITE_LEAKS_ONLY		1

/*
 * Considering that this is meant to help us reduce memory consumption, my use of memory
 * here is pretty appalling. However, I think optimizations here are only worth the time
 * if they're needed. The most important thing is to get this working quickly and start
 * using it. Then I'll come back and fix this other stuff up.
 */
 
#define	kNumBlocksTracked			100000
#define	kBlockAllocationLongs		(( kNumBlocksTracked + 31 ) >> 5 )
#define	kHashMod					1091
#define	kNumAllocationSites			30000
#define	kNumAllocationSets			20
#define	kMaxInstructionScanDistance	4096
#define	kInvalidIndex				0xFFFFFFFF


/*
 * Hash Table crap
 */
typedef struct HashBlock HashBlock;
typedef struct HashTable HashTable;
typedef struct AllocationSite AllocationSite;

typedef unsigned long (*HashFunction)( void * key );
typedef Boolean (*HashCompare) ( void * key1, void *key2 );


struct HashBlock {
	struct HashBlock *	next;
	char				key[];
};

struct HashTable {
	HashFunction	hashFunction;
	HashCompare		compareFunction;
	HashBlock *		index[ kHashMod ];
};


static void HashInitialize ( HashTable * table, HashFunction hash, HashCompare compare );
static HashBlock * HashFind ( HashTable * table, void * key );
static void HashInsert ( HashTable * table, HashBlock * block );
static void HashRemove ( HashTable * table, HashBlock * block );



typedef struct Block {
	unsigned long		blockNum;
	unsigned long		blockID;
	unsigned long		blockSize;
	void *				blockAddress;
	AllocationSite *	site;
	unsigned char		allocType;
	unsigned char		blockState;
	unsigned char		refCount;
	unsigned char		overhead;
	struct Block *		next;
} Block;

struct AllocationSet {
	unsigned long	numBlocks;
	unsigned long	totalAllocation;
	unsigned long	currentAllocation;
	unsigned long	maxAllocation;
	unsigned long	blockSet[ kBlockAllocationLongs ];
	unsigned char	inUse;
	unsigned char	enabledState;
	char			name[ 256 ];
};

typedef Block * Bucket;

struct AllocationSite {
	AllocationSite *	next;
	void *				stackCrawl[ kStackDepth ];
	unsigned long		siteIndex;
	unsigned long		currentBlocks;
	unsigned long		currentMemUsed;
	unsigned long		maxMemUsed;
	unsigned long		totalBlocks;
	unsigned long		totalMemUsed;
};


pascal void TrackerExitToShellPatch(void);
asm void * GetCurrentStackPointer();

#if DEBUG_MAC_MEMORY
Block				gBlockPool[ kNumBlocksTracked ];
Block *				gFreeBlocks;
Block *				gBlockIndex[ kHashMod ];
unsigned long		gIndexSize;
unsigned long		gBlockNumber;
unsigned long		gNumActiveTrackerBlocks;
unsigned long		gNumHeapBlocks;
unsigned long		gMaxNumHeapBlocks;
unsigned long		gNumAllocations;
AllocationSite		gAllocationSites[ kNumAllocationSites ];
unsigned long		gAllocationSitesUsed;
HashTable			gSiteTable;
AllocationSet		gAllocationSetPool[ kNumAllocationSets ];
unsigned char		gTrackerInitialized = false;
Boolean				gNeverInitialized = true;
unsigned long		gTrackerEnableState = 0;

static void *		gMemoryTop;
static short		gLogFile = 0;


UniversalProcPtr	gExitToShellPatchCallThru = NULL;
#if GENERATINGCFM
enum {
	uppExitToShellProcInfo 				= kPascalStackBased
};

RoutineDescriptor 	gExitToShellPatchRD = BUILD_ROUTINE_DESCRIPTOR(uppExitToShellProcInfo, &TrackerExitToShellPatch);
#else
#define gExitToShellPatchRD ExitToShellPatch
#endif
#endif

void		*		gOurApplicationHeapBase;
void		*		gOurApplicationHeapMax;


static Block * AllocateBlockFromPool ( void );
static void FreeBlockFromPool ( Block * block );
static Block * FindBlock ( void * address );
static void InsertBlock ( Block * block );
static void RemoveBlock ( Block * block );
static Block * FindBlockBucket ( void * address, Bucket ** bucket );
static void AddBlockReference ( Block * block );
static void RemoveBlockReference ( Block * block );
static void CatenateRoutineNameFromPC( char * string, void *currentPC );
static void PrintHexNumber( char * string, UInt32 hexNumber );
static void PrintStackCrawl ( char * name, void ** stackCrawl );

static void AddNewBlockToAllocationSet ( AllocationSet * set, Block * block );
static void MarkBlockFreeFromSet ( AllocationSet * set, Block * block );
static void BlockFreedFromSite ( AllocationSite * site, unsigned long blockSize );


static unsigned long AllocationSiteHash( void ** stackCrawl );
static Boolean AllocationSiteHashCompare ( void ** site1, void ** site2 );

void InitializeMemoryTracker ( void )
{
#if DEBUG_MAC_MEMORY
	unsigned long		count;
	unsigned long		blockCount;
	AllocationSet *		set;
	Block *				block;
	Block **			blockIndex;
	OSErr				err;
	ProcessSerialNumber	thisProcess = { 0, kCurrentProcess };
	ProcessInfoRec		processInfo;

	processInfo.processInfoLength = sizeof(processInfo);
	processInfo.processName = NULL;
	processInfo.processAppSpec = NULL;

	GetProcessInformation(&thisProcess, &processInfo);

	gOurApplicationHeapBase = processInfo.processLocation;
	gOurApplicationHeapMax = (Ptr)gOurApplicationHeapBase + processInfo.processSize;

	err = Gestalt ( gestaltLogicalRAMSize, (long *) &gMemoryTop );
	if ( err != noErr )
		{
		DebugStr ( "\pThis machine has no logical ram?" );
		ExitToShell();
		}

	/* do any one time init */
	if ( gNeverInitialized )
		{
		gNeverInitialized = false;
		gExitToShellPatchCallThru = GetToolboxTrapAddress(0x01F4);
		SetToolboxTrapAddress((UniversalProcPtr)&gExitToShellPatchRD, 0x01F4);
		}
				
	/* Be sure to dispose of ourselves if already allocated */
	if ( gTrackerInitialized != false )
		{
		ExitMemoryTracker();
		}
		
	gBlockNumber = 0;
	gNumActiveTrackerBlocks = 0;
	gNumHeapBlocks = 0;
	gMaxNumHeapBlocks = 0;
	gNumAllocations = 0;
	gAllocationSitesUsed = 0;
	gIndexSize = 0;
	
	FSDelete ( "\pMemoryLog.bin", 0 );
	Create ( "\pMemoryLog.bin", 0, 'shan', 'binl' );
	err = FSOpen ( "\pMemoryLog.bin", 0, &gLogFile );
	if ( err != noErr )
		{
		gLogFile = 0;
		}
	
	block = gBlockPool;
	gFreeBlocks = NULL;
	
	for ( count = 0; count < kNumBlocksTracked; ++count )
		{
		block->refCount = 0;
		block->blockAddress = NULL;
		block->blockID = count;
		block->next = gFreeBlocks;
		gFreeBlocks = block;
		
		++block;
		}
		
	blockIndex = gBlockIndex;

	for ( count = 0; count < kHashMod; ++count )
		{
		*blockIndex++ = NULL;
		}	
		
	set = gAllocationSetPool;
	
	for ( count = 0; count < kNumAllocationSets; ++count )
		{
		set->inUse = false;
		
		for ( blockCount = 0; blockCount < kBlockAllocationLongs; ++blockCount )
			{
			set->blockSet[ blockCount ] = 0;
			}
		
		++set;
		}
	
	HashInitialize ( &gSiteTable, (HashFunction) &AllocationSiteHash,
		(HashCompare) AllocationSiteHashCompare );
		
	gTrackerInitialized = true;
	gTrackerEnableState = 0;
	
	NewAllocationSet ( 0, "InitializeMemoryTracker" );
#endif
}


void ExitMemoryTracker ( void )
{
#if DEBUG_MAC_MEMORY
	gTrackerInitialized = false;
	
	if ( gLogFile != NULL )
		{
		FSClose ( gLogFile );
		gLogFile = 0;
		}
#endif
}


void DisableMemoryTracker ( void )
{
#if DEBUG_MAC_MEMORY
	++gTrackerEnableState;
#endif
}


void EnableMemoryTracker ( void )
{
#if DEBUG_MAC_MEMORY
	if ( gTrackerEnableState > 0 )
		{
		--gTrackerEnableState;
		}
#endif
}


void DumpMemoryTrackerState ( void )
{
#if DEBUG_MAC_MEMORY
	unsigned long		count;
	
	printf ( "Number of tracker blocks in use: %ld\n", gNumActiveTrackerBlocks );
	printf ( "Number of heap blocks in use: %ld\n", gNumHeapBlocks );
	printf ( "Max Number of heap blocks in use: %ld\n", gMaxNumHeapBlocks );
	printf ( "Total number of allcations %ld\n", gNumAllocations );

#if LOG_ALL_OPEN_SETS	
	/* Log all active allocation sets */
	for ( count = 0; count < kNumAllocationSets; ++count )
		{
		if ( gAllocationSetPool[ count ].inUse == true )
			{
			LogAllocationSetState ( &gAllocationSetPool[ count ] );
			}
		}
#endif
	
	DumpAllocationSites();
#endif
}


void DumpAllocationSites ( void )
{
#if DEBUG_MAC_MEMORY
	unsigned long			count;
	AllocationSite *		site;
	AllocationSiteLogEntry	logEntry;
	AllocationSiteLog		logHeader;
	long					size;
	OSErr					err;
	
	if ( gLogFile != NULL )
		{
		logHeader.header.logTag = kALLOCATION_SITE_LIST;
		logHeader.header.logSize = sizeof(AllocationSiteLog) +
			( gAllocationSitesUsed * sizeof(AllocationSiteLogEntry) );
		logHeader.numEntries = gAllocationSitesUsed;
		
		size = sizeof(logHeader);
		err = FSWrite ( gLogFile, &size, &logHeader );
		if ( err != noErr )
			{
			DebugStr ( "\pWrite failed" );
			return;
			}
		
		for ( count = 0; count < gAllocationSitesUsed; ++count )
			{
			site = &gAllocationSites[ count ];

			memset( &logEntry, 0, sizeof(logEntry) );

			PrintStackCrawl ( logEntry.stackNames, site->stackCrawl );
			
			logEntry.currentBlocks = site->currentBlocks;
			logEntry.currentMemUsed = site->currentMemUsed;
			logEntry.maxMemUsed = site->maxMemUsed;
			logEntry.totalBlocks = site->totalBlocks;
			logEntry.totalMemUsed = site->totalMemUsed;
			
			size = sizeof(logEntry);
			err = FSWrite ( gLogFile, &size, &logEntry );
			if ( err != noErr )
				{
				DebugStr ( "\pWrite failed" );
				return;
				}
			}
		}
#endif
}


void AddNewBlock ( void * address, size_t blockSize, size_t overhead, AllocatorType allocType,
	void * stackCrawl )
{
#if DEBUG_MAC_MEMORY
	AllocationSite *	site;
	Block *				block;
	unsigned long		setCount;
	
	++gNumHeapBlocks;
	++gNumAllocations;

	if ( gNumHeapBlocks > gMaxNumHeapBlocks )
		{
		gMaxNumHeapBlocks = gNumHeapBlocks;
		}
		
	/* tracking a null block will hose us big time */
	if ( ( address == NULL ) || ( gTrackerInitialized == false ) || ( gTrackerEnableState > 0 ) )
		{
		return;
		}
		
	/*
	 * Find a free block in our block pool
	 */
	block = AllocateBlockFromPool();
	
	/* if we found a block, allocate it */
	if ( block != NULL )
		{
		/* Find the allocation site for this block */
		site = NewAllocationSite ( stackCrawl, blockSize );
		
		block->blockNum = gBlockNumber++;
		block->blockSize = blockSize;
		block->blockAddress = address;
		block->allocType = allocType;
		block->refCount = 0;
		block->overhead = overhead;
		block->blockState = kBlockAllocated;
		block->site = site;
		
		/* insert this block into the block index */
		InsertBlock ( block );
		
		/* add our own reference to this block */
		AddBlockReference ( block );
		
		/* and then add it to all relevant allocation sets */
		for ( setCount = 0; setCount < kNumAllocationSets; ++setCount )
			{
			if ( gAllocationSetPool[ setCount ].inUse == true )
				{
				AddNewBlockToAllocationSet ( &gAllocationSetPool[ setCount ], block );
				}
			}
		}
#else
#pragma unused(address, blockSize, overhead, allocType, stackCrawl)
#endif
}


void FreeBlock ( void * address )
{
#if DEBUG_MAC_MEMORY
	unsigned long		count;
	Block *				block;
		
	--gNumHeapBlocks;

	if ( ( gTrackerInitialized == false ) || ( gTrackerEnableState > 0 ) )
		{
		return;
		}
		
	block = NULL;
	
	block = FindBlockBucket ( address, NULL );
	
	if ( block != NULL )
		{	
		block->blockState = kBlockFree;

		BlockFreedFromSite ( block->site, block->blockSize );
		
		/* remove our own reference from this block */
		RemoveBlockReference ( block );

		/* remove block from all sets */
		for ( count = 0; count < kNumAllocationSets; ++count )
			{
			if ( gAllocationSetPool[ count ].inUse == true )
				{
				MarkBlockFreeFromSet ( &gAllocationSetPool[ count ], block );
				}
			}
		}
	else
		{
//		DebugStr ( "\pCan't find block?" );
		}
#else
#pragma unused(address)
#endif
}

AllocationSite * NewAllocationSite ( void ** stackCrawl, unsigned long blockSize )
{
#if DEBUG_MAC_MEMORY
	AllocationSite *	site;
	unsigned long		stackCount;

		site = (AllocationSite *) HashFind ( &gSiteTable, stackCrawl );
		if ( site == NULL )
			{
			if ( gAllocationSitesUsed < kNumAllocationSites )
				{
				site = &gAllocationSites[ gAllocationSitesUsed++ ];
				for ( stackCount = 0; stackCount < kStackDepth; ++stackCount )
					{
					site->stackCrawl[ stackCount ] = stackCrawl[ stackCount ];
					}

				site->siteIndex = gAllocationSitesUsed - 1;
				site->currentBlocks = 0;
				site->currentMemUsed = 0;
				site->maxMemUsed = 0;
				site->totalBlocks = 0;
				site->totalMemUsed = 0;
				}
			else
				{
	//			DebugStr ( "\pOut of allocation sites..." );
				}
			
			HashInsert ( &gSiteTable, (HashBlock *) site );
			}

	if ( site != NULL )
		{
		++site->currentBlocks;
		site->currentMemUsed += blockSize;
		if ( site->currentMemUsed > site->maxMemUsed )
			{
			site->maxMemUsed = site->currentMemUsed;
			}
			
		++site->totalBlocks;
		site->totalMemUsed += blockSize;
		}
		
	return site;
#else
#pragma unused(stackCrawl, blockSize)
	return NULL;
#endif
}


void BlockFreedFromSite ( AllocationSite * site, unsigned long blockSize )
{
	if ( site != NULL )
		{
		--site->currentBlocks;
		site->currentMemUsed -= blockSize;
		}
}


static unsigned long AllocationSiteHash( void ** stackCrawl )
{
	unsigned long	hash;
	unsigned long	count;
	
	hash = 0;

	for ( count = 0; count < kStackDepth; ++count )
		{
		hash += (unsigned long) stackCrawl[ count ];
		}
	
	return hash;
}


static Boolean AllocationSiteHashCompare ( void ** site1, void ** site2 )
{
	Boolean			matched;
	unsigned long	count;
	
	matched = true;
	for ( count = 0; count < kStackDepth; ++count )
		{
		if ( site1[ count ] != site2[ count ] )
			{
			matched = false;
			break;
			}
		}
	
	return matched;
}


AllocationSet * NewAllocationSet ( unsigned long trackingOptions, char * name )
{
#pragma unused(trackingOptions)
#if DEBUG_MAC_MEMORY
	AllocationSet *	set;
	unsigned long	count;
	
	set = NULL;

	/* find a free set */
	for ( count = 0; count < kNumAllocationSets; ++count )
		{
		if ( gAllocationSetPool[ count ].inUse == false )
			{
			set = &gAllocationSetPool[ count ];
			break;
			}
		}
	
	if ( set != NULL )
		{
		set->inUse = true;
		set->numBlocks = 0;
		set->totalAllocation = 0;
		set->currentAllocation = 0;
		set->maxAllocation = 0;
		set->enabledState = 0;
		strcpy ( set->name, name );
		
		/* clear all blocks from this set */
		for ( count = 0; count < kBlockAllocationLongs; ++count )
			{
			set->blockSet[ count ] = 0;
			}
		}
	
	return set;
#else
#pragma unused(name)
	return NULL;
#endif
}


void EnableAllocationSet ( AllocationSet * set )
{
	if ( set->enabledState > 0 )
		{
		--set->enabledState;
		}
}


void DisableAllocationSet ( AllocationSet * set )
{
	if ( set->enabledState == 255 )
		{
//		DebugStr ( "\pAllocationSet's enabledState overflowed!" );
		}
	
	++set->enabledState;
}


void AddNewBlockToAllocationSet ( AllocationSet * set, Block * block )
{
#if DEBUG_MAC_MEMORY
	unsigned long	blockID;
	unsigned long	blockMask;
	unsigned long *	blockSetLong;
	
	/* if we're not enabled, then bail */
	if ( set->enabledState != 0 )
		{
		return;
		}
		
	blockID = block->blockID;
	blockSetLong = &set->blockSet[ blockID >> 5 ];
	
	blockMask = 1L << ( 31 - ( blockID & 0x1F ) );
	
	if ( *blockSetLong & blockMask )
		{
//		DebugStr ( "\pWe suck, this block is already part of the set" );
		return;
		}

	set->numBlocks++;
	set->totalAllocation += block->blockSize;
	set->currentAllocation += block->blockSize;
		
	if ( set->currentAllocation > set->maxAllocation )
		{
		set->maxAllocation = set->currentAllocation;
		}
		
	*blockSetLong |= blockMask;
	AddBlockReference ( block );
#else
#pragma unused(set, block)
#endif
}


void MarkBlockFreeFromSet ( AllocationSet * set, Block * block )
{
#if DEBUG_MAC_MEMORY
	unsigned long	blockID;
	unsigned long	blockMask;
	unsigned long *	blockSetLong;
	
	blockID = block->blockID;
	blockSetLong = &set->blockSet[ blockID >> 5 ];
	
	blockMask = 1L << ( 31 - ( blockID & 0x1F ) );
	
	if ( ( *blockSetLong & blockMask ) == 0 )
		{
		return;
		}
	
	*blockSetLong &= ~blockMask;

	set->numBlocks--;
	set->currentAllocation -= block->blockSize;

	RemoveBlockReference ( block );
#else
#pragma unused(set, block)
#endif
}


void LogAllocationSetState ( AllocationSet * set )
{
#if DEBUG_MAC_MEMORY
	unsigned long	blockCount;
	unsigned long	blockMask;
	unsigned long *	setLongPtr;
	unsigned long	setLong;
	Block *			block;
	AllocationSetLogEntry	logEntry;
	AllocationSetLog		logHeader;
	long					size;
	OSErr					err;
	
	if ( set == NULL )
		{
		return;
		}
	
	if ( gLogFile != NULL )
		{
		memset( &logHeader, 0, sizeof(logHeader) );
		logHeader.header.logTag = kSET_BLOCK_LIST;
		logHeader.header.logSize = sizeof(AllocationSetLog) +
			( set->numBlocks * sizeof(AllocationSetLogEntry) );
		
		logHeader.numEntries = set->numBlocks;
		logHeader.totalAllocation = set->totalAllocation;
		logHeader.currentAllocation = set->currentAllocation;
		logHeader.maxAllocation = set->maxAllocation;
		strcpy ( logHeader.name, set->name );
		
		size = sizeof(logHeader);
		err = FSWrite ( gLogFile, &size, &logHeader );
		if ( err != noErr )
			{
			DebugStr ( "\pWrite failed" );
			return;
			}
		
		blockMask = 0;
		setLongPtr = set->blockSet;

		for ( blockCount = 0; blockCount < kNumBlocksTracked; ++blockCount )
			{
			if ( blockMask == 0 )
				{
				blockMask = 0x80000000;
				setLong = *setLongPtr++;
				}
			
			if ( setLong & blockMask )
				{
				block = &gBlockPool[ blockCount ];
				
				memset( &logEntry, 0, sizeof(logEntry) );
				logEntry.address = (unsigned long) block->blockAddress;
				logEntry.blockNumber = block->blockNum;
				logEntry.size = block->blockSize;
				if ( block->site != NULL )
					{
					logEntry.siteIndex = block->site->siteIndex;
					}
				else
					{
					logEntry.siteIndex = -1;
					}
					
				logEntry.allocType = block->allocType;
				logEntry.blockState = block->blockState;
				logEntry.overhead = block->overhead;
				
				size = sizeof(logEntry);
				err = FSWrite ( gLogFile, &size, &logEntry );
				if ( err != noErr )
					{
					DebugStr ( "\pWrite failed" );
					return;
					}
				}
			
			blockMask >>= 1;
			}
		}
#else
#pragma unused(set)
#endif
}


void DisposeAllocationSet ( AllocationSet * set )
{
#if DEBUG_MAC_MEMORY
	unsigned long	blockIndex;
	unsigned long *	setBlocksPtr;
	unsigned long	setBlocksLong;
	unsigned long	blockMask;
		
	/* release all the blocks we own */
	setBlocksPtr = set->blockSet;
	blockMask = 0;
	
	for ( blockIndex = 0; blockIndex < kNumBlocksTracked; ++blockIndex )
		{
		if ( blockMask == 0 )
			{
			blockMask = 0x80000000;
			setBlocksLong = *setBlocksPtr++;
			}
		
		if ( blockMask & setBlocksLong )
			{
			RemoveBlockReference ( &gBlockPool[ blockIndex ] );
			}
		
		blockMask >>= 1;
		}

	set->inUse = false;
#else
#pragma unused(set)
#endif
}


/* These utility routines can all go if we're not on */
#if DEBUG_MAC_MEMORY
static Block * AllocateBlockFromPool ( void )
{
	Block *			block;
	
	block = gFreeBlocks;
	if ( block != NULL )
		{
		++gNumActiveTrackerBlocks;
		gFreeBlocks = block->next;
		}

	return block;
}


static void FreeBlockFromPool ( Block * block )
{
	/* this sucks to research the hash table, but I don't want to eat more */
	/* memory */
	RemoveBlock ( block );
	
	--gNumActiveTrackerBlocks;
	
	block->next = gFreeBlocks;
	gFreeBlocks = block;
}


static void InsertBlock ( Block * block )
{
	Bucket *		bucket;
	
	bucket = &gBlockIndex[ (unsigned long) block->blockAddress % kHashMod ];
	block->next = *bucket;
	*bucket = block;
}


static void RemoveBlock ( Block * block )
{
	Bucket *		bucket;
	Block *			prev;
	Block *			next;
	Block *			walker;
	
	bucket = &gBlockIndex[ (unsigned long) block->blockAddress % kHashMod ];
		
	/* walk the list, find our guy and remove it */
	prev = NULL;
	walker = *bucket;
	
	while ( walker != NULL )
		{
		next = walker->next;
		
		if ( walker == block )
			{
			if ( prev == NULL )
				{
				/* first block in the list */
				*bucket = next;
				}
			else
				{
				prev->next = next;
				}
			
			break;
			}
		
		prev = walker;
		walker = next;
		}
}


static Block * FindBlockBucket ( void * address, Bucket ** bucket )
{
	unsigned long	hashIndex;
	Bucket *		hashBucket;
	Block *			blockWalker;
	Block *			block;
	
	block = NULL;
	
	hashIndex = (unsigned long) address % kHashMod;
	hashBucket = &gBlockIndex[ hashIndex ];
	if ( bucket != NULL )
		{
		*bucket = hashBucket;
		}
	
	blockWalker = *hashBucket;
	while ( blockWalker != NULL )
		{
		if ( blockWalker->blockAddress == address )
			{
			block = blockWalker;
			break;
			}
		
		blockWalker = blockWalker->next;
		}
	
	return block;
}


static void AddBlockReference ( Block * block )
{
	++block->refCount;
		
	/* make sure we didn't wrap the refCount */
	assert(block->refCount != 0);
}

static void RemoveBlockReference ( Block * block )
{
	if ( --block->refCount == 0 )
		{
		FreeBlockFromPool ( block );
		}
}


static void PrintStackCrawl ( char * name, void ** stackCrawl )
{
	unsigned long		currentStackLevel;
	
	*name = 0;
	
	for (currentStackLevel = 0; currentStackLevel < kStackDepth; currentStackLevel++)
		{
		CatenateRoutineNameFromPC ( name, stackCrawl[ currentStackLevel ] );
		
		if (currentStackLevel < kStackDepth - 1)
			strcat( name, "," );
		}
}


static void CatenateRoutineNameFromPC( char * string, void *currentPC )
{
	UInt32		instructionsToLook = kMaxInstructionScanDistance;
	UInt32		*currentPCAsLong = (UInt32 *)currentPC;
	UInt32		offset = 0;
	char		labelString[ 512 ];
	char		lameString[ 512 ];
	
	if (currentPC == NULL)
		return;

	/* make sure we're not out in the weeds */
	if ( (unsigned long) currentPC > (unsigned long) gMemoryTop )
		return;
		
	if ( (unsigned long) currentPC & 0x3 )
		return;
	
	*labelString = 0;
	*lameString = 0;
	
	while (instructionsToLook--) {
	
		if (*currentPCAsLong == 0x4E800020) {
			char	stringLength = *((char *)currentPCAsLong + 21);
			memset( labelString, 0, 512 );
			BlockMoveData( ((char *)currentPCAsLong + 22), labelString, stringLength );
			
			// if there was no routine name, then just put down the address.
			if ( *labelString == 0 )
				{
				PrintHexNumber( lameString, (unsigned long) currentPC );
				goto bail;
				}
			else
				{
				strcat ( lameString, labelString );
				}
			break;
		}
	
		currentPCAsLong++;
	}
	
	instructionsToLook = kMaxInstructionScanDistance;
	currentPCAsLong = (UInt32 *)currentPC;
	
	*labelString = 0;
	
	while (instructionsToLook--) {
		if (*currentPCAsLong == 0x7C0802A6) {
			strcat ( lameString, "+" );

			PrintHexNumber( labelString, offset - 4 );
			strcat ( lameString, labelString );
			break;
		}
		currentPCAsLong--;
		offset += 4;
	}

bail:	
	/* make sure we don't end up being too big */
	lameString[ kNameMaxLength ] = 0;
	strcat ( string, lameString );
}


static void PrintHexNumber( char * string, UInt32 hexNumber )
{
	char		hexString[11];
	char		hexMap[] = "0123456789ABCDEF";
	long		digits = 8;
		
	
	hexString[0] = '0';
	hexString[1] = 'x';
	
	while (digits) {
		hexString[digits + 1] = *((hexNumber & 0x0F) + hexMap);
		hexNumber = hexNumber >> 4;
		digits--;
	}
	hexString[10] = 0;
	
	strcat ( string, hexString );
}


static void HashInitialize ( HashTable * table, HashFunction hash, HashCompare compare )
{
	unsigned long	count;
	
	table->hashFunction = hash;
	table->compareFunction = compare;
	
	for ( count = 0; count < kHashMod; ++count )
		{
		table->index[ count ] = NULL;
		}
}


static void HashInsert ( HashTable * table, HashBlock * block )
{
	HashBlock **	bucket;
	unsigned long	hash;
	
	hash = (*table->hashFunction) ( block->key ) % kHashMod;
	
	bucket = &table->index[ hash ];
	block->next = *bucket;
	*bucket = block;
}


static void HashRemove ( HashTable * table, HashBlock * block )
{
	HashBlock **	bucket;
	HashBlock *		prev;
	HashBlock *		next;
	HashBlock *		walker;
	unsigned long	hash;
	
	hash = (*table->hashFunction) ( block->key ) % kHashMod;
	
	bucket = &table->index[ hash ];
		
	/* walk the list, find our guy and remove it */
	prev = NULL;
	walker = *bucket;
	
	while ( walker != NULL )
		{
		next = walker->next;
		
		if ( (*table->compareFunction)( walker->key, block->key ) )
			{
			if ( prev == NULL )
				{
				/* first block in the list */
				*bucket = next;
				}
			else
				{
				prev->next = next;
				}
			
			break;
			}
		
		prev = walker;
		walker = next;
		}
}


static HashBlock * HashFind ( HashTable * table, void * key )
{
	HashBlock **	bucket;
	HashBlock *		blockWalker;
	HashBlock *		block;
	unsigned long	hash;
	
	hash = (*table->hashFunction) ( key ) % kHashMod;
	
	bucket = &table->index[ hash ];
	
	block = NULL;
	blockWalker = *bucket;
	
	while ( blockWalker != NULL )
		{
		if ( (*table->compareFunction) ( blockWalker->key, key ) )
			{
			block = blockWalker;
			break;
			}
		
		blockWalker = blockWalker->next;
		}
	
	return block;
}

pascal void TrackerExitToShellPatch(void)
{
static Boolean gBeenHere = false;

	/* if we haven't been here, then dump our memory state, otherwise don't */
	/* this way we don't hose ourselves if we crash in here */
	if ( !gBeenHere )
		{
		gBeenHere = true;
		DumpMemoryTrackerState();
		}

	ExitMemoryTracker();
	
#if GENERATINGCFM
	CallUniversalProc(gExitToShellPatchCallThru, uppExitToShellProcInfo);
#else 
	{
		ExitToShellProc	*exitProc = (ExitToShellProc *)&gExitToShellPatchCallThru;
		(*exitProc)();
	}
#endif
}

#endif

asm void *GetCurrentStackPointer() 
{
	mr		r3, sp
	lwz		r3, 0(r3)
	blr
}


void GetCurrentStackTrace(void **stackCrawl)
{
	void *		currentSP;
	void *		interfaceLibSP;
	void *		callerFirstStackFrame;
	void *		callerSP;
	UInt32		stackFrameLevel;
	UInt8		isPowerPC = true;
	void *		nextFrame;
	long		nativeFrames;
	PRThread *	curThread;
	void *		stackMin;
	void *		stackMax;
		
	memset(stackCrawl, 0, sizeof(void *) * kStackDepth);
	
#if !GENERATINGPOWERPC
	return;
#endif

	curThread = PR_CurrentThread();
	if ( curThread != NULL && curThread->stack->stackBottom != 0 )
		{
		stackMin = curThread->stack->stackBottom;
		stackMax = curThread->stack->stackTop;
		}
	else
		{
		stackMin = gOurApplicationHeapBase;
		stackMax = gOurApplicationHeapMax;
		}
 
	//	If the current SP is not in our heap, then bail .
	
#if GENERATINGPOWERPC
	currentSP = GetCurrentStackPointer();
#endif

	if ((currentSP > gOurApplicationHeapMax) || (currentSP < gOurApplicationHeapBase))
		return;
		
	interfaceLibSP = *((void **)currentSP);
	
	callerFirstStackFrame = interfaceLibSP;
	nextFrame = callerFirstStackFrame;
		
	//	What we really want to do here is walk up the stack until we get to a native frame that is in
	//	one of our Shared libraries... Since they can live in temp mem, this gets whacky.
	
	//	So, for now, I just walk up until I hit two native frames in a row...
	nativeFrames = 0;
	
	while (1) {
		// if this frame is outside of our thread's stack, then bail
		if ( ( nextFrame > stackMax ) || ( nextFrame < stackMin ) )
			{
			return;
			}
				
		//	Walk the stack differently whether we are at a
		//	PowerPC or 68K frame...

		if (isPowerPC) {

#if 0
			void	*framePC;

			//	If we are PowerPC, check to see if the PC
			//	corresponding to the current frame is in the
			//	the app's code space.  If so, then we are
			//	done and we break out.
			
			framePC = *((void **)callerFirstStackFrame + 2);
			if ((framePC < gOurApplicationHeapMax) && (framePC > gOurApplicationHeapBase))
				break;
#endif
				
			//	Otherwise, check the pointer to the next
			//	stack frame.  If its low bit is set, then
			//	it is a mixed-mode switch frame, and 
			//	we need to switch to 68K frames.

			nextFrame = *((void **)callerFirstStackFrame);
			
			isPowerPC = ((UInt32)nextFrame & 0x00000001) == 0;			
			callerFirstStackFrame = (void *)((UInt32)nextFrame & 0xFFFFFFFE);
			if ( isPowerPC != false )
				{
				if ( ++nativeFrames >= 1 )
					{
					break;
					}
				}
		}
	
		else {
			
			nativeFrames = 0;
			
			//	68K case:  If the location immediately above the stack
			//	frame pointer is -1, then we are at a switch frame,
			//	move back to PowerPC.

			if (*((UInt32 *)callerFirstStackFrame - 1) == 0xFFFFFFFF)
				isPowerPC = true;
			callerFirstStackFrame = *((void **)callerFirstStackFrame);
			nextFrame = callerFirstStackFrame;
		}
	
	}
	
	callerSP = callerFirstStackFrame;
	
	for (stackFrameLevel = 0; stackFrameLevel < kStackDepth; stackFrameLevel++) {
	
		if ( ( callerSP > stackMax ) || ( callerSP < stackMin ) )
			{
			return;
			}
			
		stackCrawl[stackFrameLevel] = *(((void **)callerSP) + 2);
		
		callerSP = *((void **)callerSP);
	
	}
}


void GetCurrentNativeStackTrace ( void ** stackCrawl )
{
	void *		currentSP;
	UInt32		stackFrameLevel;
	PRThread *	curThread;
	void *		stackMin;
	void *		stackMax;
	
	memset(stackCrawl, 0, sizeof(void *) * kStackDepth);
	
#if GENERATINGPOWERPC
	currentSP = GetCurrentStackPointer();
#else
	return;
#endif

	curThread = PR_CurrentThread();
	if ( curThread != NULL && curThread->stack->stackBottom != 0 )
		{
		stackMin = curThread->stack->stackBottom;
		stackMax = curThread->stack->stackTop;
		}
	else
		{
		stackMin = gOurApplicationHeapBase;
		stackMax = gOurApplicationHeapMax;
		}
		
	for (stackFrameLevel = 0; stackFrameLevel < kStackDepth; stackFrameLevel++) {
		if ( ( currentSP > stackMax ) || ( currentSP < stackMin ) )
			{
			return;
			}
			
		stackCrawl[stackFrameLevel] = *((void **)((char *)currentSP + 8));
		currentSP = *((void **)currentSP);
	}
}

