1 //===-- xray_allocator.h ---------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file is a part of XRay, a dynamic runtime instrumentation system. 10 // 11 // Defines the allocator interface for an arena allocator, used primarily for 12 // the profiling runtime. 13 // 14 //===----------------------------------------------------------------------===// 15 #ifndef XRAY_ALLOCATOR_H 16 #define XRAY_ALLOCATOR_H 17 18 #include "sanitizer_common/sanitizer_common.h" 19 #include "sanitizer_common/sanitizer_internal_defs.h" 20 #include "sanitizer_common/sanitizer_mutex.h" 21 #if SANITIZER_FUCHSIA 22 #include <zircon/process.h> 23 #include <zircon/status.h> 24 #include <zircon/syscalls.h> 25 #else 26 #include "sanitizer_common/sanitizer_posix.h" 27 #endif 28 #include "xray_defs.h" 29 #include "xray_utils.h" 30 #include <cstddef> 31 #include <cstdint> 32 #include <sys/mman.h> 33 34 namespace __xray { 35 36 // We implement our own memory allocation routine which will bypass the 37 // internal allocator. This allows us to manage the memory directly, using 38 // mmap'ed memory to back the allocators. 39 template <class T> T *allocate() XRAY_NEVER_INSTRUMENT { 40 uptr RoundedSize = RoundUpTo(sizeof(T), GetPageSizeCached()); 41 #if SANITIZER_FUCHSIA 42 zx_handle_t Vmo; 43 zx_status_t Status = _zx_vmo_create(RoundedSize, 0, &Vmo); 44 if (Status != ZX_OK) { 45 if (Verbosity()) 46 Report("XRay Profiling: Failed to create VMO of size %zu: %s\n", 47 sizeof(T), _zx_status_get_string(Status)); 48 return nullptr; 49 } 50 uintptr_t B; 51 Status = 52 _zx_vmar_map(_zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, 53 Vmo, 0, sizeof(T), &B); 54 _zx_handle_close(Vmo); 55 if (Status != ZX_OK) { 56 if (Verbosity()) 57 Report("XRay Profiling: Failed to map VMAR of size %zu: %s\n", sizeof(T), 58 _zx_status_get_string(Status)); 59 return nullptr; 60 } 61 return reinterpret_cast<T *>(B); 62 #else 63 uptr B = internal_mmap(NULL, RoundedSize, PROT_READ | PROT_WRITE, 64 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 65 int ErrNo = 0; 66 if (UNLIKELY(internal_iserror(B, &ErrNo))) { 67 if (Verbosity()) 68 Report("XRay Profiling: Failed to allocate memory of size %zu; Error = " 69 "%zu\n", 70 RoundedSize, B); 71 return nullptr; 72 } 73 #endif 74 return reinterpret_cast<T *>(B); 75 } 76 77 template <class T> void deallocate(T *B) XRAY_NEVER_INSTRUMENT { 78 if (B == nullptr) 79 return; 80 uptr RoundedSize = RoundUpTo(sizeof(T), GetPageSizeCached()); 81 #if SANITIZER_FUCHSIA 82 _zx_vmar_unmap(_zx_vmar_root_self(), reinterpret_cast<uintptr_t>(B), 83 RoundedSize); 84 #else 85 internal_munmap(B, RoundedSize); 86 #endif 87 } 88 89 template <class T = unsigned char> 90 T *allocateBuffer(size_t S) XRAY_NEVER_INSTRUMENT { 91 uptr RoundedSize = RoundUpTo(S * sizeof(T), GetPageSizeCached()); 92 #if SANITIZER_FUCHSIA 93 zx_handle_t Vmo; 94 zx_status_t Status = _zx_vmo_create(RoundedSize, 0, &Vmo); 95 if (Status != ZX_OK) { 96 if (Verbosity()) 97 Report("XRay Profiling: Failed to create VMO of size %zu: %s\n", S, 98 _zx_status_get_string(Status)); 99 return nullptr; 100 } 101 uintptr_t B; 102 Status = _zx_vmar_map(_zx_vmar_root_self(), 103 ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, Vmo, 0, S, &B); 104 _zx_handle_close(Vmo); 105 if (Status != ZX_OK) { 106 if (Verbosity()) 107 Report("XRay Profiling: Failed to map VMAR of size %zu: %s\n", S, 108 _zx_status_get_string(Status)); 109 return nullptr; 110 } 111 #else 112 uptr B = internal_mmap(NULL, RoundedSize, PROT_READ | PROT_WRITE, 113 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 114 int ErrNo = 0; 115 if (UNLIKELY(internal_iserror(B, &ErrNo))) { 116 if (Verbosity()) 117 Report("XRay Profiling: Failed to allocate memory of size %zu; Error = " 118 "%zu\n", 119 RoundedSize, B); 120 return nullptr; 121 } 122 #endif 123 return reinterpret_cast<T *>(B); 124 } 125 126 template <class T> void deallocateBuffer(T *B, size_t S) XRAY_NEVER_INSTRUMENT { 127 if (B == nullptr) 128 return; 129 uptr RoundedSize = RoundUpTo(S * sizeof(T), GetPageSizeCached()); 130 #if SANITIZER_FUCHSIA 131 _zx_vmar_unmap(_zx_vmar_root_self(), reinterpret_cast<uintptr_t>(B), 132 RoundedSize); 133 #else 134 internal_munmap(B, RoundedSize); 135 #endif 136 } 137 138 template <class T, class... U> 139 T *initArray(size_t N, U &&... Us) XRAY_NEVER_INSTRUMENT { 140 auto A = allocateBuffer<T>(N); 141 if (A != nullptr) 142 while (N > 0) 143 new (A + (--N)) T(std::forward<U>(Us)...); 144 return A; 145 } 146 147 /// The Allocator type hands out fixed-sized chunks of memory that are 148 /// cache-line aligned and sized. This is useful for placement of 149 /// performance-sensitive data in memory that's frequently accessed. The 150 /// allocator also self-limits the peak memory usage to a dynamically defined 151 /// maximum. 152 /// 153 /// N is the lower-bound size of the block of memory to return from the 154 /// allocation function. N is used to compute the size of a block, which is 155 /// cache-line-size multiples worth of memory. We compute the size of a block by 156 /// determining how many cache lines worth of memory is required to subsume N. 157 /// 158 /// The Allocator instance will manage its own memory acquired through mmap. 159 /// This severely constrains the platforms on which this can be used to POSIX 160 /// systems where mmap semantics are well-defined. 161 /// 162 /// FIXME: Isolate the lower-level memory management to a different abstraction 163 /// that can be platform-specific. 164 template <size_t N> struct Allocator { 165 // The Allocator returns memory as Block instances. 166 struct Block { 167 /// Compute the minimum cache-line size multiple that is >= N. 168 static constexpr auto Size = nearest_boundary(N, kCacheLineSize); 169 void *Data; 170 }; 171 172 private: 173 size_t MaxMemory{0}; 174 unsigned char *BackingStore = nullptr; 175 unsigned char *AlignedNextBlock = nullptr; 176 size_t AllocatedBlocks = 0; 177 bool Owned; 178 SpinMutex Mutex{}; 179 180 void *Alloc() XRAY_NEVER_INSTRUMENT { 181 SpinMutexLock Lock(&Mutex); 182 if (UNLIKELY(BackingStore == nullptr)) { 183 BackingStore = allocateBuffer(MaxMemory); 184 if (BackingStore == nullptr) { 185 if (Verbosity()) 186 Report("XRay Profiling: Failed to allocate memory for allocator\n"); 187 return nullptr; 188 } 189 190 AlignedNextBlock = BackingStore; 191 192 // Ensure that NextBlock is aligned appropriately. 193 auto BackingStoreNum = reinterpret_cast<uintptr_t>(BackingStore); 194 auto AlignedNextBlockNum = nearest_boundary( 195 reinterpret_cast<uintptr_t>(AlignedNextBlock), kCacheLineSize); 196 if (diff(AlignedNextBlockNum, BackingStoreNum) > ptrdiff_t(MaxMemory)) { 197 deallocateBuffer(BackingStore, MaxMemory); 198 AlignedNextBlock = BackingStore = nullptr; 199 if (Verbosity()) 200 Report("XRay Profiling: Cannot obtain enough memory from " 201 "preallocated region\n"); 202 return nullptr; 203 } 204 205 AlignedNextBlock = reinterpret_cast<unsigned char *>(AlignedNextBlockNum); 206 207 // Assert that AlignedNextBlock is cache-line aligned. 208 DCHECK_EQ(reinterpret_cast<uintptr_t>(AlignedNextBlock) % kCacheLineSize, 209 0); 210 } 211 212 if (((AllocatedBlocks + 1) * Block::Size) > MaxMemory) 213 return nullptr; 214 215 // Align the pointer we'd like to return to an appropriate alignment, then 216 // advance the pointer from where to start allocations. 217 void *Result = AlignedNextBlock; 218 AlignedNextBlock = 219 reinterpret_cast<unsigned char *>(AlignedNextBlock) + Block::Size; 220 ++AllocatedBlocks; 221 return Result; 222 } 223 224 public: 225 explicit Allocator(size_t M) XRAY_NEVER_INSTRUMENT 226 : MaxMemory(RoundUpTo(M, kCacheLineSize)), 227 BackingStore(nullptr), 228 AlignedNextBlock(nullptr), 229 AllocatedBlocks(0), 230 Owned(true), 231 Mutex() {} 232 233 explicit Allocator(void *P, size_t M) XRAY_NEVER_INSTRUMENT 234 : MaxMemory(M), 235 BackingStore(reinterpret_cast<unsigned char *>(P)), 236 AlignedNextBlock(reinterpret_cast<unsigned char *>(P)), 237 AllocatedBlocks(0), 238 Owned(false), 239 Mutex() {} 240 241 Allocator(const Allocator &) = delete; 242 Allocator &operator=(const Allocator &) = delete; 243 244 Allocator(Allocator &&O) XRAY_NEVER_INSTRUMENT { 245 SpinMutexLock L0(&Mutex); 246 SpinMutexLock L1(&O.Mutex); 247 MaxMemory = O.MaxMemory; 248 O.MaxMemory = 0; 249 BackingStore = O.BackingStore; 250 O.BackingStore = nullptr; 251 AlignedNextBlock = O.AlignedNextBlock; 252 O.AlignedNextBlock = nullptr; 253 AllocatedBlocks = O.AllocatedBlocks; 254 O.AllocatedBlocks = 0; 255 Owned = O.Owned; 256 O.Owned = false; 257 } 258 259 Allocator &operator=(Allocator &&O) XRAY_NEVER_INSTRUMENT { 260 SpinMutexLock L0(&Mutex); 261 SpinMutexLock L1(&O.Mutex); 262 MaxMemory = O.MaxMemory; 263 O.MaxMemory = 0; 264 if (BackingStore != nullptr) 265 deallocateBuffer(BackingStore, MaxMemory); 266 BackingStore = O.BackingStore; 267 O.BackingStore = nullptr; 268 AlignedNextBlock = O.AlignedNextBlock; 269 O.AlignedNextBlock = nullptr; 270 AllocatedBlocks = O.AllocatedBlocks; 271 O.AllocatedBlocks = 0; 272 Owned = O.Owned; 273 O.Owned = false; 274 return *this; 275 } 276 277 Block Allocate() XRAY_NEVER_INSTRUMENT { return {Alloc()}; } 278 279 ~Allocator() NOEXCEPT XRAY_NEVER_INSTRUMENT { 280 if (Owned && BackingStore != nullptr) { 281 deallocateBuffer(BackingStore, MaxMemory); 282 } 283 } 284 }; 285 286 } // namespace __xray 287 288 #endif // XRAY_ALLOCATOR_H 289