| 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(size: sizeof(T), boundary: 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, length: RoundedSize, PROT_READ | PROT_WRITE, |
| 64 | MAP_PRIVATE | MAP_ANONYMOUS, fd: -1, offset: 0); |
| 65 | int ErrNo = 0; |
| 66 | if (UNLIKELY(internal_iserror(B, &ErrNo))) { |
| 67 | if (Verbosity()) |
| 68 | Report(format: "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(size: sizeof(T), boundary: 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(size: S * sizeof(T), boundary: 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, length: RoundedSize, PROT_READ | PROT_WRITE, |
| 113 | MAP_PRIVATE | MAP_ANONYMOUS, fd: -1, offset: 0); |
| 114 | int ErrNo = 0; |
| 115 | if (UNLIKELY(internal_iserror(B, &ErrNo))) { |
| 116 | if (Verbosity()) |
| 117 | Report(format: "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(size: S * sizeof(T), boundary: 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(number: N, multiple: 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(S: MaxMemory); |
| 184 | if (BackingStore == nullptr) { |
| 185 | if (Verbosity()) |
| 186 | Report(format: "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 | number: reinterpret_cast<uintptr_t>(AlignedNextBlock), multiple: kCacheLineSize); |
| 196 | if (diff(A: AlignedNextBlockNum, B: BackingStoreNum) > ptrdiff_t(MaxMemory)) { |
| 197 | deallocateBuffer(B: BackingStore, S: MaxMemory); |
| 198 | AlignedNextBlock = BackingStore = nullptr; |
| 199 | if (Verbosity()) |
| 200 | Report(format: "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(size: M, boundary: 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(B: BackingStore, S: 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(B: BackingStore, S: MaxMemory); |
| 282 | } |
| 283 | } |
| 284 | }; |
| 285 | |
| 286 | } // namespace __xray |
| 287 | |
| 288 | #endif // XRAY_ALLOCATOR_H |
| 289 | |