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