| //===-- xray_buffer_queue.cpp ----------------------------------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file is a part of XRay, a dynamic runtime instruementation system. | |
| // | |
| // Defines the interface for a buffer queue implementation. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "xray_buffer_queue.h" | |
| #include "sanitizer_common/sanitizer_atomic.h" | |
| #include "sanitizer_common/sanitizer_common.h" | |
| #include "sanitizer_common/sanitizer_libc.h" | |
| #if !SANITIZER_FUCHSIA | |
| #include "sanitizer_common/sanitizer_posix.h" | |
| #endif | |
| #include "xray_allocator.h" | |
| #include "xray_defs.h" | |
| #include <memory> | |
| #include <sys/mman.h> | |
| using namespace __xray; | |
| namespace { | |
| BufferQueue::ControlBlock *allocControlBlock(size_t Size, size_t Count) { | |
| auto B = | |
| allocateBuffer((sizeof(BufferQueue::ControlBlock) - 1) + (Size * Count)); | |
| return B == nullptr ? nullptr | |
| : reinterpret_cast<BufferQueue::ControlBlock *>(B); | |
| } | |
| void deallocControlBlock(BufferQueue::ControlBlock *C, size_t Size, | |
| size_t Count) { | |
| deallocateBuffer(reinterpret_cast<unsigned char *>(C), | |
| (sizeof(BufferQueue::ControlBlock) - 1) + (Size * Count)); | |
| } | |
| void decRefCount(BufferQueue::ControlBlock *C, size_t Size, size_t Count) { | |
| if (C == nullptr) | |
| return; | |
| if (atomic_fetch_sub(&C->RefCount, 1, memory_order_acq_rel) == 1) | |
| deallocControlBlock(C, Size, Count); | |
| } | |
| void incRefCount(BufferQueue::ControlBlock *C) { | |
| if (C == nullptr) | |
| return; | |
| atomic_fetch_add(&C->RefCount, 1, memory_order_acq_rel); | |
| } | |
| // We use a struct to ensure that we are allocating one atomic_uint64_t per | |
| // cache line. This allows us to not worry about false-sharing among atomic | |
| // objects being updated (constantly) by different threads. | |
| struct ExtentsPadded { | |
| union { | |
| atomic_uint64_t Extents; | |
| unsigned char Storage[kCacheLineSize]; | |
| }; | |
| }; | |
| constexpr size_t kExtentsSize = sizeof(ExtentsPadded); | |
| } // namespace | |
| BufferQueue::ErrorCode BufferQueue::init(size_t BS, size_t BC) { | |
| SpinMutexLock Guard(&Mutex); | |
| if (!finalizing()) | |
| return BufferQueue::ErrorCode::AlreadyInitialized; | |
| cleanupBuffers(); | |
| bool Success = false; | |
| BufferSize = BS; | |
| BufferCount = BC; | |
| BackingStore = allocControlBlock(BufferSize, BufferCount); | |
| if (BackingStore == nullptr) | |
| return BufferQueue::ErrorCode::NotEnoughMemory; | |
| auto CleanupBackingStore = at_scope_exit([&, this] { | |
| if (Success) | |
| return; | |
| deallocControlBlock(BackingStore, BufferSize, BufferCount); | |
| BackingStore = nullptr; | |
| }); | |
| // Initialize enough atomic_uint64_t instances, each | |
| ExtentsBackingStore = allocControlBlock(kExtentsSize, BufferCount); | |
| if (ExtentsBackingStore == nullptr) | |
| return BufferQueue::ErrorCode::NotEnoughMemory; | |
| auto CleanupExtentsBackingStore = at_scope_exit([&, this] { | |
| if (Success) | |
| return; | |
| deallocControlBlock(ExtentsBackingStore, kExtentsSize, BufferCount); | |
| ExtentsBackingStore = nullptr; | |
| }); | |
| Buffers = initArray<BufferRep>(BufferCount); | |
| if (Buffers == nullptr) | |
| return BufferQueue::ErrorCode::NotEnoughMemory; | |
| // At this point we increment the generation number to associate the buffers | |
| // to the new generation. | |
| atomic_fetch_add(&Generation, 1, memory_order_acq_rel); | |
| // First, we initialize the refcount in the ControlBlock, which we treat as | |
| // being at the start of the BackingStore pointer. | |
| atomic_store(&BackingStore->RefCount, 1, memory_order_release); | |
| atomic_store(&ExtentsBackingStore->RefCount, 1, memory_order_release); | |
| // Then we initialise the individual buffers that sub-divide the whole backing | |
| // store. Each buffer will start at the `Data` member of the ControlBlock, and | |
| // will be offsets from these locations. | |
| for (size_t i = 0; i < BufferCount; ++i) { | |
| auto &T = Buffers[i]; | |
| auto &Buf = T.Buff; | |
| auto *E = reinterpret_cast<ExtentsPadded *>(&ExtentsBackingStore->Data + | |
| (kExtentsSize * i)); | |
| Buf.Extents = &E->Extents; | |
| atomic_store(Buf.Extents, 0, memory_order_release); | |
| Buf.Generation = generation(); | |
| Buf.Data = &BackingStore->Data + (BufferSize * i); | |
| Buf.Size = BufferSize; | |
| Buf.BackingStore = BackingStore; | |
| Buf.ExtentsBackingStore = ExtentsBackingStore; | |
| Buf.Count = BufferCount; | |
| T.Used = false; | |
| } | |
| Next = Buffers; | |
| First = Buffers; | |
| LiveBuffers = 0; | |
| atomic_store(&Finalizing, 0, memory_order_release); | |
| Success = true; | |
| return BufferQueue::ErrorCode::Ok; | |
| } | |
| BufferQueue::BufferQueue(size_t B, size_t N, | |
| bool &Success) XRAY_NEVER_INSTRUMENT | |
| : BufferSize(B), | |
| BufferCount(N), | |
| Mutex(), | |
| Finalizing{1}, | |
| BackingStore(nullptr), | |
| ExtentsBackingStore(nullptr), | |
| Buffers(nullptr), | |
| Next(Buffers), | |
| First(Buffers), | |
| LiveBuffers(0), | |
| Generation{0} { | |
| Success = init(B, N) == BufferQueue::ErrorCode::Ok; | |
| } | |
| BufferQueue::ErrorCode BufferQueue::getBuffer(Buffer &Buf) { | |
| if (atomic_load(&Finalizing, memory_order_acquire)) | |
| return ErrorCode::QueueFinalizing; | |
| BufferRep *B = nullptr; | |
| { | |
| SpinMutexLock Guard(&Mutex); | |
| if (LiveBuffers == BufferCount) | |
| return ErrorCode::NotEnoughMemory; | |
| B = Next++; | |
| if (Next == (Buffers + BufferCount)) | |
| Next = Buffers; | |
| ++LiveBuffers; | |
| } | |
| incRefCount(BackingStore); | |
| incRefCount(ExtentsBackingStore); | |
| Buf = B->Buff; | |
| Buf.Generation = generation(); | |
| B->Used = true; | |
| return ErrorCode::Ok; | |
| } | |
| BufferQueue::ErrorCode BufferQueue::releaseBuffer(Buffer &Buf) { | |
| // Check whether the buffer being referred to is within the bounds of the | |
| // backing store's range. | |
| BufferRep *B = nullptr; | |
| { | |
| SpinMutexLock Guard(&Mutex); | |
| if (Buf.Generation != generation() || LiveBuffers == 0) { | |
| Buf = {}; | |
| decRefCount(Buf.BackingStore, Buf.Size, Buf.Count); | |
| decRefCount(Buf.ExtentsBackingStore, kExtentsSize, Buf.Count); | |
| return BufferQueue::ErrorCode::Ok; | |
| } | |
| if (Buf.Data < &BackingStore->Data || | |
| Buf.Data > &BackingStore->Data + (BufferCount * BufferSize)) | |
| return BufferQueue::ErrorCode::UnrecognizedBuffer; | |
| --LiveBuffers; | |
| B = First++; | |
| if (First == (Buffers + BufferCount)) | |
| First = Buffers; | |
| } | |
| // Now that the buffer has been released, we mark it as "used". | |
| B->Buff = Buf; | |
| B->Used = true; | |
| decRefCount(Buf.BackingStore, Buf.Size, Buf.Count); | |
| decRefCount(Buf.ExtentsBackingStore, kExtentsSize, Buf.Count); | |
| atomic_store(B->Buff.Extents, atomic_load(Buf.Extents, memory_order_acquire), | |
| memory_order_release); | |
| Buf = {}; | |
| return ErrorCode::Ok; | |
| } | |
| BufferQueue::ErrorCode BufferQueue::finalize() { | |
| if (atomic_exchange(&Finalizing, 1, memory_order_acq_rel)) | |
| return ErrorCode::QueueFinalizing; | |
| return ErrorCode::Ok; | |
| } | |
| void BufferQueue::cleanupBuffers() { | |
| for (auto B = Buffers, E = Buffers + BufferCount; B != E; ++B) | |
| B->~BufferRep(); | |
| deallocateBuffer(Buffers, BufferCount); | |
| decRefCount(BackingStore, BufferSize, BufferCount); | |
| decRefCount(ExtentsBackingStore, kExtentsSize, BufferCount); | |
| BackingStore = nullptr; | |
| ExtentsBackingStore = nullptr; | |
| Buffers = nullptr; | |
| BufferCount = 0; | |
| BufferSize = 0; | |
| } | |
| BufferQueue::~BufferQueue() { cleanupBuffers(); } |