//===-- 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 instrumentation 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 #include 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(B); } void deallocControlBlock(BufferQueue::ControlBlock *C, size_t Size, size_t Count) { deallocateBuffer(reinterpret_cast(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(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(&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(); }