10b57cec5SDimitry Andric //===-- local_cache.h -------------------------------------------*- C++ -*-===// 20b57cec5SDimitry Andric // 30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60b57cec5SDimitry Andric // 70b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 80b57cec5SDimitry Andric 90b57cec5SDimitry Andric #ifndef SCUDO_LOCAL_CACHE_H_ 100b57cec5SDimitry Andric #define SCUDO_LOCAL_CACHE_H_ 110b57cec5SDimitry Andric 120b57cec5SDimitry Andric #include "internal_defs.h" 13bdd1243dSDimitry Andric #include "list.h" 14bdd1243dSDimitry Andric #include "platform.h" 150b57cec5SDimitry Andric #include "report.h" 160b57cec5SDimitry Andric #include "stats.h" 1706c3fb27SDimitry Andric #include "string_utils.h" 180b57cec5SDimitry Andric 190b57cec5SDimitry Andric namespace scudo { 200b57cec5SDimitry Andric 210b57cec5SDimitry Andric template <class SizeClassAllocator> struct SizeClassAllocatorLocalCache { 220b57cec5SDimitry Andric typedef typename SizeClassAllocator::SizeClassMap SizeClassMap; 23fe6060f1SDimitry Andric typedef typename SizeClassAllocator::CompactPtrT CompactPtrT; 240b57cec5SDimitry Andric 25fe6060f1SDimitry Andric void init(GlobalStats *S, SizeClassAllocator *A) { 26fe6060f1SDimitry Andric DCHECK(isEmpty()); 27fe6060f1SDimitry Andric Stats.init(); 2868d75effSDimitry Andric if (LIKELY(S)) 290b57cec5SDimitry Andric S->link(&Stats); 300b57cec5SDimitry Andric Allocator = A; 31*5f757f3fSDimitry Andric initCache(); 320b57cec5SDimitry Andric } 330b57cec5SDimitry Andric 340b57cec5SDimitry Andric void destroy(GlobalStats *S) { 350b57cec5SDimitry Andric drain(); 3668d75effSDimitry Andric if (LIKELY(S)) 370b57cec5SDimitry Andric S->unlink(&Stats); 380b57cec5SDimitry Andric } 390b57cec5SDimitry Andric 400b57cec5SDimitry Andric void *allocate(uptr ClassId) { 4168d75effSDimitry Andric DCHECK_LT(ClassId, NumClasses); 420b57cec5SDimitry Andric PerClass *C = &PerClassArray[ClassId]; 430b57cec5SDimitry Andric if (C->Count == 0) { 44*5f757f3fSDimitry Andric // Refill half of the number of max cached. 45*5f757f3fSDimitry Andric DCHECK_GT(C->MaxCount / 2, 0U); 46*5f757f3fSDimitry Andric if (UNLIKELY(!refill(C, ClassId, C->MaxCount / 2))) 470b57cec5SDimitry Andric return nullptr; 480b57cec5SDimitry Andric DCHECK_GT(C->Count, 0); 490b57cec5SDimitry Andric } 500b57cec5SDimitry Andric // We read ClassSize first before accessing Chunks because it's adjacent to 510b57cec5SDimitry Andric // Count, while Chunks might be further off (depending on Count). That keeps 520b57cec5SDimitry Andric // the memory accesses in close quarters. 530b57cec5SDimitry Andric const uptr ClassSize = C->ClassSize; 54fe6060f1SDimitry Andric CompactPtrT CompactP = C->Chunks[--C->Count]; 550b57cec5SDimitry Andric Stats.add(StatAllocated, ClassSize); 5668d75effSDimitry Andric Stats.sub(StatFree, ClassSize); 57fe6060f1SDimitry Andric return Allocator->decompactPtr(ClassId, CompactP); 580b57cec5SDimitry Andric } 590b57cec5SDimitry Andric 6006c3fb27SDimitry Andric bool deallocate(uptr ClassId, void *P) { 610b57cec5SDimitry Andric CHECK_LT(ClassId, NumClasses); 620b57cec5SDimitry Andric PerClass *C = &PerClassArray[ClassId]; 6306c3fb27SDimitry Andric 6406c3fb27SDimitry Andric // If the cache is full, drain half of blocks back to the main allocator. 6506c3fb27SDimitry Andric const bool NeedToDrainCache = C->Count == C->MaxCount; 6606c3fb27SDimitry Andric if (NeedToDrainCache) 670b57cec5SDimitry Andric drain(C, ClassId); 680b57cec5SDimitry Andric // See comment in allocate() about memory accesses. 690b57cec5SDimitry Andric const uptr ClassSize = C->ClassSize; 70fe6060f1SDimitry Andric C->Chunks[C->Count++] = 71fe6060f1SDimitry Andric Allocator->compactPtr(ClassId, reinterpret_cast<uptr>(P)); 720b57cec5SDimitry Andric Stats.sub(StatAllocated, ClassSize); 7368d75effSDimitry Andric Stats.add(StatFree, ClassSize); 7406c3fb27SDimitry Andric 7506c3fb27SDimitry Andric return NeedToDrainCache; 760b57cec5SDimitry Andric } 770b57cec5SDimitry Andric 78fe6060f1SDimitry Andric bool isEmpty() const { 79fe6060f1SDimitry Andric for (uptr I = 0; I < NumClasses; ++I) 80fe6060f1SDimitry Andric if (PerClassArray[I].Count) 81fe6060f1SDimitry Andric return false; 82fe6060f1SDimitry Andric return true; 830b57cec5SDimitry Andric } 84fe6060f1SDimitry Andric 85fe6060f1SDimitry Andric void drain() { 86*5f757f3fSDimitry Andric // Drain BatchClassId last as it may be needed while draining normal blocks. 87fe6060f1SDimitry Andric for (uptr I = 0; I < NumClasses; ++I) { 88fe6060f1SDimitry Andric if (I == BatchClassId) 89fe6060f1SDimitry Andric continue; 90fe6060f1SDimitry Andric while (PerClassArray[I].Count > 0) 91fe6060f1SDimitry Andric drain(&PerClassArray[I], I); 92fe6060f1SDimitry Andric } 93fe6060f1SDimitry Andric while (PerClassArray[BatchClassId].Count > 0) 94fe6060f1SDimitry Andric drain(&PerClassArray[BatchClassId], BatchClassId); 95fe6060f1SDimitry Andric DCHECK(isEmpty()); 960b57cec5SDimitry Andric } 970b57cec5SDimitry Andric 98*5f757f3fSDimitry Andric void *getBatchClassBlock() { 99*5f757f3fSDimitry Andric void *B = allocate(BatchClassId); 100bdd1243dSDimitry Andric if (UNLIKELY(!B)) 101bdd1243dSDimitry Andric reportOutOfMemory(SizeClassAllocator::getSizeByClassId(BatchClassId)); 102*5f757f3fSDimitry Andric return B; 103bdd1243dSDimitry Andric } 104bdd1243dSDimitry Andric 1050b57cec5SDimitry Andric LocalStats &getStats() { return Stats; } 1060b57cec5SDimitry Andric 10706c3fb27SDimitry Andric void getStats(ScopedString *Str) { 10806c3fb27SDimitry Andric bool EmptyCache = true; 10906c3fb27SDimitry Andric for (uptr I = 0; I < NumClasses; ++I) { 11006c3fb27SDimitry Andric if (PerClassArray[I].Count == 0) 11106c3fb27SDimitry Andric continue; 11206c3fb27SDimitry Andric 11306c3fb27SDimitry Andric EmptyCache = false; 11406c3fb27SDimitry Andric // The size of BatchClass is set to 0 intentionally. See the comment in 11506c3fb27SDimitry Andric // initCache() for more details. 11606c3fb27SDimitry Andric const uptr ClassSize = I == BatchClassId 11706c3fb27SDimitry Andric ? SizeClassAllocator::getSizeByClassId(I) 11806c3fb27SDimitry Andric : PerClassArray[I].ClassSize; 11906c3fb27SDimitry Andric // Note that the string utils don't support printing u16 thus we cast it 12006c3fb27SDimitry Andric // to a common use type uptr. 12106c3fb27SDimitry Andric Str->append(" %02zu (%6zu): cached: %4zu max: %4zu\n", I, ClassSize, 12206c3fb27SDimitry Andric static_cast<uptr>(PerClassArray[I].Count), 12306c3fb27SDimitry Andric static_cast<uptr>(PerClassArray[I].MaxCount)); 12406c3fb27SDimitry Andric } 12506c3fb27SDimitry Andric 12606c3fb27SDimitry Andric if (EmptyCache) 12706c3fb27SDimitry Andric Str->append(" No block is cached.\n"); 12806c3fb27SDimitry Andric } 12906c3fb27SDimitry Andric 130*5f757f3fSDimitry Andric static u16 getMaxCached(uptr Size) { 131*5f757f3fSDimitry Andric return Min(SizeClassMap::MaxNumCachedHint, 132*5f757f3fSDimitry Andric SizeClassMap::getMaxCachedHint(Size)); 133*5f757f3fSDimitry Andric } 134*5f757f3fSDimitry Andric 1350b57cec5SDimitry Andric private: 1360b57cec5SDimitry Andric static const uptr NumClasses = SizeClassMap::NumClasses; 137fe6060f1SDimitry Andric static const uptr BatchClassId = SizeClassMap::BatchClassId; 138bdd1243dSDimitry Andric struct alignas(SCUDO_CACHE_LINE_SIZE) PerClass { 139bdd1243dSDimitry Andric u16 Count; 140bdd1243dSDimitry Andric u16 MaxCount; 141fe6060f1SDimitry Andric // Note: ClassSize is zero for the transfer batch. 1420b57cec5SDimitry Andric uptr ClassSize; 143*5f757f3fSDimitry Andric CompactPtrT Chunks[2 * SizeClassMap::MaxNumCachedHint]; 1440b57cec5SDimitry Andric }; 145fe6060f1SDimitry Andric PerClass PerClassArray[NumClasses] = {}; 1460b57cec5SDimitry Andric LocalStats Stats; 147fe6060f1SDimitry Andric SizeClassAllocator *Allocator = nullptr; 1480b57cec5SDimitry Andric 1490b57cec5SDimitry Andric NOINLINE void initCache() { 1500b57cec5SDimitry Andric for (uptr I = 0; I < NumClasses; I++) { 1510b57cec5SDimitry Andric PerClass *P = &PerClassArray[I]; 1520b57cec5SDimitry Andric const uptr Size = SizeClassAllocator::getSizeByClassId(I); 153*5f757f3fSDimitry Andric P->MaxCount = static_cast<u16>(2 * getMaxCached(Size)); 154fe6060f1SDimitry Andric if (I != BatchClassId) { 1550b57cec5SDimitry Andric P->ClassSize = Size; 156fe6060f1SDimitry Andric } else { 157fe6060f1SDimitry Andric // ClassSize in this struct is only used for malloc/free stats, which 158fe6060f1SDimitry Andric // should only track user allocations, not internal movements. 159fe6060f1SDimitry Andric P->ClassSize = 0; 160fe6060f1SDimitry Andric } 1610b57cec5SDimitry Andric } 1620b57cec5SDimitry Andric } 1630b57cec5SDimitry Andric 1640b57cec5SDimitry Andric void destroyBatch(uptr ClassId, void *B) { 165fe6060f1SDimitry Andric if (ClassId != BatchClassId) 166fe6060f1SDimitry Andric deallocate(BatchClassId, B); 1670b57cec5SDimitry Andric } 1680b57cec5SDimitry Andric 169*5f757f3fSDimitry Andric NOINLINE bool refill(PerClass *C, uptr ClassId, u16 MaxRefill) { 170*5f757f3fSDimitry Andric const u16 NumBlocksRefilled = 171*5f757f3fSDimitry Andric Allocator->popBlocks(this, ClassId, C->Chunks, MaxRefill); 172*5f757f3fSDimitry Andric DCHECK_LE(NumBlocksRefilled, MaxRefill); 173*5f757f3fSDimitry Andric C->Count = static_cast<u16>(C->Count + NumBlocksRefilled); 174*5f757f3fSDimitry Andric return NumBlocksRefilled != 0; 1750b57cec5SDimitry Andric } 1760b57cec5SDimitry Andric 1770b57cec5SDimitry Andric NOINLINE void drain(PerClass *C, uptr ClassId) { 178bdd1243dSDimitry Andric const u16 Count = Min(static_cast<u16>(C->MaxCount / 2), C->Count); 179bdd1243dSDimitry Andric Allocator->pushBlocks(this, ClassId, &C->Chunks[0], Count); 180bdd1243dSDimitry Andric // u16 will be promoted to int by arithmetic type conversion. 181bdd1243dSDimitry Andric C->Count = static_cast<u16>(C->Count - Count); 182bdd1243dSDimitry Andric for (u16 I = 0; I < C->Count; I++) 1835ffd83dbSDimitry Andric C->Chunks[I] = C->Chunks[I + Count]; 1840b57cec5SDimitry Andric } 1850b57cec5SDimitry Andric }; 1860b57cec5SDimitry Andric 1870b57cec5SDimitry Andric } // namespace scudo 1880b57cec5SDimitry Andric 1890b57cec5SDimitry Andric #endif // SCUDO_LOCAL_CACHE_H_ 190