//===-- xray_buffer_queue.cc -----------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // 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_common.h" #include "sanitizer_common/sanitizer_libc.h" #include "sanitizer_common/sanitizer_posix.h" #include #include #ifndef MAP_NORESERVE // no-op on NetBSD (at least), unsupported flag on FreeBSD #define MAP_NORESERVE 0 #endif using namespace __xray; using namespace __sanitizer; template static T *allocRaw(size_t N) { // TODO: Report errors? // We use MAP_NORESERVE on platforms where it's supported to ensure that the // pages we're allocating for XRay never end up in pages that can be swapped // in/out. We're doing this because for FDR mode, we want to ensure that // writes to the buffers stay resident in memory to prevent XRay itself from // causing swapping/thrashing. // // In the case when XRay pages cannot be swapped in/out or there's not enough // RAM to back these pages, we're willing to cause a segmentation fault // instead of introducing latency in the measurement. We assume here that // there are enough pages that are swappable in/out outside of the buffers // being used by FDR mode (which are bounded and configurable anyway) to allow // us to keep using always-resident memory. // // TODO: Make this configurable? void *A = reinterpret_cast( internal_mmap(NULL, N * sizeof(T), PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, -1, 0)); return (A == MAP_FAILED) ? nullptr : reinterpret_cast(A); } template static void deallocRaw(T *ptr, size_t N) { // TODO: Report errors? if (ptr != nullptr) internal_munmap(ptr, N); } template static T *initArray(size_t N) { auto A = allocRaw(N); if (A != nullptr) while (N > 0) new (A + (--N)) T(); return A; } BufferQueue::BufferQueue(size_t B, size_t N, bool &Success) : BufferSize(B), Buffers(initArray(N)), BufferCount(N), Finalizing{0}, OwnedBuffers(initArray(N)), Next(Buffers), First(Buffers), LiveBuffers(0) { if (Buffers == nullptr) { Success = false; return; } if (OwnedBuffers == nullptr) { // Clean up the buffers we've already allocated. for (auto B = Buffers, E = Buffers + BufferCount; B != E; ++B) B->~BufferRep(); deallocRaw(Buffers, N); Success = false; return; }; for (size_t i = 0; i < N; ++i) { auto &T = Buffers[i]; void *Tmp = allocRaw(BufferSize); if (Tmp == nullptr) { Success = false; return; } auto *Extents = allocRaw(1); if (Extents == nullptr) { Success = false; return; } auto &Buf = T.Buff; Buf.Data = Tmp; Buf.Size = B; Buf.Extents = Extents; OwnedBuffers[i] = Tmp; } Success = true; } BufferQueue::ErrorCode BufferQueue::getBuffer(Buffer &Buf) { if (atomic_load(&Finalizing, memory_order_acquire)) return ErrorCode::QueueFinalizing; SpinMutexLock Guard(&Mutex); if (LiveBuffers == BufferCount) return ErrorCode::NotEnoughMemory; auto &T = *Next; auto &B = T.Buff; Buf = B; T.Used = true; ++LiveBuffers; if (++Next == (Buffers + BufferCount)) Next = Buffers; return ErrorCode::Ok; } BufferQueue::ErrorCode BufferQueue::releaseBuffer(Buffer &Buf) { // Blitz through the buffers array to find the buffer. bool Found = false; for (auto I = OwnedBuffers, E = OwnedBuffers + BufferCount; I != E; ++I) { if (*I == Buf.Data) { Found = true; break; } } if (!Found) return ErrorCode::UnrecognizedBuffer; SpinMutexLock Guard(&Mutex); // This points to a semantic bug, we really ought to not be releasing more // buffers than we actually get. if (LiveBuffers == 0) return ErrorCode::NotEnoughMemory; // Now that the buffer has been released, we mark it as "used". First->Buff = Buf; First->Used = true; Buf.Data = nullptr; Buf.Size = 0; --LiveBuffers; if (++First == (Buffers + BufferCount)) First = Buffers; return ErrorCode::Ok; } BufferQueue::ErrorCode BufferQueue::finalize() { if (atomic_exchange(&Finalizing, 1, memory_order_acq_rel)) return ErrorCode::QueueFinalizing; return ErrorCode::Ok; } BufferQueue::~BufferQueue() { for (auto I = Buffers, E = Buffers + BufferCount; I != E; ++I) { auto &T = *I; auto &Buf = T.Buff; deallocRaw(Buf.Data, Buf.Size); deallocRaw(Buf.Extents, 1); } for (auto B = Buffers, E = Buffers + BufferCount; B != E; ++B) B->~BufferRep(); deallocRaw(Buffers, BufferCount); deallocRaw(OwnedBuffers, BufferCount); }