| 1 | //===-- xray_buffer_queue.cpp ----------------------------------*- 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 XRay, a dynamic runtime instrumentation system. |
| 10 | // |
| 11 | // Defines the interface for a buffer queue implementation. |
| 12 | // |
| 13 | //===----------------------------------------------------------------------===// |
| 14 | #include "xray_buffer_queue.h" |
| 15 | #include "sanitizer_common/sanitizer_atomic.h" |
| 16 | #include "sanitizer_common/sanitizer_common.h" |
| 17 | #include "sanitizer_common/sanitizer_libc.h" |
| 18 | #if !SANITIZER_FUCHSIA |
| 19 | #include "sanitizer_common/sanitizer_posix.h" |
| 20 | #endif |
| 21 | #include "xray_allocator.h" |
| 22 | #include "xray_defs.h" |
| 23 | #include <memory> |
| 24 | #include <sys/mman.h> |
| 25 | |
| 26 | using namespace __xray; |
| 27 | |
| 28 | namespace { |
| 29 | |
| 30 | BufferQueue::ControlBlock *allocControlBlock(size_t Size, size_t Count) { |
| 31 | auto B = |
| 32 | allocateBuffer(S: (sizeof(BufferQueue::ControlBlock) - 1) + (Size * Count)); |
| 33 | return B == nullptr ? nullptr |
| 34 | : reinterpret_cast<BufferQueue::ControlBlock *>(B); |
| 35 | } |
| 36 | |
| 37 | void deallocControlBlock(BufferQueue::ControlBlock *C, size_t Size, |
| 38 | size_t Count) { |
| 39 | deallocateBuffer(B: reinterpret_cast<unsigned char *>(C), |
| 40 | S: (sizeof(BufferQueue::ControlBlock) - 1) + (Size * Count)); |
| 41 | } |
| 42 | |
| 43 | void decRefCount(BufferQueue::ControlBlock *C, size_t Size, size_t Count) { |
| 44 | if (C == nullptr) |
| 45 | return; |
| 46 | if (atomic_fetch_sub(a: &C->RefCount, v: 1, mo: memory_order_acq_rel) == 1) |
| 47 | deallocControlBlock(C, Size, Count); |
| 48 | } |
| 49 | |
| 50 | void incRefCount(BufferQueue::ControlBlock *C) { |
| 51 | if (C == nullptr) |
| 52 | return; |
| 53 | atomic_fetch_add(a: &C->RefCount, v: 1, mo: memory_order_acq_rel); |
| 54 | } |
| 55 | |
| 56 | // We use a struct to ensure that we are allocating one atomic_uint64_t per |
| 57 | // cache line. This allows us to not worry about false-sharing among atomic |
| 58 | // objects being updated (constantly) by different threads. |
| 59 | struct ExtentsPadded { |
| 60 | union { |
| 61 | atomic_uint64_t Extents; |
| 62 | unsigned char Storage[kCacheLineSize]; |
| 63 | }; |
| 64 | }; |
| 65 | |
| 66 | constexpr size_t kExtentsSize = sizeof(ExtentsPadded); |
| 67 | |
| 68 | } // namespace |
| 69 | |
| 70 | BufferQueue::ErrorCode BufferQueue::init(size_t BS, size_t BC) { |
| 71 | SpinMutexLock Guard(&Mutex); |
| 72 | |
| 73 | if (!finalizing()) |
| 74 | return BufferQueue::ErrorCode::AlreadyInitialized; |
| 75 | |
| 76 | cleanupBuffers(); |
| 77 | |
| 78 | bool Success = false; |
| 79 | BufferSize = BS; |
| 80 | BufferCount = BC; |
| 81 | |
| 82 | BackingStore = allocControlBlock(Size: BufferSize, Count: BufferCount); |
| 83 | if (BackingStore == nullptr) |
| 84 | return BufferQueue::ErrorCode::NotEnoughMemory; |
| 85 | |
| 86 | auto CleanupBackingStore = at_scope_exit(fn: [&, this] { |
| 87 | if (Success) |
| 88 | return; |
| 89 | deallocControlBlock(C: BackingStore, Size: BufferSize, Count: BufferCount); |
| 90 | BackingStore = nullptr; |
| 91 | }); |
| 92 | |
| 93 | // Initialize enough atomic_uint64_t instances, each |
| 94 | ExtentsBackingStore = allocControlBlock(Size: kExtentsSize, Count: BufferCount); |
| 95 | if (ExtentsBackingStore == nullptr) |
| 96 | return BufferQueue::ErrorCode::NotEnoughMemory; |
| 97 | |
| 98 | auto CleanupExtentsBackingStore = at_scope_exit(fn: [&, this] { |
| 99 | if (Success) |
| 100 | return; |
| 101 | deallocControlBlock(C: ExtentsBackingStore, Size: kExtentsSize, Count: BufferCount); |
| 102 | ExtentsBackingStore = nullptr; |
| 103 | }); |
| 104 | |
| 105 | Buffers = initArray<BufferRep>(N: BufferCount); |
| 106 | if (Buffers == nullptr) |
| 107 | return BufferQueue::ErrorCode::NotEnoughMemory; |
| 108 | |
| 109 | // At this point we increment the generation number to associate the buffers |
| 110 | // to the new generation. |
| 111 | atomic_fetch_add(a: &Generation, v: 1, mo: memory_order_acq_rel); |
| 112 | |
| 113 | // First, we initialize the refcount in the ControlBlock, which we treat as |
| 114 | // being at the start of the BackingStore pointer. |
| 115 | atomic_store(a: &BackingStore->RefCount, v: 1, mo: memory_order_release); |
| 116 | atomic_store(a: &ExtentsBackingStore->RefCount, v: 1, mo: memory_order_release); |
| 117 | |
| 118 | // Then we initialise the individual buffers that sub-divide the whole backing |
| 119 | // store. Each buffer will start at the `Data` member of the ControlBlock, and |
| 120 | // will be offsets from these locations. |
| 121 | for (size_t i = 0; i < BufferCount; ++i) { |
| 122 | auto &T = Buffers[i]; |
| 123 | auto &Buf = T.Buff; |
| 124 | auto *E = reinterpret_cast<ExtentsPadded *>(&ExtentsBackingStore->Data + |
| 125 | (kExtentsSize * i)); |
| 126 | Buf.Extents = &E->Extents; |
| 127 | atomic_store(a: Buf.Extents, v: 0, mo: memory_order_release); |
| 128 | Buf.Generation = generation(); |
| 129 | Buf.Data = &BackingStore->Data + (BufferSize * i); |
| 130 | Buf.Size = BufferSize; |
| 131 | Buf.BackingStore = BackingStore; |
| 132 | Buf.ExtentsBackingStore = ExtentsBackingStore; |
| 133 | Buf.Count = BufferCount; |
| 134 | T.Used = false; |
| 135 | } |
| 136 | |
| 137 | Next = Buffers; |
| 138 | First = Buffers; |
| 139 | LiveBuffers = 0; |
| 140 | atomic_store(a: &Finalizing, v: 0, mo: memory_order_release); |
| 141 | Success = true; |
| 142 | return BufferQueue::ErrorCode::Ok; |
| 143 | } |
| 144 | |
| 145 | BufferQueue::BufferQueue(size_t B, size_t N, |
| 146 | bool &Success) XRAY_NEVER_INSTRUMENT |
| 147 | : BufferSize(B), |
| 148 | BufferCount(N), |
| 149 | Mutex(), |
| 150 | Finalizing{.val_dont_use: 1}, |
| 151 | BackingStore(nullptr), |
| 152 | ExtentsBackingStore(nullptr), |
| 153 | Buffers(nullptr), |
| 154 | Next(Buffers), |
| 155 | First(Buffers), |
| 156 | LiveBuffers(0), |
| 157 | Generation{.val_dont_use: 0} { |
| 158 | Success = init(BS: B, BC: N) == BufferQueue::ErrorCode::Ok; |
| 159 | } |
| 160 | |
| 161 | BufferQueue::ErrorCode BufferQueue::getBuffer(Buffer &Buf) { |
| 162 | if (atomic_load(a: &Finalizing, mo: memory_order_acquire)) |
| 163 | return ErrorCode::QueueFinalizing; |
| 164 | |
| 165 | BufferRep *B = nullptr; |
| 166 | { |
| 167 | SpinMutexLock Guard(&Mutex); |
| 168 | if (LiveBuffers == BufferCount) |
| 169 | return ErrorCode::NotEnoughMemory; |
| 170 | B = Next++; |
| 171 | if (Next == (Buffers + BufferCount)) |
| 172 | Next = Buffers; |
| 173 | ++LiveBuffers; |
| 174 | } |
| 175 | |
| 176 | incRefCount(C: BackingStore); |
| 177 | incRefCount(C: ExtentsBackingStore); |
| 178 | Buf = B->Buff; |
| 179 | Buf.Generation = generation(); |
| 180 | B->Used = true; |
| 181 | return ErrorCode::Ok; |
| 182 | } |
| 183 | |
| 184 | BufferQueue::ErrorCode BufferQueue::releaseBuffer(Buffer &Buf) { |
| 185 | // Check whether the buffer being referred to is within the bounds of the |
| 186 | // backing store's range. |
| 187 | BufferRep *B = nullptr; |
| 188 | { |
| 189 | SpinMutexLock Guard(&Mutex); |
| 190 | if (Buf.Generation != generation() || LiveBuffers == 0) { |
| 191 | Buf = {}; |
| 192 | decRefCount(C: Buf.BackingStore, Size: Buf.Size, Count: Buf.Count); |
| 193 | decRefCount(C: Buf.ExtentsBackingStore, Size: kExtentsSize, Count: Buf.Count); |
| 194 | return BufferQueue::ErrorCode::Ok; |
| 195 | } |
| 196 | |
| 197 | if (Buf.Data < &BackingStore->Data || |
| 198 | Buf.Data > &BackingStore->Data + (BufferCount * BufferSize)) |
| 199 | return BufferQueue::ErrorCode::UnrecognizedBuffer; |
| 200 | |
| 201 | --LiveBuffers; |
| 202 | B = First++; |
| 203 | if (First == (Buffers + BufferCount)) |
| 204 | First = Buffers; |
| 205 | } |
| 206 | |
| 207 | // Now that the buffer has been released, we mark it as "used". |
| 208 | B->Buff = Buf; |
| 209 | B->Used = true; |
| 210 | decRefCount(C: Buf.BackingStore, Size: Buf.Size, Count: Buf.Count); |
| 211 | decRefCount(C: Buf.ExtentsBackingStore, Size: kExtentsSize, Count: Buf.Count); |
| 212 | atomic_store(a: B->Buff.Extents, v: atomic_load(a: Buf.Extents, mo: memory_order_acquire), |
| 213 | mo: memory_order_release); |
| 214 | Buf = {}; |
| 215 | return ErrorCode::Ok; |
| 216 | } |
| 217 | |
| 218 | BufferQueue::ErrorCode BufferQueue::finalize() { |
| 219 | if (atomic_exchange(a: &Finalizing, v: 1, mo: memory_order_acq_rel)) |
| 220 | return ErrorCode::QueueFinalizing; |
| 221 | return ErrorCode::Ok; |
| 222 | } |
| 223 | |
| 224 | void BufferQueue::cleanupBuffers() { |
| 225 | for (auto B = Buffers, E = Buffers + BufferCount; B != E; ++B) |
| 226 | B->~BufferRep(); |
| 227 | deallocateBuffer(B: Buffers, S: BufferCount); |
| 228 | decRefCount(C: BackingStore, Size: BufferSize, Count: BufferCount); |
| 229 | decRefCount(C: ExtentsBackingStore, Size: kExtentsSize, Count: BufferCount); |
| 230 | BackingStore = nullptr; |
| 231 | ExtentsBackingStore = nullptr; |
| 232 | Buffers = nullptr; |
| 233 | BufferCount = 0; |
| 234 | BufferSize = 0; |
| 235 | } |
| 236 | |
| 237 | BufferQueue::~BufferQueue() { cleanupBuffers(); } |
| 238 | |