xref: /freebsd/contrib/llvm-project/compiler-rt/lib/tsan/rtl/tsan_dense_alloc.h (revision fcaf7f8644a9988098ac6be2165bce3ea4786e91)
10b57cec5SDimitry Andric //===-- tsan_dense_alloc.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 // This file is a part of ThreadSanitizer (TSan), a race detector.
100b57cec5SDimitry Andric //
110b57cec5SDimitry Andric // A DenseSlabAlloc is a freelist-based allocator of fixed-size objects.
120b57cec5SDimitry Andric // DenseSlabAllocCache is a thread-local cache for DenseSlabAlloc.
130b57cec5SDimitry Andric // The only difference with traditional slab allocators is that DenseSlabAlloc
140b57cec5SDimitry Andric // allocates/free indices of objects and provide a functionality to map
150b57cec5SDimitry Andric // the index onto the real pointer. The index is u32, that is, 2 times smaller
160b57cec5SDimitry Andric // than uptr (hense the Dense prefix).
170b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
180b57cec5SDimitry Andric #ifndef TSAN_DENSE_ALLOC_H
190b57cec5SDimitry Andric #define TSAN_DENSE_ALLOC_H
200b57cec5SDimitry Andric 
210b57cec5SDimitry Andric #include "sanitizer_common/sanitizer_common.h"
220b57cec5SDimitry Andric #include "tsan_defs.h"
230b57cec5SDimitry Andric 
240b57cec5SDimitry Andric namespace __tsan {
250b57cec5SDimitry Andric 
260b57cec5SDimitry Andric class DenseSlabAllocCache {
270b57cec5SDimitry Andric   static const uptr kSize = 128;
280b57cec5SDimitry Andric   typedef u32 IndexT;
290b57cec5SDimitry Andric   uptr pos;
300b57cec5SDimitry Andric   IndexT cache[kSize];
31fe6060f1SDimitry Andric   template <typename, uptr, uptr, u64>
32fe6060f1SDimitry Andric   friend class DenseSlabAlloc;
330b57cec5SDimitry Andric };
340b57cec5SDimitry Andric 
35fe6060f1SDimitry Andric template <typename T, uptr kL1Size, uptr kL2Size, u64 kReserved = 0>
360b57cec5SDimitry Andric class DenseSlabAlloc {
370b57cec5SDimitry Andric  public:
380b57cec5SDimitry Andric   typedef DenseSlabAllocCache Cache;
390b57cec5SDimitry Andric   typedef typename Cache::IndexT IndexT;
400b57cec5SDimitry Andric 
41fe6060f1SDimitry Andric   static_assert((kL1Size & (kL1Size - 1)) == 0,
42fe6060f1SDimitry Andric                 "kL1Size must be a power-of-two");
43fe6060f1SDimitry Andric   static_assert((kL2Size & (kL2Size - 1)) == 0,
44fe6060f1SDimitry Andric                 "kL2Size must be a power-of-two");
45fe6060f1SDimitry Andric   static_assert((kL1Size * kL2Size) <= (1ull << (sizeof(IndexT) * 8)),
46fe6060f1SDimitry Andric                 "kL1Size/kL2Size are too large");
47fe6060f1SDimitry Andric   static_assert(((kL1Size * kL2Size - 1) & kReserved) == 0,
48fe6060f1SDimitry Andric                 "reserved bits don't fit");
49fe6060f1SDimitry Andric   static_assert(sizeof(T) > sizeof(IndexT),
50fe6060f1SDimitry Andric                 "it doesn't make sense to use dense alloc");
51fe6060f1SDimitry Andric 
DenseSlabAlloc(LinkerInitialized,const char * name)52349cc55cSDimitry Andric   DenseSlabAlloc(LinkerInitialized, const char *name) : name_(name) {}
530b57cec5SDimitry Andric 
DenseSlabAlloc(const char * name)54fe6060f1SDimitry Andric   explicit DenseSlabAlloc(const char *name)
55fe6060f1SDimitry Andric       : DenseSlabAlloc(LINKER_INITIALIZED, name) {
56fe6060f1SDimitry Andric     // It can be very large.
57fe6060f1SDimitry Andric     // Don't page it in for linker initialized objects.
58fe6060f1SDimitry Andric     internal_memset(map_, 0, sizeof(map_));
59fe6060f1SDimitry Andric   }
60fe6060f1SDimitry Andric 
~DenseSlabAlloc()610b57cec5SDimitry Andric   ~DenseSlabAlloc() {
620b57cec5SDimitry Andric     for (uptr i = 0; i < kL1Size; i++) {
630b57cec5SDimitry Andric       if (map_[i] != 0)
640b57cec5SDimitry Andric         UnmapOrDie(map_[i], kL2Size * sizeof(T));
650b57cec5SDimitry Andric     }
660b57cec5SDimitry Andric   }
670b57cec5SDimitry Andric 
Alloc(Cache * c)680b57cec5SDimitry Andric   IndexT Alloc(Cache *c) {
690b57cec5SDimitry Andric     if (c->pos == 0)
700b57cec5SDimitry Andric       Refill(c);
710b57cec5SDimitry Andric     return c->cache[--c->pos];
720b57cec5SDimitry Andric   }
730b57cec5SDimitry Andric 
Free(Cache * c,IndexT idx)740b57cec5SDimitry Andric   void Free(Cache *c, IndexT idx) {
750b57cec5SDimitry Andric     DCHECK_NE(idx, 0);
760b57cec5SDimitry Andric     if (c->pos == Cache::kSize)
770b57cec5SDimitry Andric       Drain(c);
780b57cec5SDimitry Andric     c->cache[c->pos++] = idx;
790b57cec5SDimitry Andric   }
800b57cec5SDimitry Andric 
Map(IndexT idx)810b57cec5SDimitry Andric   T *Map(IndexT idx) {
820b57cec5SDimitry Andric     DCHECK_NE(idx, 0);
830b57cec5SDimitry Andric     DCHECK_LE(idx, kL1Size * kL2Size);
840b57cec5SDimitry Andric     return &map_[idx / kL2Size][idx % kL2Size];
850b57cec5SDimitry Andric   }
860b57cec5SDimitry Andric 
FlushCache(Cache * c)870b57cec5SDimitry Andric   void FlushCache(Cache *c) {
88*fcaf7f86SDimitry Andric     while (c->pos) Drain(c);
890b57cec5SDimitry Andric   }
900b57cec5SDimitry Andric 
InitCache(Cache * c)910b57cec5SDimitry Andric   void InitCache(Cache *c) {
920b57cec5SDimitry Andric     c->pos = 0;
930b57cec5SDimitry Andric     internal_memset(c->cache, 0, sizeof(c->cache));
940b57cec5SDimitry Andric   }
950b57cec5SDimitry Andric 
AllocatedMemory()96349cc55cSDimitry Andric   uptr AllocatedMemory() const {
97349cc55cSDimitry Andric     return atomic_load_relaxed(&fillpos_) * kL2Size * sizeof(T);
98349cc55cSDimitry Andric   }
99349cc55cSDimitry Andric 
1000eae32dcSDimitry Andric   template <typename Func>
ForEach(Func func)1010eae32dcSDimitry Andric   void ForEach(Func func) {
102*fcaf7f86SDimitry Andric     Lock lock(&mtx_);
1030eae32dcSDimitry Andric     uptr fillpos = atomic_load_relaxed(&fillpos_);
1040eae32dcSDimitry Andric     for (uptr l1 = 0; l1 < fillpos; l1++) {
1050eae32dcSDimitry Andric       for (IndexT l2 = l1 == 0 ? 1 : 0; l2 < kL2Size; l2++) func(&map_[l1][l2]);
1060eae32dcSDimitry Andric     }
1070eae32dcSDimitry Andric   }
1080eae32dcSDimitry Andric 
1090b57cec5SDimitry Andric  private:
1100b57cec5SDimitry Andric   T *map_[kL1Size];
111*fcaf7f86SDimitry Andric   Mutex mtx_;
112*fcaf7f86SDimitry Andric   // The freelist is organized as a lock-free stack of batches of nodes.
113*fcaf7f86SDimitry Andric   // The stack itself uses Block::next links, while the batch within each
114*fcaf7f86SDimitry Andric   // stack node uses Block::batch links.
115*fcaf7f86SDimitry Andric   // Low 32-bits of freelist_ is the node index, top 32-bits is ABA-counter.
116*fcaf7f86SDimitry Andric   atomic_uint64_t freelist_ = {0};
117349cc55cSDimitry Andric   atomic_uintptr_t fillpos_ = {0};
118349cc55cSDimitry Andric   const char *const name_;
1190b57cec5SDimitry Andric 
120*fcaf7f86SDimitry Andric   struct Block {
121*fcaf7f86SDimitry Andric     IndexT next;
122*fcaf7f86SDimitry Andric     IndexT batch;
123*fcaf7f86SDimitry Andric   };
124*fcaf7f86SDimitry Andric 
MapBlock(IndexT idx)125*fcaf7f86SDimitry Andric   Block *MapBlock(IndexT idx) { return reinterpret_cast<Block *>(Map(idx)); }
126*fcaf7f86SDimitry Andric 
127*fcaf7f86SDimitry Andric   static constexpr u64 kCounterInc = 1ull << 32;
128*fcaf7f86SDimitry Andric   static constexpr u64 kCounterMask = ~(kCounterInc - 1);
129*fcaf7f86SDimitry Andric 
Refill(Cache * c)130*fcaf7f86SDimitry Andric   NOINLINE void Refill(Cache *c) {
131*fcaf7f86SDimitry Andric     // Pop 1 batch of nodes from the freelist.
132*fcaf7f86SDimitry Andric     IndexT idx;
133*fcaf7f86SDimitry Andric     u64 xchg;
134*fcaf7f86SDimitry Andric     u64 cmp = atomic_load(&freelist_, memory_order_acquire);
135*fcaf7f86SDimitry Andric     do {
136*fcaf7f86SDimitry Andric       idx = static_cast<IndexT>(cmp);
137*fcaf7f86SDimitry Andric       if (!idx)
138*fcaf7f86SDimitry Andric         return AllocSuperBlock(c);
139*fcaf7f86SDimitry Andric       Block *ptr = MapBlock(idx);
140*fcaf7f86SDimitry Andric       xchg = ptr->next | (cmp & kCounterMask);
141*fcaf7f86SDimitry Andric     } while (!atomic_compare_exchange_weak(&freelist_, &cmp, xchg,
142*fcaf7f86SDimitry Andric                                            memory_order_acq_rel));
143*fcaf7f86SDimitry Andric     // Unpack it into c->cache.
144*fcaf7f86SDimitry Andric     while (idx) {
145*fcaf7f86SDimitry Andric       c->cache[c->pos++] = idx;
146*fcaf7f86SDimitry Andric       idx = MapBlock(idx)->batch;
147*fcaf7f86SDimitry Andric     }
148*fcaf7f86SDimitry Andric   }
149*fcaf7f86SDimitry Andric 
Drain(Cache * c)150*fcaf7f86SDimitry Andric   NOINLINE void Drain(Cache *c) {
151*fcaf7f86SDimitry Andric     // Build a batch of at most Cache::kSize / 2 nodes linked by Block::batch.
152*fcaf7f86SDimitry Andric     IndexT head_idx = 0;
153*fcaf7f86SDimitry Andric     for (uptr i = 0; i < Cache::kSize / 2 && c->pos; i++) {
154*fcaf7f86SDimitry Andric       IndexT idx = c->cache[--c->pos];
155*fcaf7f86SDimitry Andric       Block *ptr = MapBlock(idx);
156*fcaf7f86SDimitry Andric       ptr->batch = head_idx;
157*fcaf7f86SDimitry Andric       head_idx = idx;
158*fcaf7f86SDimitry Andric     }
159*fcaf7f86SDimitry Andric     // Push it onto the freelist stack.
160*fcaf7f86SDimitry Andric     Block *head = MapBlock(head_idx);
161*fcaf7f86SDimitry Andric     u64 xchg;
162*fcaf7f86SDimitry Andric     u64 cmp = atomic_load(&freelist_, memory_order_acquire);
163*fcaf7f86SDimitry Andric     do {
164*fcaf7f86SDimitry Andric       head->next = static_cast<IndexT>(cmp);
165*fcaf7f86SDimitry Andric       xchg = head_idx | (cmp & kCounterMask) + kCounterInc;
166*fcaf7f86SDimitry Andric     } while (!atomic_compare_exchange_weak(&freelist_, &cmp, xchg,
167*fcaf7f86SDimitry Andric                                            memory_order_acq_rel));
168*fcaf7f86SDimitry Andric   }
169*fcaf7f86SDimitry Andric 
AllocSuperBlock(Cache * c)170*fcaf7f86SDimitry Andric   NOINLINE void AllocSuperBlock(Cache *c) {
171*fcaf7f86SDimitry Andric     Lock lock(&mtx_);
172349cc55cSDimitry Andric     uptr fillpos = atomic_load_relaxed(&fillpos_);
173349cc55cSDimitry Andric     if (fillpos == kL1Size) {
174*fcaf7f86SDimitry Andric       Printf("ThreadSanitizer: %s overflow (%zu*%zu). Dying.\n", name_, kL1Size,
175*fcaf7f86SDimitry Andric              kL2Size);
1760b57cec5SDimitry Andric       Die();
1770b57cec5SDimitry Andric     }
178349cc55cSDimitry Andric     VPrintf(2, "ThreadSanitizer: growing %s: %zu out of %zu*%zu\n", name_,
179349cc55cSDimitry Andric             fillpos, kL1Size, kL2Size);
1800b57cec5SDimitry Andric     T *batch = (T *)MmapOrDie(kL2Size * sizeof(T), name_);
181349cc55cSDimitry Andric     map_[fillpos] = batch;
182*fcaf7f86SDimitry Andric     // Reserve 0 as invalid index.
183*fcaf7f86SDimitry Andric     for (IndexT i = fillpos ? 0 : 1; i < kL2Size; i++) {
184*fcaf7f86SDimitry Andric       new (batch + i) T;
185*fcaf7f86SDimitry Andric       c->cache[c->pos++] = i + fillpos * kL2Size;
186*fcaf7f86SDimitry Andric       if (c->pos == Cache::kSize)
187*fcaf7f86SDimitry Andric         Drain(c);
188*fcaf7f86SDimitry Andric     }
189349cc55cSDimitry Andric     atomic_store_relaxed(&fillpos_, fillpos + 1);
190*fcaf7f86SDimitry Andric     CHECK(c->pos);
1910b57cec5SDimitry Andric   }
1920b57cec5SDimitry Andric };
1930b57cec5SDimitry Andric 
1940b57cec5SDimitry Andric }  // namespace __tsan
1950b57cec5SDimitry Andric 
1960b57cec5SDimitry Andric #endif  // TSAN_DENSE_ALLOC_H
197