Daily bump.
[official-gcc.git] / libsanitizer / sanitizer_common / sanitizer_allocator_local_cache.h
blobe495c56f03775e26a3752e203dcc40d437cc1d3f
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.
11 //===----------------------------------------------------------------------===//
12 #ifndef SANITIZER_ALLOCATOR_H
13 #error This file must be included inside sanitizer_allocator.h
14 #endif
16 // Cache used by SizeClassAllocator64.
17 template <class SizeClassAllocator>
18 struct SizeClassAllocator64LocalCache {
19 typedef SizeClassAllocator Allocator;
20 typedef MemoryMapper<Allocator> MemoryMapperT;
22 void Init(AllocatorGlobalStats *s) {
23 stats_.Init();
24 if (s)
25 s->Register(&stats_);
28 void Destroy(SizeClassAllocator *allocator, AllocatorGlobalStats *s) {
29 Drain(allocator);
30 if (s)
31 s->Unregister(&stats_);
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);
43 CompactPtrT chunk = c->chunks[--c->count];
44 stats_.Add(AllocatorStatAllocated, c->class_size);
45 return reinterpret_cast<void *>(allocator->CompactPtrToPointer(
46 allocator->GetRegionBeginBySizeClass(class_id), chunk));
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(AllocatorStatAllocated, c->class_size);
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);
73 private:
74 typedef typename Allocator::SizeClassMapT SizeClassMap;
75 static const uptr kNumClasses = SizeClassMap::kNumClasses;
76 typedef typename Allocator::CompactPtrT CompactPtrT;
78 struct PerClass {
79 u32 count;
80 u32 max_count;
81 uptr class_size;
82 CompactPtrT chunks[2 * SizeClassMap::kMaxNumCachedHint];
84 PerClass per_class_[kNumClasses];
85 AllocatorStats stats_;
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;
96 DCHECK_NE(c->max_count, 0UL);
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;
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);
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);
126 // Cache used by SizeClassAllocator32.
127 template <class SizeClassAllocator>
128 struct SizeClassAllocator32LocalCache {
129 typedef SizeClassAllocator Allocator;
130 typedef typename Allocator::TransferBatch TransferBatch;
132 void Init(AllocatorGlobalStats *s) {
133 stats_.Init();
134 if (s)
135 s->Register(&stats_);
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, batch_class_id);
143 return b;
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, batch_class_id, b);
153 void Destroy(SizeClassAllocator *allocator, AllocatorGlobalStats *s) {
154 Drain(allocator);
155 if (s)
156 s->Unregister(&stats_);
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);
168 void *res = c->batch[--c->count];
169 PREFETCH(c->batch[c->count - 1]);
170 stats_.Add(AllocatorStatAllocated, c->class_size);
171 return res;
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(AllocatorStatAllocated, c->class_size);
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);
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;
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];
213 PerClass per_class_[kNumClasses];
214 AllocatorStats stats_;
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;
238 DCHECK_NE(c->max_count, 0UL);
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;
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, (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("FATAL: Internal error: %s's allocator failed to allocate a "
264 "transfer batch.\n", SanitizerToolName);
265 Die();
267 b->SetFromArray(&c->batch[first_idx_to_drain], count);
268 c->count -= count;
269 allocator->DeallocateBatch(&stats_, class_id, b);