/* -*- Mode: C++; tab-width: 8; 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. */ #ifdef XP_MAC #include "TypesAndSwitches.h" #if DEBUG_MAC_MEMORY #define BUILD_TRACKER 1 #endif #include #include #include #endif /* XP_MAC */ #include #include #include "xp_tracker.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 kInvalidIndex 0xFFFFFFFF /* * Hash Table crap */ typedef struct HashBlock HashBlock; typedef struct HashTable HashTable; 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 ); /* * Internal tracker data structures and constants */ // decoder table #define kNumDecoderTypes 10 typedef struct DecoderTable { UInt32 decoderTag[ kNumDecoderTypes ]; DecoderProc decoderProc[ kNumDecoderTypes ]; } DecoderTable; typedef struct AllocationSite AllocationSite; typedef struct Block { unsigned long blockNum; unsigned long blockID; unsigned long blockSize; void * blockAddress; AllocationSite * site; unsigned char blockState; unsigned char refCount; unsigned char overhead; unsigned char pad; 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; /* This is gross. The next two fields need to be in this order as the hash */ /* key function depends on it */ unsigned long tag; void * stackCrawl[ kStackDepth ]; unsigned long siteIndex; unsigned long currentBlocks; unsigned long currentMemUsed; unsigned long maxMemUsed; unsigned long maxBlocks; unsigned long totalBlocks; unsigned long totalMemUsed; }; AllocationSite * NewAllocationSite ( void ** stackCrawl, UInt32 tag, UInt32 blockSize ); #if BUILD_TRACKER 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; DecoderTable gDecoderTable; HistogramLog gSizeHistogram; long gNumActiveSets = 0; long gNumEnabledSets = 0; static PRFileHandle gLogFile = NULL; #endif 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 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 BUILD_TRACKER unsigned long count; unsigned long blockCount; AllocationSet * set; Block * block; Block ** blockIndex; /* do any one time init */ if ( gNeverInitialized ) { gNeverInitialized = false; } /* 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; gNumActiveSets = 0; gNumEnabledSets = 0; gLogFile = NULL; 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; } for ( count = 0; count < kNumDecoderTypes; ++count ) { gDecoderTable.decoderTag[ count ] = 0; gDecoderTable.decoderProc[ count ] = 0L; } memset ( &gSizeHistogram, 0, sizeof(gSizeHistogram) ); gSizeHistogram.header.logTag = kSIZE_HISTOGRAM; gSizeHistogram.header.logSize = sizeof(HistogramLog); HashInitialize ( &gSiteTable, (HashFunction) &AllocationSiteHash, (HashCompare) AllocationSiteHashCompare ); gTrackerInitialized = true; gTrackerEnableState = 0; NewAllocationSet ( 0, "InitializeMemoryTracker" ); #endif } void ExitMemoryTracker ( void ) { #if BUILD_TRACKER gTrackerInitialized = false; if ( gLogFile != NULL ) { gLogFile = 0; } #endif } void DisableMemoryTracker ( void ) { #if BUILD_TRACKER ++gTrackerEnableState; #endif } void EnableMemoryTracker ( void ) { #if BUILD_TRACKER if ( gTrackerEnableState > 0 ) { --gTrackerEnableState; } #endif } void SetTrackerDataDecoder ( UInt32 tag, DecoderProc proc ) { #if BUILD_TRACKER UInt32 count; /* find a free entry and set it */ for ( count = 0; count < kNumDecoderTypes; ++count ) { if ( gDecoderTable.decoderProc[ count ] == NULL ) { gDecoderTable.decoderTag[ count ] = tag; gDecoderTable.decoderProc[ count ] = proc; break; } } #else #pragma unused( tag, proc ) #endif } void DumpMemoryTrackerState ( void ) { #if BUILD_TRACKER UInt32 count; UInt32 bytesOut; if ( gLogFile == NULL ) { gLogFile = PR_OpenFile ( "MemoryLog.bin", O_WRONLY | O_CREAT | O_TRUNC, 0644 ); PR_ASSERT(gLogFile); } /* dump the size histogram */ bytesOut = _OS_WRITE ( gLogFile, (char *) &gSizeHistogram, sizeof(gSizeHistogram) ); PR_ASSERT(bytesOut == sizeof(gSizeHistogram)); #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 BUILD_TRACKER unsigned long count; AllocationSite * site; AllocationSiteLogEntry logEntry; AllocationSiteLog logHeader; long size; int32 err; UInt32 decoderCount; DecoderProc proc; if ( gLogFile != NULL ) { logHeader.header.logTag = kALLOCATION_SITE_LIST; logHeader.header.logSize = sizeof(AllocationSiteLog) + ( gAllocationSitesUsed * sizeof(AllocationSiteLogEntry) ); logHeader.numEntries = gAllocationSitesUsed; size = sizeof(logHeader); err = _OS_WRITE ( gLogFile, (char *) &logHeader, size ); PR_ASSERT(err == size); for ( count = 0; count < gAllocationSitesUsed; ++count ) { site = &gAllocationSites[ count ]; memset( &logEntry, 0, sizeof(logEntry) ); // find the decoder routine and call it */ for ( decoderCount = 0; decoderCount < kNumDecoderTypes; ++decoderCount ) { if ( gDecoderTable.decoderTag[ decoderCount ] == site->tag ) { proc = gDecoderTable.decoderProc[ decoderCount ]; if ( proc != NULL ) { proc ( site->stackCrawl, logEntry.stackNames ); break; } } } logEntry.tag = site->tag; logEntry.currentBlocks = site->currentBlocks; logEntry.currentMemUsed = site->currentMemUsed; logEntry.maxMemUsed = site->maxMemUsed; logEntry.maxBlocks = site->maxBlocks; logEntry.totalBlocks = site->totalBlocks; logEntry.totalMemUsed = site->totalMemUsed; size = sizeof(logEntry); err = _OS_WRITE ( gLogFile, (char *) &logEntry, size ); PR_ASSERT(err == size); } } #endif } void TrackItem ( void * address, size_t blockSize, size_t overhead, UInt32 tag, void * decoderData ) { #if BUILD_TRACKER AllocationSite * site; Block * block; unsigned long setCount; UInt32 histIndex; ++gNumHeapBlocks; ++gNumAllocations; if ( gNumHeapBlocks > gMaxNumHeapBlocks ) { gMaxNumHeapBlocks = gNumHeapBlocks; } histIndex = CONVERT_SIZE_TO_INDEX(blockSize); gSizeHistogram.count[ histIndex ].total++; gSizeHistogram.count[ histIndex ].current++; if ( gSizeHistogram.count[ histIndex ].current > gSizeHistogram.count[ histIndex ].max ) { gSizeHistogram.count[ histIndex ].max = gSizeHistogram.count[ histIndex ].current; } /* don't do anything if we have nowhere to put it */ if ( gNumActiveSets == 0 ) { return; } /* if we don't have any enabled sets, then bail */ if ( gNumEnabledSets == 0 ) { return; } /* 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 ( decoderData, tag, blockSize ); block->blockNum = gBlockNumber++; block->blockSize = blockSize; block->blockAddress = address; 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, tag, decoderData) #endif } void ReleaseItem ( void * address ) { #if BUILD_TRACKER unsigned long count; Block * block; --gNumHeapBlocks; if ( ( gTrackerInitialized == false ) || ( gTrackerEnableState > 0 ) ) { return; } block = NULL; block = FindBlockBucket ( address, NULL ); if ( block != NULL ) { block->blockState = kBlockFree; gSizeHistogram.count[ CONVERT_SIZE_TO_INDEX(block->blockSize) ].current--; 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 #pragma unused(address) #endif } AllocationSite * NewAllocationSite ( void ** stackCrawl, UInt32 tag, UInt32 blockSize ) { #if BUILD_TRACKER AllocationSite * site; unsigned long stackCount; void * hashKey[ kStackDepth + 1 ]; /* turn the stack crawl and data tag into a hash key */ hashKey[ 0 ] = (void *) tag; for ( stackCount = 1; stackCount < kStackDepth + 1; ++stackCount ) { hashKey[ stackCount ] = stackCrawl[ stackCount - 1 ]; } site = (AllocationSite *) HashFind ( &gSiteTable, hashKey ); 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->tag = tag; site->currentBlocks = 0; site->currentMemUsed = 0; site->maxMemUsed = 0; site->maxBlocks = 0; site->totalBlocks = 0; site->totalMemUsed = 0; HashInsert ( &gSiteTable, (HashBlock *) site ); } } if ( site != NULL ) { ++site->currentBlocks; if ( site->currentBlocks > site->maxBlocks ) { site->maxBlocks = 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,tag, 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 + 1; ++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 BUILD_TRACKER 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; } ++gNumActiveSets; ++gNumEnabledSets; } return set; #else #pragma unused(name) return NULL; #endif } void EnableAllocationSet ( AllocationSet * set ) { #if BUILD_TRACKER if ( set->enabledState > 0 ) { --set->enabledState; ++gNumEnabledSets; } #else #pragma unused(set) #endif } void DisableAllocationSet ( AllocationSet * set ) { #if BUILD_TRACKER PR_ASSERT(set->enabledState != 255); ++set->enabledState; --gNumEnabledSets; #else #pragma unused(set) #endif } void AddNewBlockToAllocationSet ( AllocationSet * set, Block * block ) { #if BUILD_TRACKER 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 ) { 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 BUILD_TRACKER 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 BUILD_TRACKER 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 ) { gLogFile = PR_OpenFile ( "MemoryLog.bin", O_WRONLY | O_CREAT | O_TRUNC, 0644 ); PR_ASSERT(gLogFile); } 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 = _OS_WRITE ( gLogFile, (char *) &logHeader, size ); PR_ASSERT(err == size); 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.blockState = block->blockState; logEntry.overhead = block->overhead; size = sizeof(logEntry); err = _OS_WRITE ( gLogFile, (char *) &logEntry, size ); PR_ASSERT(err == size); } blockMask >>= 1; } } #else #pragma unused(set) #endif } void DisposeAllocationSet ( AllocationSet * set ) { #if BUILD_TRACKER unsigned long blockIndex; unsigned long * setBlocksPtr; unsigned long setBlocksLong; unsigned long blockMask; if ( set == NULL ) { return; } /* 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; --gNumActiveSets; #else #pragma unused(set) #endif } /* These utility routines can all go if we're not on */ #if BUILD_TRACKER 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 */ PR_ASSERT(block->refCount != 0); } static void RemoveBlockReference ( Block * block ) { if ( --block->refCount == 0 ) { FreeBlockFromPool ( block ); } } 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; } #endif