#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); }