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