xref: /freebsd/contrib/llvm-project/compiler-rt/lib/scudo/standalone/local_cache.h (revision 5f757f3ff9144b609b3c433dfd370cc6bdc191ad)
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