| 1 | //===-- tsan_dense_alloc.h --------------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file is a part of ThreadSanitizer (TSan), a race detector. |
| 10 | // |
| 11 | // A DenseSlabAlloc is a freelist-based allocator of fixed-size objects. |
| 12 | // DenseSlabAllocCache is a thread-local cache for DenseSlabAlloc. |
| 13 | // The only difference with traditional slab allocators is that DenseSlabAlloc |
| 14 | // allocates/free indices of objects and provide a functionality to map |
| 15 | // the index onto the real pointer. The index is u32, that is, 2 times smaller |
| 16 | // than uptr (hense the Dense prefix). |
| 17 | //===----------------------------------------------------------------------===// |
| 18 | #ifndef TSAN_DENSE_ALLOC_H |
| 19 | #define TSAN_DENSE_ALLOC_H |
| 20 | |
| 21 | #include "sanitizer_common/sanitizer_common.h" |
| 22 | #include "tsan_defs.h" |
| 23 | |
| 24 | namespace __tsan { |
| 25 | |
| 26 | class DenseSlabAllocCache { |
| 27 | static const uptr kSize = 128; |
| 28 | typedef u32 IndexT; |
| 29 | uptr pos; |
| 30 | IndexT cache[kSize]; |
| 31 | template <typename, uptr, uptr, u64> |
| 32 | friend class DenseSlabAlloc; |
| 33 | }; |
| 34 | |
| 35 | template <typename T, uptr kL1Size, uptr kL2Size, u64 kReserved = 0> |
| 36 | class DenseSlabAlloc { |
| 37 | public: |
| 38 | typedef DenseSlabAllocCache Cache; |
| 39 | typedef typename Cache::IndexT IndexT; |
| 40 | |
| 41 | static_assert((kL1Size & (kL1Size - 1)) == 0, |
| 42 | "kL1Size must be a power-of-two" ); |
| 43 | static_assert((kL2Size & (kL2Size - 1)) == 0, |
| 44 | "kL2Size must be a power-of-two" ); |
| 45 | static_assert((kL1Size * kL2Size) <= (1ull << (sizeof(IndexT) * 8)), |
| 46 | "kL1Size/kL2Size are too large" ); |
| 47 | static_assert(((kL1Size * kL2Size - 1) & kReserved) == 0, |
| 48 | "reserved bits don't fit" ); |
| 49 | static_assert(sizeof(T) > sizeof(IndexT), |
| 50 | "it doesn't make sense to use dense alloc" ); |
| 51 | |
| 52 | DenseSlabAlloc(LinkerInitialized, const char *name) : name_(name) {} |
| 53 | |
| 54 | explicit DenseSlabAlloc(const char *name) |
| 55 | : DenseSlabAlloc(LINKER_INITIALIZED, name) { |
| 56 | // It can be very large. |
| 57 | // Don't page it in for linker initialized objects. |
| 58 | internal_memset(map_, 0, sizeof(map_)); |
| 59 | } |
| 60 | |
| 61 | ~DenseSlabAlloc() { |
| 62 | for (uptr i = 0; i < kL1Size; i++) { |
| 63 | if (map_[i] != 0) |
| 64 | UnmapOrDie(map_[i], kL2Size * sizeof(T)); |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | IndexT Alloc(Cache *c) { |
| 69 | if (c->pos == 0) |
| 70 | Refill(c); |
| 71 | return c->cache[--c->pos]; |
| 72 | } |
| 73 | |
| 74 | void Free(Cache *c, IndexT idx) { |
| 75 | DCHECK_NE(idx, 0); |
| 76 | if (c->pos == Cache::kSize) |
| 77 | Drain(c); |
| 78 | c->cache[c->pos++] = idx; |
| 79 | } |
| 80 | |
| 81 | T *Map(IndexT idx) { |
| 82 | DCHECK_NE(idx, 0); |
| 83 | DCHECK_LE(idx, kL1Size * kL2Size); |
| 84 | return &map_[idx / kL2Size][idx % kL2Size]; |
| 85 | } |
| 86 | |
| 87 | void FlushCache(Cache *c) { |
| 88 | while (c->pos) Drain(c); |
| 89 | } |
| 90 | |
| 91 | void InitCache(Cache *c) { |
| 92 | c->pos = 0; |
| 93 | internal_memset(s: c->cache, c: 0, n: sizeof(c->cache)); |
| 94 | } |
| 95 | |
| 96 | uptr AllocatedMemory() const { |
| 97 | return atomic_load_relaxed(a: &fillpos_) * kL2Size * sizeof(T); |
| 98 | } |
| 99 | |
| 100 | template <typename Func> |
| 101 | void ForEach(Func func) { |
| 102 | Lock lock(&mtx_); |
| 103 | uptr fillpos = atomic_load_relaxed(a: &fillpos_); |
| 104 | for (uptr l1 = 0; l1 < fillpos; l1++) { |
| 105 | for (IndexT l2 = l1 == 0 ? 1 : 0; l2 < kL2Size; l2++) func(&map_[l1][l2]); |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | private: |
| 110 | T *map_[kL1Size]; |
| 111 | Mutex mtx_; |
| 112 | // The freelist is organized as a lock-free stack of batches of nodes. |
| 113 | // The stack itself uses Block::next links, while the batch within each |
| 114 | // stack node uses Block::batch links. |
| 115 | // Low 32-bits of freelist_ is the node index, top 32-bits is ABA-counter. |
| 116 | atomic_uint64_t freelist_ = {.val_dont_use: 0}; |
| 117 | atomic_uintptr_t fillpos_ = {.val_dont_use: 0}; |
| 118 | const char *const name_; |
| 119 | |
| 120 | struct Block { |
| 121 | IndexT next; |
| 122 | IndexT batch; |
| 123 | }; |
| 124 | |
| 125 | Block *MapBlock(IndexT idx) { return reinterpret_cast<Block *>(Map(idx)); } |
| 126 | |
| 127 | static constexpr u64 kCounterInc = 1ull << 32; |
| 128 | static constexpr u64 kCounterMask = ~(kCounterInc - 1); |
| 129 | |
| 130 | NOINLINE void Refill(Cache *c) { |
| 131 | // Pop 1 batch of nodes from the freelist. |
| 132 | IndexT idx; |
| 133 | u64 xchg; |
| 134 | u64 cmp = atomic_load(a: &freelist_, mo: memory_order_acquire); |
| 135 | do { |
| 136 | idx = static_cast<IndexT>(cmp); |
| 137 | if (!idx) |
| 138 | return AllocSuperBlock(c); |
| 139 | Block *ptr = MapBlock(idx); |
| 140 | xchg = ptr->next | (cmp & kCounterMask); |
| 141 | } while (!atomic_compare_exchange_weak(a: &freelist_, cmp: &cmp, xchg, |
| 142 | mo: memory_order_acq_rel)); |
| 143 | // Unpack it into c->cache. |
| 144 | while (idx) { |
| 145 | c->cache[c->pos++] = idx; |
| 146 | idx = MapBlock(idx)->batch; |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | NOINLINE void Drain(Cache *c) { |
| 151 | // Build a batch of at most Cache::kSize / 2 nodes linked by Block::batch. |
| 152 | IndexT head_idx = 0; |
| 153 | for (uptr i = 0; i < Cache::kSize / 2 && c->pos; i++) { |
| 154 | IndexT idx = c->cache[--c->pos]; |
| 155 | Block *ptr = MapBlock(idx); |
| 156 | ptr->batch = head_idx; |
| 157 | head_idx = idx; |
| 158 | } |
| 159 | // Push it onto the freelist stack. |
| 160 | Block *head = MapBlock(idx: head_idx); |
| 161 | u64 xchg; |
| 162 | u64 cmp = atomic_load(a: &freelist_, mo: memory_order_acquire); |
| 163 | do { |
| 164 | head->next = static_cast<IndexT>(cmp); |
| 165 | xchg = head_idx | (cmp & kCounterMask) + kCounterInc; |
| 166 | } while (!atomic_compare_exchange_weak(a: &freelist_, cmp: &cmp, xchg, |
| 167 | mo: memory_order_acq_rel)); |
| 168 | } |
| 169 | |
| 170 | NOINLINE void AllocSuperBlock(Cache *c) { |
| 171 | Lock lock(&mtx_); |
| 172 | uptr fillpos = atomic_load_relaxed(a: &fillpos_); |
| 173 | if (fillpos == kL1Size) { |
| 174 | Printf(format: "ThreadSanitizer: %s overflow (%zu*%zu). Dying.\n" , name_, kL1Size, |
| 175 | kL2Size); |
| 176 | Die(); |
| 177 | } |
| 178 | VPrintf(2, "ThreadSanitizer: growing %s: %zu out of %zu*%zu\n" , name_, |
| 179 | fillpos, kL1Size, kL2Size); |
| 180 | T *batch = (T *)MmapOrDie(size: kL2Size * sizeof(T), mem_type: name_); |
| 181 | map_[fillpos] = batch; |
| 182 | // Reserve 0 as invalid index. |
| 183 | for (IndexT i = fillpos ? 0 : 1; i < kL2Size; i++) { |
| 184 | new (batch + i) T; |
| 185 | c->cache[c->pos++] = i + fillpos * kL2Size; |
| 186 | if (c->pos == Cache::kSize) |
| 187 | Drain(c); |
| 188 | } |
| 189 | atomic_store_relaxed(a: &fillpos_, v: fillpos + 1); |
| 190 | CHECK(c->pos); |
| 191 | } |
| 192 | }; |
| 193 | |
| 194 | } // namespace __tsan |
| 195 | |
| 196 | #endif // TSAN_DENSE_ALLOC_H |
| 197 | |