1 //===-- sanitizer_allocator_secondary.h -------------------------*- C++ -*-===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 // Part of the Sanitizer Allocator.
12 //===----------------------------------------------------------------------===//
13 #ifndef SANITIZER_ALLOCATOR_H
14 #error This file must be included inside sanitizer_allocator.h
17 // Fixed array to store LargeMmapAllocator chunks list, limited to 32K total
18 // allocated chunks. To be used in memory constrained or not memory hungry cases
19 // (currently, 32 bits and internal allocator).
20 class LargeMmapAllocatorPtrArrayStatic {
22 INLINE void *Init() { return &p_[0]; }
23 INLINE void EnsureSpace(uptr n) { CHECK_LT(n, kMaxNumChunks); }
25 static const int kMaxNumChunks = 1 << 15;
26 uptr p_[kMaxNumChunks];
29 // Much less restricted LargeMmapAllocator chunks list (comparing to
30 // PtrArrayStatic). Backed by mmaped memory region and can hold up to 1M chunks.
31 // ReservedAddressRange was used instead of just MAP_NORESERVE to achieve the
32 // same functionality in Fuchsia case, which does not support MAP_NORESERVE.
33 class LargeMmapAllocatorPtrArrayDynamic {
36 uptr p = address_range_.Init(kMaxNumChunks * sizeof(uptr),
37 SecondaryAllocatorName);
39 return reinterpret_cast<void*>(p);
42 INLINE void EnsureSpace(uptr n) {
43 CHECK_LT(n, kMaxNumChunks);
44 DCHECK(n <= n_reserved_);
45 if (UNLIKELY(n == n_reserved_)) {
46 address_range_.MapOrDie(
47 reinterpret_cast<uptr>(address_range_.base()) +
48 n_reserved_ * sizeof(uptr),
49 kChunksBlockCount * sizeof(uptr));
50 n_reserved_ += kChunksBlockCount;
55 static const int kMaxNumChunks = 1 << 20;
56 static const int kChunksBlockCount = 1 << 14;
57 ReservedAddressRange address_range_;
61 #if SANITIZER_WORDSIZE == 32
62 typedef LargeMmapAllocatorPtrArrayStatic DefaultLargeMmapAllocatorPtrArray;
64 typedef LargeMmapAllocatorPtrArrayDynamic DefaultLargeMmapAllocatorPtrArray;
67 // This class can (de)allocate only large chunks of memory using mmap/unmap.
68 // The main purpose of this allocator is to cover large and rare allocation
69 // sizes not covered by more efficient allocators (e.g. SizeClassAllocator64).
70 template <class MapUnmapCallback = NoOpMapUnmapCallback,
71 class PtrArrayT = DefaultLargeMmapAllocatorPtrArray,
72 class AddressSpaceViewTy = LocalAddressSpaceView>
73 class LargeMmapAllocator {
75 using AddressSpaceView = AddressSpaceViewTy;
76 void InitLinkerInitialized() {
77 page_size_ = GetPageSizeCached();
78 chunks_ = reinterpret_cast<Header**>(ptr_array_.Init());
82 internal_memset(this, 0, sizeof(*this));
83 InitLinkerInitialized();
86 void *Allocate(AllocatorStats *stat, uptr size, uptr alignment) {
87 CHECK(IsPowerOfTwo(alignment));
88 uptr map_size = RoundUpMapSize(size);
89 if (alignment > page_size_)
90 map_size += alignment;
92 if (map_size < size) {
93 Report("WARNING: %s: LargeMmapAllocator allocation overflow: "
94 "0x%zx bytes with 0x%zx alignment requested\n",
95 SanitizerToolName, map_size, alignment);
98 uptr map_beg = reinterpret_cast<uptr>(
99 MmapOrDieOnFatalError(map_size, SecondaryAllocatorName));
102 CHECK(IsAligned(map_beg, page_size_));
103 MapUnmapCallback().OnMap(map_beg, map_size);
104 uptr map_end = map_beg + map_size;
105 uptr res = map_beg + page_size_;
106 if (res & (alignment - 1)) // Align.
107 res += alignment - (res & (alignment - 1));
108 CHECK(IsAligned(res, alignment));
109 CHECK(IsAligned(res, page_size_));
110 CHECK_GE(res + size, map_beg);
111 CHECK_LE(res + size, map_end);
112 Header *h = GetHeader(res);
114 h->map_beg = map_beg;
115 h->map_size = map_size;
116 uptr size_log = MostSignificantSetBitIndex(map_size);
117 CHECK_LT(size_log, ARRAY_SIZE(stats.by_size_log));
119 SpinMutexLock l(&mutex_);
120 ptr_array_.EnsureSpace(n_chunks_);
121 uptr idx = n_chunks_++;
124 chunks_sorted_ = false;
126 stats.currently_allocated += map_size;
127 stats.max_allocated = Max(stats.max_allocated, stats.currently_allocated);
128 stats.by_size_log[size_log]++;
129 stat->Add(AllocatorStatAllocated, map_size);
130 stat->Add(AllocatorStatMapped, map_size);
132 return reinterpret_cast<void*>(res);
135 void Deallocate(AllocatorStats *stat, void *p) {
136 Header *h = GetHeader(p);
138 SpinMutexLock l(&mutex_);
139 uptr idx = h->chunk_idx;
140 CHECK_EQ(chunks_[idx], h);
141 CHECK_LT(idx, n_chunks_);
142 chunks_[idx] = chunks_[--n_chunks_];
143 chunks_[idx]->chunk_idx = idx;
144 chunks_sorted_ = false;
146 stats.currently_allocated -= h->map_size;
147 stat->Sub(AllocatorStatAllocated, h->map_size);
148 stat->Sub(AllocatorStatMapped, h->map_size);
150 MapUnmapCallback().OnUnmap(h->map_beg, h->map_size);
151 UnmapOrDie(reinterpret_cast<void*>(h->map_beg), h->map_size);
154 uptr TotalMemoryUsed() {
155 SpinMutexLock l(&mutex_);
157 for (uptr i = 0; i < n_chunks_; i++) {
158 Header *h = chunks_[i];
159 CHECK_EQ(h->chunk_idx, i);
160 res += RoundUpMapSize(h->size);
165 bool PointerIsMine(const void *p) {
166 return GetBlockBegin(p) != nullptr;
169 uptr GetActuallyAllocatedSize(void *p) {
170 return RoundUpTo(GetHeader(p)->size, page_size_);
173 // At least page_size_/2 metadata bytes is available.
174 void *GetMetaData(const void *p) {
175 // Too slow: CHECK_EQ(p, GetBlockBegin(p));
176 if (!IsAligned(reinterpret_cast<uptr>(p), page_size_)) {
177 Printf("%s: bad pointer %p\n", SanitizerToolName, p);
178 CHECK(IsAligned(reinterpret_cast<uptr>(p), page_size_));
180 return GetHeader(p) + 1;
183 void *GetBlockBegin(const void *ptr) {
184 uptr p = reinterpret_cast<uptr>(ptr);
185 SpinMutexLock l(&mutex_);
186 uptr nearest_chunk = 0;
187 // Cache-friendly linear search.
188 for (uptr i = 0; i < n_chunks_; i++) {
189 uptr ch = reinterpret_cast<uptr>(chunks_[i]);
190 if (p < ch) continue; // p is at left to this chunk, skip it.
191 if (p - ch < p - nearest_chunk)
196 Header *h = reinterpret_cast<Header *>(nearest_chunk);
197 CHECK_GE(nearest_chunk, h->map_beg);
198 CHECK_LT(nearest_chunk, h->map_beg + h->map_size);
199 CHECK_LE(nearest_chunk, p);
200 if (h->map_beg + h->map_size <= p)
205 void EnsureSortedChunks() {
206 if (chunks_sorted_) return;
207 Header **chunks = AddressSpaceView::LoadWritable(chunks_, n_chunks_);
208 Sort(reinterpret_cast<uptr *>(chunks), n_chunks_);
209 for (uptr i = 0; i < n_chunks_; i++)
210 AddressSpaceView::LoadWritable(chunks[i])->chunk_idx = i;
211 chunks_sorted_ = true;
214 // This function does the same as GetBlockBegin, but is much faster.
215 // Must be called with the allocator locked.
216 void *GetBlockBeginFastLocked(void *ptr) {
217 mutex_.CheckLocked();
218 uptr p = reinterpret_cast<uptr>(ptr);
220 if (!n) return nullptr;
221 EnsureSortedChunks();
222 auto min_mmap_ = reinterpret_cast<uptr>(chunks_[0]);
224 reinterpret_cast<uptr>(chunks_[n - 1]) + chunks_[n - 1]->map_size;
225 if (p < min_mmap_ || p >= max_mmap_)
227 uptr beg = 0, end = n - 1;
228 // This loop is a log(n) lower_bound. It does not check for the exact match
229 // to avoid expensive cache-thrashing loads.
230 while (end - beg >= 2) {
231 uptr mid = (beg + end) / 2; // Invariant: mid >= beg + 1
232 if (p < reinterpret_cast<uptr>(chunks_[mid]))
233 end = mid - 1; // We are not interested in chunks_[mid].
235 beg = mid; // chunks_[mid] may still be what we want.
239 CHECK_EQ(beg + 1, end);
240 // There are 2 chunks left, choose one.
241 if (p >= reinterpret_cast<uptr>(chunks_[end]))
245 Header *h = chunks_[beg];
246 if (h->map_beg + h->map_size <= p || p < h->map_beg)
252 Printf("Stats: LargeMmapAllocator: allocated %zd times, "
253 "remains %zd (%zd K) max %zd M; by size logs: ",
254 stats.n_allocs, stats.n_allocs - stats.n_frees,
255 stats.currently_allocated >> 10, stats.max_allocated >> 20);
256 for (uptr i = 0; i < ARRAY_SIZE(stats.by_size_log); i++) {
257 uptr c = stats.by_size_log[i];
259 Printf("%zd:%zd; ", i, c);
264 // ForceLock() and ForceUnlock() are needed to implement Darwin malloc zone
265 // introspection API.
274 // Iterate over all existing chunks.
275 // The allocator must be locked when calling this function.
276 void ForEachChunk(ForEachChunkCallback callback, void *arg) {
277 EnsureSortedChunks(); // Avoid doing the sort while iterating.
278 const Header *const *chunks = AddressSpaceView::Load(chunks_, n_chunks_);
279 for (uptr i = 0; i < n_chunks_; i++) {
280 const Header *t = chunks[i];
281 callback(reinterpret_cast<uptr>(GetUser(t)), arg);
282 // Consistency check: verify that the array did not change.
283 CHECK_EQ(chunks[i], t);
284 CHECK_EQ(AddressSpaceView::Load(chunks[i])->chunk_idx, i);
296 Header *GetHeader(uptr p) {
297 CHECK(IsAligned(p, page_size_));
298 return reinterpret_cast<Header*>(p - page_size_);
300 Header *GetHeader(const void *p) {
301 return GetHeader(reinterpret_cast<uptr>(p));
304 void *GetUser(const Header *h) {
305 CHECK(IsAligned((uptr)h, page_size_));
306 return reinterpret_cast<void*>(reinterpret_cast<uptr>(h) + page_size_);
309 uptr RoundUpMapSize(uptr size) {
310 return RoundUpTo(size, page_size_) + page_size_;
315 PtrArrayT ptr_array_;
319 uptr n_allocs, n_frees, currently_allocated, max_allocated, by_size_log[64];
321 StaticSpinMutex mutex_;