/****************************************************************************/ // (C) 1997-2000 Microsoft Corp. // // chdisp.c // // Cache Handler - display driver portion. // // The Cache Handler is a data structure manager that holds hash keys // generated from original data. CH deals with individual caches. There can // be multiple caches in the system, e.g. the memblt caches in SBC and the // cursor cache in CM. Each cache can be searched (CH_SearchCache), // added to (CH_CacheKey), and removed from (CH_RemoveCacheEntry). // // Each cache has associated with it the concept of an MRU/LRU list, where // incoming cached items to a full cache cause other items to be evicted // based on recent usage. The cache owner is notified of evicted items via // a callback. /****************************************************************************/ #include #define hdrstop #define TRC_FILE "nchdisp" #include #include #include #define DC_INCLUDE_DATA #include #undef DC_INCLUDE_DATA #include /* * Calculate number of hash buckets given cache enties */ UINT RDPCALL CH_CalculateHashBuckets(UINT NumEntries) { UINT NumHashBuckets, Temp, i; DC_BEGIN_FN("CH_CalculateHashBuckets"); if (NumEntries) { // Good hash table performance can be created by allocating four times // the number of hash buckets as there are items. Round this number to // the next higher power of 2 to make masking the hash key bits // efficient. Temp = NumHashBuckets = 4 * NumEntries; // Find the highest bit set in the hash bucket value. for (i = 0; Temp > 1; i++) Temp >>= 1; // See if the original value was actually a power of two, if so we // need not waste the extra memory by doubling the number of buckets. if ((unsigned)(1 << i) != NumHashBuckets) NumHashBuckets = (1 << (i + 1)); } else { NumHashBuckets = 0; } DC_END_FN(); return NumHashBuckets; } /* * Calculate cache size in bytes given cache entries */ UINT RDPCALL CH_CalculateCacheSize(UINT NumEntries) { UINT CacheSize, NumHashBuckets; DC_BEGIN_FN("CH_CalculateCacheSize"); if (NumEntries) { NumHashBuckets = CH_CalculateHashBuckets(NumEntries); CacheSize = sizeof(CHCACHEDATA) + (NumHashBuckets - 1) * sizeof(LIST_ENTRY) + NumEntries * sizeof(CHNODE); } else { CacheSize = 0; } DC_END_FN(); return CacheSize; } /* * Init a cache in pre-allocated memory. pContext is caller- * defined information particular to the cache. bNotifyRemoveLRU signals that * the creator wants to be notified via its cache callback of removal of an * LRU entry. bQueryRemoveLRU means the creator wants to be queried for whether * a particular LRU cache entry can be removed. If either of bNotifyRemoveLRU * or bQueryRemoveLRU is nonzero, a cache callback must be provided. * Returns TRUE on success. */ void RDPCALL CH_InitCache( PCHCACHEDATA pCacheData, unsigned NumEntries, void *pContext, BOOLEAN bNotifyRemoveLRU, BOOLEAN bQueryRemoveLRU, CHCACHECALLBACK pfnCacheCallback) { BOOLEAN rc = FALSE; unsigned i; unsigned NumHashBuckets; DC_BEGIN_FN("CH_InitCache"); TRC_ASSERT((NumEntries > 0), (TB, "Must have > 0 cache entries")); NumHashBuckets = CH_CalculateHashBuckets(NumEntries); // Initialize the cache. Since the cache mem was not zeroed during // alloc, be sure to init all members whose initial value matters. pCacheData->HashKeyMask = NumHashBuckets - 1; for (i = 0; i < NumHashBuckets; i++) InitializeListHead(&pCacheData->HashBuckets[i]); pCacheData->pContext = pContext; pCacheData->pfnCacheCallback = pfnCacheCallback; pCacheData->bNotifyRemoveLRU = (bNotifyRemoveLRU ? TRUE : FALSE); pCacheData->bQueryRemoveLRU = (bQueryRemoveLRU ? TRUE : FALSE); InitializeListHead(&pCacheData->MRUList); InitializeListHead(&pCacheData->FreeList); pCacheData->NumEntries = 0; #ifdef DC_DEBUG // Init stat counters. pCacheData->MaxEntries = NumEntries; pCacheData->NumSearches = 0; pCacheData->DeepestSearch = 0; pCacheData->NumHits = 0; pCacheData->TotalDepthOnHit = 0; pCacheData->TotalDepthOnMiss = 0; memset(&pCacheData->SearchHitDepthHistogram, 0, sizeof(unsigned) * 8); memset(&pCacheData->SearchMissDepthHistogram, 0, sizeof(unsigned) * 8); #endif // DC_DEBUG // Add all nodes to the free list. pCacheData->NodeArray = (CHNODE *)((BYTE *)pCacheData + sizeof(CHCACHEDATA) + (NumHashBuckets - 1) * sizeof(LIST_ENTRY)); for (i = 0; i < NumEntries; i++) InsertTailList(&pCacheData->FreeList, &pCacheData->NodeArray[i].HashList); TRC_NRM((TB, "Created %u slot cache (%p), hash buckets = %u", NumEntries, pCacheData, NumHashBuckets)); DC_END_FN(); } /* * Search the cache using the provided key. * Returns FALSE if the key is not present. If the key is present, returns * TRUE and fills in *pUserDefined with the UserDefined value associated * with the key and *piCacheEntry with the cache index of the item. */ BOOLEAN RDPCALL CH_SearchCache( CHCACHEHANDLE hCache, UINT32 Key1, UINT32 Key2, void **pUserDefined, unsigned *piCacheEntry) { PCHCACHEDATA pCacheData; BOOLEAN rc = FALSE; CHNODE *pNode; PLIST_ENTRY pBucketListHead, pCurrentListEntry; #ifdef DC_DEBUG unsigned SearchDepth = 0; #endif DC_BEGIN_FN("CH_SearchCache"); TRC_ASSERT((hCache != NULL), (TB, "NULL cache handle")); pCacheData = (CHCACHEDATA *)hCache; // Find the appropriate hash bucket. Then search the bucket list for the // right key. pBucketListHead = &pCacheData->HashBuckets[Key1 & pCacheData->HashKeyMask]; pCurrentListEntry = pBucketListHead->Flink; while (pCurrentListEntry != pBucketListHead) { #ifdef DC_DEBUG SearchDepth++; #endif pNode = CONTAINING_RECORD(pCurrentListEntry, CHNODE, HashList); if (pNode->Key1 == Key1 && pNode->Key2 == Key2) { // Whenever we search a cache entry we bump it to the top of // both its bucket list (for perf on real access patterns -- // add an entry then access it a lot) and its MRU list. RemoveEntryList(pCurrentListEntry); InsertHeadList(pBucketListHead, pCurrentListEntry); RemoveEntryList(&pNode->MRUList); InsertHeadList(&pCacheData->MRUList, &pNode->MRUList); *pUserDefined = pNode->UserDefined; *piCacheEntry = (unsigned)(pNode - pCacheData->NodeArray); rc = TRUE; break; } pCurrentListEntry = pCurrentListEntry->Flink; } #ifdef DC_DEBUG TRC_NRM((TB, "Searched hCache %p, depth count %lu, rc = %d", hCache, SearchDepth, rc)); // Add search to various search stats. if (SearchDepth > pCacheData->DeepestSearch) { pCacheData->DeepestSearch = SearchDepth; TRC_NRM((TB,"hCache %p: New deepest search depth %u", hCache, SearchDepth)); } pCacheData->NumSearches++; if (SearchDepth > 7) SearchDepth = 7; if (rc) { pCacheData->NumHits++; pCacheData->TotalDepthOnHit += SearchDepth; pCacheData->SearchHitDepthHistogram[SearchDepth]++; } else { pCacheData->TotalDepthOnMiss += SearchDepth; pCacheData->SearchMissDepthHistogram[SearchDepth]++; } if ((pCacheData->NumSearches % CH_STAT_DISPLAY_FREQ) == 0) TRC_NRM((TB,"hCache %p: entr=%u/%u, hits/searches=%u/%u, " "avg hit srch depth=%u, avg miss srch depth=%u, " "hit depth hist: %u %u %u %u %u %u %u %u; " "miss depth hist: %u %u %u %u %u %u %u %u", hCache, pCacheData->NumEntries, pCacheData->MaxEntries, pCacheData->NumHits, pCacheData->NumSearches, ((pCacheData->TotalDepthOnHit + (pCacheData->NumHits / 2)) / pCacheData->NumHits), ((pCacheData->TotalDepthOnMiss + ((pCacheData->NumSearches - pCacheData->NumHits) / 2)) / (pCacheData->NumSearches - pCacheData->NumHits)), pCacheData->SearchHitDepthHistogram[0], pCacheData->SearchHitDepthHistogram[1], pCacheData->SearchHitDepthHistogram[2], pCacheData->SearchHitDepthHistogram[3], pCacheData->SearchHitDepthHistogram[4], pCacheData->SearchHitDepthHistogram[5], pCacheData->SearchHitDepthHistogram[6], pCacheData->SearchHitDepthHistogram[7], pCacheData->SearchMissDepthHistogram[0], pCacheData->SearchMissDepthHistogram[1], pCacheData->SearchMissDepthHistogram[2], pCacheData->SearchMissDepthHistogram[3], pCacheData->SearchMissDepthHistogram[4], pCacheData->SearchMissDepthHistogram[5], pCacheData->SearchMissDepthHistogram[6], pCacheData->SearchMissDepthHistogram[7])); #endif // defined(DC_DEBUG) DC_END_FN(); return rc; } /* * Adds a key to a cache. Returns the index of the new entry within the cache. * If no entry could be allocated because the cache callback refused all * requests to evict least-recently-used entries, returns CH_KEY_UNCACHABLE. */ unsigned RDPCALL CH_CacheKey( CHCACHEHANDLE hCache, UINT32 Key1, UINT32 Key2, void *UserDefined) { PCHCACHEDATA pCacheData; PCHNODE pNode; PLIST_ENTRY pListEntry; unsigned CacheEntryIndex; DC_BEGIN_FN("CH_CacheKey"); TRC_ASSERT((hCache != NULL), (TB, "NULL cache handle")); pCacheData = (CHCACHEDATA *)hCache; // Get a free cache node, either from the free list or by removing // the last entry in the MRU list. if (!IsListEmpty(&pCacheData->FreeList)) { pListEntry = RemoveHeadList(&pCacheData->FreeList); pNode = CONTAINING_RECORD(pListEntry, CHNODE, HashList); CacheEntryIndex = (unsigned)(pNode - pCacheData->NodeArray); pCacheData->NumEntries++; } else { TRC_ASSERT((!IsListEmpty(&pCacheData->MRUList)), (TB,"Empty free and MRU lists!")); // Different code paths depending on whether the cache creator // wants to be queried for LRU removal. if (pCacheData->bQueryRemoveLRU) { // Start iterating from the end of the MRU list, asking // the caller if the entry can be removed. pListEntry = pCacheData->MRUList.Blink; for (;;) { pNode = CONTAINING_RECORD(pListEntry, CHNODE, MRUList); CacheEntryIndex = (unsigned)(pNode - pCacheData->NodeArray); if ((*(pCacheData->pfnCacheCallback)) (hCache, CH_EVT_QUERYREMOVEENTRY, CacheEntryIndex, pNode->UserDefined)) { // We can use this entry. RemoveEntryList(pListEntry); RemoveEntryList(&pNode->HashList); break; } pListEntry = pListEntry->Blink; if (pListEntry == &pCacheData->MRUList) { // We reached the end of the list, no removable entries. CacheEntryIndex = CH_KEY_UNCACHABLE; goto EndFunc; } } } else { pListEntry = RemoveTailList(&pCacheData->MRUList); pNode = CONTAINING_RECORD(pListEntry, CHNODE, MRUList); RemoveEntryList(&pNode->HashList); CacheEntryIndex = (unsigned)(pNode - pCacheData->NodeArray); } // Call the cache callback to inform of the entry removal. if (pCacheData->bNotifyRemoveLRU) (*(pCacheData->pfnCacheCallback)) (hCache, CH_EVT_ENTRYREMOVED, CacheEntryIndex, pNode->UserDefined); } // Prepare the node for use. pNode->Key1 = Key1; pNode->Key2 = Key2; pNode->UserDefined = UserDefined; pNode->hCache = hCache; // Add the node to the front of its bucket list and the MRU list. InsertHeadList(&pCacheData->MRUList, &pNode->MRUList); InsertHeadList(&pCacheData->HashBuckets[Key1 & pCacheData->HashKeyMask], &pNode->HashList); TRC_NRM((TB, "Cache %p index %u key1 %lx key2 %lx userdef %p", pCacheData, CacheEntryIndex, Key1, Key2, UserDefined)); EndFunc: DC_END_FN(); return CacheEntryIndex; } /* * Used by bitmap cache code for force the internal representation of * the cache structures to a known initial state. This function does minimal * checking to make sure of cache integrity -- the cache should be cleared * before forcing the contents of the cache else some cache nodes may illegally * remain in the MRU lists. */ void RDPCALL CH_ForceCacheKeyAtIndex( CHCACHEHANDLE hCache, unsigned CacheEntryIndex, UINT32 Key1, UINT32 Key2, void *UserDefined) { PCHCACHEDATA pCacheData; PCHNODE pNode; DC_BEGIN_FN("CH_ForceCacheKeyAtIndex"); TRC_ASSERT((hCache != NULL), (TB, "NULL cache handle")); pCacheData = (CHCACHEDATA *)hCache; // Find the node. Remove it from the free list. TRC_ASSERT((CacheEntryIndex <= pCacheData->MaxEntries), (TB,"Index out of bounds!")); pNode = &pCacheData->NodeArray[CacheEntryIndex]; RemoveEntryList(&pNode->HashList); // Prepare the node for use. pNode->Key1 = Key1; pNode->Key2 = Key2; pNode->UserDefined = UserDefined; pNode->hCache = hCache; // Add the node to the front of its bucket list and the end of the MRU list. InsertTailList(&pCacheData->MRUList, &pNode->MRUList); InsertHeadList(&pCacheData->HashBuckets[Key1 & pCacheData->HashKeyMask], &pNode->HashList); pCacheData->NumEntries++; TRC_NRM((TB, "Cache %p index %u key1 %lx key2 %lx userdef %p", pCacheData, CacheEntryIndex, Key1, Key2, UserDefined)); DC_END_FN(); } /* * Remove an entry by its index number. */ void RDPCALL CH_RemoveCacheEntry( CHCACHEHANDLE hCache, unsigned CacheEntryIndex) { PCHNODE pNode; PCHCACHEDATA pCacheData; DC_BEGIN_FN("CH_RemoveCacheEntry"); TRC_ASSERT((hCache != NULL), (TB, "NULL cache handle")); pCacheData = (CHCACHEDATA *)hCache; pNode = &pCacheData->NodeArray[CacheEntryIndex]; RemoveEntryList(&pNode->MRUList); RemoveEntryList(&pNode->HashList); InsertHeadList(&pCacheData->FreeList, &pNode->HashList); // Call the cache callback to inform of the entry removal. if (pCacheData->bNotifyRemoveLRU) (*(pCacheData->pfnCacheCallback)) (hCache, CH_EVT_ENTRYREMOVED, CacheEntryIndex, pNode->UserDefined); pCacheData->NumEntries--; DC_END_FN(); } /* * Clears the cache contents. */ void RDPCALL CH_ClearCache(CHCACHEHANDLE hCache) { PCHCACHEDATA pCacheData; PLIST_ENTRY pListEntry; PCHNODE pNode; DC_BEGIN_FN("CH_ClearCache"); TRC_ASSERT((hCache != NULL), (TB, "NULL cache handle")); pCacheData = (CHCACHEDATA *)hCache; TRC_NRM((TB, "Clear cache %p", pCacheData)); // Remove all entries in the MRU list. while (!IsListEmpty(&pCacheData->MRUList)) { pListEntry = RemoveHeadList(&pCacheData->MRUList); pNode = CONTAINING_RECORD(pListEntry, CHNODE, MRUList); RemoveEntryList(&pNode->HashList); InsertHeadList(&pCacheData->FreeList, &pNode->HashList); // Call the cache callback to inform of the entry removal. if (pCacheData->bNotifyRemoveLRU) (*(pCacheData->pfnCacheCallback)) (hCache, CH_EVT_ENTRYREMOVED, (unsigned)(pNode - pCacheData->NodeArray), pNode->UserDefined); } pCacheData->NumEntries = 0; #if DC_DEBUG // Reset stats. pCacheData->NumSearches = 0; pCacheData->DeepestSearch = 0; pCacheData->NumHits = 0; pCacheData->TotalDepthOnHit = 0; memset(pCacheData->SearchHitDepthHistogram, 0, sizeof(unsigned) * 8); pCacheData->TotalDepthOnMiss = 0; memset(pCacheData->SearchMissDepthHistogram, 0, sizeof(unsigned) * 8); #endif DC_END_FN(); } /* * Touch the node so that it will be moved to the top of the mru list */ void RDPCALL CH_TouchCacheEntry( CHCACHEHANDLE hCache, unsigned CacheEntryIndex) { PCHCACHEDATA pCacheData; CHNODE *pNode; DC_BEGIN_FN("CH_TouchCacheEntry"); TRC_ASSERT((hCache != NULL), (TB, "NULL cache handle")); pCacheData = (CHCACHEDATA *)hCache; pNode = pCacheData->NodeArray + CacheEntryIndex; TRC_ASSERT((pNode != NULL), (TB, "NULL cache node")); RemoveEntryList(&pNode->MRUList); InsertHeadList(&pCacheData->MRUList, &pNode->MRUList); DC_END_FN(); } /* * Return the LRU cache entry */ unsigned RDPCALL CH_GetLRUCacheEntry( CHCACHEHANDLE hCache) { PCHCACHEDATA pCacheData; PCHNODE pNode; PLIST_ENTRY pListEntry; unsigned CacheEntryIndex; DC_BEGIN_FN("CH_GetLRUCacheEntry"); TRC_ASSERT((hCache != NULL), (TB, "NULL cache handle")); pCacheData = (CHCACHEDATA *)hCache; // Get a free cache node, either from the free list or by removing // the last entry in the MRU list. if (!IsListEmpty(&pCacheData->MRUList)) { pListEntry = (&pCacheData->MRUList)->Blink; pNode = CONTAINING_RECORD(pListEntry, CHNODE, MRUList); CacheEntryIndex = (unsigned)(pNode - pCacheData->NodeArray); } else { CacheEntryIndex = CH_KEY_UNCACHABLE; } DC_END_FN(); return CacheEntryIndex; }