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 
allocControlBlock(size_t Size,size_t Count)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 
deallocControlBlock(BufferQueue::ControlBlock * C,size_t Size,size_t Count)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 
decRefCount(BufferQueue::ControlBlock * C,size_t Size,size_t Count)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 
incRefCount(BufferQueue::ControlBlock * C)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 
init(size_t BS,size_t BC)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 
BufferQueue(size_t B,size_t N,bool & Success)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 
getBuffer(Buffer & Buf)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 
releaseBuffer(Buffer & Buf)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 
finalize()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 
cleanupBuffers()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 
~BufferQueue()238 BufferQueue::~BufferQueue() { cleanupBuffers(); }
239