1 //===-- sanitizer_allocator_local_cache.h -----------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 // Part of the Sanitizer Allocator.
11 //===----------------------------------------------------------------------===//
12 #ifndef SANITIZER_ALLOCATOR_H
13 #error This file must be included inside sanitizer_allocator.h
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
) {
28 void Destroy(SizeClassAllocator
*allocator
, AllocatorGlobalStats
*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
)))
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
];
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
);
74 typedef typename
Allocator::SizeClassMapT SizeClassMap
;
75 static const uptr kNumClasses
= SizeClassMap::kNumClasses
;
76 typedef typename
Allocator::CompactPtrT CompactPtrT
;
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
))
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
);
96 DCHECK_NE(c
->max_count
, 0UL);
99 NOINLINE
bool Refill(PerClass
*c
, SizeClassAllocator
*allocator
,
102 const uptr num_requested_chunks
= c
->max_count
/ 2;
103 if (UNLIKELY(!allocator
->GetFromAllocator(&stats_
, class_id
, c
->chunks
,
104 num_requested_chunks
)))
106 c
->count
= num_requested_chunks
;
110 NOINLINE
void DrainHalfMax(PerClass
*c
, SizeClassAllocator
*allocator
,
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
;
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
) {
135 s
->Register(&stats_
);
138 // Returns a TransferBatch suitable for class_id.
139 TransferBatch
*CreateBatch(uptr class_id
, SizeClassAllocator
*allocator
,
141 if (uptr batch_class_id
= per_class_
[class_id
].batch_class_id
)
142 return (TransferBatch
*)Allocate(allocator
, batch_class_id
);
146 // Destroys TransferBatch b.
147 void DestroyBatch(uptr class_id
, SizeClassAllocator
*allocator
,
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
) {
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
)))
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
);
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
];
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
];
191 Drain(c
, allocator
, i
);
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
;
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
))
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
;
233 c
->batch_class_id
= (size
<
234 TransferBatch::AllocationSizeRequiredForNElements(max_cached
)) ?
238 DCHECK_NE(c
->max_count
, 0UL);
241 NOINLINE
bool Refill(PerClass
*c
, SizeClassAllocator
*allocator
,
244 TransferBatch
*b
= allocator
->AllocateBatch(&stats_
, this, class_id
);
247 CHECK_GT(b
->Count(), 0);
248 b
->CopyToArray(c
->batch
);
249 c
->count
= b
->Count();
250 DestroyBatch(class_id
, allocator
, b
);
254 NOINLINE
void Drain(PerClass
*c
, SizeClassAllocator
*allocator
,
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.
263 Report("FATAL: Internal error: %s's allocator failed to allocate a "
264 "transfer batch.\n", SanitizerToolName
);
267 b
->SetFromArray(&c
->batch
[first_idx_to_drain
], count
);
269 allocator
->DeallocateBatch(&stats_
, class_id
, b
);