Home | History | Annotate | Line # | Download | only in xray
      1 //===-- xray_buffer_queue.cc -----------------------------------*- C++ -*-===//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 //
     10 // This file is a part of XRay, a dynamic runtime instruementation system.
     11 //
     12 // Defines the interface for a buffer queue implementation.
     13 //
     14 //===----------------------------------------------------------------------===//
     15 #include "xray_buffer_queue.h"
     16 #include "sanitizer_common/sanitizer_atomic.h"
     17 #include "sanitizer_common/sanitizer_common.h"
     18 #include "sanitizer_common/sanitizer_libc.h"
     19 #if !SANITIZER_FUCHSIA
     20 #include "sanitizer_common/sanitizer_posix.h"
     21 #endif
     22 #include "xray_allocator.h"
     23 #include "xray_defs.h"
     24 #include <memory>
     25 #include <sys/mman.h>
     26 
     27 using namespace __xray;
     28 
     29 namespace {
     30 
     31 BufferQueue::ControlBlock *allocControlBlock(size_t Size, size_t Count) {
     32   auto B =
     33       allocateBuffer((sizeof(BufferQueue::ControlBlock) - 1) + (Size * Count));
     34   return B == nullptr ? nullptr
     35                       : reinterpret_cast<BufferQueue::ControlBlock *>(B);
     36 }
     37 
     38 void deallocControlBlock(BufferQueue::ControlBlock *C, size_t Size,
     39                          size_t Count) {
     40   deallocateBuffer(reinterpret_cast<unsigned char *>(C),
     41                    (sizeof(BufferQueue::ControlBlock) - 1) + (Size * Count));
     42 }
     43 
     44 void decRefCount(BufferQueue::ControlBlock *C, size_t Size, size_t Count) {
     45   if (C == nullptr)
     46     return;
     47   if (atomic_fetch_sub(&C->RefCount, 1, memory_order_acq_rel) == 1)
     48     deallocControlBlock(C, Size, Count);
     49 }
     50 
     51 void incRefCount(BufferQueue::ControlBlock *C) {
     52   if (C == nullptr)
     53     return;
     54   atomic_fetch_add(&C->RefCount, 1, memory_order_acq_rel);
     55 }
     56 
     57 // We use a struct to ensure that we are allocating one atomic_uint64_t per
     58 // cache line. This allows us to not worry about false-sharing among atomic
     59 // objects being updated (constantly) by different threads.
     60 struct ExtentsPadded {
     61   union {
     62     atomic_uint64_t Extents;
     63     unsigned char Storage[kCacheLineSize];
     64   };
     65 };
     66 
     67 constexpr size_t kExtentsSize = sizeof(ExtentsPadded);
     68 
     69 } // namespace
     70 
     71 BufferQueue::ErrorCode BufferQueue::init(size_t BS, size_t BC) {
     72   SpinMutexLock Guard(&Mutex);
     73 
     74   if (!finalizing())
     75     return BufferQueue::ErrorCode::AlreadyInitialized;
     76 
     77   cleanupBuffers();
     78 
     79   bool Success = false;
     80   BufferSize = BS;
     81   BufferCount = BC;
     82 
     83   BackingStore = allocControlBlock(BufferSize, BufferCount);
     84   if (BackingStore == nullptr)
     85     return BufferQueue::ErrorCode::NotEnoughMemory;
     86 
     87   auto CleanupBackingStore = at_scope_exit([&, this] {
     88     if (Success)
     89       return;
     90     deallocControlBlock(BackingStore, BufferSize, BufferCount);
     91     BackingStore = nullptr;
     92   });
     93 
     94   // Initialize enough atomic_uint64_t instances, each
     95   ExtentsBackingStore = allocControlBlock(kExtentsSize, BufferCount);
     96   if (ExtentsBackingStore == nullptr)
     97     return BufferQueue::ErrorCode::NotEnoughMemory;
     98 
     99   auto CleanupExtentsBackingStore = at_scope_exit([&, this] {
    100     if (Success)
    101       return;
    102     deallocControlBlock(ExtentsBackingStore, kExtentsSize, BufferCount);
    103     ExtentsBackingStore = nullptr;
    104   });
    105 
    106   Buffers = initArray<BufferRep>(BufferCount);
    107   if (Buffers == nullptr)
    108     return BufferQueue::ErrorCode::NotEnoughMemory;
    109 
    110   // At this point we increment the generation number to associate the buffers
    111   // to the new generation.
    112   atomic_fetch_add(&Generation, 1, memory_order_acq_rel);
    113 
    114   // First, we initialize the refcount in the ControlBlock, which we treat as
    115   // being at the start of the BackingStore pointer.
    116   atomic_store(&BackingStore->RefCount, 1, memory_order_release);
    117   atomic_store(&ExtentsBackingStore->RefCount, 1, memory_order_release);
    118 
    119   // Then we initialise the individual buffers that sub-divide the whole backing
    120   // store. Each buffer will start at the `Data` member of the ControlBlock, and
    121   // will be offsets from these locations.
    122   for (size_t i = 0; i < BufferCount; ++i) {
    123     auto &T = Buffers[i];
    124     auto &Buf = T.Buff;
    125     auto *E = reinterpret_cast<ExtentsPadded *>(&ExtentsBackingStore->Data +
    126                                                 (kExtentsSize * i));
    127     Buf.Extents = &E->Extents;
    128     atomic_store(Buf.Extents, 0, memory_order_release);
    129     Buf.Generation = generation();
    130     Buf.Data = &BackingStore->Data + (BufferSize * i);
    131     Buf.Size = BufferSize;
    132     Buf.BackingStore = BackingStore;
    133     Buf.ExtentsBackingStore = ExtentsBackingStore;
    134     Buf.Count = BufferCount;
    135     T.Used = false;
    136   }
    137 
    138   Next = Buffers;
    139   First = Buffers;
    140   LiveBuffers = 0;
    141   atomic_store(&Finalizing, 0, memory_order_release);
    142   Success = true;
    143   return BufferQueue::ErrorCode::Ok;
    144 }
    145 
    146 BufferQueue::BufferQueue(size_t B, size_t N,
    147                          bool &Success) XRAY_NEVER_INSTRUMENT
    148     : BufferSize(B),
    149       BufferCount(N),
    150       Mutex(),
    151       Finalizing{1},
    152       BackingStore(nullptr),
    153       ExtentsBackingStore(nullptr),
    154       Buffers(nullptr),
    155       Next(Buffers),
    156       First(Buffers),
    157       LiveBuffers(0),
    158       Generation{0} {
    159   Success = init(B, N) == BufferQueue::ErrorCode::Ok;
    160 }
    161 
    162 BufferQueue::ErrorCode BufferQueue::getBuffer(Buffer &Buf) {
    163   if (atomic_load(&Finalizing, memory_order_acquire))
    164     return ErrorCode::QueueFinalizing;
    165 
    166   BufferRep *B = nullptr;
    167   {
    168     SpinMutexLock Guard(&Mutex);
    169     if (LiveBuffers == BufferCount)
    170       return ErrorCode::NotEnoughMemory;
    171     B = Next++;
    172     if (Next == (Buffers + BufferCount))
    173       Next = Buffers;
    174     ++LiveBuffers;
    175   }
    176 
    177   incRefCount(BackingStore);
    178   incRefCount(ExtentsBackingStore);
    179   Buf = B->Buff;
    180   Buf.Generation = generation();
    181   B->Used = true;
    182   return ErrorCode::Ok;
    183 }
    184 
    185 BufferQueue::ErrorCode BufferQueue::releaseBuffer(Buffer &Buf) {
    186   // Check whether the buffer being referred to is within the bounds of the
    187   // backing store's range.
    188   BufferRep *B = nullptr;
    189   {
    190     SpinMutexLock Guard(&Mutex);
    191     if (Buf.Generation != generation() || LiveBuffers == 0) {
    192       Buf = {};
    193       decRefCount(Buf.BackingStore, Buf.Size, Buf.Count);
    194       decRefCount(Buf.ExtentsBackingStore, kExtentsSize, Buf.Count);
    195       return BufferQueue::ErrorCode::Ok;
    196     }
    197 
    198     if (Buf.Data < &BackingStore->Data ||
    199         Buf.Data > &BackingStore->Data + (BufferCount * BufferSize))
    200       return BufferQueue::ErrorCode::UnrecognizedBuffer;
    201 
    202     --LiveBuffers;
    203     B = First++;
    204     if (First == (Buffers + BufferCount))
    205       First = Buffers;
    206   }
    207 
    208   // Now that the buffer has been released, we mark it as "used".
    209   B->Buff = Buf;
    210   B->Used = true;
    211   decRefCount(Buf.BackingStore, Buf.Size, Buf.Count);
    212   decRefCount(Buf.ExtentsBackingStore, kExtentsSize, Buf.Count);
    213   atomic_store(B->Buff.Extents, atomic_load(Buf.Extents, memory_order_acquire),
    214                memory_order_release);
    215   Buf = {};
    216   return ErrorCode::Ok;
    217 }
    218 
    219 BufferQueue::ErrorCode BufferQueue::finalize() {
    220   if (atomic_exchange(&Finalizing, 1, memory_order_acq_rel))
    221     return ErrorCode::QueueFinalizing;
    222   return ErrorCode::Ok;
    223 }
    224 
    225 void BufferQueue::cleanupBuffers() {
    226   for (auto B = Buffers, E = Buffers + BufferCount; B != E; ++B)
    227     B->~BufferRep();
    228   deallocateBuffer(Buffers, BufferCount);
    229   decRefCount(BackingStore, BufferSize, BufferCount);
    230   decRefCount(ExtentsBackingStore, kExtentsSize, BufferCount);
    231   BackingStore = nullptr;
    232   ExtentsBackingStore = nullptr;
    233   Buffers = nullptr;
    234   BufferCount = 0;
    235   BufferSize = 0;
    236 }
    237 
    238 BufferQueue::~BufferQueue() { cleanupBuffers(); }
    239