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

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

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

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