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