2016-07-21 06:06:41 +08:00
|
|
|
//===-- sanitizer_allocator_local_cache.h -----------------------*- C++ -*-===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
//
|
|
|
|
// Part of the Sanitizer Allocator.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SANITIZER_ALLOCATOR_H
|
|
|
|
#error This file must be included inside sanitizer_allocator.h
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Objects of this type should be used as local caches for SizeClassAllocator64
|
|
|
|
// or SizeClassAllocator32. Since the typical use of this class is to have one
|
|
|
|
// object per thread in TLS, is has to be POD.
|
|
|
|
template<class SizeClassAllocator>
|
2016-08-23 08:30:43 +08:00
|
|
|
struct SizeClassAllocatorLocalCache
|
|
|
|
: SizeClassAllocator::AllocatorCache {
|
|
|
|
};
|
|
|
|
|
|
|
|
// Cache used by SizeClassAllocator64.
|
|
|
|
template <class SizeClassAllocator>
|
|
|
|
struct SizeClassAllocator64LocalCache {
|
|
|
|
typedef SizeClassAllocator Allocator;
|
|
|
|
static const uptr kNumClasses = SizeClassAllocator::kNumClasses;
|
2016-08-25 05:20:10 +08:00
|
|
|
typedef typename Allocator::SizeClassMapT SizeClassMap;
|
|
|
|
typedef typename Allocator::CompactPtrT CompactPtrT;
|
2016-08-23 08:30:43 +08:00
|
|
|
|
|
|
|
void Init(AllocatorGlobalStats *s) {
|
|
|
|
stats_.Init();
|
|
|
|
if (s)
|
|
|
|
s->Register(&stats_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Destroy(SizeClassAllocator *allocator, AllocatorGlobalStats *s) {
|
|
|
|
Drain(allocator);
|
|
|
|
if (s)
|
|
|
|
s->Unregister(&stats_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void *Allocate(SizeClassAllocator *allocator, uptr class_id) {
|
|
|
|
CHECK_NE(class_id, 0UL);
|
|
|
|
CHECK_LT(class_id, kNumClasses);
|
|
|
|
PerClass *c = &per_class_[class_id];
|
2017-06-27 06:54:10 +08:00
|
|
|
if (UNLIKELY(c->count == 0)) {
|
|
|
|
if (UNLIKELY(!Refill(c, allocator, class_id)))
|
|
|
|
return nullptr;
|
|
|
|
}
|
2017-04-14 00:49:16 +08:00
|
|
|
stats_.Add(AllocatorStatAllocated, c->class_size);
|
2016-08-25 05:20:10 +08:00
|
|
|
CHECK_GT(c->count, 0);
|
|
|
|
CompactPtrT chunk = c->chunks[--c->count];
|
|
|
|
void *res = reinterpret_cast<void *>(allocator->CompactPtrToPointer(
|
|
|
|
allocator->GetRegionBeginBySizeClass(class_id), chunk));
|
2016-08-23 08:30:43 +08:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Deallocate(SizeClassAllocator *allocator, uptr class_id, void *p) {
|
|
|
|
CHECK_NE(class_id, 0UL);
|
|
|
|
CHECK_LT(class_id, kNumClasses);
|
|
|
|
// If the first allocator call on a new thread is a deallocation, then
|
|
|
|
// max_count will be zero, leading to check failure.
|
|
|
|
InitCache();
|
|
|
|
PerClass *c = &per_class_[class_id];
|
2017-04-14 00:49:16 +08:00
|
|
|
stats_.Sub(AllocatorStatAllocated, c->class_size);
|
2016-08-23 08:30:43 +08:00
|
|
|
CHECK_NE(c->max_count, 0UL);
|
|
|
|
if (UNLIKELY(c->count == c->max_count))
|
2016-08-25 05:20:10 +08:00
|
|
|
Drain(c, allocator, class_id, c->max_count / 2);
|
|
|
|
CompactPtrT chunk = allocator->PointerToCompactPtr(
|
|
|
|
allocator->GetRegionBeginBySizeClass(class_id),
|
|
|
|
reinterpret_cast<uptr>(p));
|
|
|
|
c->chunks[c->count++] = chunk;
|
2016-08-23 08:30:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void Drain(SizeClassAllocator *allocator) {
|
|
|
|
for (uptr class_id = 0; class_id < kNumClasses; class_id++) {
|
|
|
|
PerClass *c = &per_class_[class_id];
|
|
|
|
while (c->count > 0)
|
2016-08-25 05:20:10 +08:00
|
|
|
Drain(c, allocator, class_id, c->count);
|
2016-08-23 08:30:43 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// private:
|
|
|
|
struct PerClass {
|
2016-08-25 05:20:10 +08:00
|
|
|
u32 count;
|
|
|
|
u32 max_count;
|
2017-04-14 00:49:16 +08:00
|
|
|
uptr class_size;
|
2016-08-25 05:20:10 +08:00
|
|
|
CompactPtrT chunks[2 * SizeClassMap::kMaxNumCachedHint];
|
2016-08-23 08:30:43 +08:00
|
|
|
};
|
|
|
|
PerClass per_class_[kNumClasses];
|
|
|
|
AllocatorStats stats_;
|
|
|
|
|
|
|
|
void InitCache() {
|
|
|
|
if (per_class_[1].max_count)
|
|
|
|
return;
|
|
|
|
for (uptr i = 0; i < kNumClasses; i++) {
|
|
|
|
PerClass *c = &per_class_[i];
|
2016-08-25 05:20:10 +08:00
|
|
|
c->max_count = 2 * SizeClassMap::MaxCachedHint(i);
|
2017-04-14 00:49:16 +08:00
|
|
|
c->class_size = Allocator::ClassIdToSize(i);
|
2016-08-23 08:30:43 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-27 06:54:10 +08:00
|
|
|
NOINLINE bool Refill(PerClass *c, SizeClassAllocator *allocator,
|
2016-08-25 05:20:10 +08:00
|
|
|
uptr class_id) {
|
2016-08-23 08:30:43 +08:00
|
|
|
InitCache();
|
2017-04-13 04:51:42 +08:00
|
|
|
uptr num_requested_chunks = c->max_count / 2;
|
2017-06-27 06:54:10 +08:00
|
|
|
if (UNLIKELY(!allocator->GetFromAllocator(&stats_, class_id, c->chunks,
|
|
|
|
num_requested_chunks)))
|
|
|
|
return false;
|
2016-08-25 05:20:10 +08:00
|
|
|
c->count = num_requested_chunks;
|
2017-06-27 06:54:10 +08:00
|
|
|
return true;
|
2016-08-23 08:30:43 +08:00
|
|
|
}
|
|
|
|
|
2016-08-25 05:20:10 +08:00
|
|
|
NOINLINE void Drain(PerClass *c, SizeClassAllocator *allocator, uptr class_id,
|
|
|
|
uptr count) {
|
2016-08-23 08:30:43 +08:00
|
|
|
InitCache();
|
2016-08-25 05:20:10 +08:00
|
|
|
CHECK_GE(c->count, count);
|
|
|
|
uptr first_idx_to_drain = c->count - count;
|
|
|
|
c->count -= count;
|
|
|
|
allocator->ReturnToAllocator(&stats_, class_id,
|
|
|
|
&c->chunks[first_idx_to_drain], count);
|
2016-08-23 08:30:43 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Cache used by SizeClassAllocator32.
|
|
|
|
template <class SizeClassAllocator>
|
|
|
|
struct SizeClassAllocator32LocalCache {
|
2016-07-21 06:06:41 +08:00
|
|
|
typedef SizeClassAllocator Allocator;
|
2016-08-06 09:24:11 +08:00
|
|
|
typedef typename Allocator::TransferBatch TransferBatch;
|
2016-07-21 06:06:41 +08:00
|
|
|
static const uptr kNumClasses = SizeClassAllocator::kNumClasses;
|
|
|
|
|
|
|
|
void Init(AllocatorGlobalStats *s) {
|
|
|
|
stats_.Init();
|
|
|
|
if (s)
|
|
|
|
s->Register(&stats_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Destroy(SizeClassAllocator *allocator, AllocatorGlobalStats *s) {
|
|
|
|
Drain(allocator);
|
|
|
|
if (s)
|
|
|
|
s->Unregister(&stats_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void *Allocate(SizeClassAllocator *allocator, uptr class_id) {
|
|
|
|
CHECK_NE(class_id, 0UL);
|
|
|
|
CHECK_LT(class_id, kNumClasses);
|
|
|
|
PerClass *c = &per_class_[class_id];
|
2017-06-22 08:02:37 +08:00
|
|
|
if (UNLIKELY(c->count == 0)) {
|
|
|
|
if (UNLIKELY(!Refill(allocator, class_id)))
|
|
|
|
return nullptr;
|
|
|
|
}
|
2017-04-14 00:49:16 +08:00
|
|
|
stats_.Add(AllocatorStatAllocated, c->class_size);
|
2016-07-21 06:06:41 +08:00
|
|
|
void *res = c->batch[--c->count];
|
|
|
|
PREFETCH(c->batch[c->count - 1]);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Deallocate(SizeClassAllocator *allocator, uptr class_id, void *p) {
|
|
|
|
CHECK_NE(class_id, 0UL);
|
|
|
|
CHECK_LT(class_id, kNumClasses);
|
|
|
|
// If the first allocator call on a new thread is a deallocation, then
|
|
|
|
// max_count will be zero, leading to check failure.
|
|
|
|
InitCache();
|
|
|
|
PerClass *c = &per_class_[class_id];
|
2017-04-14 00:49:16 +08:00
|
|
|
stats_.Sub(AllocatorStatAllocated, c->class_size);
|
2016-07-21 06:06:41 +08:00
|
|
|
CHECK_NE(c->max_count, 0UL);
|
|
|
|
if (UNLIKELY(c->count == c->max_count))
|
|
|
|
Drain(allocator, class_id);
|
|
|
|
c->batch[c->count++] = p;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Drain(SizeClassAllocator *allocator) {
|
|
|
|
for (uptr class_id = 0; class_id < kNumClasses; class_id++) {
|
|
|
|
PerClass *c = &per_class_[class_id];
|
|
|
|
while (c->count > 0)
|
|
|
|
Drain(allocator, class_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// private:
|
|
|
|
typedef typename SizeClassAllocator::SizeClassMapT SizeClassMap;
|
|
|
|
struct PerClass {
|
|
|
|
uptr count;
|
|
|
|
uptr max_count;
|
2017-04-14 00:49:16 +08:00
|
|
|
uptr class_size;
|
2017-04-24 22:53:38 +08:00
|
|
|
uptr class_id_for_transfer_batch;
|
2016-08-10 07:30:22 +08:00
|
|
|
void *batch[2 * TransferBatch::kMaxNumCached];
|
2016-07-21 06:06:41 +08:00
|
|
|
};
|
|
|
|
PerClass per_class_[kNumClasses];
|
|
|
|
AllocatorStats stats_;
|
|
|
|
|
|
|
|
void InitCache() {
|
|
|
|
if (per_class_[1].max_count)
|
|
|
|
return;
|
2017-04-24 22:53:38 +08:00
|
|
|
// TransferBatch class is declared in SizeClassAllocator.
|
|
|
|
uptr class_id_for_transfer_batch =
|
|
|
|
SizeClassMap::ClassID(sizeof(TransferBatch));
|
2016-07-21 06:06:41 +08:00
|
|
|
for (uptr i = 0; i < kNumClasses; i++) {
|
|
|
|
PerClass *c = &per_class_[i];
|
2017-04-24 22:53:38 +08:00
|
|
|
uptr max_cached = TransferBatch::MaxCached(i);
|
|
|
|
c->max_count = 2 * max_cached;
|
2017-04-14 00:49:16 +08:00
|
|
|
c->class_size = Allocator::ClassIdToSize(i);
|
2017-04-24 22:53:38 +08:00
|
|
|
// We transfer chunks between central and thread-local free lists in
|
|
|
|
// batches. For small size classes we allocate batches separately. For
|
|
|
|
// large size classes we may use one of the chunks to store the batch.
|
|
|
|
// sizeof(TransferBatch) must be a power of 2 for more efficient
|
|
|
|
// allocation.
|
|
|
|
c->class_id_for_transfer_batch = (c->class_size <
|
|
|
|
TransferBatch::AllocationSizeRequiredForNElements(max_cached)) ?
|
|
|
|
class_id_for_transfer_batch : 0;
|
2016-07-21 06:06:41 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-06 09:24:11 +08:00
|
|
|
// Returns a TransferBatch suitable for class_id.
|
2016-07-22 02:47:53 +08:00
|
|
|
// For small size classes allocates the batch from the allocator.
|
|
|
|
// For large size classes simply returns b.
|
2016-08-06 09:24:11 +08:00
|
|
|
TransferBatch *CreateBatch(uptr class_id, SizeClassAllocator *allocator,
|
|
|
|
TransferBatch *b) {
|
2017-04-24 22:53:38 +08:00
|
|
|
if (uptr batch_class_id = per_class_[class_id].class_id_for_transfer_batch)
|
2016-08-06 09:24:11 +08:00
|
|
|
return (TransferBatch*)Allocate(allocator, batch_class_id);
|
2016-07-22 02:47:53 +08:00
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
2016-08-06 09:24:11 +08:00
|
|
|
// Destroys TransferBatch b.
|
2016-07-22 02:47:53 +08:00
|
|
|
// For small size classes deallocates b to the allocator.
|
|
|
|
// Does notthing for large size classes.
|
2016-08-06 09:24:11 +08:00
|
|
|
void DestroyBatch(uptr class_id, SizeClassAllocator *allocator,
|
|
|
|
TransferBatch *b) {
|
2017-04-24 22:53:38 +08:00
|
|
|
if (uptr batch_class_id = per_class_[class_id].class_id_for_transfer_batch)
|
2016-07-22 09:13:13 +08:00
|
|
|
Deallocate(allocator, batch_class_id, b);
|
2016-07-22 02:47:53 +08:00
|
|
|
}
|
|
|
|
|
2017-06-22 08:02:37 +08:00
|
|
|
NOINLINE bool Refill(SizeClassAllocator *allocator, uptr class_id) {
|
2016-07-21 06:06:41 +08:00
|
|
|
InitCache();
|
|
|
|
PerClass *c = &per_class_[class_id];
|
2016-08-06 09:24:11 +08:00
|
|
|
TransferBatch *b = allocator->AllocateBatch(&stats_, this, class_id);
|
2017-06-22 08:02:37 +08:00
|
|
|
if (UNLIKELY(!b))
|
|
|
|
return false;
|
2016-08-03 08:14:10 +08:00
|
|
|
CHECK_GT(b->Count(), 0);
|
2016-08-10 04:54:50 +08:00
|
|
|
b->CopyToArray(c->batch);
|
2016-08-03 08:14:10 +08:00
|
|
|
c->count = b->Count();
|
2016-07-22 02:47:53 +08:00
|
|
|
DestroyBatch(class_id, allocator, b);
|
2017-06-22 08:02:37 +08:00
|
|
|
return true;
|
2016-07-21 06:06:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
NOINLINE void Drain(SizeClassAllocator *allocator, uptr class_id) {
|
|
|
|
InitCache();
|
|
|
|
PerClass *c = &per_class_[class_id];
|
|
|
|
uptr cnt = Min(c->max_count / 2, c->count);
|
2016-08-03 11:42:55 +08:00
|
|
|
uptr first_idx_to_drain = c->count - cnt;
|
2016-08-06 09:24:11 +08:00
|
|
|
TransferBatch *b = CreateBatch(
|
|
|
|
class_id, allocator, (TransferBatch *)c->batch[first_idx_to_drain]);
|
2017-06-22 08:02:37 +08:00
|
|
|
// Failure to allocate a batch while releasing memory is non recoverable.
|
|
|
|
// TODO(alekseys): Figure out how to do it without allocating a new batch.
|
|
|
|
if (UNLIKELY(!b))
|
|
|
|
DieOnFailure::OnOOM();
|
2016-08-10 07:30:22 +08:00
|
|
|
b->SetFromArray(allocator->GetRegionBeginBySizeClass(class_id),
|
|
|
|
&c->batch[first_idx_to_drain], cnt);
|
2016-07-21 06:06:41 +08:00
|
|
|
c->count -= cnt;
|
|
|
|
allocator->DeallocateBatch(&stats_, class_id, b);
|
|
|
|
}
|
|
|
|
};
|
2016-08-23 08:30:43 +08:00
|
|
|
|