1 | //===-- xray_buffer_queue.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 XRay, a dynamic runtime instrumentation system. |
10 | // |
11 | // Defines the interface for a buffer queue implementation. |
12 | // |
13 | //===----------------------------------------------------------------------===// |
14 | #ifndef XRAY_BUFFER_QUEUE_H |
15 | #define XRAY_BUFFER_QUEUE_H |
16 | |
17 | #include "sanitizer_common/sanitizer_atomic.h" |
18 | #include "sanitizer_common/sanitizer_common.h" |
19 | #include "sanitizer_common/sanitizer_mutex.h" |
20 | #include "xray_defs.h" |
21 | #include <cstddef> |
22 | #include <cstdint> |
23 | |
24 | namespace __xray { |
25 | |
26 | /// BufferQueue implements a circular queue of fixed sized buffers (much like a |
27 | /// freelist) but is concerned with making it quick to initialise, finalise, and |
28 | /// get from or return buffers to the queue. This is one key component of the |
29 | /// "flight data recorder" (FDR) mode to support ongoing XRay function call |
30 | /// trace collection. |
31 | class BufferQueue { |
32 | public: |
33 | /// ControlBlock represents the memory layout of how we interpret the backing |
34 | /// store for all buffers and extents managed by a BufferQueue instance. The |
35 | /// ControlBlock has the reference count as the first member, sized according |
36 | /// to platform-specific cache-line size. We never use the Buffer member of |
37 | /// the union, which is only there for compiler-supported alignment and |
38 | /// sizing. |
39 | /// |
40 | /// This ensures that the `Data` member will be placed at least kCacheLineSize |
41 | /// bytes from the beginning of the structure. |
42 | struct ControlBlock { |
43 | union { |
44 | atomic_uint64_t RefCount; |
45 | char Buffer[kCacheLineSize]; |
46 | }; |
47 | |
48 | /// We need to make this size 1, to conform to the C++ rules for array data |
49 | /// members. Typically, we want to subtract this 1 byte for sizing |
50 | /// information. |
51 | char Data[1]; |
52 | }; |
53 | |
54 | struct Buffer { |
55 | atomic_uint64_t *Extents = nullptr; |
56 | uint64_t Generation{0}; |
57 | void *Data = nullptr; |
58 | size_t Size = 0; |
59 | |
60 | private: |
61 | friend class BufferQueue; |
62 | ControlBlock *BackingStore = nullptr; |
63 | ControlBlock *ExtentsBackingStore = nullptr; |
64 | size_t Count = 0; |
65 | }; |
66 | |
67 | struct BufferRep { |
68 | // The managed buffer. |
69 | Buffer Buff; |
70 | |
71 | // This is true if the buffer has been returned to the available queue, and |
72 | // is considered "used" by another thread. |
73 | bool Used = false; |
74 | }; |
75 | |
76 | private: |
77 | // This models a ForwardIterator. |T| Must be either a `Buffer` or `const |
78 | // Buffer`. Note that we only advance to the "used" buffers, when |
79 | // incrementing, so that at dereference we're always at a valid point. |
80 | template <class T> class Iterator { |
81 | public: |
82 | BufferRep *Buffers = nullptr; |
83 | size_t Offset = 0; |
84 | size_t Max = 0; |
85 | |
86 | Iterator &operator++() { |
87 | DCHECK_NE(Offset, Max); |
88 | do { |
89 | ++Offset; |
90 | } while (Offset != Max && !Buffers[Offset].Used); |
91 | return *this; |
92 | } |
93 | |
94 | Iterator operator++(int) { |
95 | Iterator C = *this; |
96 | ++(*this); |
97 | return C; |
98 | } |
99 | |
100 | T &operator*() const { return Buffers[Offset].Buff; } |
101 | |
102 | T *operator->() const { return &(Buffers[Offset].Buff); } |
103 | |
104 | Iterator(BufferRep *Root, size_t O, size_t M) XRAY_NEVER_INSTRUMENT |
105 | : Buffers(Root), |
106 | Offset(O), |
107 | Max(M) { |
108 | // We want to advance to the first Offset where the 'Used' property is |
109 | // true, or to the end of the list/queue. |
110 | while (Offset != Max && !Buffers[Offset].Used) { |
111 | ++Offset; |
112 | } |
113 | } |
114 | |
115 | Iterator() = default; |
116 | Iterator(const Iterator &) = default; |
117 | Iterator(Iterator &&) = default; |
118 | Iterator &operator=(const Iterator &) = default; |
119 | Iterator &operator=(Iterator &&) = default; |
120 | ~Iterator() = default; |
121 | |
122 | template <class V> |
123 | friend bool operator==(const Iterator &L, const Iterator<V> &R) { |
124 | DCHECK_EQ(L.Max, R.Max); |
125 | return L.Buffers == R.Buffers && L.Offset == R.Offset; |
126 | } |
127 | |
128 | template <class V> |
129 | friend bool operator!=(const Iterator &L, const Iterator<V> &R) { |
130 | return !(L == R); |
131 | } |
132 | }; |
133 | |
134 | // Size of each individual Buffer. |
135 | size_t BufferSize; |
136 | |
137 | // Amount of pre-allocated buffers. |
138 | size_t BufferCount; |
139 | |
140 | SpinMutex Mutex; |
141 | atomic_uint8_t Finalizing; |
142 | |
143 | // The collocated ControlBlock and buffer storage. |
144 | ControlBlock *BackingStore; |
145 | |
146 | // The collocated ControlBlock and extents storage. |
147 | ControlBlock *ExtentsBackingStore; |
148 | |
149 | // A dynamically allocated array of BufferRep instances. |
150 | BufferRep *Buffers; |
151 | |
152 | // Pointer to the next buffer to be handed out. |
153 | BufferRep *Next; |
154 | |
155 | // Pointer to the entry in the array where the next released buffer will be |
156 | // placed. |
157 | BufferRep *First; |
158 | |
159 | // Count of buffers that have been handed out through 'getBuffer'. |
160 | size_t LiveBuffers; |
161 | |
162 | // We use a generation number to identify buffers and which generation they're |
163 | // associated with. |
164 | atomic_uint64_t Generation; |
165 | |
166 | /// Releases references to the buffers backed by the current buffer queue. |
167 | void cleanupBuffers(); |
168 | |
169 | public: |
170 | enum class ErrorCode : unsigned { |
171 | Ok, |
172 | NotEnoughMemory, |
173 | QueueFinalizing, |
174 | UnrecognizedBuffer, |
175 | AlreadyFinalized, |
176 | AlreadyInitialized, |
177 | }; |
178 | |
179 | static const char *getErrorString(ErrorCode E) { |
180 | switch (E) { |
181 | case ErrorCode::Ok: |
182 | return "(none)" ; |
183 | case ErrorCode::NotEnoughMemory: |
184 | return "no available buffers in the queue" ; |
185 | case ErrorCode::QueueFinalizing: |
186 | return "queue already finalizing" ; |
187 | case ErrorCode::UnrecognizedBuffer: |
188 | return "buffer being returned not owned by buffer queue" ; |
189 | case ErrorCode::AlreadyFinalized: |
190 | return "queue already finalized" ; |
191 | case ErrorCode::AlreadyInitialized: |
192 | return "queue already initialized" ; |
193 | } |
194 | return "unknown error" ; |
195 | } |
196 | |
197 | /// Initialise a queue of size |N| with buffers of size |B|. We report success |
198 | /// through |Success|. |
199 | BufferQueue(size_t B, size_t N, bool &Success); |
200 | |
201 | /// Updates |Buf| to contain the pointer to an appropriate buffer. Returns an |
202 | /// error in case there are no available buffers to return when we will run |
203 | /// over the upper bound for the total buffers. |
204 | /// |
205 | /// Requirements: |
206 | /// - BufferQueue is not finalising. |
207 | /// |
208 | /// Returns: |
209 | /// - ErrorCode::NotEnoughMemory on exceeding MaxSize. |
210 | /// - ErrorCode::Ok when we find a Buffer. |
211 | /// - ErrorCode::QueueFinalizing or ErrorCode::AlreadyFinalized on |
212 | /// a finalizing/finalized BufferQueue. |
213 | ErrorCode getBuffer(Buffer &Buf); |
214 | |
215 | /// Updates |Buf| to point to nullptr, with size 0. |
216 | /// |
217 | /// Returns: |
218 | /// - ErrorCode::Ok when we successfully release the buffer. |
219 | /// - ErrorCode::UnrecognizedBuffer for when this BufferQueue does not own |
220 | /// the buffer being released. |
221 | ErrorCode releaseBuffer(Buffer &Buf); |
222 | |
223 | /// Initializes the buffer queue, starting a new generation. We can re-set the |
224 | /// size of buffers with |BS| along with the buffer count with |BC|. |
225 | /// |
226 | /// Returns: |
227 | /// - ErrorCode::Ok when we successfully initialize the buffer. This |
228 | /// requires that the buffer queue is previously finalized. |
229 | /// - ErrorCode::AlreadyInitialized when the buffer queue is not finalized. |
230 | ErrorCode init(size_t BS, size_t BC); |
231 | |
232 | bool finalizing() const { |
233 | return atomic_load(a: &Finalizing, mo: memory_order_acquire); |
234 | } |
235 | |
236 | uint64_t generation() const { |
237 | return atomic_load(a: &Generation, mo: memory_order_acquire); |
238 | } |
239 | |
240 | /// Returns the configured size of the buffers in the buffer queue. |
241 | size_t ConfiguredBufferSize() const { return BufferSize; } |
242 | |
243 | /// Sets the state of the BufferQueue to finalizing, which ensures that: |
244 | /// |
245 | /// - All subsequent attempts to retrieve a Buffer will fail. |
246 | /// - All releaseBuffer operations will not fail. |
247 | /// |
248 | /// After a call to finalize succeeds, all subsequent calls to finalize will |
249 | /// fail with ErrorCode::QueueFinalizing. |
250 | ErrorCode finalize(); |
251 | |
252 | /// Applies the provided function F to each Buffer in the queue, only if the |
253 | /// Buffer is marked 'used' (i.e. has been the result of getBuffer(...) and a |
254 | /// releaseBuffer(...) operation). |
255 | template <class F> void apply(F Fn) XRAY_NEVER_INSTRUMENT { |
256 | SpinMutexLock G(&Mutex); |
257 | for (auto I = begin(), E = end(); I != E; ++I) |
258 | Fn(*I); |
259 | } |
260 | |
261 | using const_iterator = Iterator<const Buffer>; |
262 | using iterator = Iterator<Buffer>; |
263 | |
264 | /// Provides iterator access to the raw Buffer instances. |
265 | iterator begin() const { return iterator(Buffers, 0, BufferCount); } |
266 | const_iterator cbegin() const { |
267 | return const_iterator(Buffers, 0, BufferCount); |
268 | } |
269 | iterator end() const { return iterator(Buffers, BufferCount, BufferCount); } |
270 | const_iterator cend() const { |
271 | return const_iterator(Buffers, BufferCount, BufferCount); |
272 | } |
273 | |
274 | // Cleans up allocated buffers. |
275 | ~BufferQueue(); |
276 | }; |
277 | |
278 | } // namespace __xray |
279 | |
280 | #endif // XRAY_BUFFER_QUEUE_H |
281 | |