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