1 | //===-- sanitizer_allocator_local_cache.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 | // Part of the Sanitizer Allocator. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | #ifndef SANITIZER_ALLOCATOR_H |
13 | #error This file must be included inside sanitizer_allocator.h |
14 | #endif |
15 | |
16 | // Cache used by SizeClassAllocator64. |
17 | template <class SizeClassAllocator> |
18 | struct SizeClassAllocator64LocalCache { |
19 | typedef SizeClassAllocator Allocator; |
20 | typedef MemoryMapper<Allocator> MemoryMapperT; |
21 | |
22 | void Init(AllocatorGlobalStats *s) { |
23 | stats_.Init(); |
24 | if (s) |
25 | s->Register(s: &stats_); |
26 | } |
27 | |
28 | void Destroy(SizeClassAllocator *allocator, AllocatorGlobalStats *s) { |
29 | Drain(allocator); |
30 | if (s) |
31 | s->Unregister(s: &stats_); |
32 | } |
33 | |
34 | void *Allocate(SizeClassAllocator *allocator, uptr class_id) { |
35 | CHECK_NE(class_id, 0UL); |
36 | CHECK_LT(class_id, kNumClasses); |
37 | PerClass *c = &per_class_[class_id]; |
38 | if (UNLIKELY(c->count == 0)) { |
39 | if (UNLIKELY(!Refill(c, allocator, class_id))) |
40 | return nullptr; |
41 | DCHECK_GT(c->count, 0); |
42 | } |
43 | CompactPtrT chunk = c->chunks[--c->count]; |
44 | stats_.Add(i: AllocatorStatAllocated, v: c->class_size); |
45 | return reinterpret_cast<void *>(allocator->CompactPtrToPointer( |
46 | allocator->GetRegionBeginBySizeClass(class_id), chunk)); |
47 | } |
48 | |
49 | void Deallocate(SizeClassAllocator *allocator, uptr class_id, void *p) { |
50 | CHECK_NE(class_id, 0UL); |
51 | CHECK_LT(class_id, kNumClasses); |
52 | // If the first allocator call on a new thread is a deallocation, then |
53 | // max_count will be zero, leading to check failure. |
54 | PerClass *c = &per_class_[class_id]; |
55 | InitCache(c); |
56 | if (UNLIKELY(c->count == c->max_count)) |
57 | DrainHalfMax(c, allocator, class_id); |
58 | CompactPtrT chunk = allocator->PointerToCompactPtr( |
59 | allocator->GetRegionBeginBySizeClass(class_id), |
60 | reinterpret_cast<uptr>(p)); |
61 | c->chunks[c->count++] = chunk; |
62 | stats_.Sub(i: AllocatorStatAllocated, v: c->class_size); |
63 | } |
64 | |
65 | void Drain(SizeClassAllocator *allocator) { |
66 | MemoryMapperT memory_mapper(*allocator); |
67 | for (uptr i = 1; i < kNumClasses; i++) { |
68 | PerClass *c = &per_class_[i]; |
69 | while (c->count > 0) Drain(&memory_mapper, c, allocator, i, c->count); |
70 | } |
71 | } |
72 | |
73 | private: |
74 | typedef typename Allocator::SizeClassMapT SizeClassMap; |
75 | static const uptr kNumClasses = SizeClassMap::kNumClasses; |
76 | typedef typename Allocator::CompactPtrT CompactPtrT; |
77 | |
78 | struct PerClass { |
79 | u32 count; |
80 | u32 max_count; |
81 | uptr class_size; |
82 | CompactPtrT chunks[2 * SizeClassMap::kMaxNumCachedHint]; |
83 | }; |
84 | PerClass per_class_[kNumClasses]; |
85 | AllocatorStats stats_; |
86 | |
87 | void InitCache(PerClass *c) { |
88 | if (LIKELY(c->max_count)) |
89 | return; |
90 | for (uptr i = 1; i < kNumClasses; i++) { |
91 | PerClass *c = &per_class_[i]; |
92 | const uptr size = Allocator::ClassIdToSize(i); |
93 | c->max_count = 2 * SizeClassMap::MaxCachedHint(size); |
94 | c->class_size = size; |
95 | } |
96 | DCHECK_NE(c->max_count, 0UL); |
97 | } |
98 | |
99 | NOINLINE bool Refill(PerClass *c, SizeClassAllocator *allocator, |
100 | uptr class_id) { |
101 | InitCache(c); |
102 | const uptr num_requested_chunks = c->max_count / 2; |
103 | if (UNLIKELY(!allocator->GetFromAllocator(&stats_, class_id, c->chunks, |
104 | num_requested_chunks))) |
105 | return false; |
106 | c->count = num_requested_chunks; |
107 | return true; |
108 | } |
109 | |
110 | NOINLINE void DrainHalfMax(PerClass *c, SizeClassAllocator *allocator, |
111 | uptr class_id) { |
112 | MemoryMapperT memory_mapper(*allocator); |
113 | Drain(&memory_mapper, c, allocator, class_id, c->max_count / 2); |
114 | } |
115 | |
116 | void Drain(MemoryMapperT *memory_mapper, PerClass *c, |
117 | SizeClassAllocator *allocator, uptr class_id, uptr count) { |
118 | CHECK_GE(c->count, count); |
119 | const uptr first_idx_to_drain = c->count - count; |
120 | c->count -= count; |
121 | allocator->ReturnToAllocator(memory_mapper, &stats_, class_id, |
122 | &c->chunks[first_idx_to_drain], count); |
123 | } |
124 | }; |
125 | |
126 | // Cache used by SizeClassAllocator32. |
127 | template <class SizeClassAllocator> |
128 | struct SizeClassAllocator32LocalCache { |
129 | typedef SizeClassAllocator Allocator; |
130 | typedef typename Allocator::TransferBatch TransferBatch; |
131 | |
132 | void Init(AllocatorGlobalStats *s) { |
133 | stats_.Init(); |
134 | if (s) |
135 | s->Register(s: &stats_); |
136 | } |
137 | |
138 | // Returns a TransferBatch suitable for class_id. |
139 | TransferBatch *CreateBatch(uptr class_id, SizeClassAllocator *allocator, |
140 | TransferBatch *b) { |
141 | if (uptr batch_class_id = per_class_[class_id].batch_class_id) |
142 | return (TransferBatch*)Allocate(allocator, class_id: batch_class_id); |
143 | return b; |
144 | } |
145 | |
146 | // Destroys TransferBatch b. |
147 | void DestroyBatch(uptr class_id, SizeClassAllocator *allocator, |
148 | TransferBatch *b) { |
149 | if (uptr batch_class_id = per_class_[class_id].batch_class_id) |
150 | Deallocate(allocator, class_id: batch_class_id, p: b); |
151 | } |
152 | |
153 | void Destroy(SizeClassAllocator *allocator, AllocatorGlobalStats *s) { |
154 | Drain(allocator); |
155 | if (s) |
156 | s->Unregister(s: &stats_); |
157 | } |
158 | |
159 | void *Allocate(SizeClassAllocator *allocator, uptr class_id) { |
160 | CHECK_NE(class_id, 0UL); |
161 | CHECK_LT(class_id, kNumClasses); |
162 | PerClass *c = &per_class_[class_id]; |
163 | if (UNLIKELY(c->count == 0)) { |
164 | if (UNLIKELY(!Refill(c, allocator, class_id))) |
165 | return nullptr; |
166 | DCHECK_GT(c->count, 0); |
167 | } |
168 | void *res = c->batch[--c->count]; |
169 | PREFETCH(c->batch[c->count - 1]); |
170 | stats_.Add(i: AllocatorStatAllocated, v: c->class_size); |
171 | return res; |
172 | } |
173 | |
174 | void Deallocate(SizeClassAllocator *allocator, uptr class_id, void *p) { |
175 | CHECK_NE(class_id, 0UL); |
176 | CHECK_LT(class_id, kNumClasses); |
177 | // If the first allocator call on a new thread is a deallocation, then |
178 | // max_count will be zero, leading to check failure. |
179 | PerClass *c = &per_class_[class_id]; |
180 | InitCache(c); |
181 | if (UNLIKELY(c->count == c->max_count)) |
182 | Drain(c, allocator, class_id); |
183 | c->batch[c->count++] = p; |
184 | stats_.Sub(i: AllocatorStatAllocated, v: c->class_size); |
185 | } |
186 | |
187 | void Drain(SizeClassAllocator *allocator) { |
188 | for (uptr i = 1; i < kNumClasses; i++) { |
189 | PerClass *c = &per_class_[i]; |
190 | while (c->count > 0) |
191 | Drain(c, allocator, i); |
192 | } |
193 | } |
194 | |
195 | private: |
196 | typedef typename Allocator::SizeClassMapT SizeClassMap; |
197 | static const uptr kBatchClassID = SizeClassMap::kBatchClassID; |
198 | static const uptr kNumClasses = SizeClassMap::kNumClasses; |
199 | // If kUseSeparateSizeClassForBatch is true, all TransferBatch objects are |
200 | // allocated from kBatchClassID size class (except for those that are needed |
201 | // for kBatchClassID itself). The goal is to have TransferBatches in a totally |
202 | // different region of RAM to improve security. |
203 | static const bool kUseSeparateSizeClassForBatch = |
204 | Allocator::kUseSeparateSizeClassForBatch; |
205 | |
206 | struct PerClass { |
207 | uptr count; |
208 | uptr max_count; |
209 | uptr class_size; |
210 | uptr batch_class_id; |
211 | void *batch[2 * TransferBatch::kMaxNumCached]; |
212 | }; |
213 | PerClass per_class_[kNumClasses]; |
214 | AllocatorStats stats_; |
215 | |
216 | void InitCache(PerClass *c) { |
217 | if (LIKELY(c->max_count)) |
218 | return; |
219 | const uptr batch_class_id = SizeClassMap::ClassID(sizeof(TransferBatch)); |
220 | for (uptr i = 1; i < kNumClasses; i++) { |
221 | PerClass *c = &per_class_[i]; |
222 | const uptr size = Allocator::ClassIdToSize(i); |
223 | const uptr max_cached = TransferBatch::MaxCached(size); |
224 | c->max_count = 2 * max_cached; |
225 | c->class_size = size; |
226 | // Precompute the class id to use to store batches for the current class |
227 | // id. 0 means the class size is large enough to store a batch within one |
228 | // of the chunks. If using a separate size class, it will always be |
229 | // kBatchClassID, except for kBatchClassID itself. |
230 | if (kUseSeparateSizeClassForBatch) { |
231 | c->batch_class_id = (i == kBatchClassID) ? 0 : kBatchClassID; |
232 | } else { |
233 | c->batch_class_id = (size < |
234 | TransferBatch::AllocationSizeRequiredForNElements(max_cached)) ? |
235 | batch_class_id : 0; |
236 | } |
237 | } |
238 | DCHECK_NE(c->max_count, 0UL); |
239 | } |
240 | |
241 | NOINLINE bool Refill(PerClass *c, SizeClassAllocator *allocator, |
242 | uptr class_id) { |
243 | InitCache(c); |
244 | TransferBatch *b = allocator->AllocateBatch(&stats_, this, class_id); |
245 | if (UNLIKELY(!b)) |
246 | return false; |
247 | CHECK_GT(b->Count(), 0); |
248 | b->CopyToArray(c->batch); |
249 | c->count = b->Count(); |
250 | DestroyBatch(class_id, allocator, b); |
251 | return true; |
252 | } |
253 | |
254 | NOINLINE void Drain(PerClass *c, SizeClassAllocator *allocator, |
255 | uptr class_id) { |
256 | const uptr count = Min(c->max_count / 2, c->count); |
257 | const uptr first_idx_to_drain = c->count - count; |
258 | TransferBatch *b = CreateBatch( |
259 | class_id, allocator, b: (TransferBatch *)c->batch[first_idx_to_drain]); |
260 | // Failure to allocate a batch while releasing memory is non recoverable. |
261 | // TODO(alekseys): Figure out how to do it without allocating a new batch. |
262 | if (UNLIKELY(!b)) { |
263 | Report(format: "FATAL: Internal error: %s's allocator failed to allocate a " |
264 | "transfer batch.\n" , SanitizerToolName); |
265 | Die(); |
266 | } |
267 | b->SetFromArray(&c->batch[first_idx_to_drain], count); |
268 | c->count -= count; |
269 | allocator->DeallocateBatch(&stats_, class_id, b); |
270 | } |
271 | }; |
272 | |