xref: /freebsd/contrib/llvm-project/compiler-rt/lib/xray/xray_buffer_queue.h (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
10b57cec5SDimitry Andric //===-- xray_buffer_queue.h ------------------------------------*- C++ -*-===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric // This file is a part of XRay, a dynamic runtime instrumentation system.
100b57cec5SDimitry Andric //
110b57cec5SDimitry Andric // Defines the interface for a buffer queue implementation.
120b57cec5SDimitry Andric //
130b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
140b57cec5SDimitry Andric #ifndef XRAY_BUFFER_QUEUE_H
150b57cec5SDimitry Andric #define XRAY_BUFFER_QUEUE_H
160b57cec5SDimitry Andric 
170b57cec5SDimitry Andric #include "sanitizer_common/sanitizer_atomic.h"
180b57cec5SDimitry Andric #include "sanitizer_common/sanitizer_common.h"
190b57cec5SDimitry Andric #include "sanitizer_common/sanitizer_mutex.h"
200b57cec5SDimitry Andric #include "xray_defs.h"
210b57cec5SDimitry Andric #include <cstddef>
220b57cec5SDimitry Andric #include <cstdint>
230b57cec5SDimitry Andric 
240b57cec5SDimitry Andric namespace __xray {
250b57cec5SDimitry Andric 
260b57cec5SDimitry Andric /// BufferQueue implements a circular queue of fixed sized buffers (much like a
270b57cec5SDimitry Andric /// freelist) but is concerned with making it quick to initialise, finalise, and
280b57cec5SDimitry Andric /// get from or return buffers to the queue. This is one key component of the
290b57cec5SDimitry Andric /// "flight data recorder" (FDR) mode to support ongoing XRay function call
300b57cec5SDimitry Andric /// trace collection.
310b57cec5SDimitry Andric class BufferQueue {
320b57cec5SDimitry Andric public:
330b57cec5SDimitry Andric   /// ControlBlock represents the memory layout of how we interpret the backing
340b57cec5SDimitry Andric   /// store for all buffers and extents managed by a BufferQueue instance. The
350b57cec5SDimitry Andric   /// ControlBlock has the reference count as the first member, sized according
360b57cec5SDimitry Andric   /// to platform-specific cache-line size. We never use the Buffer member of
370b57cec5SDimitry Andric   /// the union, which is only there for compiler-supported alignment and
380b57cec5SDimitry Andric   /// sizing.
390b57cec5SDimitry Andric   ///
400b57cec5SDimitry Andric   /// This ensures that the `Data` member will be placed at least kCacheLineSize
410b57cec5SDimitry Andric   /// bytes from the beginning of the structure.
420b57cec5SDimitry Andric   struct ControlBlock {
430b57cec5SDimitry Andric     union {
440b57cec5SDimitry Andric       atomic_uint64_t RefCount;
450b57cec5SDimitry Andric       char Buffer[kCacheLineSize];
460b57cec5SDimitry Andric     };
470b57cec5SDimitry Andric 
480b57cec5SDimitry Andric     /// We need to make this size 1, to conform to the C++ rules for array data
490b57cec5SDimitry Andric     /// members. Typically, we want to subtract this 1 byte for sizing
500b57cec5SDimitry Andric     /// information.
510b57cec5SDimitry Andric     char Data[1];
520b57cec5SDimitry Andric   };
530b57cec5SDimitry Andric 
540b57cec5SDimitry Andric   struct Buffer {
550b57cec5SDimitry Andric     atomic_uint64_t *Extents = nullptr;
560b57cec5SDimitry Andric     uint64_t Generation{0};
570b57cec5SDimitry Andric     void *Data = nullptr;
580b57cec5SDimitry Andric     size_t Size = 0;
590b57cec5SDimitry Andric 
600b57cec5SDimitry Andric   private:
610b57cec5SDimitry Andric     friend class BufferQueue;
620b57cec5SDimitry Andric     ControlBlock *BackingStore = nullptr;
630b57cec5SDimitry Andric     ControlBlock *ExtentsBackingStore = nullptr;
640b57cec5SDimitry Andric     size_t Count = 0;
650b57cec5SDimitry Andric   };
660b57cec5SDimitry Andric 
670b57cec5SDimitry Andric   struct BufferRep {
680b57cec5SDimitry Andric     // The managed buffer.
690b57cec5SDimitry Andric     Buffer Buff;
700b57cec5SDimitry Andric 
710b57cec5SDimitry Andric     // This is true if the buffer has been returned to the available queue, and
720b57cec5SDimitry Andric     // is considered "used" by another thread.
730b57cec5SDimitry Andric     bool Used = false;
740b57cec5SDimitry Andric   };
750b57cec5SDimitry Andric 
760b57cec5SDimitry Andric private:
770b57cec5SDimitry Andric   // This models a ForwardIterator. |T| Must be either a `Buffer` or `const
780b57cec5SDimitry Andric   // Buffer`. Note that we only advance to the "used" buffers, when
790b57cec5SDimitry Andric   // incrementing, so that at dereference we're always at a valid point.
800b57cec5SDimitry Andric   template <class T> class Iterator {
810b57cec5SDimitry Andric   public:
820b57cec5SDimitry Andric     BufferRep *Buffers = nullptr;
830b57cec5SDimitry Andric     size_t Offset = 0;
840b57cec5SDimitry Andric     size_t Max = 0;
850b57cec5SDimitry Andric 
860b57cec5SDimitry Andric     Iterator &operator++() {
870b57cec5SDimitry Andric       DCHECK_NE(Offset, Max);
880b57cec5SDimitry Andric       do {
890b57cec5SDimitry Andric         ++Offset;
90*0fca6ea1SDimitry Andric       } while (Offset != Max && !Buffers[Offset].Used);
910b57cec5SDimitry Andric       return *this;
920b57cec5SDimitry Andric     }
930b57cec5SDimitry Andric 
940b57cec5SDimitry Andric     Iterator operator++(int) {
950b57cec5SDimitry Andric       Iterator C = *this;
960b57cec5SDimitry Andric       ++(*this);
970b57cec5SDimitry Andric       return C;
980b57cec5SDimitry Andric     }
990b57cec5SDimitry Andric 
1000b57cec5SDimitry Andric     T &operator*() const { return Buffers[Offset].Buff; }
1010b57cec5SDimitry Andric 
1020b57cec5SDimitry Andric     T *operator->() const { return &(Buffers[Offset].Buff); }
1030b57cec5SDimitry Andric 
Iterator(BufferRep * Root,size_t O,size_t M)1040b57cec5SDimitry Andric     Iterator(BufferRep *Root, size_t O, size_t M) XRAY_NEVER_INSTRUMENT
1050b57cec5SDimitry Andric         : Buffers(Root),
1060b57cec5SDimitry Andric           Offset(O),
1070b57cec5SDimitry Andric           Max(M) {
1080b57cec5SDimitry Andric       // We want to advance to the first Offset where the 'Used' property is
1090b57cec5SDimitry Andric       // true, or to the end of the list/queue.
110*0fca6ea1SDimitry Andric       while (Offset != Max && !Buffers[Offset].Used) {
1110b57cec5SDimitry Andric         ++Offset;
1120b57cec5SDimitry Andric       }
1130b57cec5SDimitry Andric     }
1140b57cec5SDimitry Andric 
1150b57cec5SDimitry Andric     Iterator() = default;
1160b57cec5SDimitry Andric     Iterator(const Iterator &) = default;
1170b57cec5SDimitry Andric     Iterator(Iterator &&) = default;
1180b57cec5SDimitry Andric     Iterator &operator=(const Iterator &) = default;
1190b57cec5SDimitry Andric     Iterator &operator=(Iterator &&) = default;
1200b57cec5SDimitry Andric     ~Iterator() = default;
1210b57cec5SDimitry Andric 
1220b57cec5SDimitry Andric     template <class V>
1230b57cec5SDimitry Andric     friend bool operator==(const Iterator &L, const Iterator<V> &R) {
1240b57cec5SDimitry Andric       DCHECK_EQ(L.Max, R.Max);
1250b57cec5SDimitry Andric       return L.Buffers == R.Buffers && L.Offset == R.Offset;
1260b57cec5SDimitry Andric     }
1270b57cec5SDimitry Andric 
1280b57cec5SDimitry Andric     template <class V>
1290b57cec5SDimitry Andric     friend bool operator!=(const Iterator &L, const Iterator<V> &R) {
1300b57cec5SDimitry Andric       return !(L == R);
1310b57cec5SDimitry Andric     }
1320b57cec5SDimitry Andric   };
1330b57cec5SDimitry Andric 
1340b57cec5SDimitry Andric   // Size of each individual Buffer.
1350b57cec5SDimitry Andric   size_t BufferSize;
1360b57cec5SDimitry Andric 
1370b57cec5SDimitry Andric   // Amount of pre-allocated buffers.
1380b57cec5SDimitry Andric   size_t BufferCount;
1390b57cec5SDimitry Andric 
1400b57cec5SDimitry Andric   SpinMutex Mutex;
1410b57cec5SDimitry Andric   atomic_uint8_t Finalizing;
1420b57cec5SDimitry Andric 
1430b57cec5SDimitry Andric   // The collocated ControlBlock and buffer storage.
1440b57cec5SDimitry Andric   ControlBlock *BackingStore;
1450b57cec5SDimitry Andric 
1460b57cec5SDimitry Andric   // The collocated ControlBlock and extents storage.
1470b57cec5SDimitry Andric   ControlBlock *ExtentsBackingStore;
1480b57cec5SDimitry Andric 
1490b57cec5SDimitry Andric   // A dynamically allocated array of BufferRep instances.
1500b57cec5SDimitry Andric   BufferRep *Buffers;
1510b57cec5SDimitry Andric 
1520b57cec5SDimitry Andric   // Pointer to the next buffer to be handed out.
1530b57cec5SDimitry Andric   BufferRep *Next;
1540b57cec5SDimitry Andric 
1550b57cec5SDimitry Andric   // Pointer to the entry in the array where the next released buffer will be
1560b57cec5SDimitry Andric   // placed.
1570b57cec5SDimitry Andric   BufferRep *First;
1580b57cec5SDimitry Andric 
1590b57cec5SDimitry Andric   // Count of buffers that have been handed out through 'getBuffer'.
1600b57cec5SDimitry Andric   size_t LiveBuffers;
1610b57cec5SDimitry Andric 
1620b57cec5SDimitry Andric   // We use a generation number to identify buffers and which generation they're
1630b57cec5SDimitry Andric   // associated with.
1640b57cec5SDimitry Andric   atomic_uint64_t Generation;
1650b57cec5SDimitry Andric 
1660b57cec5SDimitry Andric   /// Releases references to the buffers backed by the current buffer queue.
1670b57cec5SDimitry Andric   void cleanupBuffers();
1680b57cec5SDimitry Andric 
1690b57cec5SDimitry Andric public:
1700b57cec5SDimitry Andric   enum class ErrorCode : unsigned {
1710b57cec5SDimitry Andric     Ok,
1720b57cec5SDimitry Andric     NotEnoughMemory,
1730b57cec5SDimitry Andric     QueueFinalizing,
1740b57cec5SDimitry Andric     UnrecognizedBuffer,
1750b57cec5SDimitry Andric     AlreadyFinalized,
1760b57cec5SDimitry Andric     AlreadyInitialized,
1770b57cec5SDimitry Andric   };
1780b57cec5SDimitry Andric 
getErrorString(ErrorCode E)1790b57cec5SDimitry Andric   static const char *getErrorString(ErrorCode E) {
1800b57cec5SDimitry Andric     switch (E) {
1810b57cec5SDimitry Andric     case ErrorCode::Ok:
1820b57cec5SDimitry Andric       return "(none)";
1830b57cec5SDimitry Andric     case ErrorCode::NotEnoughMemory:
1840b57cec5SDimitry Andric       return "no available buffers in the queue";
1850b57cec5SDimitry Andric     case ErrorCode::QueueFinalizing:
1860b57cec5SDimitry Andric       return "queue already finalizing";
1870b57cec5SDimitry Andric     case ErrorCode::UnrecognizedBuffer:
1880b57cec5SDimitry Andric       return "buffer being returned not owned by buffer queue";
1890b57cec5SDimitry Andric     case ErrorCode::AlreadyFinalized:
1900b57cec5SDimitry Andric       return "queue already finalized";
1910b57cec5SDimitry Andric     case ErrorCode::AlreadyInitialized:
1920b57cec5SDimitry Andric       return "queue already initialized";
1930b57cec5SDimitry Andric     }
1940b57cec5SDimitry Andric     return "unknown error";
1950b57cec5SDimitry Andric   }
1960b57cec5SDimitry Andric 
1970b57cec5SDimitry Andric   /// Initialise a queue of size |N| with buffers of size |B|. We report success
1980b57cec5SDimitry Andric   /// through |Success|.
1990b57cec5SDimitry Andric   BufferQueue(size_t B, size_t N, bool &Success);
2000b57cec5SDimitry Andric 
2010b57cec5SDimitry Andric   /// Updates |Buf| to contain the pointer to an appropriate buffer. Returns an
2020b57cec5SDimitry Andric   /// error in case there are no available buffers to return when we will run
2030b57cec5SDimitry Andric   /// over the upper bound for the total buffers.
2040b57cec5SDimitry Andric   ///
2050b57cec5SDimitry Andric   /// Requirements:
2060b57cec5SDimitry Andric   ///   - BufferQueue is not finalising.
2070b57cec5SDimitry Andric   ///
2080b57cec5SDimitry Andric   /// Returns:
2090b57cec5SDimitry Andric   ///   - ErrorCode::NotEnoughMemory on exceeding MaxSize.
2100b57cec5SDimitry Andric   ///   - ErrorCode::Ok when we find a Buffer.
2110b57cec5SDimitry Andric   ///   - ErrorCode::QueueFinalizing or ErrorCode::AlreadyFinalized on
2120b57cec5SDimitry Andric   ///     a finalizing/finalized BufferQueue.
2130b57cec5SDimitry Andric   ErrorCode getBuffer(Buffer &Buf);
2140b57cec5SDimitry Andric 
2150b57cec5SDimitry Andric   /// Updates |Buf| to point to nullptr, with size 0.
2160b57cec5SDimitry Andric   ///
2170b57cec5SDimitry Andric   /// Returns:
2180b57cec5SDimitry Andric   ///   - ErrorCode::Ok when we successfully release the buffer.
2190b57cec5SDimitry Andric   ///   - ErrorCode::UnrecognizedBuffer for when this BufferQueue does not own
2200b57cec5SDimitry Andric   ///     the buffer being released.
2210b57cec5SDimitry Andric   ErrorCode releaseBuffer(Buffer &Buf);
2220b57cec5SDimitry Andric 
2230b57cec5SDimitry Andric   /// Initializes the buffer queue, starting a new generation. We can re-set the
2240b57cec5SDimitry Andric   /// size of buffers with |BS| along with the buffer count with |BC|.
2250b57cec5SDimitry Andric   ///
2260b57cec5SDimitry Andric   /// Returns:
2270b57cec5SDimitry Andric   ///   - ErrorCode::Ok when we successfully initialize the buffer. This
2280b57cec5SDimitry Andric   ///   requires that the buffer queue is previously finalized.
2290b57cec5SDimitry Andric   ///   - ErrorCode::AlreadyInitialized when the buffer queue is not finalized.
2300b57cec5SDimitry Andric   ErrorCode init(size_t BS, size_t BC);
2310b57cec5SDimitry Andric 
finalizing()2320b57cec5SDimitry Andric   bool finalizing() const {
2330b57cec5SDimitry Andric     return atomic_load(&Finalizing, memory_order_acquire);
2340b57cec5SDimitry Andric   }
2350b57cec5SDimitry Andric 
generation()2360b57cec5SDimitry Andric   uint64_t generation() const {
2370b57cec5SDimitry Andric     return atomic_load(&Generation, memory_order_acquire);
2380b57cec5SDimitry Andric   }
2390b57cec5SDimitry Andric 
2400b57cec5SDimitry Andric   /// Returns the configured size of the buffers in the buffer queue.
ConfiguredBufferSize()2410b57cec5SDimitry Andric   size_t ConfiguredBufferSize() const { return BufferSize; }
2420b57cec5SDimitry Andric 
2430b57cec5SDimitry Andric   /// Sets the state of the BufferQueue to finalizing, which ensures that:
2440b57cec5SDimitry Andric   ///
2450b57cec5SDimitry Andric   ///   - All subsequent attempts to retrieve a Buffer will fail.
2460b57cec5SDimitry Andric   ///   - All releaseBuffer operations will not fail.
2470b57cec5SDimitry Andric   ///
2480b57cec5SDimitry Andric   /// After a call to finalize succeeds, all subsequent calls to finalize will
2490b57cec5SDimitry Andric   /// fail with ErrorCode::QueueFinalizing.
2500b57cec5SDimitry Andric   ErrorCode finalize();
2510b57cec5SDimitry Andric 
2520b57cec5SDimitry Andric   /// Applies the provided function F to each Buffer in the queue, only if the
2530b57cec5SDimitry Andric   /// Buffer is marked 'used' (i.e. has been the result of getBuffer(...) and a
2540b57cec5SDimitry Andric   /// releaseBuffer(...) operation).
apply(F Fn)2550b57cec5SDimitry Andric   template <class F> void apply(F Fn) XRAY_NEVER_INSTRUMENT {
2560b57cec5SDimitry Andric     SpinMutexLock G(&Mutex);
2570b57cec5SDimitry Andric     for (auto I = begin(), E = end(); I != E; ++I)
2580b57cec5SDimitry Andric       Fn(*I);
2590b57cec5SDimitry Andric   }
2600b57cec5SDimitry Andric 
2610b57cec5SDimitry Andric   using const_iterator = Iterator<const Buffer>;
2620b57cec5SDimitry Andric   using iterator = Iterator<Buffer>;
2630b57cec5SDimitry Andric 
2640b57cec5SDimitry Andric   /// Provides iterator access to the raw Buffer instances.
begin()2650b57cec5SDimitry Andric   iterator begin() const { return iterator(Buffers, 0, BufferCount); }
cbegin()2660b57cec5SDimitry Andric   const_iterator cbegin() const {
2670b57cec5SDimitry Andric     return const_iterator(Buffers, 0, BufferCount);
2680b57cec5SDimitry Andric   }
end()2690b57cec5SDimitry Andric   iterator end() const { return iterator(Buffers, BufferCount, BufferCount); }
cend()2700b57cec5SDimitry Andric   const_iterator cend() const {
2710b57cec5SDimitry Andric     return const_iterator(Buffers, BufferCount, BufferCount);
2720b57cec5SDimitry Andric   }
2730b57cec5SDimitry Andric 
2740b57cec5SDimitry Andric   // Cleans up allocated buffers.
2750b57cec5SDimitry Andric   ~BufferQueue();
2760b57cec5SDimitry Andric };
2770b57cec5SDimitry Andric 
2780b57cec5SDimitry Andric } // namespace __xray
2790b57cec5SDimitry Andric 
2800b57cec5SDimitry Andric #endif // XRAY_BUFFER_QUEUE_H
281