13cab2bb3Spatrick //===-- local_cache.h -------------------------------------------*- C++ -*-===// 23cab2bb3Spatrick // 33cab2bb3Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 43cab2bb3Spatrick // See https://llvm.org/LICENSE.txt for license information. 53cab2bb3Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 63cab2bb3Spatrick // 73cab2bb3Spatrick //===----------------------------------------------------------------------===// 83cab2bb3Spatrick 93cab2bb3Spatrick #ifndef SCUDO_LOCAL_CACHE_H_ 103cab2bb3Spatrick #define SCUDO_LOCAL_CACHE_H_ 113cab2bb3Spatrick 123cab2bb3Spatrick #include "internal_defs.h" 13*810390e3Srobert #include "list.h" 14*810390e3Srobert #include "platform.h" 153cab2bb3Spatrick #include "report.h" 163cab2bb3Spatrick #include "stats.h" 173cab2bb3Spatrick 183cab2bb3Spatrick namespace scudo { 193cab2bb3Spatrick 203cab2bb3Spatrick template <class SizeClassAllocator> struct SizeClassAllocatorLocalCache { 213cab2bb3Spatrick typedef typename SizeClassAllocator::SizeClassMap SizeClassMap; 22d89ec533Spatrick typedef typename SizeClassAllocator::CompactPtrT CompactPtrT; 233cab2bb3Spatrick 243cab2bb3Spatrick struct TransferBatch { 25*810390e3Srobert static const u16 MaxNumCached = SizeClassMap::MaxNumCachedHint; setFromArraySizeClassAllocatorLocalCache::TransferBatch26*810390e3Srobert void setFromArray(CompactPtrT *Array, u16 N) { 273cab2bb3Spatrick DCHECK_LE(N, MaxNumCached); 283cab2bb3Spatrick Count = N; 29d89ec533Spatrick memcpy(Batch, Array, sizeof(Batch[0]) * Count); 303cab2bb3Spatrick } appendFromArraySizeClassAllocatorLocalCache::TransferBatch31*810390e3Srobert void appendFromArray(CompactPtrT *Array, u16 N) { 32*810390e3Srobert DCHECK_LE(N, MaxNumCached - Count); 33*810390e3Srobert memcpy(Batch + Count, Array, sizeof(Batch[0]) * N); 34*810390e3Srobert // u16 will be promoted to int by arithmetic type conversion. 35*810390e3Srobert Count = static_cast<u16>(Count + N); 36*810390e3Srobert } clearSizeClassAllocatorLocalCache::TransferBatch373cab2bb3Spatrick void clear() { Count = 0; } addSizeClassAllocatorLocalCache::TransferBatch38d89ec533Spatrick void add(CompactPtrT P) { 393cab2bb3Spatrick DCHECK_LT(Count, MaxNumCached); 403cab2bb3Spatrick Batch[Count++] = P; 413cab2bb3Spatrick } copyToArraySizeClassAllocatorLocalCache::TransferBatch42d89ec533Spatrick void copyToArray(CompactPtrT *Array) const { 43d89ec533Spatrick memcpy(Array, Batch, sizeof(Batch[0]) * Count); 443cab2bb3Spatrick } getCountSizeClassAllocatorLocalCache::TransferBatch45*810390e3Srobert u16 getCount() const { return Count; } getSizeClassAllocatorLocalCache::TransferBatch46*810390e3Srobert CompactPtrT get(u16 I) const { 473cab2bb3Spatrick DCHECK_LE(I, Count); 483cab2bb3Spatrick return Batch[I]; 493cab2bb3Spatrick } getMaxCachedSizeClassAllocatorLocalCache::TransferBatch50*810390e3Srobert static u16 getMaxCached(uptr Size) { 513cab2bb3Spatrick return Min(MaxNumCached, SizeClassMap::getMaxCachedHint(Size)); 523cab2bb3Spatrick } 533cab2bb3Spatrick TransferBatch *Next; 543cab2bb3Spatrick 553cab2bb3Spatrick private: 56d89ec533Spatrick CompactPtrT Batch[MaxNumCached]; 57*810390e3Srobert u16 Count; 583cab2bb3Spatrick }; 593cab2bb3Spatrick 60*810390e3Srobert // A BatchGroup is used to collect blocks. Each group has a group id to 61*810390e3Srobert // identify the group kind of contained blocks. 62*810390e3Srobert struct BatchGroup { 63*810390e3Srobert // `Next` is used by IntrusiveList. 64*810390e3Srobert BatchGroup *Next; 65*810390e3Srobert // The identifier of each group 66*810390e3Srobert uptr GroupId; 67*810390e3Srobert // Cache value of TransferBatch::getMaxCached() 68*810390e3Srobert u16 MaxCachedPerBatch; 69*810390e3Srobert // Number of blocks pushed into this group. This is an increment-only 70*810390e3Srobert // counter. 71*810390e3Srobert uptr PushedBlocks; 72*810390e3Srobert // This is used to track how many blocks are pushed since last time we 73*810390e3Srobert // checked `PushedBlocks`. It's useful for page releasing to determine the 74*810390e3Srobert // usage of a BatchGroup. 75*810390e3Srobert uptr PushedBlocksAtLastCheckpoint; 76*810390e3Srobert // Blocks are managed by TransferBatch in a list. 77*810390e3Srobert SinglyLinkedList<TransferBatch> Batches; 78*810390e3Srobert }; 79*810390e3Srobert 80*810390e3Srobert static_assert(sizeof(BatchGroup) <= sizeof(TransferBatch), 81*810390e3Srobert "BatchGroup uses the same class size as TransferBatch"); 82*810390e3Srobert initSizeClassAllocatorLocalCache83d89ec533Spatrick void init(GlobalStats *S, SizeClassAllocator *A) { 84d89ec533Spatrick DCHECK(isEmpty()); 85d89ec533Spatrick Stats.init(); 863cab2bb3Spatrick if (LIKELY(S)) 873cab2bb3Spatrick S->link(&Stats); 883cab2bb3Spatrick Allocator = A; 893cab2bb3Spatrick } 903cab2bb3Spatrick destroySizeClassAllocatorLocalCache913cab2bb3Spatrick void destroy(GlobalStats *S) { 923cab2bb3Spatrick drain(); 933cab2bb3Spatrick if (LIKELY(S)) 943cab2bb3Spatrick S->unlink(&Stats); 953cab2bb3Spatrick } 963cab2bb3Spatrick allocateSizeClassAllocatorLocalCache973cab2bb3Spatrick void *allocate(uptr ClassId) { 983cab2bb3Spatrick DCHECK_LT(ClassId, NumClasses); 993cab2bb3Spatrick PerClass *C = &PerClassArray[ClassId]; 1003cab2bb3Spatrick if (C->Count == 0) { 1013cab2bb3Spatrick if (UNLIKELY(!refill(C, ClassId))) 1023cab2bb3Spatrick return nullptr; 1033cab2bb3Spatrick DCHECK_GT(C->Count, 0); 1043cab2bb3Spatrick } 1053cab2bb3Spatrick // We read ClassSize first before accessing Chunks because it's adjacent to 1063cab2bb3Spatrick // Count, while Chunks might be further off (depending on Count). That keeps 1073cab2bb3Spatrick // the memory accesses in close quarters. 1083cab2bb3Spatrick const uptr ClassSize = C->ClassSize; 109d89ec533Spatrick CompactPtrT CompactP = C->Chunks[--C->Count]; 1103cab2bb3Spatrick Stats.add(StatAllocated, ClassSize); 1113cab2bb3Spatrick Stats.sub(StatFree, ClassSize); 112d89ec533Spatrick return Allocator->decompactPtr(ClassId, CompactP); 1133cab2bb3Spatrick } 1143cab2bb3Spatrick deallocateSizeClassAllocatorLocalCache1153cab2bb3Spatrick void deallocate(uptr ClassId, void *P) { 1163cab2bb3Spatrick CHECK_LT(ClassId, NumClasses); 1173cab2bb3Spatrick PerClass *C = &PerClassArray[ClassId]; 1183cab2bb3Spatrick // We still have to initialize the cache in the event that the first heap 1193cab2bb3Spatrick // operation in a thread is a deallocation. 1203cab2bb3Spatrick initCacheMaybe(C); 1213cab2bb3Spatrick if (C->Count == C->MaxCount) 1223cab2bb3Spatrick drain(C, ClassId); 1233cab2bb3Spatrick // See comment in allocate() about memory accesses. 1243cab2bb3Spatrick const uptr ClassSize = C->ClassSize; 125d89ec533Spatrick C->Chunks[C->Count++] = 126d89ec533Spatrick Allocator->compactPtr(ClassId, reinterpret_cast<uptr>(P)); 1273cab2bb3Spatrick Stats.sub(StatAllocated, ClassSize); 1283cab2bb3Spatrick Stats.add(StatFree, ClassSize); 1293cab2bb3Spatrick } 1303cab2bb3Spatrick isEmptySizeClassAllocatorLocalCache131d89ec533Spatrick bool isEmpty() const { 132d89ec533Spatrick for (uptr I = 0; I < NumClasses; ++I) 133d89ec533Spatrick if (PerClassArray[I].Count) 134d89ec533Spatrick return false; 135d89ec533Spatrick return true; 1363cab2bb3Spatrick } 137d89ec533Spatrick drainSizeClassAllocatorLocalCache138d89ec533Spatrick void drain() { 139d89ec533Spatrick // Drain BatchClassId last as createBatch can refill it. 140d89ec533Spatrick for (uptr I = 0; I < NumClasses; ++I) { 141d89ec533Spatrick if (I == BatchClassId) 142d89ec533Spatrick continue; 143d89ec533Spatrick while (PerClassArray[I].Count > 0) 144d89ec533Spatrick drain(&PerClassArray[I], I); 145d89ec533Spatrick } 146d89ec533Spatrick while (PerClassArray[BatchClassId].Count > 0) 147d89ec533Spatrick drain(&PerClassArray[BatchClassId], BatchClassId); 148d89ec533Spatrick DCHECK(isEmpty()); 1493cab2bb3Spatrick } 1503cab2bb3Spatrick createBatchSizeClassAllocatorLocalCache1513cab2bb3Spatrick TransferBatch *createBatch(uptr ClassId, void *B) { 152d89ec533Spatrick if (ClassId != BatchClassId) 153d89ec533Spatrick B = allocate(BatchClassId); 154*810390e3Srobert if (UNLIKELY(!B)) 155*810390e3Srobert reportOutOfMemory(SizeClassAllocator::getSizeByClassId(BatchClassId)); 1563cab2bb3Spatrick return reinterpret_cast<TransferBatch *>(B); 1573cab2bb3Spatrick } 1583cab2bb3Spatrick createGroupSizeClassAllocatorLocalCache159*810390e3Srobert BatchGroup *createGroup() { 160*810390e3Srobert void *Ptr = allocate(BatchClassId); 161*810390e3Srobert if (UNLIKELY(!Ptr)) 162*810390e3Srobert reportOutOfMemory(SizeClassAllocator::getSizeByClassId(BatchClassId)); 163*810390e3Srobert return reinterpret_cast<BatchGroup *>(Ptr); 164*810390e3Srobert } 165*810390e3Srobert getStatsSizeClassAllocatorLocalCache1663cab2bb3Spatrick LocalStats &getStats() { return Stats; } 1673cab2bb3Spatrick 1683cab2bb3Spatrick private: 1693cab2bb3Spatrick static const uptr NumClasses = SizeClassMap::NumClasses; 170d89ec533Spatrick static const uptr BatchClassId = SizeClassMap::BatchClassId; 171*810390e3Srobert struct alignas(SCUDO_CACHE_LINE_SIZE) PerClass { 172*810390e3Srobert u16 Count; 173*810390e3Srobert u16 MaxCount; 174d89ec533Spatrick // Note: ClassSize is zero for the transfer batch. 1753cab2bb3Spatrick uptr ClassSize; 176d89ec533Spatrick CompactPtrT Chunks[2 * TransferBatch::MaxNumCached]; 1773cab2bb3Spatrick }; 178d89ec533Spatrick PerClass PerClassArray[NumClasses] = {}; 1793cab2bb3Spatrick LocalStats Stats; 180d89ec533Spatrick SizeClassAllocator *Allocator = nullptr; 1813cab2bb3Spatrick initCacheMaybeSizeClassAllocatorLocalCache1823cab2bb3Spatrick ALWAYS_INLINE void initCacheMaybe(PerClass *C) { 1833cab2bb3Spatrick if (LIKELY(C->MaxCount)) 1843cab2bb3Spatrick return; 1853cab2bb3Spatrick initCache(); 1863cab2bb3Spatrick DCHECK_NE(C->MaxCount, 0U); 1873cab2bb3Spatrick } 1883cab2bb3Spatrick initCacheSizeClassAllocatorLocalCache1893cab2bb3Spatrick NOINLINE void initCache() { 1903cab2bb3Spatrick for (uptr I = 0; I < NumClasses; I++) { 1913cab2bb3Spatrick PerClass *P = &PerClassArray[I]; 1923cab2bb3Spatrick const uptr Size = SizeClassAllocator::getSizeByClassId(I); 193*810390e3Srobert P->MaxCount = static_cast<u16>(2 * TransferBatch::getMaxCached(Size)); 194d89ec533Spatrick if (I != BatchClassId) { 1953cab2bb3Spatrick P->ClassSize = Size; 196d89ec533Spatrick } else { 197d89ec533Spatrick // ClassSize in this struct is only used for malloc/free stats, which 198d89ec533Spatrick // should only track user allocations, not internal movements. 199d89ec533Spatrick P->ClassSize = 0; 200d89ec533Spatrick } 2013cab2bb3Spatrick } 2023cab2bb3Spatrick } 2033cab2bb3Spatrick destroyBatchSizeClassAllocatorLocalCache2043cab2bb3Spatrick void destroyBatch(uptr ClassId, void *B) { 205d89ec533Spatrick if (ClassId != BatchClassId) 206d89ec533Spatrick deallocate(BatchClassId, B); 2073cab2bb3Spatrick } 2083cab2bb3Spatrick refillSizeClassAllocatorLocalCache2093cab2bb3Spatrick NOINLINE bool refill(PerClass *C, uptr ClassId) { 2103cab2bb3Spatrick initCacheMaybe(C); 2113cab2bb3Spatrick TransferBatch *B = Allocator->popBatch(this, ClassId); 2123cab2bb3Spatrick if (UNLIKELY(!B)) 2133cab2bb3Spatrick return false; 2143cab2bb3Spatrick DCHECK_GT(B->getCount(), 0); 2153cab2bb3Spatrick C->Count = B->getCount(); 2163cab2bb3Spatrick B->copyToArray(C->Chunks); 217d89ec533Spatrick B->clear(); 2183cab2bb3Spatrick destroyBatch(ClassId, B); 2193cab2bb3Spatrick return true; 2203cab2bb3Spatrick } 2213cab2bb3Spatrick drainSizeClassAllocatorLocalCache2223cab2bb3Spatrick NOINLINE void drain(PerClass *C, uptr ClassId) { 223*810390e3Srobert const u16 Count = Min(static_cast<u16>(C->MaxCount / 2), C->Count); 224*810390e3Srobert Allocator->pushBlocks(this, ClassId, &C->Chunks[0], Count); 225*810390e3Srobert // u16 will be promoted to int by arithmetic type conversion. 226*810390e3Srobert C->Count = static_cast<u16>(C->Count - Count); 227*810390e3Srobert for (u16 I = 0; I < C->Count; I++) 2281f9cb04fSpatrick C->Chunks[I] = C->Chunks[I + Count]; 2293cab2bb3Spatrick } 2303cab2bb3Spatrick }; 2313cab2bb3Spatrick 2323cab2bb3Spatrick } // namespace scudo 2333cab2bb3Spatrick 2343cab2bb3Spatrick #endif // SCUDO_LOCAL_CACHE_H_ 235