13cab2bb3Spatrick //===-- tsan_dense_alloc.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 // This file is a part of ThreadSanitizer (TSan), a race detector. 103cab2bb3Spatrick // 113cab2bb3Spatrick // A DenseSlabAlloc is a freelist-based allocator of fixed-size objects. 123cab2bb3Spatrick // DenseSlabAllocCache is a thread-local cache for DenseSlabAlloc. 133cab2bb3Spatrick // The only difference with traditional slab allocators is that DenseSlabAlloc 143cab2bb3Spatrick // allocates/free indices of objects and provide a functionality to map 153cab2bb3Spatrick // the index onto the real pointer. The index is u32, that is, 2 times smaller 163cab2bb3Spatrick // than uptr (hense the Dense prefix). 173cab2bb3Spatrick //===----------------------------------------------------------------------===// 183cab2bb3Spatrick #ifndef TSAN_DENSE_ALLOC_H 193cab2bb3Spatrick #define TSAN_DENSE_ALLOC_H 203cab2bb3Spatrick 213cab2bb3Spatrick #include "sanitizer_common/sanitizer_common.h" 223cab2bb3Spatrick #include "tsan_defs.h" 233cab2bb3Spatrick 243cab2bb3Spatrick namespace __tsan { 253cab2bb3Spatrick 263cab2bb3Spatrick class DenseSlabAllocCache { 273cab2bb3Spatrick static const uptr kSize = 128; 283cab2bb3Spatrick typedef u32 IndexT; 293cab2bb3Spatrick uptr pos; 303cab2bb3Spatrick IndexT cache[kSize]; 31d89ec533Spatrick template <typename, uptr, uptr, u64> 32d89ec533Spatrick friend class DenseSlabAlloc; 333cab2bb3Spatrick }; 343cab2bb3Spatrick 35d89ec533Spatrick template <typename T, uptr kL1Size, uptr kL2Size, u64 kReserved = 0> 363cab2bb3Spatrick class DenseSlabAlloc { 373cab2bb3Spatrick public: 383cab2bb3Spatrick typedef DenseSlabAllocCache Cache; 393cab2bb3Spatrick typedef typename Cache::IndexT IndexT; 403cab2bb3Spatrick 41d89ec533Spatrick static_assert((kL1Size & (kL1Size - 1)) == 0, 42d89ec533Spatrick "kL1Size must be a power-of-two"); 43d89ec533Spatrick static_assert((kL2Size & (kL2Size - 1)) == 0, 44d89ec533Spatrick "kL2Size must be a power-of-two"); 45d89ec533Spatrick static_assert((kL1Size * kL2Size) <= (1ull << (sizeof(IndexT) * 8)), 46d89ec533Spatrick "kL1Size/kL2Size are too large"); 47d89ec533Spatrick static_assert(((kL1Size * kL2Size - 1) & kReserved) == 0, 48d89ec533Spatrick "reserved bits don't fit"); 49d89ec533Spatrick static_assert(sizeof(T) > sizeof(IndexT), 50d89ec533Spatrick "it doesn't make sense to use dense alloc"); 51d89ec533Spatrick DenseSlabAlloc(LinkerInitialized,const char * name)52*810390e3Srobert DenseSlabAlloc(LinkerInitialized, const char *name) : name_(name) {} 533cab2bb3Spatrick DenseSlabAlloc(const char * name)54d89ec533Spatrick explicit DenseSlabAlloc(const char *name) 55d89ec533Spatrick : DenseSlabAlloc(LINKER_INITIALIZED, name) { 56d89ec533Spatrick // It can be very large. 57d89ec533Spatrick // Don't page it in for linker initialized objects. 58d89ec533Spatrick internal_memset(map_, 0, sizeof(map_)); 59d89ec533Spatrick } 60d89ec533Spatrick ~DenseSlabAlloc()613cab2bb3Spatrick ~DenseSlabAlloc() { 623cab2bb3Spatrick for (uptr i = 0; i < kL1Size; i++) { 633cab2bb3Spatrick if (map_[i] != 0) 643cab2bb3Spatrick UnmapOrDie(map_[i], kL2Size * sizeof(T)); 653cab2bb3Spatrick } 663cab2bb3Spatrick } 673cab2bb3Spatrick Alloc(Cache * c)683cab2bb3Spatrick IndexT Alloc(Cache *c) { 693cab2bb3Spatrick if (c->pos == 0) 703cab2bb3Spatrick Refill(c); 713cab2bb3Spatrick return c->cache[--c->pos]; 723cab2bb3Spatrick } 733cab2bb3Spatrick Free(Cache * c,IndexT idx)743cab2bb3Spatrick void Free(Cache *c, IndexT idx) { 753cab2bb3Spatrick DCHECK_NE(idx, 0); 763cab2bb3Spatrick if (c->pos == Cache::kSize) 773cab2bb3Spatrick Drain(c); 783cab2bb3Spatrick c->cache[c->pos++] = idx; 793cab2bb3Spatrick } 803cab2bb3Spatrick Map(IndexT idx)813cab2bb3Spatrick T *Map(IndexT idx) { 823cab2bb3Spatrick DCHECK_NE(idx, 0); 833cab2bb3Spatrick DCHECK_LE(idx, kL1Size * kL2Size); 843cab2bb3Spatrick return &map_[idx / kL2Size][idx % kL2Size]; 853cab2bb3Spatrick } 863cab2bb3Spatrick FlushCache(Cache * c)873cab2bb3Spatrick void FlushCache(Cache *c) { 88*810390e3Srobert while (c->pos) Drain(c); 893cab2bb3Spatrick } 903cab2bb3Spatrick InitCache(Cache * c)913cab2bb3Spatrick void InitCache(Cache *c) { 923cab2bb3Spatrick c->pos = 0; 933cab2bb3Spatrick internal_memset(c->cache, 0, sizeof(c->cache)); 943cab2bb3Spatrick } 953cab2bb3Spatrick AllocatedMemory()96*810390e3Srobert uptr AllocatedMemory() const { 97*810390e3Srobert return atomic_load_relaxed(&fillpos_) * kL2Size * sizeof(T); 98*810390e3Srobert } 99*810390e3Srobert 100*810390e3Srobert template <typename Func> ForEach(Func func)101*810390e3Srobert void ForEach(Func func) { 102*810390e3Srobert Lock lock(&mtx_); 103*810390e3Srobert uptr fillpos = atomic_load_relaxed(&fillpos_); 104*810390e3Srobert for (uptr l1 = 0; l1 < fillpos; l1++) { 105*810390e3Srobert for (IndexT l2 = l1 == 0 ? 1 : 0; l2 < kL2Size; l2++) func(&map_[l1][l2]); 106*810390e3Srobert } 107*810390e3Srobert } 108*810390e3Srobert 1093cab2bb3Spatrick private: 1103cab2bb3Spatrick T *map_[kL1Size]; 111*810390e3Srobert Mutex mtx_; 112*810390e3Srobert // The freelist is organized as a lock-free stack of batches of nodes. 113*810390e3Srobert // The stack itself uses Block::next links, while the batch within each 114*810390e3Srobert // stack node uses Block::batch links. 115*810390e3Srobert // Low 32-bits of freelist_ is the node index, top 32-bits is ABA-counter. 116*810390e3Srobert atomic_uint64_t freelist_ = {0}; 117*810390e3Srobert atomic_uintptr_t fillpos_ = {0}; 118*810390e3Srobert const char *const name_; 1193cab2bb3Spatrick 120*810390e3Srobert struct Block { 121*810390e3Srobert IndexT next; 122*810390e3Srobert IndexT batch; 123*810390e3Srobert }; 124*810390e3Srobert MapBlock(IndexT idx)125*810390e3Srobert Block *MapBlock(IndexT idx) { return reinterpret_cast<Block *>(Map(idx)); } 126*810390e3Srobert 127*810390e3Srobert static constexpr u64 kCounterInc = 1ull << 32; 128*810390e3Srobert static constexpr u64 kCounterMask = ~(kCounterInc - 1); 129*810390e3Srobert Refill(Cache * c)130*810390e3Srobert NOINLINE void Refill(Cache *c) { 131*810390e3Srobert // Pop 1 batch of nodes from the freelist. 132*810390e3Srobert IndexT idx; 133*810390e3Srobert u64 xchg; 134*810390e3Srobert u64 cmp = atomic_load(&freelist_, memory_order_acquire); 135*810390e3Srobert do { 136*810390e3Srobert idx = static_cast<IndexT>(cmp); 137*810390e3Srobert if (!idx) 138*810390e3Srobert return AllocSuperBlock(c); 139*810390e3Srobert Block *ptr = MapBlock(idx); 140*810390e3Srobert xchg = ptr->next | (cmp & kCounterMask); 141*810390e3Srobert } while (!atomic_compare_exchange_weak(&freelist_, &cmp, xchg, 142*810390e3Srobert memory_order_acq_rel)); 143*810390e3Srobert // Unpack it into c->cache. 144*810390e3Srobert while (idx) { 145*810390e3Srobert c->cache[c->pos++] = idx; 146*810390e3Srobert idx = MapBlock(idx)->batch; 147*810390e3Srobert } 148*810390e3Srobert } 149*810390e3Srobert Drain(Cache * c)150*810390e3Srobert NOINLINE void Drain(Cache *c) { 151*810390e3Srobert // Build a batch of at most Cache::kSize / 2 nodes linked by Block::batch. 152*810390e3Srobert IndexT head_idx = 0; 153*810390e3Srobert for (uptr i = 0; i < Cache::kSize / 2 && c->pos; i++) { 154*810390e3Srobert IndexT idx = c->cache[--c->pos]; 155*810390e3Srobert Block *ptr = MapBlock(idx); 156*810390e3Srobert ptr->batch = head_idx; 157*810390e3Srobert head_idx = idx; 158*810390e3Srobert } 159*810390e3Srobert // Push it onto the freelist stack. 160*810390e3Srobert Block *head = MapBlock(head_idx); 161*810390e3Srobert u64 xchg; 162*810390e3Srobert u64 cmp = atomic_load(&freelist_, memory_order_acquire); 163*810390e3Srobert do { 164*810390e3Srobert head->next = static_cast<IndexT>(cmp); 165*810390e3Srobert xchg = head_idx | (cmp & kCounterMask) + kCounterInc; 166*810390e3Srobert } while (!atomic_compare_exchange_weak(&freelist_, &cmp, xchg, 167*810390e3Srobert memory_order_acq_rel)); 168*810390e3Srobert } 169*810390e3Srobert AllocSuperBlock(Cache * c)170*810390e3Srobert NOINLINE void AllocSuperBlock(Cache *c) { 171*810390e3Srobert Lock lock(&mtx_); 172*810390e3Srobert uptr fillpos = atomic_load_relaxed(&fillpos_); 173*810390e3Srobert if (fillpos == kL1Size) { 174*810390e3Srobert Printf("ThreadSanitizer: %s overflow (%zu*%zu). Dying.\n", name_, kL1Size, 175*810390e3Srobert kL2Size); 1763cab2bb3Spatrick Die(); 1773cab2bb3Spatrick } 178*810390e3Srobert VPrintf(2, "ThreadSanitizer: growing %s: %zu out of %zu*%zu\n", name_, 179*810390e3Srobert fillpos, kL1Size, kL2Size); 1803cab2bb3Spatrick T *batch = (T *)MmapOrDie(kL2Size * sizeof(T), name_); 181*810390e3Srobert map_[fillpos] = batch; 1823cab2bb3Spatrick // Reserve 0 as invalid index. 183*810390e3Srobert for (IndexT i = fillpos ? 0 : 1; i < kL2Size; i++) { 1843cab2bb3Spatrick new (batch + i) T; 185*810390e3Srobert c->cache[c->pos++] = i + fillpos * kL2Size; 186*810390e3Srobert if (c->pos == Cache::kSize) 187*810390e3Srobert Drain(c); 1883cab2bb3Spatrick } 189*810390e3Srobert atomic_store_relaxed(&fillpos_, fillpos + 1); 190*810390e3Srobert CHECK(c->pos); 1913cab2bb3Spatrick } 1923cab2bb3Spatrick }; 1933cab2bb3Spatrick 1943cab2bb3Spatrick } // namespace __tsan 1953cab2bb3Spatrick 1963cab2bb3Spatrick #endif // TSAN_DENSE_ALLOC_H 197