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