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 | |