1 //===-- sanitizer_quarantine.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 // Memory quarantine for AddressSanitizer and potentially other tools.
10 // Quarantine caches some specified amount of memory in per-thread caches,
11 // then evicts to global FIFO queue. When the queue reaches specified threshold,
12 // oldest memory is recycled.
14 //===----------------------------------------------------------------------===//
16 #ifndef SANITIZER_QUARANTINE_H
17 #define SANITIZER_QUARANTINE_H
19 #include "sanitizer_internal_defs.h"
20 #include "sanitizer_mutex.h"
21 #include "sanitizer_list.h"
23 namespace __sanitizer
{
25 template<typename Node
> class QuarantineCache
;
27 struct QuarantineBatch
{
28 static const uptr kSize
= 1021;
29 QuarantineBatch
*next
;
34 void init(void *ptr
, uptr size
) {
37 this->size
= size
+ sizeof(QuarantineBatch
); // Account for the batch size.
40 // The total size of quarantined nodes recorded in this batch.
41 uptr
quarantined_size() const {
42 return size
- sizeof(QuarantineBatch
);
45 void push_back(void *ptr
, uptr size
) {
46 CHECK_LT(count
, kSize
);
51 bool can_merge(const QuarantineBatch
* const from
) const {
52 return count
+ from
->count
<= kSize
;
55 void merge(QuarantineBatch
* const from
) {
56 CHECK_LE(count
+ from
->count
, kSize
);
57 CHECK_GE(size
, sizeof(QuarantineBatch
));
59 for (uptr i
= 0; i
< from
->count
; ++i
)
60 batch
[count
+ i
] = from
->batch
[i
];
62 size
+= from
->quarantined_size();
65 from
->size
= sizeof(QuarantineBatch
);
69 COMPILER_CHECK(sizeof(QuarantineBatch
) <= (1 << 13)); // 8Kb.
71 template<typename Callback
, typename Node
>
74 typedef QuarantineCache
<Callback
> Cache
;
76 explicit Quarantine(LinkerInitialized
)
77 : cache_(LINKER_INITIALIZED
) {
80 void Init(uptr size
, uptr cache_size
) {
81 // Thread local quarantine size can be zero only when global quarantine size
82 // is zero (it allows us to perform just one atomic read per Put() call).
83 CHECK((size
== 0 && cache_size
== 0) || cache_size
!= 0);
85 atomic_store_relaxed(&max_size_
, size
);
86 atomic_store_relaxed(&min_size_
, size
/ 10 * 9); // 90% of max size.
87 atomic_store_relaxed(&max_cache_size_
, cache_size
);
90 recycle_mutex_
.Init();
93 uptr
GetMaxSize() const { return atomic_load_relaxed(&max_size_
); }
94 uptr
GetMaxCacheSize() const { return atomic_load_relaxed(&max_cache_size_
); }
96 void Put(Cache
*c
, Callback cb
, Node
*ptr
, uptr size
) {
97 uptr max_cache_size
= GetMaxCacheSize();
98 if (max_cache_size
&& size
<= GetMaxSize()) {
99 cb
.PreQuarantine(ptr
);
100 c
->Enqueue(cb
, ptr
, size
);
102 // GetMaxCacheSize() == 0 only when GetMaxSize() == 0 (see Init).
103 cb
.RecyclePassThrough(ptr
);
105 // Check cache size anyway to accommodate for runtime cache_size change.
106 if (c
->Size() > max_cache_size
)
110 void NOINLINE
Drain(Cache
*c
, Callback cb
) {
112 SpinMutexLock
l(&cache_mutex_
);
115 if (cache_
.Size() > GetMaxSize() && recycle_mutex_
.TryLock())
116 Recycle(atomic_load_relaxed(&min_size_
), cb
);
119 void NOINLINE
DrainAndRecycle(Cache
*c
, Callback cb
) {
121 SpinMutexLock
l(&cache_mutex_
);
124 recycle_mutex_
.Lock();
128 void PrintStats() const {
129 // It assumes that the world is stopped, just as the allocator's PrintStats.
130 Printf("Quarantine limits: global: %zdMb; thread local: %zdKb\n",
131 GetMaxSize() >> 20, GetMaxCacheSize() >> 10);
137 char pad0_
[kCacheLineSize
];
138 atomic_uintptr_t max_size_
;
139 atomic_uintptr_t min_size_
;
140 atomic_uintptr_t max_cache_size_
;
141 char pad1_
[kCacheLineSize
];
142 StaticSpinMutex cache_mutex_
;
143 StaticSpinMutex recycle_mutex_
;
145 char pad2_
[kCacheLineSize
];
147 void NOINLINE
Recycle(uptr min_size
, Callback cb
)
148 SANITIZER_REQUIRES(recycle_mutex_
) SANITIZER_RELEASE(recycle_mutex_
) {
151 SpinMutexLock
l(&cache_mutex_
);
152 // Go over the batches and merge partially filled ones to
153 // save some memory, otherwise batches themselves (since the memory used
154 // by them is counted against quarantine limit) can overcome the actual
155 // user's quarantined chunks, which diminishes the purpose of the
157 uptr cache_size
= cache_
.Size();
158 uptr overhead_size
= cache_
.OverheadSize();
159 CHECK_GE(cache_size
, overhead_size
);
160 // Do the merge only when overhead exceeds this predefined limit (might
161 // require some tuning). It saves us merge attempt when the batch list
162 // quarantine is unlikely to contain batches suitable for merge.
163 const uptr kOverheadThresholdPercents
= 100;
164 if (cache_size
> overhead_size
&&
165 overhead_size
* (100 + kOverheadThresholdPercents
) >
166 cache_size
* kOverheadThresholdPercents
) {
167 cache_
.MergeBatches(&tmp
);
169 // Extract enough chunks from the quarantine to get below the max
170 // quarantine size and leave some leeway for the newly quarantined chunks.
171 while (cache_
.Size() > min_size
) {
172 tmp
.EnqueueBatch(cache_
.DequeueBatch());
175 recycle_mutex_
.Unlock();
179 void NOINLINE
DoRecycle(Cache
*c
, Callback cb
) {
180 while (QuarantineBatch
*b
= c
->DequeueBatch()) {
181 const uptr kPrefetch
= 16;
182 CHECK(kPrefetch
<= ARRAY_SIZE(b
->batch
));
183 for (uptr i
= 0; i
< kPrefetch
; i
++)
184 PREFETCH(b
->batch
[i
]);
185 for (uptr i
= 0, count
= b
->count
; i
< count
; i
++) {
186 if (i
+ kPrefetch
< count
)
187 PREFETCH(b
->batch
[i
+ kPrefetch
]);
188 cb
.Recycle((Node
*)b
->batch
[i
]);
195 // Per-thread cache of memory blocks.
196 template<typename Callback
>
197 class QuarantineCache
{
199 explicit QuarantineCache(LinkerInitialized
) {
207 // Total memory used, including internal accounting.
209 return atomic_load_relaxed(&size_
);
212 // Memory used for internal accounting.
213 uptr
OverheadSize() const {
214 return list_
.size() * sizeof(QuarantineBatch
);
217 void Enqueue(Callback cb
, void *ptr
, uptr size
) {
218 if (list_
.empty() || list_
.back()->count
== QuarantineBatch::kSize
) {
219 QuarantineBatch
*b
= (QuarantineBatch
*)cb
.Allocate(sizeof(*b
));
224 list_
.back()->push_back(ptr
, size
);
229 void Transfer(QuarantineCache
*from_cache
) {
230 list_
.append_back(&from_cache
->list_
);
231 SizeAdd(from_cache
->Size());
233 atomic_store_relaxed(&from_cache
->size_
, 0);
236 void EnqueueBatch(QuarantineBatch
*b
) {
241 QuarantineBatch
*DequeueBatch() {
244 QuarantineBatch
*b
= list_
.front();
250 void MergeBatches(QuarantineCache
*to_deallocate
) {
251 uptr extracted_size
= 0;
252 QuarantineBatch
*current
= list_
.front();
253 while (current
&& current
->next
) {
254 if (current
->can_merge(current
->next
)) {
255 QuarantineBatch
*extracted
= current
->next
;
256 // Move all the chunks into the current batch.
257 current
->merge(extracted
);
258 CHECK_EQ(extracted
->count
, 0);
259 CHECK_EQ(extracted
->size
, sizeof(QuarantineBatch
));
260 // Remove the next batch from the list and account for its size.
261 list_
.extract(current
, extracted
);
262 extracted_size
+= extracted
->size
;
263 // Add it to deallocation list.
264 to_deallocate
->EnqueueBatch(extracted
);
266 current
= current
->next
;
269 SizeSub(extracted_size
);
272 void PrintStats() const {
273 uptr batch_count
= 0;
274 uptr total_overhead_bytes
= 0;
275 uptr total_bytes
= 0;
276 uptr total_quarantine_chunks
= 0;
277 for (List::ConstIterator it
= list_
.begin(); it
!= list_
.end(); ++it
) {
279 total_bytes
+= (*it
).size
;
280 total_overhead_bytes
+= (*it
).size
- (*it
).quarantined_size();
281 total_quarantine_chunks
+= (*it
).count
;
283 uptr quarantine_chunks_capacity
= batch_count
* QuarantineBatch::kSize
;
284 int chunks_usage_percent
= quarantine_chunks_capacity
== 0 ?
285 0 : total_quarantine_chunks
* 100 / quarantine_chunks_capacity
;
286 uptr total_quarantined_bytes
= total_bytes
- total_overhead_bytes
;
287 int memory_overhead_percent
= total_quarantined_bytes
== 0 ?
288 0 : total_overhead_bytes
* 100 / total_quarantined_bytes
;
289 Printf("Global quarantine stats: batches: %zd; bytes: %zd (user: %zd); "
290 "chunks: %zd (capacity: %zd); %d%% chunks used; %d%% memory overhead"
292 batch_count
, total_bytes
, total_quarantined_bytes
,
293 total_quarantine_chunks
, quarantine_chunks_capacity
,
294 chunks_usage_percent
, memory_overhead_percent
);
298 typedef IntrusiveList
<QuarantineBatch
> List
;
301 atomic_uintptr_t size_
;
303 void SizeAdd(uptr add
) {
304 atomic_store_relaxed(&size_
, Size() + add
);
306 void SizeSub(uptr sub
) {
307 atomic_store_relaxed(&size_
, Size() - sub
);
311 } // namespace __sanitizer
313 #endif // SANITIZER_QUARANTINE_H