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