xref: /freebsd/contrib/llvm-project/compiler-rt/lib/xray/xray_allocator.h (revision 0eae32dcef82f6f06de6419a0d623d7def0cc8f6)
10b57cec5SDimitry Andric //===-- xray_allocator.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 allocator interface for an arena allocator, used primarily for
120b57cec5SDimitry Andric // the profiling runtime.
130b57cec5SDimitry Andric //
140b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
150b57cec5SDimitry Andric #ifndef XRAY_ALLOCATOR_H
160b57cec5SDimitry Andric #define XRAY_ALLOCATOR_H
170b57cec5SDimitry Andric 
180b57cec5SDimitry Andric #include "sanitizer_common/sanitizer_common.h"
190b57cec5SDimitry Andric #include "sanitizer_common/sanitizer_internal_defs.h"
200b57cec5SDimitry Andric #include "sanitizer_common/sanitizer_mutex.h"
210b57cec5SDimitry Andric #if SANITIZER_FUCHSIA
220b57cec5SDimitry Andric #include <zircon/process.h>
230b57cec5SDimitry Andric #include <zircon/status.h>
240b57cec5SDimitry Andric #include <zircon/syscalls.h>
250b57cec5SDimitry Andric #else
260b57cec5SDimitry Andric #include "sanitizer_common/sanitizer_posix.h"
270b57cec5SDimitry Andric #endif
280b57cec5SDimitry Andric #include "xray_defs.h"
290b57cec5SDimitry Andric #include "xray_utils.h"
300b57cec5SDimitry Andric #include <cstddef>
310b57cec5SDimitry Andric #include <cstdint>
320b57cec5SDimitry Andric #include <sys/mman.h>
330b57cec5SDimitry Andric 
340b57cec5SDimitry Andric namespace __xray {
350b57cec5SDimitry Andric 
360b57cec5SDimitry Andric // We implement our own memory allocation routine which will bypass the
370b57cec5SDimitry Andric // internal allocator. This allows us to manage the memory directly, using
380b57cec5SDimitry Andric // mmap'ed memory to back the allocators.
allocate()390b57cec5SDimitry Andric template <class T> T *allocate() XRAY_NEVER_INSTRUMENT {
400b57cec5SDimitry Andric   uptr RoundedSize = RoundUpTo(sizeof(T), GetPageSizeCached());
410b57cec5SDimitry Andric #if SANITIZER_FUCHSIA
420b57cec5SDimitry Andric   zx_handle_t Vmo;
430b57cec5SDimitry Andric   zx_status_t Status = _zx_vmo_create(RoundedSize, 0, &Vmo);
440b57cec5SDimitry Andric   if (Status != ZX_OK) {
450b57cec5SDimitry Andric     if (Verbosity())
460b57cec5SDimitry Andric       Report("XRay Profiling: Failed to create VMO of size %zu: %s\n",
470b57cec5SDimitry Andric              sizeof(T), _zx_status_get_string(Status));
480b57cec5SDimitry Andric     return nullptr;
490b57cec5SDimitry Andric   }
500b57cec5SDimitry Andric   uintptr_t B;
510b57cec5SDimitry Andric   Status =
520b57cec5SDimitry Andric       _zx_vmar_map(_zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0,
530b57cec5SDimitry Andric                    Vmo, 0, sizeof(T), &B);
540b57cec5SDimitry Andric   _zx_handle_close(Vmo);
550b57cec5SDimitry Andric   if (Status != ZX_OK) {
560b57cec5SDimitry Andric     if (Verbosity())
570b57cec5SDimitry Andric       Report("XRay Profiling: Failed to map VMAR of size %zu: %s\n", sizeof(T),
580b57cec5SDimitry Andric              _zx_status_get_string(Status));
590b57cec5SDimitry Andric     return nullptr;
600b57cec5SDimitry Andric   }
610b57cec5SDimitry Andric   return reinterpret_cast<T *>(B);
620b57cec5SDimitry Andric #else
630b57cec5SDimitry Andric   uptr B = internal_mmap(NULL, RoundedSize, PROT_READ | PROT_WRITE,
640b57cec5SDimitry Andric                          MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
650b57cec5SDimitry Andric   int ErrNo = 0;
660b57cec5SDimitry Andric   if (UNLIKELY(internal_iserror(B, &ErrNo))) {
670b57cec5SDimitry Andric     if (Verbosity())
68*0eae32dcSDimitry Andric       Report("XRay Profiling: Failed to allocate memory of size %zu; Error = "
69*0eae32dcSDimitry Andric              "%zu\n",
700b57cec5SDimitry Andric              RoundedSize, B);
710b57cec5SDimitry Andric     return nullptr;
720b57cec5SDimitry Andric   }
730b57cec5SDimitry Andric #endif
740b57cec5SDimitry Andric   return reinterpret_cast<T *>(B);
750b57cec5SDimitry Andric }
760b57cec5SDimitry Andric 
deallocate(T * B)770b57cec5SDimitry Andric template <class T> void deallocate(T *B) XRAY_NEVER_INSTRUMENT {
780b57cec5SDimitry Andric   if (B == nullptr)
790b57cec5SDimitry Andric     return;
800b57cec5SDimitry Andric   uptr RoundedSize = RoundUpTo(sizeof(T), GetPageSizeCached());
810b57cec5SDimitry Andric #if SANITIZER_FUCHSIA
820b57cec5SDimitry Andric   _zx_vmar_unmap(_zx_vmar_root_self(), reinterpret_cast<uintptr_t>(B),
830b57cec5SDimitry Andric                  RoundedSize);
840b57cec5SDimitry Andric #else
850b57cec5SDimitry Andric   internal_munmap(B, RoundedSize);
860b57cec5SDimitry Andric #endif
870b57cec5SDimitry Andric }
880b57cec5SDimitry Andric 
890b57cec5SDimitry Andric template <class T = unsigned char>
allocateBuffer(size_t S)900b57cec5SDimitry Andric T *allocateBuffer(size_t S) XRAY_NEVER_INSTRUMENT {
910b57cec5SDimitry Andric   uptr RoundedSize = RoundUpTo(S * sizeof(T), GetPageSizeCached());
920b57cec5SDimitry Andric #if SANITIZER_FUCHSIA
930b57cec5SDimitry Andric   zx_handle_t Vmo;
940b57cec5SDimitry Andric   zx_status_t Status = _zx_vmo_create(RoundedSize, 0, &Vmo);
950b57cec5SDimitry Andric   if (Status != ZX_OK) {
960b57cec5SDimitry Andric     if (Verbosity())
970b57cec5SDimitry Andric       Report("XRay Profiling: Failed to create VMO of size %zu: %s\n", S,
980b57cec5SDimitry Andric              _zx_status_get_string(Status));
990b57cec5SDimitry Andric     return nullptr;
1000b57cec5SDimitry Andric   }
1010b57cec5SDimitry Andric   uintptr_t B;
1020b57cec5SDimitry Andric   Status = _zx_vmar_map(_zx_vmar_root_self(),
1030b57cec5SDimitry Andric                         ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, Vmo, 0, S, &B);
1040b57cec5SDimitry Andric   _zx_handle_close(Vmo);
1050b57cec5SDimitry Andric   if (Status != ZX_OK) {
1060b57cec5SDimitry Andric     if (Verbosity())
1070b57cec5SDimitry Andric       Report("XRay Profiling: Failed to map VMAR of size %zu: %s\n", S,
1080b57cec5SDimitry Andric              _zx_status_get_string(Status));
1090b57cec5SDimitry Andric     return nullptr;
1100b57cec5SDimitry Andric   }
1110b57cec5SDimitry Andric #else
1120b57cec5SDimitry Andric   uptr B = internal_mmap(NULL, RoundedSize, PROT_READ | PROT_WRITE,
1130b57cec5SDimitry Andric                          MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
1140b57cec5SDimitry Andric   int ErrNo = 0;
1150b57cec5SDimitry Andric   if (UNLIKELY(internal_iserror(B, &ErrNo))) {
1160b57cec5SDimitry Andric     if (Verbosity())
117*0eae32dcSDimitry Andric       Report("XRay Profiling: Failed to allocate memory of size %zu; Error = "
118*0eae32dcSDimitry Andric              "%zu\n",
1190b57cec5SDimitry Andric              RoundedSize, B);
1200b57cec5SDimitry Andric     return nullptr;
1210b57cec5SDimitry Andric   }
1220b57cec5SDimitry Andric #endif
1230b57cec5SDimitry Andric   return reinterpret_cast<T *>(B);
1240b57cec5SDimitry Andric }
1250b57cec5SDimitry Andric 
deallocateBuffer(T * B,size_t S)1260b57cec5SDimitry Andric template <class T> void deallocateBuffer(T *B, size_t S) XRAY_NEVER_INSTRUMENT {
1270b57cec5SDimitry Andric   if (B == nullptr)
1280b57cec5SDimitry Andric     return;
1290b57cec5SDimitry Andric   uptr RoundedSize = RoundUpTo(S * sizeof(T), GetPageSizeCached());
1300b57cec5SDimitry Andric #if SANITIZER_FUCHSIA
1310b57cec5SDimitry Andric   _zx_vmar_unmap(_zx_vmar_root_self(), reinterpret_cast<uintptr_t>(B),
1320b57cec5SDimitry Andric                  RoundedSize);
1330b57cec5SDimitry Andric #else
1340b57cec5SDimitry Andric   internal_munmap(B, RoundedSize);
1350b57cec5SDimitry Andric #endif
1360b57cec5SDimitry Andric }
1370b57cec5SDimitry Andric 
1380b57cec5SDimitry Andric template <class T, class... U>
initArray(size_t N,U &&...Us)1390b57cec5SDimitry Andric T *initArray(size_t N, U &&... Us) XRAY_NEVER_INSTRUMENT {
1400b57cec5SDimitry Andric   auto A = allocateBuffer<T>(N);
1410b57cec5SDimitry Andric   if (A != nullptr)
1420b57cec5SDimitry Andric     while (N > 0)
1430b57cec5SDimitry Andric       new (A + (--N)) T(std::forward<U>(Us)...);
1440b57cec5SDimitry Andric   return A;
1450b57cec5SDimitry Andric }
1460b57cec5SDimitry Andric 
1470b57cec5SDimitry Andric /// The Allocator type hands out fixed-sized chunks of memory that are
1480b57cec5SDimitry Andric /// cache-line aligned and sized. This is useful for placement of
1490b57cec5SDimitry Andric /// performance-sensitive data in memory that's frequently accessed. The
1500b57cec5SDimitry Andric /// allocator also self-limits the peak memory usage to a dynamically defined
1510b57cec5SDimitry Andric /// maximum.
1520b57cec5SDimitry Andric ///
1530b57cec5SDimitry Andric /// N is the lower-bound size of the block of memory to return from the
1540b57cec5SDimitry Andric /// allocation function. N is used to compute the size of a block, which is
1550b57cec5SDimitry Andric /// cache-line-size multiples worth of memory. We compute the size of a block by
1560b57cec5SDimitry Andric /// determining how many cache lines worth of memory is required to subsume N.
1570b57cec5SDimitry Andric ///
1580b57cec5SDimitry Andric /// The Allocator instance will manage its own memory acquired through mmap.
1590b57cec5SDimitry Andric /// This severely constrains the platforms on which this can be used to POSIX
1600b57cec5SDimitry Andric /// systems where mmap semantics are well-defined.
1610b57cec5SDimitry Andric ///
1620b57cec5SDimitry Andric /// FIXME: Isolate the lower-level memory management to a different abstraction
1630b57cec5SDimitry Andric /// that can be platform-specific.
1640b57cec5SDimitry Andric template <size_t N> struct Allocator {
1650b57cec5SDimitry Andric   // The Allocator returns memory as Block instances.
1660b57cec5SDimitry Andric   struct Block {
1670b57cec5SDimitry Andric     /// Compute the minimum cache-line size multiple that is >= N.
1680b57cec5SDimitry Andric     static constexpr auto Size = nearest_boundary(N, kCacheLineSize);
1690b57cec5SDimitry Andric     void *Data;
1700b57cec5SDimitry Andric   };
1710b57cec5SDimitry Andric 
1720b57cec5SDimitry Andric private:
1730b57cec5SDimitry Andric   size_t MaxMemory{0};
1740b57cec5SDimitry Andric   unsigned char *BackingStore = nullptr;
1750b57cec5SDimitry Andric   unsigned char *AlignedNextBlock = nullptr;
1760b57cec5SDimitry Andric   size_t AllocatedBlocks = 0;
1770b57cec5SDimitry Andric   bool Owned;
1780b57cec5SDimitry Andric   SpinMutex Mutex{};
1790b57cec5SDimitry Andric 
AllocAllocator1800b57cec5SDimitry Andric   void *Alloc() XRAY_NEVER_INSTRUMENT {
1810b57cec5SDimitry Andric     SpinMutexLock Lock(&Mutex);
1820b57cec5SDimitry Andric     if (UNLIKELY(BackingStore == nullptr)) {
1830b57cec5SDimitry Andric       BackingStore = allocateBuffer(MaxMemory);
1840b57cec5SDimitry Andric       if (BackingStore == nullptr) {
1850b57cec5SDimitry Andric         if (Verbosity())
186*0eae32dcSDimitry Andric           Report("XRay Profiling: Failed to allocate memory for allocator\n");
1870b57cec5SDimitry Andric         return nullptr;
1880b57cec5SDimitry Andric       }
1890b57cec5SDimitry Andric 
1900b57cec5SDimitry Andric       AlignedNextBlock = BackingStore;
1910b57cec5SDimitry Andric 
1920b57cec5SDimitry Andric       // Ensure that NextBlock is aligned appropriately.
1930b57cec5SDimitry Andric       auto BackingStoreNum = reinterpret_cast<uintptr_t>(BackingStore);
1940b57cec5SDimitry Andric       auto AlignedNextBlockNum = nearest_boundary(
1950b57cec5SDimitry Andric           reinterpret_cast<uintptr_t>(AlignedNextBlock), kCacheLineSize);
1960b57cec5SDimitry Andric       if (diff(AlignedNextBlockNum, BackingStoreNum) > ptrdiff_t(MaxMemory)) {
1970b57cec5SDimitry Andric         deallocateBuffer(BackingStore, MaxMemory);
1980b57cec5SDimitry Andric         AlignedNextBlock = BackingStore = nullptr;
1990b57cec5SDimitry Andric         if (Verbosity())
2000b57cec5SDimitry Andric           Report("XRay Profiling: Cannot obtain enough memory from "
201*0eae32dcSDimitry Andric                  "preallocated region\n");
2020b57cec5SDimitry Andric         return nullptr;
2030b57cec5SDimitry Andric       }
2040b57cec5SDimitry Andric 
2050b57cec5SDimitry Andric       AlignedNextBlock = reinterpret_cast<unsigned char *>(AlignedNextBlockNum);
2060b57cec5SDimitry Andric 
2070b57cec5SDimitry Andric       // Assert that AlignedNextBlock is cache-line aligned.
2080b57cec5SDimitry Andric       DCHECK_EQ(reinterpret_cast<uintptr_t>(AlignedNextBlock) % kCacheLineSize,
2090b57cec5SDimitry Andric                 0);
2100b57cec5SDimitry Andric     }
2110b57cec5SDimitry Andric 
2120b57cec5SDimitry Andric     if (((AllocatedBlocks + 1) * Block::Size) > MaxMemory)
2130b57cec5SDimitry Andric       return nullptr;
2140b57cec5SDimitry Andric 
2150b57cec5SDimitry Andric     // Align the pointer we'd like to return to an appropriate alignment, then
2160b57cec5SDimitry Andric     // advance the pointer from where to start allocations.
2170b57cec5SDimitry Andric     void *Result = AlignedNextBlock;
2180b57cec5SDimitry Andric     AlignedNextBlock =
2190b57cec5SDimitry Andric         reinterpret_cast<unsigned char *>(AlignedNextBlock) + Block::Size;
2200b57cec5SDimitry Andric     ++AllocatedBlocks;
2210b57cec5SDimitry Andric     return Result;
2220b57cec5SDimitry Andric   }
2230b57cec5SDimitry Andric 
2240b57cec5SDimitry Andric public:
AllocatorAllocator2250b57cec5SDimitry Andric   explicit Allocator(size_t M) XRAY_NEVER_INSTRUMENT
2260b57cec5SDimitry Andric       : MaxMemory(RoundUpTo(M, kCacheLineSize)),
2270b57cec5SDimitry Andric         BackingStore(nullptr),
2280b57cec5SDimitry Andric         AlignedNextBlock(nullptr),
2290b57cec5SDimitry Andric         AllocatedBlocks(0),
2300b57cec5SDimitry Andric         Owned(true),
2310b57cec5SDimitry Andric         Mutex() {}
2320b57cec5SDimitry Andric 
AllocatorAllocator2330b57cec5SDimitry Andric   explicit Allocator(void *P, size_t M) XRAY_NEVER_INSTRUMENT
2340b57cec5SDimitry Andric       : MaxMemory(M),
2350b57cec5SDimitry Andric         BackingStore(reinterpret_cast<unsigned char *>(P)),
2360b57cec5SDimitry Andric         AlignedNextBlock(reinterpret_cast<unsigned char *>(P)),
2370b57cec5SDimitry Andric         AllocatedBlocks(0),
2380b57cec5SDimitry Andric         Owned(false),
2390b57cec5SDimitry Andric         Mutex() {}
2400b57cec5SDimitry Andric 
2410b57cec5SDimitry Andric   Allocator(const Allocator &) = delete;
2420b57cec5SDimitry Andric   Allocator &operator=(const Allocator &) = delete;
2430b57cec5SDimitry Andric 
AllocatorAllocator2440b57cec5SDimitry Andric   Allocator(Allocator &&O) XRAY_NEVER_INSTRUMENT {
2450b57cec5SDimitry Andric     SpinMutexLock L0(&Mutex);
2460b57cec5SDimitry Andric     SpinMutexLock L1(&O.Mutex);
2470b57cec5SDimitry Andric     MaxMemory = O.MaxMemory;
2480b57cec5SDimitry Andric     O.MaxMemory = 0;
2490b57cec5SDimitry Andric     BackingStore = O.BackingStore;
2500b57cec5SDimitry Andric     O.BackingStore = nullptr;
2510b57cec5SDimitry Andric     AlignedNextBlock = O.AlignedNextBlock;
2520b57cec5SDimitry Andric     O.AlignedNextBlock = nullptr;
2530b57cec5SDimitry Andric     AllocatedBlocks = O.AllocatedBlocks;
2540b57cec5SDimitry Andric     O.AllocatedBlocks = 0;
2550b57cec5SDimitry Andric     Owned = O.Owned;
2560b57cec5SDimitry Andric     O.Owned = false;
2570b57cec5SDimitry Andric   }
2580b57cec5SDimitry Andric 
2590b57cec5SDimitry Andric   Allocator &operator=(Allocator &&O) XRAY_NEVER_INSTRUMENT {
2600b57cec5SDimitry Andric     SpinMutexLock L0(&Mutex);
2610b57cec5SDimitry Andric     SpinMutexLock L1(&O.Mutex);
2620b57cec5SDimitry Andric     MaxMemory = O.MaxMemory;
2630b57cec5SDimitry Andric     O.MaxMemory = 0;
2640b57cec5SDimitry Andric     if (BackingStore != nullptr)
2650b57cec5SDimitry Andric       deallocateBuffer(BackingStore, MaxMemory);
2660b57cec5SDimitry Andric     BackingStore = O.BackingStore;
2670b57cec5SDimitry Andric     O.BackingStore = nullptr;
2680b57cec5SDimitry Andric     AlignedNextBlock = O.AlignedNextBlock;
2690b57cec5SDimitry Andric     O.AlignedNextBlock = nullptr;
2700b57cec5SDimitry Andric     AllocatedBlocks = O.AllocatedBlocks;
2710b57cec5SDimitry Andric     O.AllocatedBlocks = 0;
2720b57cec5SDimitry Andric     Owned = O.Owned;
2730b57cec5SDimitry Andric     O.Owned = false;
2740b57cec5SDimitry Andric     return *this;
2750b57cec5SDimitry Andric   }
2760b57cec5SDimitry Andric 
AllocateAllocator2770b57cec5SDimitry Andric   Block Allocate() XRAY_NEVER_INSTRUMENT { return {Alloc()}; }
2780b57cec5SDimitry Andric 
~AllocatorAllocator2790b57cec5SDimitry Andric   ~Allocator() NOEXCEPT XRAY_NEVER_INSTRUMENT {
2800b57cec5SDimitry Andric     if (Owned && BackingStore != nullptr) {
2810b57cec5SDimitry Andric       deallocateBuffer(BackingStore, MaxMemory);
2820b57cec5SDimitry Andric     }
2830b57cec5SDimitry Andric   }
2840b57cec5SDimitry Andric };
2850b57cec5SDimitry Andric 
2860b57cec5SDimitry Andric } // namespace __xray
2870b57cec5SDimitry Andric 
2880b57cec5SDimitry Andric #endif // XRAY_ALLOCATOR_H
289