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