xref: /openbsd-src/gnu/llvm/compiler-rt/lib/scudo/standalone/local_cache.h (revision 810390e339a5425391477d5d41c78d7cab2424ac)
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