2007 lines
51 KiB
C++
2007 lines
51 KiB
C++
#include "precomp.h"
|
|
|
|
|
|
//
|
|
// CH.CPP
|
|
// Cache Handler
|
|
//
|
|
// Copyright(c) Microsoft 1997-
|
|
//
|
|
|
|
#define MLZ_FILE_ZONE ZONE_CORE
|
|
|
|
//
|
|
// CACHE HANDLER
|
|
//
|
|
// The Cache Handler is a generic cache manager that handles blocks of
|
|
// memory supplied by the calling component.
|
|
//
|
|
// Once a cache of a particular size has been created, blocks of memory can
|
|
// be added to it (CH_CacheData). The cache can then be searched
|
|
// (CH_SearchCache) to try and match the contents of a given block of
|
|
// memory with the blocks in the cache.
|
|
//
|
|
// When a block is added to the cache and the cache is full, one of the
|
|
// blocks currently in the cache is discarded on a Least-Recently Used
|
|
// (LRU) basis.
|
|
//
|
|
// The component that creates the cache specifies a callback function which
|
|
// is called every time a block is removed from the cache. This allows the
|
|
// caller to free up memory blocks when they are no longer in use.
|
|
//
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: CH_CreateCache
|
|
//
|
|
BOOL ASHost::CH_CreateCache
|
|
(
|
|
PCHCACHE * ppCache,
|
|
UINT cCacheEntries,
|
|
UINT cNumEvictionCategories,
|
|
UINT cbNotHashed,
|
|
PFNCACHEDEL pfnCacheDel
|
|
)
|
|
{
|
|
UINT cbCacheSize;
|
|
UINT i;
|
|
PCHCACHE pCache;
|
|
|
|
DebugEntry(ASHost::CH_CreateCache);
|
|
|
|
|
|
//
|
|
// Initialize return value
|
|
//
|
|
pCache = NULL;
|
|
|
|
//
|
|
// Do a few parameter validation checks.
|
|
//
|
|
ASSERT((cCacheEntries > 0));
|
|
ASSERT((cCacheEntries < CH_MAX_CACHE_ENTRIES));
|
|
ASSERT(cNumEvictionCategories > 0);
|
|
ASSERT(cNumEvictionCategories <= CH_NUM_EVICTION_CATEGORIES);
|
|
|
|
|
|
//
|
|
// Calculate the amount of memory required.
|
|
// NOTE that the CHCACHE definition includes one cache entry
|
|
//
|
|
cbCacheSize = sizeof(CHCACHE) + ((cCacheEntries-1) * sizeof(CHENTRY));
|
|
|
|
//
|
|
// Allocate memory for the cache.
|
|
//
|
|
pCache = (PCHCACHE)new BYTE[cbCacheSize];
|
|
if (pCache == NULL)
|
|
{
|
|
ERROR_OUT(("Failed to alloc cache"));
|
|
DC_QUIT;
|
|
}
|
|
|
|
SET_STAMP(pCache, CHCACHE);
|
|
|
|
pCache->pRoot = NULL;
|
|
pCache->pFirst = NULL;
|
|
pCache->pLast= NULL;
|
|
pCache->free = 0;
|
|
|
|
pCache->cEntries = cCacheEntries;
|
|
pCache->cNumEvictionCategories = cNumEvictionCategories;
|
|
pCache->cbNotHashed = cbNotHashed;
|
|
pCache->pfnCacheDel = pfnCacheDel;
|
|
|
|
//
|
|
// Initialize the cache entries
|
|
//
|
|
for (i = 0; i < cCacheEntries; i++)
|
|
{
|
|
CHInitEntry(&pCache->Entry[i]);
|
|
pCache->Entry[i].free = (WORD)(i+1);
|
|
}
|
|
pCache->Entry[cCacheEntries-1].free = CH_MAX_CACHE_ENTRIES;
|
|
|
|
//
|
|
// Set up the default eviction category limits. Default is to balance
|
|
// at 75% to the high category, 75% of the remainder to the next lower
|
|
// and so on
|
|
//
|
|
for (i = cNumEvictionCategories; i > 0; i--)
|
|
{
|
|
pCache->iMRUHead[i-1] = CH_MAX_CACHE_ENTRIES;
|
|
pCache->iMRUTail[i-1] = CH_MAX_CACHE_ENTRIES;
|
|
pCache->cEvictThreshold[i-1] = (WORD)((cCacheEntries*3)/4);
|
|
}
|
|
|
|
DC_EXIT_POINT:
|
|
*ppCache = pCache;
|
|
DebugExitBOOL(ASHost::CH_CreateCache, (pCache != NULL));
|
|
return(pCache != NULL);
|
|
}
|
|
|
|
|
|
//
|
|
// CH_DestroyCache
|
|
// Destroys a created cache, if it is valid.
|
|
//
|
|
void ASHost::CH_DestroyCache(PCHCACHE pCache)
|
|
{
|
|
DebugEntry(ASHost::CH_DestroyCache);
|
|
|
|
ASSERT(IsValidCache(pCache));
|
|
|
|
//
|
|
// Clear the entries in the cache
|
|
//
|
|
CH_ClearCache(pCache);
|
|
|
|
//
|
|
// Free the memory
|
|
//
|
|
delete pCache;
|
|
|
|
DebugExitVOID(ASHost::CH_DestroyCache);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CH_SearchCache
|
|
//
|
|
BOOL ASHost::CH_SearchCache
|
|
(
|
|
PCHCACHE pCache,
|
|
LPBYTE pData,
|
|
UINT cbDataSize,
|
|
UINT evictionCategory,
|
|
UINT * piCacheEntry
|
|
)
|
|
{
|
|
BOOL rc = FALSE;
|
|
UINT checkSum;
|
|
|
|
DebugEntry(ASHost::CH_SearchCache);
|
|
|
|
ASSERT(IsValidCache(pCache));
|
|
|
|
checkSum = CHCheckSum(pData + pCache->cbNotHashed,
|
|
cbDataSize - pCache->cbNotHashed);
|
|
|
|
*piCacheEntry = CHTreeSearch(pCache, checkSum, cbDataSize, pData);
|
|
if ( *piCacheEntry != CH_MAX_CACHE_ENTRIES )
|
|
{
|
|
//
|
|
// Found a match
|
|
//
|
|
CHUpdateMRUList(pCache, *piCacheEntry, evictionCategory);
|
|
rc = TRUE;
|
|
}
|
|
|
|
DebugExitBOOL(ASHost::CH_SearchCache, rc);
|
|
return(rc);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: CH_CacheData
|
|
//
|
|
UINT ASHost::CH_CacheData
|
|
(
|
|
PCHCACHE pCache,
|
|
LPBYTE pData,
|
|
UINT cbDataSize,
|
|
UINT evictionCategory
|
|
)
|
|
{
|
|
UINT evictionCount;
|
|
UINT iEntry = CH_MAX_CACHE_ENTRIES;
|
|
PCHENTRY pEntry;
|
|
|
|
DebugEntry(ASHost::CH_CacheData);
|
|
|
|
ASSERT(IsValidCache(pCache));
|
|
ASSERT((evictionCategory < pCache->cNumEvictionCategories));
|
|
|
|
if (!CHFindFreeCacheEntry(pCache, &iEntry, &evictionCount))
|
|
{
|
|
iEntry = CHEvictLRUCacheEntry(pCache, evictionCategory, evictionCount);
|
|
|
|
//
|
|
// MNM1422: Ideally we would now call CHFindFreeCacheEntry again to
|
|
// get the entry freed up by the eviction process - but since we
|
|
// have just been returned that entry, we may as well use it to
|
|
// improve performance.
|
|
//
|
|
// However, the processing has left pTreeCacheData->tree.free
|
|
// pointing to the entry we have just evicted - which we are about
|
|
// to use. So we need to perform the same processing on the free
|
|
// list as CHFindFreeCacheEntry would have done, or next time
|
|
// through, the first 'free' entry will really be in use, and the
|
|
// insert code will assert!
|
|
//
|
|
ASSERT(pCache->free == iEntry);
|
|
pCache->free = pCache->Entry[iEntry].free;
|
|
}
|
|
|
|
pEntry = &pCache->Entry[iEntry];
|
|
pEntry->pData = pData;
|
|
pEntry->cbData = cbDataSize;
|
|
pEntry->checkSum = CHCheckSum(pData + pCache->cbNotHashed,
|
|
cbDataSize - pCache->cbNotHashed);
|
|
pEntry->evictionCategory = (WORD)evictionCategory;
|
|
CHAvlInsert(pCache, pEntry);
|
|
|
|
TRACE_OUT(( "Cache 0x%08x entry %d checksum 0x%08x data 0x%08x",
|
|
pCache, iEntry, pEntry->checkSum, pEntry->pData));
|
|
|
|
CHUpdateMRUList(pCache, iEntry, evictionCategory);
|
|
|
|
DebugExitDWORD(ASHost::CH_CacheData, iEntry);
|
|
return(iEntry);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CH_SearchAndCacheData
|
|
//
|
|
BOOL ASHost::CH_SearchAndCacheData
|
|
(
|
|
PCHCACHE pCache,
|
|
LPBYTE pData,
|
|
UINT cbDataSize,
|
|
UINT evictionCategory,
|
|
UINT * piCacheEntry
|
|
)
|
|
{
|
|
UINT checkSum;
|
|
UINT i;
|
|
BOOL preExisting;
|
|
UINT iEntry = CH_MAX_CACHE_ENTRIES;
|
|
UINT evictionCount = 0;
|
|
PCHENTRY pEntry;
|
|
|
|
DebugEntry(ASHost::CH_SearchAndCacheData);
|
|
|
|
ASSERT(IsValidCache(pCache));
|
|
ASSERT(evictionCategory < pCache->cNumEvictionCategories);
|
|
|
|
//
|
|
// Does this entry exist?
|
|
//
|
|
checkSum = CHCheckSum(pData + pCache->cbNotHashed,
|
|
cbDataSize - pCache->cbNotHashed);
|
|
|
|
iEntry = CHTreeSearch(pCache, checkSum, cbDataSize, pData);
|
|
if ( iEntry == CH_MAX_CACHE_ENTRIES)
|
|
{
|
|
preExisting = FALSE;
|
|
//
|
|
// We didn't find the entry--can we add it?
|
|
//
|
|
TRACE_OUT(("CACHE: entry not found in cache 0x%08x csum 0x%08x",
|
|
pCache, checkSum));
|
|
|
|
if (!CHFindFreeCacheEntry(pCache, &iEntry, &evictionCount))
|
|
{
|
|
//
|
|
// Nope. Evict an entry
|
|
//
|
|
iEntry = CHEvictLRUCacheEntry(pCache, evictionCategory, evictionCount);
|
|
|
|
ASSERT(iEntry != CH_MAX_CACHE_ENTRIES);
|
|
|
|
TRACE_OUT(("CACHE: no free entries so evicted cache 0x%08x entry %d",
|
|
pCache, iEntry));
|
|
|
|
//
|
|
// Ideally we would now call CHFindFreeCacheEntry again to
|
|
// get the entry freed up via the eviction process, but since
|
|
// we just returned that entry use to to improve perf.
|
|
//
|
|
// However, the processing has left pCache->free pointing
|
|
// to the entry we just evicted and are about to use. So
|
|
// we need to fix it up.
|
|
//
|
|
ASSERT(pCache->free == iEntry);
|
|
pCache->free = pCache->Entry[iEntry].free;
|
|
}
|
|
|
|
|
|
//
|
|
// Fill in this entry's data
|
|
//
|
|
pEntry = &pCache->Entry[iEntry];
|
|
pEntry->pData = pData;
|
|
pEntry->cbData = cbDataSize;
|
|
pEntry->checkSum = checkSum;
|
|
pEntry->evictionCategory = (WORD)evictionCategory;
|
|
|
|
CHAvlInsert(pCache, pEntry);
|
|
TRACE_OUT(( "CACHE: NEW ENTRY cache 0x%08x entry %d csum 0x%08x pdata 0x%08x",
|
|
pCache, iEntry, checkSum, pEntry->pData));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We found the entry
|
|
//
|
|
preExisting = TRUE;
|
|
|
|
TRACE_OUT(( "CACHE: entry found in cache 0x%08x entry %d csum 0x%08x",
|
|
pCache, iEntry, checkSum));
|
|
}
|
|
|
|
CHUpdateMRUList(pCache, iEntry, evictionCategory);
|
|
*piCacheEntry = iEntry;
|
|
|
|
DebugExitBOOL(ASHost::CH_SearchAndCacheData, preExisting);
|
|
return(preExisting);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CH_RemoveCacheEntry
|
|
//
|
|
void ASHost::CH_RemoveCacheEntry
|
|
(
|
|
PCHCACHE pCache,
|
|
UINT iCacheEntry
|
|
)
|
|
{
|
|
DebugEntry(ASHost::CH_RemoveCacheEntry);
|
|
|
|
ASSERT(IsValidCache(pCache));
|
|
// ASSERT(IsValidCacheIndex(pCache, iCacheEntry)); Always True
|
|
|
|
CHEvictCacheEntry(pCache, iCacheEntry, pCache->Entry[iCacheEntry].evictionCategory);
|
|
|
|
DebugExitVOID(ASHost::CH_RemoveCacheEntry);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: CH_ClearCache
|
|
//
|
|
void ASHost::CH_ClearCache
|
|
(
|
|
PCHCACHE pCache
|
|
)
|
|
{
|
|
UINT i;
|
|
|
|
DebugEntry(ASHost::CH_ClearCache);
|
|
|
|
ASSERT(IsValidCache(pCache));
|
|
|
|
//
|
|
// Remove the cache entries
|
|
//
|
|
for (i = 0; i < pCache->cEntries; i++)
|
|
{
|
|
if (pCache->Entry[i].pData != NULL)
|
|
{
|
|
CHRemoveEntry(pCache, i);
|
|
}
|
|
}
|
|
|
|
DebugExitVOID(ASHost::CH_ClearCache);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// CH_TouchCacheEntry() - see ch.h
|
|
//
|
|
void ASHost::CH_TouchCacheEntry
|
|
(
|
|
PCHCACHE pCache,
|
|
UINT iCacheEntry
|
|
)
|
|
{
|
|
DebugEntry(ASHost::CH_TouchCacheEntry);
|
|
|
|
ASSERT(IsValidCache(pCache));
|
|
// ASSERT(IsValidCacheIndex(pCache, iCacheEntry)); Always True
|
|
|
|
TRACE_OUT(( "Touching cache entry 0x%08x %d", pCache, iCacheEntry));
|
|
|
|
CHUpdateMRUList(pCache, iCacheEntry, 0);
|
|
|
|
DebugExitVOID(ASHost::CH_TouchCacheEntry);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// CHInitEntry
|
|
// Initializes a cache entry
|
|
//
|
|
//
|
|
void ASHost::CHInitEntry(PCHENTRY pEntry)
|
|
{
|
|
pEntry->pParent = NULL;
|
|
pEntry->pLeft = NULL;
|
|
pEntry->pRight = NULL;
|
|
pEntry->pData = NULL;
|
|
pEntry->checkSum = 0;
|
|
pEntry->lHeight = 0xFFFF;
|
|
pEntry->rHeight = 0xFFFF;
|
|
pEntry->chain.next = CH_MAX_CACHE_ENTRIES;
|
|
pEntry->chain.prev = CH_MAX_CACHE_ENTRIES;
|
|
pEntry->cbData = 0;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: CHUpdateMRUList
|
|
//
|
|
void ASHost::CHUpdateMRUList
|
|
(
|
|
PCHCACHE pCache,
|
|
UINT iEntry,
|
|
UINT evictionCategory
|
|
)
|
|
{
|
|
WORD inext;
|
|
WORD iprev;
|
|
|
|
DebugEntry(ASHost::CHUpdateMRUList);
|
|
|
|
//
|
|
// Move the given entry to the head of the MRU if isn't there already
|
|
//
|
|
|
|
if (pCache->iMRUHead[evictionCategory] != iEntry)
|
|
{
|
|
//
|
|
// Remove the supplied entry from the MRU list, if it is currently
|
|
// chained. Since we never do this if the entry is already in the
|
|
// front, an iprev of CH_MAX_CACHE_ENTRIES indicates that we are
|
|
// updated an unchained entry.
|
|
//
|
|
iprev = pCache->Entry[iEntry].chain.prev;
|
|
inext = pCache->Entry[iEntry].chain.next;
|
|
TRACE_OUT(("Add/promote entry %u which was chained off %hu to %hu",
|
|
iEntry,iprev,inext));
|
|
|
|
if (iprev != CH_MAX_CACHE_ENTRIES)
|
|
{
|
|
pCache->Entry[iprev].chain.next = inext;
|
|
if (inext != CH_MAX_CACHE_ENTRIES)
|
|
{
|
|
pCache->Entry[inext].chain.prev = iprev;
|
|
}
|
|
else
|
|
{
|
|
TRACE_OUT(("Removing final entry(%u) from MRU chain leaving %hu at tail",
|
|
iEntry, iprev));
|
|
pCache->iMRUTail[evictionCategory] = iprev;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now add this entry to the head of the MRU list
|
|
//
|
|
inext = pCache->iMRUHead[evictionCategory];
|
|
pCache->Entry[iEntry].chain.next = inext;
|
|
pCache->Entry[iEntry].chain.prev = CH_MAX_CACHE_ENTRIES;
|
|
pCache->iMRUHead[evictionCategory] = (WORD)iEntry;
|
|
|
|
if (inext != CH_MAX_CACHE_ENTRIES)
|
|
{
|
|
pCache->Entry[inext].chain.prev = (WORD)iEntry;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// If the MRU chain is currently empty, then we must first add
|
|
// the entry to the tail of the chain.
|
|
//
|
|
pCache->iMRUTail[evictionCategory] = (WORD)iEntry;
|
|
TRACE_OUT(("Cache 0x%08x entry %u is first so add to MRU %u tail",
|
|
pCache, iEntry, evictionCategory));
|
|
}
|
|
|
|
TRACE_OUT(( "Cache 0x%08x entry %u to head of MRU category %u",
|
|
pCache, iEntry, evictionCategory));
|
|
|
|
}
|
|
else
|
|
{
|
|
TRACE_OUT(("Cache 0x%08x entry %u already at head of eviction category %u",
|
|
pCache, iEntry, evictionCategory));
|
|
}
|
|
|
|
DebugExitVOID(ASHost::CHUpateMRUList);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CHFindFreeCacheEntry
|
|
//
|
|
BOOL ASHost::CHFindFreeCacheEntry
|
|
(
|
|
PCHCACHE pCache,
|
|
UINT* piEntry,
|
|
UINT* pEvictionCount
|
|
)
|
|
{
|
|
UINT iEntry;
|
|
BOOL rc = FALSE;
|
|
|
|
DebugEntry(ASHost::CHFindFreeCacheEntry);
|
|
|
|
ASSERT(IsValidCache(pCache));
|
|
|
|
iEntry = pCache->free;
|
|
if (iEntry == CH_MAX_CACHE_ENTRIES)
|
|
{
|
|
TRACE_OUT(( "Cache 0x%08x is full", pCache));
|
|
|
|
*pEvictionCount = pCache->cEntries;
|
|
rc = FALSE;
|
|
}
|
|
else
|
|
{
|
|
TRACE_OUT(( "Free entry at %u",iEntry));
|
|
|
|
*piEntry = iEntry;
|
|
pCache->free = pCache->Entry[iEntry].free;
|
|
|
|
*pEvictionCount = 0;
|
|
rc = TRUE;
|
|
}
|
|
|
|
DebugExitBOOL(ASHost::CHFindFreeCacheEntry, rc);
|
|
return(rc);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: CHEvictCacheEntry
|
|
//
|
|
UINT ASHost::CHEvictCacheEntry
|
|
(
|
|
PCHCACHE pCache,
|
|
UINT iEntry,
|
|
UINT evictionCategory
|
|
)
|
|
{
|
|
WORD inext;
|
|
WORD iprev;
|
|
|
|
DebugEntry(ASHost::CHEvictCacheEntry);
|
|
|
|
//
|
|
// Evict the specified entry by removing it from the MRU chain, and
|
|
// then resetting it. If it is in the cache, it must be in an MRU
|
|
// cache.
|
|
//
|
|
|
|
inext = pCache->Entry[iEntry].chain.next;
|
|
iprev = pCache->Entry[iEntry].chain.prev;
|
|
TRACE_OUT(( "Evicting entry %u which was chained off %hu to %hu",
|
|
iEntry, iprev, inext));
|
|
|
|
if (iprev < CH_MAX_CACHE_ENTRIES)
|
|
{
|
|
pCache->Entry[iprev].chain.next = inext;
|
|
}
|
|
else
|
|
{
|
|
TRACE_OUT(("Removing head entry(%u) from MRU chain leaving %hu at head",
|
|
iEntry, inext));
|
|
pCache->iMRUHead[evictionCategory] = inext;
|
|
}
|
|
|
|
if (inext < CH_MAX_CACHE_ENTRIES)
|
|
{
|
|
pCache->Entry[inext].chain.prev = iprev;
|
|
}
|
|
else
|
|
{
|
|
TRACE_OUT(("Removing tail entry(%u) from MRU chain leaving %hu at tail",
|
|
iEntry, iprev));
|
|
pCache->iMRUTail[evictionCategory] = iprev;
|
|
}
|
|
|
|
CHRemoveEntry(pCache, iEntry);
|
|
|
|
DebugExitDWORD(ASHost::CHEvictCacheEntry, iEntry);
|
|
return(iEntry);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CHEvictLRUCacheEntry
|
|
//
|
|
UINT ASHost::CHEvictLRUCacheEntry
|
|
(
|
|
PCHCACHE pCache,
|
|
UINT evictionCategory,
|
|
UINT evictionCount
|
|
)
|
|
{
|
|
UINT iEntry;
|
|
UINT i;
|
|
|
|
DebugEntry(ASHost::CHEvictLRUCacheEntry);
|
|
|
|
TRACE_OUT(("0x%08x LRU eviction requested, category %u, count %u",
|
|
pCache, evictionCategory, evictionCount));
|
|
|
|
//
|
|
// Evict from the same eviction category provided the number cached
|
|
// is above the threshold. Otherwise, take from the category one above.
|
|
// This will allow the system to eventually stabilize at the correct
|
|
// thresholds as all cache entries get used up.
|
|
//
|
|
if (evictionCount < pCache->cEvictThreshold[evictionCategory])
|
|
{
|
|
for (i = 0; i < pCache->cNumEvictionCategories; i++)
|
|
{
|
|
evictionCategory = (evictionCategory + 1) %
|
|
pCache->cNumEvictionCategories;
|
|
if (pCache->iMRUTail[evictionCategory] != CH_MAX_CACHE_ENTRIES)
|
|
break;
|
|
}
|
|
|
|
WARNING_OUT(( "Threshold %u, count %u so set eviction category to %u",
|
|
pCache->cEvictThreshold[evictionCategory],
|
|
evictionCount,
|
|
evictionCategory));
|
|
}
|
|
|
|
//
|
|
// Evict the lasat entry in the MRU chain
|
|
//
|
|
iEntry = pCache->iMRUTail[evictionCategory];
|
|
TRACE_OUT(( "Selected %u for eviction",iEntry));
|
|
ASSERT((iEntry != CH_MAX_CACHE_ENTRIES));
|
|
|
|
CHEvictCacheEntry(pCache, iEntry, evictionCategory);
|
|
|
|
DebugExitDWORD(ASHost::CHEvictLRUCacheEntry, iEntry);
|
|
return(iEntry);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: CHRemoveEntry
|
|
//
|
|
void ASHost::CHRemoveEntry
|
|
(
|
|
PCHCACHE pCache,
|
|
UINT iCacheEntry
|
|
)
|
|
{
|
|
DebugEntry(ASHost::CHRemoveEntry);
|
|
|
|
ASSERT(IsValidCache(pCache));
|
|
// ASSERT(IsValidCacheIndex(pCache, iCacheEntry)); Always True
|
|
|
|
if (pCache->Entry[iCacheEntry].pData != NULL)
|
|
{
|
|
if (pCache->pfnCacheDel)
|
|
{
|
|
(pCache->pfnCacheDel)(this, pCache, iCacheEntry,
|
|
pCache->Entry[iCacheEntry].pData);
|
|
}
|
|
else
|
|
{
|
|
// Simple deletion -- just free memory
|
|
delete[] pCache->Entry[iCacheEntry].pData;
|
|
}
|
|
}
|
|
|
|
CHAvlDelete(pCache, &pCache->Entry[iCacheEntry], iCacheEntry);
|
|
|
|
DebugExitVOID(ASHost::CHRemoveEntry);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: CHCheckSum
|
|
//
|
|
// For processing speed we calculate the checksum based on whole multiples
|
|
// of 4 bytes followed by a final addition of the last few bytes
|
|
//
|
|
UINT ASHost::CHCheckSum
|
|
(
|
|
LPBYTE pData,
|
|
UINT cbDataSize
|
|
)
|
|
{
|
|
UINT cSum = 0;
|
|
UINT * pCh;
|
|
UINT * pEnd;
|
|
LPBYTE pCh8;
|
|
|
|
DebugEntry(ASHost::CHCheckSum);
|
|
|
|
ASSERT(cbDataSize > 3);
|
|
|
|
pCh = (UINT *)pData;
|
|
pEnd = (UINT *)(pData + cbDataSize - 4);
|
|
|
|
//
|
|
// Get the DWORD-aligned checksum
|
|
//
|
|
while (pCh <= pEnd)
|
|
{
|
|
cSum = (cSum << 1) + *pCh++ + ((cSum & 0x80000000) != 0);
|
|
}
|
|
|
|
//
|
|
// Get the rest past the last DWORD boundaray
|
|
//
|
|
pEnd = (UINT *)(pData + cbDataSize);
|
|
for (pCh8 = (LPBYTE)pCh; pCh8 < (LPBYTE)pEnd; pCh8++)
|
|
{
|
|
cSum = cSum + *pCh8;
|
|
}
|
|
|
|
DebugExitDWORD(ASHost::CHCheckSum, cSum);
|
|
return(cSum);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: CHTreeSearch
|
|
//
|
|
// Finds a node in the cache tree which matches size, checksum and data.
|
|
//
|
|
UINT ASHost::CHTreeSearch
|
|
(
|
|
PCHCACHE pCache,
|
|
UINT checkSum,
|
|
UINT cbDataSize,
|
|
LPBYTE pData
|
|
)
|
|
{
|
|
PCHENTRY pEntry;
|
|
UINT iCacheEntry = CH_MAX_CACHE_ENTRIES;
|
|
|
|
DebugEntry(ASHost::CHTreeSearch);
|
|
|
|
pEntry = CHAvlFind(pCache, checkSum, cbDataSize);
|
|
while (pEntry != NULL)
|
|
{
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
|
|
//
|
|
// Found a match based on the checksum. Now see if the data
|
|
// also matches.
|
|
//
|
|
if (!memcmp(pEntry->pData + pCache->cbNotHashed,
|
|
pData + pCache->cbNotHashed,
|
|
cbDataSize - pCache->cbNotHashed))
|
|
{
|
|
//
|
|
// Data also matches. Get an index into the memory block
|
|
// of the cache.
|
|
//
|
|
iCacheEntry = (UINT)(pEntry - pCache->Entry);
|
|
TRACE_OUT(( "Cache 0x%08x entry %d match-csum 0x%08x",
|
|
pCache, iCacheEntry, checkSum));
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
TRACE_OUT(( "Checksum 0x%08x size %u matched, data didn't",
|
|
checkSum, cbDataSize));
|
|
|
|
pEntry = CHAvlFindEqual(pCache, pEntry);
|
|
}
|
|
}
|
|
|
|
DebugExitDWORD(ASHost::CHTreeSearch, iCacheEntry);
|
|
return(iCacheEntry);
|
|
}
|
|
|
|
|
|
//
|
|
// Name: CHAvlInsert
|
|
//
|
|
// Purpose: Insert the supplied node into the specified AVL tree
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Params: IN pTree - a pointer to the AVL tree
|
|
// IN pEntry - a pointer to the node to insert
|
|
//
|
|
// Operation: Scan down the tree looking for the insert point, going left
|
|
// if the insert key is less than or equal to the key in the tree
|
|
// and right if it is greater. When the insert point is found
|
|
// insert the new node and rebalance the tree if necessary.
|
|
//
|
|
//
|
|
void ASHost::CHAvlInsert
|
|
(
|
|
PCHCACHE pCache,
|
|
PCHENTRY pEntry
|
|
)
|
|
{
|
|
PCHENTRY pParentEntry;
|
|
int result;
|
|
|
|
DebugEntry(ASHost::CHAvlInsert);
|
|
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
ASSERT(!IsCacheEntryInTree(pEntry));
|
|
|
|
pEntry->rHeight = 0;
|
|
pEntry->lHeight = 0;
|
|
|
|
if (pCache->pRoot == NULL)
|
|
{
|
|
//
|
|
// tree is empty, so insert at root
|
|
//
|
|
TRACE_OUT(( "tree is empty, so insert at root" ));
|
|
pCache->pRoot = pEntry;
|
|
pCache->pFirst = pEntry;
|
|
pCache->pLast = pEntry;
|
|
DC_QUIT;
|
|
}
|
|
|
|
//
|
|
// scan down the tree looking for the appropriate insert point
|
|
//
|
|
TRACE_OUT(( "scan for insert point" ));
|
|
pParentEntry = pCache->pRoot;
|
|
while (pParentEntry != NULL)
|
|
{
|
|
//
|
|
// go left or right, depending on comparison
|
|
//
|
|
result = CHCompare(pEntry->checkSum, pEntry->cbData, pParentEntry);
|
|
|
|
if (result > 0)
|
|
{
|
|
//
|
|
// new key is greater than this node's key, so move down right
|
|
// subtree
|
|
//
|
|
TRACE_OUT(( "move down right subtree" ));
|
|
if (pParentEntry->pRight == NULL)
|
|
{
|
|
//
|
|
// right subtree is empty, so insert here
|
|
//
|
|
TRACE_OUT(( "right subtree empty, insert here" ));
|
|
|
|
pEntry->pParent = pParentEntry;
|
|
ASSERT((pParentEntry != pEntry));
|
|
|
|
pParentEntry->pRight = pEntry;
|
|
pParentEntry->rHeight = 1;
|
|
if (pParentEntry == pCache->pLast)
|
|
{
|
|
//
|
|
// parent was the right-most node in the tree, so new
|
|
// node is now right-most
|
|
//
|
|
TRACE_OUT(( "new last node" ));
|
|
pCache->pLast = pEntry;
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// right subtree is not empty
|
|
//
|
|
TRACE_OUT(( "right subtree not empty" ));
|
|
pParentEntry = pParentEntry->pRight;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// New key is less than or equal to this node's key, so move
|
|
// down left subtree. The new node could be inserted before
|
|
// the current node when equal, but this happens so rarely
|
|
// that it's not worth special casing.
|
|
//
|
|
TRACE_OUT(( "move down left subtree" ));
|
|
if (pParentEntry->pLeft == NULL)
|
|
{
|
|
//
|
|
// left subtree is empty, so insert here
|
|
//
|
|
TRACE_OUT(( "left subtree empty, insert here" ));
|
|
pEntry->pParent = pParentEntry;
|
|
ASSERT((pParentEntry != pEntry));
|
|
|
|
pParentEntry->pLeft = pEntry;
|
|
pParentEntry->lHeight = 1;
|
|
if (pParentEntry == pCache->pFirst)
|
|
{
|
|
//
|
|
// parent was the left-most node in the tree, so new
|
|
// node is now left-most
|
|
//
|
|
TRACE_OUT(( "new first node" ));
|
|
pCache->pFirst = pEntry;
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// left subtree is not empty
|
|
//
|
|
TRACE_OUT(( "left subtree not empty" ));
|
|
pParentEntry = pParentEntry->pLeft;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// now rebalance the tree if necessary
|
|
//
|
|
CHAvlBalanceTree(pCache, pParentEntry);
|
|
|
|
DC_EXIT_POINT:
|
|
DebugExitVOID(ASHost::CHAvlInsert);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Name: CHAvlDelete
|
|
//
|
|
// Purpose: Delete the specified node from the specified AVL tree
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Params: IN pCache - a pointer to the AVL tree
|
|
// IN pEntry - a pointer to the node to delete
|
|
//
|
|
//
|
|
void ASHost::CHAvlDelete
|
|
(
|
|
PCHCACHE pCache,
|
|
PCHENTRY pEntry,
|
|
UINT iCacheEntry
|
|
)
|
|
{
|
|
PCHENTRY pReplaceEntry;
|
|
PCHENTRY pParentEntry;
|
|
WORD newHeight;
|
|
|
|
DebugEntry(ASHost::CHAvlDelete);
|
|
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
ASSERT(IsCacheEntryInTree(pEntry));
|
|
|
|
|
|
if ((pEntry->pLeft == NULL) && (pEntry->pRight == NULL))
|
|
{
|
|
//
|
|
// Barren node (no children). Update all references to it with
|
|
// our parent.
|
|
//
|
|
TRACE_OUT(( "delete barren node" ));
|
|
pReplaceEntry = NULL;
|
|
|
|
if (pCache->pFirst == pEntry)
|
|
{
|
|
//
|
|
// We are the first in the b-tree
|
|
//
|
|
TRACE_OUT(( "replace first node in tree" ));
|
|
pCache->pFirst = pEntry->pParent;
|
|
}
|
|
|
|
if (pCache->pLast == pEntry)
|
|
{
|
|
//
|
|
// We are the last in the b-tree
|
|
//
|
|
TRACE_OUT(( "replace last node in tree" ));
|
|
pCache->pLast = pEntry->pParent;
|
|
}
|
|
}
|
|
else if (pEntry->pLeft == NULL)
|
|
{
|
|
//
|
|
// This node has no left child, so update references to it with
|
|
// pointer to right child.
|
|
//
|
|
TRACE_OUT(( "node has no left child, replace with right child" ));
|
|
pReplaceEntry = pEntry->pRight;
|
|
|
|
if (pCache->pFirst == pEntry)
|
|
{
|
|
//
|
|
// We are the first in the b-tree
|
|
//
|
|
TRACE_OUT(( "replace first node in tree" ));
|
|
pCache->pFirst = pReplaceEntry;
|
|
}
|
|
|
|
// WE CAN'T BE THE LAST IN THE B-TREE SINCE WE HAVE A RIGHT CHILD
|
|
ASSERT(pCache->pLast != pEntry);
|
|
}
|
|
else if (pEntry->pRight == NULL)
|
|
{
|
|
//
|
|
// This node has no right child, so update references to it with
|
|
// pointer to left child.
|
|
//
|
|
TRACE_OUT(( "node has no right son, replace with left son" ));
|
|
pReplaceEntry = pEntry->pLeft;
|
|
|
|
// WE CAN'T BE THE FIRST IN THE B-TREE SINCE WE HAVE A LEFT CHILD
|
|
ASSERT(pCache->pFirst != pEntry);
|
|
|
|
if (pCache->pLast == pEntry)
|
|
{
|
|
//
|
|
// We are the last in the b-tree
|
|
//
|
|
TRACE_OUT(( "replace last node in tree" ));
|
|
pCache->pLast = pReplaceEntry;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// HARDEST CASE. WE HAVE LEFT AND RIGHT CHILDREN
|
|
TRACE_OUT(( "node has two sons" ));
|
|
if (pEntry->rHeight > pEntry->lHeight)
|
|
{
|
|
//
|
|
// Right subtree is bigger than left subtree
|
|
//
|
|
TRACE_OUT(( "right subtree is higher" ));
|
|
if (pEntry->pRight->pLeft == NULL)
|
|
{
|
|
//
|
|
// Replace references to entry with right child since it
|
|
// has no left child (left grandchild of us)
|
|
//
|
|
TRACE_OUT(( "replace node with right son" ));
|
|
pReplaceEntry = pEntry->pRight;
|
|
pReplaceEntry->pLeft = pEntry->pLeft;
|
|
pReplaceEntry->pLeft->pParent = pReplaceEntry;
|
|
pReplaceEntry->lHeight = pEntry->lHeight;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Swap with leftmost descendent of the right subtree
|
|
//
|
|
TRACE_OUT(( "swap with left-most right descendent" ));
|
|
CHAvlSwapLeftmost(pCache, pEntry->pRight, pEntry);
|
|
pReplaceEntry = pEntry->pRight;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Left subtree is bigger than or equal to right subtree
|
|
//
|
|
TRACE_OUT(( "left subtree is higher" ));
|
|
TRACE_OUT(( "(or both subtrees are of equal height)" ));
|
|
if (pEntry->pLeft->pRight == NULL)
|
|
{
|
|
//
|
|
// Replace references to entry with left child since it
|
|
// no right child (right grandchild of us)
|
|
//
|
|
TRACE_OUT(( "replace node with left son" ));
|
|
pReplaceEntry = pEntry->pLeft;
|
|
pReplaceEntry->pRight = pEntry->pRight;
|
|
pReplaceEntry->pRight->pParent = pReplaceEntry;
|
|
pReplaceEntry->rHeight = pEntry->rHeight;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Swap with rightmost descendent of the left subtree
|
|
//
|
|
TRACE_OUT(( "swap with right-most left descendent" ));
|
|
CHAvlSwapRightmost(pCache, pEntry->pLeft, pEntry);
|
|
pReplaceEntry = pEntry->pLeft;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// NOTE: We can not save parent entry above because some code might
|
|
// swap the tree around. In which case, our parenty entry will change
|
|
// out from underneath us.
|
|
//
|
|
pParentEntry = pEntry->pParent;
|
|
|
|
//
|
|
// Clear out the about-to-be-deleted cache entry
|
|
//
|
|
TRACE_OUT(( "reset deleted node" ));
|
|
CHInitEntry(pEntry);
|
|
|
|
if (pReplaceEntry != NULL)
|
|
{
|
|
//
|
|
// Fix up parent pointers, and calculate new heights of subtree
|
|
//
|
|
TRACE_OUT(( "fixup parent pointer of replacement node" ));
|
|
pReplaceEntry->pParent = pParentEntry;
|
|
newHeight = (WORD)(1 + max(pReplaceEntry->lHeight, pReplaceEntry->rHeight));
|
|
}
|
|
else
|
|
{
|
|
newHeight = 0;
|
|
}
|
|
TRACE_OUT(( "new height of parent is %d", newHeight ));
|
|
|
|
if (pParentEntry != NULL)
|
|
{
|
|
//
|
|
// Fixup parent entry pointers
|
|
//
|
|
TRACE_OUT(( "fix-up parent node" ));
|
|
if (pParentEntry->pRight == pEntry)
|
|
{
|
|
//
|
|
// Entry is right child of parent
|
|
//
|
|
TRACE_OUT(( "replacement node is right son" ));
|
|
pParentEntry->pRight = pReplaceEntry;
|
|
pParentEntry->rHeight = newHeight;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Entry is left child of parent
|
|
//
|
|
TRACE_OUT(( "replacement node is left son" ));
|
|
pParentEntry->pLeft = pReplaceEntry;
|
|
pParentEntry->lHeight = newHeight;
|
|
}
|
|
|
|
//
|
|
// Now rebalance the tree if necessary
|
|
//
|
|
CHAvlBalanceTree(pCache, pParentEntry);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Replacement is now root of tree
|
|
//
|
|
TRACE_OUT(( "replacement node is now root of tree" ));
|
|
pCache->pRoot = pReplaceEntry;
|
|
}
|
|
|
|
|
|
//
|
|
// Put entry back into free list.
|
|
//
|
|
pEntry->free = pCache->free;
|
|
pCache->free = (WORD)iCacheEntry;
|
|
|
|
DebugExitVOID(ASHost::CHAvlDelete);
|
|
}
|
|
|
|
|
|
//
|
|
// Name: CHAvlNext
|
|
//
|
|
// Purpose: Find next node in the AVL tree
|
|
//
|
|
// Returns: A pointer to the next node's data
|
|
//
|
|
// Params: IN pEntry - a pointer to the current node in
|
|
// the tree
|
|
//
|
|
// Operation: If the specified node has a right-son then return the left-
|
|
// most son of this. Otherwise search back up until we find a
|
|
// node of which we are in the left sub-tree and return that.
|
|
//
|
|
//
|
|
LPBYTE ASHost::CHAvlNext
|
|
(
|
|
PCHENTRY pEntry
|
|
)
|
|
{
|
|
//
|
|
// find next node in tree
|
|
//
|
|
DebugEntry(ASHost::CHAvlNext);
|
|
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
ASSERT(IsCacheEntryInTree(pEntry));
|
|
|
|
if (pEntry->pRight != NULL)
|
|
{
|
|
//
|
|
// Next entry is the left-most in the right-subtree
|
|
//
|
|
TRACE_OUT(( "next node is left-most right descendent" ));
|
|
pEntry = pEntry->pRight;
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
|
|
while (pEntry->pLeft != NULL)
|
|
{
|
|
ASSERT(IsValidCacheEntry(pEntry->pLeft));
|
|
pEntry = pEntry->pLeft;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No right child. So find an entry for which we are in its left
|
|
// subtree.
|
|
//
|
|
TRACE_OUT(( "find node which this is in left subtree of" ));
|
|
|
|
while (pEntry != NULL)
|
|
{
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
|
|
if ((pEntry->pParent == NULL) ||
|
|
(pEntry->pParent->pLeft == pEntry))
|
|
{
|
|
pEntry = pEntry->pParent;
|
|
break;
|
|
}
|
|
pEntry = pEntry->pParent;
|
|
}
|
|
}
|
|
|
|
DebugExitVOID(ASHost::CHAvlNext);
|
|
return((pEntry != NULL) ? pEntry->pData : NULL);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Name: CHAvlPrev
|
|
//
|
|
// Purpose: Find previous node in the AVL tree
|
|
//
|
|
// Returns: A pointer to the previous node's data in the tree
|
|
//
|
|
// Params: IN PNode - a pointer to the current node in
|
|
// the tree
|
|
//
|
|
// Operation: If we have a left-son then the previous node is the right-most
|
|
// son of this. Otherwise, look for a node of whom we are in the
|
|
// left subtree and return that.
|
|
//
|
|
//
|
|
LPBYTE ASHost::CHAvlPrev(PCHENTRY pEntry)
|
|
{
|
|
//
|
|
// find previous node in tree
|
|
//
|
|
DebugEntry(ASHost::CHAvlPrev);
|
|
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
ASSERT(IsCacheEntryInTree(pEntry));
|
|
|
|
if (pEntry->pLeft != NULL)
|
|
{
|
|
//
|
|
// Previous entry is right-most in left-subtree
|
|
//
|
|
TRACE_OUT(( "previous node is right-most left descendent" ));
|
|
|
|
pEntry = pEntry->pLeft;
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
|
|
while (pEntry->pRight != NULL)
|
|
{
|
|
ASSERT(IsValidCacheEntry(pEntry->pRight));
|
|
pEntry = pEntry->pRight;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No left child. So find an entry for which we are in the right
|
|
// subtree.
|
|
//
|
|
TRACE_OUT(( "find node which this is in right subtree of"));
|
|
while (pEntry != NULL)
|
|
{
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
|
|
if ((pEntry->pParent == NULL) ||
|
|
(pEntry->pParent->pRight == pEntry))
|
|
{
|
|
pEntry = pEntry->pParent;
|
|
break;
|
|
}
|
|
|
|
pEntry = pEntry->pParent;
|
|
}
|
|
}
|
|
|
|
DebugExitVOID(ASHost::CHAvlPrev);
|
|
return((pEntry != NULL) ? pEntry->pData : NULL);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Name: CHAvlFindEqual
|
|
//
|
|
// Purpose: Find the node in the AVL tree with the same key and size as
|
|
// the supplied node
|
|
//
|
|
// Returns: A pointer to the node
|
|
// NULL if no node is found with the specified key and size
|
|
//
|
|
// Params: IN pCache - a pointer to the AVL tree
|
|
// IN pEntry - a pointer to the node to test
|
|
//
|
|
// Operation: Check if the left node has the same key and size, returning
|
|
// a pointer to its data if it does.
|
|
//
|
|
//
|
|
PCHENTRY ASHost::CHAvlFindEqual
|
|
(
|
|
PCHCACHE pCache,
|
|
PCHENTRY pEntry
|
|
)
|
|
{
|
|
int result;
|
|
PCHENTRY pReturn = NULL;
|
|
|
|
DebugEntry(ASHost::CHAvlFindEqual);
|
|
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
|
|
if (pEntry->pLeft)
|
|
{
|
|
ASSERT(IsValidCacheEntry(pEntry->pLeft));
|
|
|
|
result = CHCompare(pEntry->pLeft->checkSum, pEntry->cbData, pEntry);
|
|
|
|
if (result < 0)
|
|
{
|
|
//
|
|
// specified key is less than key of this node - this is what
|
|
// will normally occur
|
|
//
|
|
TRACE_OUT(( "left node size %u csum 0x%08x",
|
|
pEntry->pLeft->cbData,
|
|
pEntry->pLeft->checkSum));
|
|
}
|
|
else if (result == 0)
|
|
{
|
|
//
|
|
// Found a match on size and key.
|
|
//
|
|
TRACE_OUT(( "left node dups size and key" ));
|
|
pReturn = pEntry->pLeft;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is an error (left node should never be greater)
|
|
//
|
|
ERROR_OUT(( "left node csum %#lx, supplied %#lx",
|
|
pEntry->pLeft->checkSum,
|
|
pEntry->checkSum));
|
|
}
|
|
}
|
|
|
|
DebugExitPVOID(ASHost::CHAvlFindEqual, pReturn);
|
|
return(pReturn);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Name: CHAvlFind
|
|
//
|
|
// Purpose: Find the node in the AVL tree with the supplied key and size
|
|
//
|
|
// Returns: A pointer to the node
|
|
// NULL if no node is found with the specified key and size
|
|
//
|
|
// Params: IN pCache - a pointer to the AVL tree
|
|
// IN checkSum - a pointer to the key
|
|
// IN cbSize - number of node data bytes
|
|
//
|
|
// Operation: Search down the tree going left if the search key is less than
|
|
// the node in the tree and right if the search key is greater.
|
|
// When we run out of tree to search through either we've found
|
|
// it or the node is not in the tree.
|
|
//
|
|
//
|
|
PCHENTRY ASHost::CHAvlFind
|
|
(
|
|
PCHCACHE pCache,
|
|
UINT checkSum,
|
|
UINT cbSize
|
|
)
|
|
{
|
|
PCHENTRY pEntry;
|
|
int result;
|
|
|
|
// DebugEntry(ASHost::CHAvlFind);
|
|
|
|
pEntry = pCache->pRoot;
|
|
|
|
while (pEntry != NULL)
|
|
{
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
|
|
//
|
|
// Compare the supplied key (checksum) with that of the current node
|
|
//
|
|
result = CHCompare(checkSum, cbSize, pEntry);
|
|
|
|
if (result > 0)
|
|
{
|
|
//
|
|
// Supplied key is greater than that of this entry, so look in
|
|
// the right subtree
|
|
//
|
|
pEntry = pEntry->pRight;
|
|
TRACE_OUT(( "move down right subtree to node 0x%08x", pEntry));
|
|
}
|
|
else if (result < 0)
|
|
{
|
|
//
|
|
// Supplied key is lesser than that of this entry, so look in
|
|
// the left subtree
|
|
//
|
|
pEntry = pEntry->pLeft;
|
|
TRACE_OUT(( "move down left subtree to node 0x%08x", pEntry));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We found the FIRST entry with an identical key (checksum).
|
|
//
|
|
TRACE_OUT(( "found requested node" ));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// DebugExitPVOID(ASHost::CHAvlFind, pEntry);
|
|
return(pEntry);
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// Name: CHAvlBalanceTree
|
|
//
|
|
// Purpose: Reblance the tree starting at the supplied node and ending at
|
|
// the root of the tree
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Params: IN pCache - a pointer to the AVL tree
|
|
// IN pEntry - a pointer to the node to start
|
|
// balancing from
|
|
//
|
|
//
|
|
void ASHost::CHAvlBalanceTree
|
|
(
|
|
PCHCACHE pCache,
|
|
PCHENTRY pEntry
|
|
)
|
|
{
|
|
//
|
|
// Balance the tree starting at the given entry, ending with the root
|
|
// of the tree
|
|
//
|
|
DebugEntry(ASHost::CHAvlBalanceTree);
|
|
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
|
|
while (pEntry->pParent != NULL)
|
|
{
|
|
ASSERT(IsValidCacheEntry(pEntry->pParent));
|
|
|
|
//
|
|
// node has uneven balance, so may need to rebalance it
|
|
//
|
|
TRACE_OUT(( "check node balance" ));
|
|
TRACE_OUT(( " rHeight = %hd", pEntry->rHeight ));
|
|
TRACE_OUT(( " lHeight = %hd", pEntry->lHeight ));
|
|
|
|
if (pEntry->pParent->pRight == pEntry)
|
|
{
|
|
//
|
|
// node is right-son of its parent
|
|
//
|
|
TRACE_OUT(( "node is right-son" ));
|
|
pEntry = pEntry->pParent;
|
|
CHAvlRebalance(&pEntry->pRight);
|
|
|
|
//
|
|
// now update the right height of the parent
|
|
//
|
|
pEntry->rHeight = (WORD)
|
|
(1 + max(pEntry->pRight->rHeight, pEntry->pRight->lHeight));
|
|
TRACE_OUT(( "new rHeight = %d", pEntry->rHeight ));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// node is left-son of its parent
|
|
//
|
|
TRACE_OUT(( "node is left-son" ));
|
|
pEntry = pEntry->pParent;
|
|
CHAvlRebalance(&pEntry->pLeft);
|
|
|
|
//
|
|
// now update the left height of the parent
|
|
//
|
|
pEntry->lHeight = (WORD)
|
|
(1 + max(pEntry->pLeft->rHeight, pEntry->pLeft->lHeight));
|
|
TRACE_OUT(( "new lHeight = %d", pEntry->lHeight ));
|
|
}
|
|
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
}
|
|
|
|
if (pEntry->lHeight != pEntry->rHeight)
|
|
{
|
|
//
|
|
// rebalance root node
|
|
//
|
|
TRACE_OUT(( "rebalance root node"));
|
|
CHAvlRebalance(&pCache->pRoot);
|
|
}
|
|
|
|
DebugExitVOID(ASHost::CHAvlBalanceTree);
|
|
}
|
|
|
|
//
|
|
// Name: CHAvlRebalance
|
|
//
|
|
// Purpose: Reblance a subtree of the AVL tree (if necessary)
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Params: IN/OUT ppSubtree - a pointer to the subtree to
|
|
// rebalance
|
|
//
|
|
//
|
|
void ASHost::CHAvlRebalance
|
|
(
|
|
PCHENTRY * ppSubtree
|
|
)
|
|
{
|
|
int moment;
|
|
|
|
DebugEntry(ASHost::CHAvlRebalance);
|
|
|
|
ASSERT(IsValidCacheEntry(*ppSubtree));
|
|
|
|
TRACE_OUT(( "rebalance subtree" ));
|
|
TRACE_OUT(( " rHeight = %hd", (*ppSubtree)->rHeight ));
|
|
TRACE_OUT(( " lHeight = %hd", (*ppSubtree)->lHeight ));
|
|
|
|
//
|
|
// How unbalanced - don't want to recalculate
|
|
//
|
|
moment = (*ppSubtree)->rHeight - (*ppSubtree)->lHeight;
|
|
|
|
if (moment > 1)
|
|
{
|
|
//
|
|
// subtree is heavy on the right side
|
|
//
|
|
TRACE_OUT(( "subtree is heavy on right side" ));
|
|
TRACE_OUT(( "right subtree" ));
|
|
TRACE_OUT(( " rHeight = %d", (*ppSubtree)->pRight->rHeight ));
|
|
TRACE_OUT(( " lHeight = %d", (*ppSubtree)->pRight->lHeight ));
|
|
if ((*ppSubtree)->pRight->lHeight > (*ppSubtree)->pRight->rHeight)
|
|
{
|
|
//
|
|
// right subtree is heavier on left side, so must perform right
|
|
// rotation on this subtree to make it heavier on the right
|
|
// side
|
|
//
|
|
TRACE_OUT(( "right subtree is heavier on left side ..." ));
|
|
TRACE_OUT(( "... so rotate it right" ));
|
|
CHAvlRotateRight(&(*ppSubtree)->pRight);
|
|
TRACE_OUT(( "right subtree" ));
|
|
TRACE_OUT(( " rHeight = %d", (*ppSubtree)->pRight->rHeight ));
|
|
TRACE_OUT(( " lHeight = %d", (*ppSubtree)->pRight->lHeight ));
|
|
}
|
|
|
|
//
|
|
// now rotate the subtree left
|
|
//
|
|
TRACE_OUT(( "rotate subtree left" ));
|
|
CHAvlRotateLeft(ppSubtree);
|
|
}
|
|
else if (moment < -1)
|
|
{
|
|
//
|
|
// subtree is heavy on the left side
|
|
//
|
|
TRACE_OUT(( "subtree is heavy on left side" ));
|
|
TRACE_OUT(( "left subtree" ));
|
|
TRACE_OUT(( " rHeight = %d", (*ppSubtree)->pLeft->rHeight ));
|
|
TRACE_OUT(( " lHeight = %d", (*ppSubtree)->pLeft->lHeight ));
|
|
if ((*ppSubtree)->pLeft->rHeight > (*ppSubtree)->pLeft->lHeight)
|
|
{
|
|
//
|
|
// left subtree is heavier on right side, so must perform left
|
|
// rotation on this subtree to make it heavier on the left side
|
|
//
|
|
TRACE_OUT(( "left subtree is heavier on right side ..." ));
|
|
TRACE_OUT(( "... so rotate it left" ));
|
|
CHAvlRotateLeft(&(*ppSubtree)->pLeft);
|
|
TRACE_OUT(( "left subtree" ));
|
|
TRACE_OUT(( " rHeight = %d", (*ppSubtree)->pLeft->rHeight ));
|
|
TRACE_OUT(( " lHeight = %d", (*ppSubtree)->pLeft->lHeight ));
|
|
}
|
|
|
|
//
|
|
// now rotate the subtree right
|
|
//
|
|
TRACE_OUT(( "rotate subtree right" ));
|
|
CHAvlRotateRight(ppSubtree);
|
|
}
|
|
|
|
TRACE_OUT(( "balanced subtree" ));
|
|
TRACE_OUT(( " rHeight = %d", (*ppSubtree)->rHeight ));
|
|
TRACE_OUT(( " lHeight = %d", (*ppSubtree)->lHeight ));
|
|
|
|
DebugExitVOID(ASHost::CHAvlRebalance);
|
|
}
|
|
|
|
//
|
|
// Name: CHAvlRotateRight
|
|
//
|
|
// Purpose: Rotate a subtree of the AVL tree right
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Params: IN/OUT ppSubtree - a pointer to the subtree to rotate
|
|
//
|
|
//
|
|
void ASHost::CHAvlRotateRight
|
|
(
|
|
PCHENTRY * ppSubtree
|
|
)
|
|
{
|
|
PCHENTRY pLeftSon;
|
|
|
|
DebugEntry(ASHost::CHAvlRotateRight);
|
|
|
|
ASSERT(IsValidCacheEntry(*ppSubtree));
|
|
pLeftSon = (*ppSubtree)->pLeft;
|
|
ASSERT(IsValidCacheEntry(pLeftSon));
|
|
|
|
(*ppSubtree)->pLeft = pLeftSon->pRight;
|
|
if ((*ppSubtree)->pLeft != NULL)
|
|
{
|
|
(*ppSubtree)->pLeft->pParent = (*ppSubtree);
|
|
}
|
|
(*ppSubtree)->lHeight = pLeftSon->rHeight;
|
|
|
|
pLeftSon->pParent = (*ppSubtree)->pParent;
|
|
|
|
pLeftSon->pRight = *ppSubtree;
|
|
pLeftSon->pRight->pParent = pLeftSon;
|
|
pLeftSon->rHeight = (WORD)
|
|
(1 + max((*ppSubtree)->rHeight, (*ppSubtree)->lHeight));
|
|
|
|
*ppSubtree = pLeftSon;
|
|
|
|
DebugExitVOID(ASHost::CHAvlRotateRight);
|
|
}
|
|
|
|
//
|
|
// Name: CHAvlRotateLeft
|
|
//
|
|
// Purpose: Rotate a subtree of the AVL tree left
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Params: IN/OUT ppSubtree - a pointer to the subtree to rotate
|
|
//
|
|
//
|
|
void ASHost::CHAvlRotateLeft
|
|
(
|
|
PCHENTRY * ppSubtree
|
|
)
|
|
{
|
|
PCHENTRY pRightSon;
|
|
|
|
DebugEntry(ASHost::CHAvlRotateLeft);
|
|
|
|
ASSERT(IsValidCacheEntry(*ppSubtree));
|
|
pRightSon = (*ppSubtree)->pRight;
|
|
ASSERT(IsValidCacheEntry(pRightSon));
|
|
|
|
(*ppSubtree)->pRight = pRightSon->pLeft;
|
|
if ((*ppSubtree)->pRight != NULL)
|
|
{
|
|
(*ppSubtree)->pRight->pParent = (*ppSubtree);
|
|
}
|
|
(*ppSubtree)->rHeight = pRightSon->lHeight;
|
|
|
|
pRightSon->pParent = (*ppSubtree)->pParent;
|
|
|
|
pRightSon->pLeft = *ppSubtree;
|
|
pRightSon->pLeft->pParent = pRightSon;
|
|
pRightSon->lHeight = (WORD)
|
|
(1 + max((*ppSubtree)->rHeight, (*ppSubtree)->lHeight));
|
|
|
|
*ppSubtree = pRightSon;
|
|
|
|
DebugExitVOID(ASHost::CHAvlRotateLeft);
|
|
}
|
|
|
|
|
|
//
|
|
// Name: CHAvlSwapRightmost
|
|
//
|
|
// Purpose: Swap node with right-most descendent of subtree
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Params: IN pCache - a pointer to the tree
|
|
// IN pSubtree - a pointer to the subtree
|
|
// IN pEntry - a pointer to the node to swap
|
|
//
|
|
//
|
|
void ASHost::CHAvlSwapRightmost
|
|
(
|
|
PCHCACHE pCache,
|
|
PCHENTRY pSubtree,
|
|
PCHENTRY pEntry
|
|
)
|
|
{
|
|
PCHENTRY pSwapEntry;
|
|
PCHENTRY pSwapParent;
|
|
PCHENTRY pSwapLeft;
|
|
|
|
DebugEntry(ASHost::CHAvlSwapRightmost);
|
|
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
ASSERT((pEntry->pRight != NULL));
|
|
ASSERT((pEntry->pLeft != NULL));
|
|
|
|
//
|
|
// find right-most descendent of subtree
|
|
//
|
|
ASSERT(IsValidCacheEntry(pSubtree));
|
|
pSwapEntry = pSubtree;
|
|
while (pSwapEntry->pRight != NULL)
|
|
{
|
|
pSwapEntry = pSwapEntry->pRight;
|
|
ASSERT(IsValidCacheEntry(pSwapEntry));
|
|
}
|
|
|
|
ASSERT((pSwapEntry->rHeight == 0));
|
|
ASSERT((pSwapEntry->lHeight <= 1));
|
|
|
|
//
|
|
// save parent and left-son of right-most descendent
|
|
//
|
|
pSwapParent = pSwapEntry->pParent;
|
|
pSwapLeft = pSwapEntry->pLeft;
|
|
|
|
//
|
|
// move swap node to its new position
|
|
//
|
|
pSwapEntry->pParent = pEntry->pParent;
|
|
pSwapEntry->pRight = pEntry->pRight;
|
|
pSwapEntry->pLeft = pEntry->pLeft;
|
|
pSwapEntry->rHeight = pEntry->rHeight;
|
|
pSwapEntry->lHeight = pEntry->lHeight;
|
|
pSwapEntry->pRight->pParent = pSwapEntry;
|
|
pSwapEntry->pLeft->pParent = pSwapEntry;
|
|
if (pEntry->pParent == NULL)
|
|
{
|
|
//
|
|
// node is at root of tree
|
|
//
|
|
pCache->pRoot = pSwapEntry;
|
|
}
|
|
else if (pEntry->pParent->pRight == pEntry)
|
|
{
|
|
//
|
|
// node is right-son of parent
|
|
//
|
|
pSwapEntry->pParent->pRight = pSwapEntry;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// node is left-son of parent
|
|
//
|
|
pSwapEntry->pParent->pLeft = pSwapEntry;
|
|
}
|
|
|
|
//
|
|
// move node to its new position
|
|
//
|
|
pEntry->pParent = pSwapParent;
|
|
pEntry->pRight = NULL;
|
|
pEntry->pLeft = pSwapLeft;
|
|
if (pEntry->pLeft != NULL)
|
|
{
|
|
pEntry->pLeft->pParent = pEntry;
|
|
pEntry->lHeight = 1;
|
|
}
|
|
else
|
|
{
|
|
pEntry->lHeight = 0;
|
|
}
|
|
pEntry->rHeight = 0;
|
|
pEntry->pParent->pRight = pEntry;
|
|
|
|
DebugExitVOID(ASHost::CHAvlSwapRightmost);
|
|
}
|
|
|
|
//
|
|
// Name: CHAvlSwapLeftmost
|
|
//
|
|
// Purpose: Swap node with left-most descendent of subtree
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Params: IN pCache - a pointer to the tree
|
|
// IN pSubtree - a pointer to the subtree
|
|
// IN pEntry - a pointer to the node to swap
|
|
//
|
|
//
|
|
void ASHost::CHAvlSwapLeftmost
|
|
(
|
|
PCHCACHE pCache,
|
|
PCHENTRY pSubtree,
|
|
PCHENTRY pEntry
|
|
)
|
|
{
|
|
PCHENTRY pSwapEntry;
|
|
PCHENTRY pSwapParent;
|
|
PCHENTRY pSwapRight;
|
|
|
|
DebugEntry(ASHost::CHAvlSwapLeftmost);
|
|
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
ASSERT((pEntry->pRight != NULL));
|
|
ASSERT((pEntry->pLeft != NULL));
|
|
|
|
//
|
|
// find left-most descendent of pSubtree
|
|
//
|
|
ASSERT(IsValidCacheEntry(pSubtree));
|
|
pSwapEntry = pSubtree;
|
|
while (pSwapEntry->pLeft != NULL)
|
|
{
|
|
pSwapEntry = pSwapEntry->pLeft;
|
|
ASSERT(IsValidCacheEntry(pSwapEntry));
|
|
}
|
|
|
|
ASSERT((pSwapEntry->lHeight == 0));
|
|
ASSERT((pSwapEntry->rHeight <= 1));
|
|
|
|
//
|
|
// save parent and right-son of left-most descendent
|
|
//
|
|
pSwapParent = pSwapEntry->pParent;
|
|
pSwapRight = pSwapEntry->pRight;
|
|
|
|
//
|
|
// move swap node to its new position
|
|
//
|
|
pSwapEntry->pParent = pEntry->pParent;
|
|
pSwapEntry->pRight = pEntry->pRight;
|
|
pSwapEntry->pLeft = pEntry->pLeft;
|
|
pSwapEntry->rHeight = pEntry->rHeight;
|
|
pSwapEntry->lHeight = pEntry->lHeight;
|
|
pSwapEntry->pRight->pParent = pSwapEntry;
|
|
pSwapEntry->pLeft->pParent = pSwapEntry;
|
|
if (pEntry->pParent == NULL)
|
|
{
|
|
//
|
|
// node is at root of tree
|
|
//
|
|
pCache->pRoot = pSwapEntry;
|
|
}
|
|
else if (pEntry->pParent->pRight == pEntry)
|
|
{
|
|
//
|
|
// node is right-son of parent
|
|
//
|
|
pSwapEntry->pParent->pRight = pSwapEntry;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// node is left-son of parent
|
|
//
|
|
pSwapEntry->pParent->pLeft = pSwapEntry;
|
|
}
|
|
|
|
//
|
|
// move node to its new position
|
|
//
|
|
pEntry->pParent = pSwapParent;
|
|
pEntry->pRight = pSwapRight;
|
|
pEntry->pLeft = NULL;
|
|
if (pEntry->pRight != NULL)
|
|
{
|
|
pEntry->pRight->pParent = pEntry;
|
|
pEntry->rHeight = 1;
|
|
}
|
|
else
|
|
{
|
|
pEntry->rHeight = 0;
|
|
}
|
|
pEntry->lHeight = 0;
|
|
pEntry->pParent->pLeft = pEntry;
|
|
|
|
DebugExitVOID(ASHost::CHAvlSwapLeftmost);
|
|
}
|
|
|
|
|
|
//
|
|
// Name: CHCompare
|
|
//
|
|
// Purpose: Standard function for comparing UINTs
|
|
//
|
|
// Returns: -1 if key < pEntry->checksum
|
|
// -1 if key = pEntry->checksum AND sizes do not match
|
|
// 0 if key = pEntry->checksum AND sizes match
|
|
// 1 if key > pEntry->checksum
|
|
//
|
|
// Params: IN key - a pointer to the comparison key
|
|
// IN cbSize - number of comparison data bytes
|
|
// IN pEntry - a pointer to the node to compare
|
|
//
|
|
//
|
|
int ASHost::CHCompare
|
|
(
|
|
UINT key,
|
|
UINT cbSize,
|
|
PCHENTRY pEntry
|
|
)
|
|
{
|
|
int ret_val;
|
|
|
|
DebugEntry(ASHost::CHCompare);
|
|
|
|
ASSERT(IsValidCacheEntry(pEntry));
|
|
|
|
if (key < pEntry->checkSum)
|
|
{
|
|
ret_val = -1;
|
|
TRACE_OUT(( "Key is less (-1)"));
|
|
}
|
|
else if (key > pEntry->checkSum)
|
|
{
|
|
ret_val = 1;
|
|
TRACE_OUT(( "Key is more (+1)"));
|
|
}
|
|
else
|
|
{
|
|
if (cbSize == pEntry->cbData)
|
|
{
|
|
ret_val = 0;
|
|
TRACE_OUT(( "Key and size match"));
|
|
}
|
|
else
|
|
{
|
|
ret_val = -1;
|
|
TRACE_OUT(( "Key match, size mismatch (-1)"));
|
|
}
|
|
}
|
|
|
|
DebugExitDWORD(ASHost::CHCompare, ret_val);
|
|
return(ret_val);
|
|
}
|
|
|