Index: compiler-rt/trunk/lib/asan/asan_allocator.h =================================================================== --- compiler-rt/trunk/lib/asan/asan_allocator.h +++ compiler-rt/trunk/lib/asan/asan_allocator.h @@ -33,6 +33,7 @@ struct AllocatorOptions { u32 quarantine_size_mb; + u32 thread_local_quarantine_size_kb; u16 min_redzone; u16 max_redzone; u8 may_return_null; Index: compiler-rt/trunk/lib/asan/asan_allocator.cc =================================================================== --- compiler-rt/trunk/lib/asan/asan_allocator.cc +++ compiler-rt/trunk/lib/asan/asan_allocator.cc @@ -207,6 +207,7 @@ void AllocatorOptions::SetFrom(const Flags *f, const CommonFlags *cf) { quarantine_size_mb = f->quarantine_size_mb; + thread_local_quarantine_size_kb = f->thread_local_quarantine_size_kb; min_redzone = f->redzone; max_redzone = f->max_redzone; may_return_null = cf->allocator_may_return_null; @@ -216,6 +217,7 @@ void AllocatorOptions::CopyTo(Flags *f, CommonFlags *cf) { f->quarantine_size_mb = quarantine_size_mb; + f->thread_local_quarantine_size_kb = thread_local_quarantine_size_kb; f->redzone = min_redzone; f->max_redzone = max_redzone; cf->allocator_may_return_null = may_return_null; @@ -226,13 +228,6 @@ struct Allocator { static const uptr kMaxAllowedMallocSize = FIRST_32_SECOND_64(3UL << 30, 1ULL << 40); - static const uptr kMaxThreadLocalQuarantine = - // It is not advised to go lower than 64Kb, otherwise quarantine batches - // pushed from thread local quarantine to global one will create too much - // overhead. One quarantine batch size is 8Kb and it holds up to 1021 - // chunk, which amounts to 1/8 memory overhead per batch when thread local - // quarantine is set to 64Kb. - (ASAN_LOW_MEMORY) ? 1 << 16 : FIRST_32_SECOND_64(1 << 18, 1 << 20); AsanAllocator allocator; AsanQuarantine quarantine; @@ -261,7 +256,7 @@ void SharedInitCode(const AllocatorOptions &options) { CheckOptions(options); quarantine.Init((uptr)options.quarantine_size_mb << 20, - kMaxThreadLocalQuarantine); + (uptr)options.thread_local_quarantine_size_kb << 10); atomic_store(&alloc_dealloc_mismatch, options.alloc_dealloc_mismatch, memory_order_release); atomic_store(&min_redzone, options.min_redzone, memory_order_release); @@ -315,6 +310,7 @@ void GetOptions(AllocatorOptions *options) const { options->quarantine_size_mb = quarantine.GetSize() >> 20; + options->thread_local_quarantine_size_kb = quarantine.GetCacheSize() >> 10; options->min_redzone = atomic_load(&min_redzone, memory_order_acquire); options->max_redzone = atomic_load(&max_redzone, memory_order_acquire); options->may_return_null = allocator.MayReturnNull(); Index: compiler-rt/trunk/lib/asan/asan_flags.cc =================================================================== --- compiler-rt/trunk/lib/asan/asan_flags.cc +++ compiler-rt/trunk/lib/asan/asan_flags.cc @@ -159,6 +159,16 @@ (ASAN_LOW_MEMORY) ? 1UL << 4 : 1UL << 8; f->quarantine_size_mb = kDefaultQuarantineSizeMb; } + if (f->thread_local_quarantine_size_kb < 0) { + const u32 kDefaultThreadLocalQuarantineSizeKb = + // It is not advised to go lower than 64Kb, otherwise quarantine batches + // pushed from thread local quarantine to global one will create too + // much overhead. One quarantine batch size is 8Kb and it holds up to + // 1021 chunk, which amounts to 1/8 memory overhead per batch when + // thread local quarantine is set to 64Kb. + (ASAN_LOW_MEMORY) ? 1 << 6 : FIRST_32_SECOND_64(1 << 8, 1 << 10); + f->thread_local_quarantine_size_kb = kDefaultThreadLocalQuarantineSizeKb; + } if (!f->replace_str && common_flags()->intercept_strlen) { Report("WARNING: strlen interceptor is enabled even though replace_str=0. " "Use intercept_strlen=0 to disable it."); Index: compiler-rt/trunk/lib/asan/asan_flags.inc =================================================================== --- compiler-rt/trunk/lib/asan/asan_flags.inc +++ compiler-rt/trunk/lib/asan/asan_flags.inc @@ -23,6 +23,12 @@ "Size (in Mb) of quarantine used to detect use-after-free " "errors. Lower value may reduce memory usage but increase the " "chance of false negatives.") +ASAN_FLAG(int, thread_local_quarantine_size_kb, -1, + "Size (in Kb) of thread local quarantine used to detect " + "use-after-free errors. Lower value may reduce memory usage but " + "increase the chance of false negatives. It is not advised to go " + "lower than 64Kb, otherwise frequent transfers to global quarantine " + "might affect performance.") ASAN_FLAG(int, redzone, 16, "Minimal size (in bytes) of redzones around heap objects. " "Requirement: redzone >= 16, is a power of two.") Index: compiler-rt/trunk/lib/asan/asan_rtl.cc =================================================================== --- compiler-rt/trunk/lib/asan/asan_rtl.cc +++ compiler-rt/trunk/lib/asan/asan_rtl.cc @@ -410,6 +410,8 @@ Printf("redzone=%zu\n", (uptr)flags()->redzone); Printf("max_redzone=%zu\n", (uptr)flags()->max_redzone); Printf("quarantine_size_mb=%zuM\n", (uptr)flags()->quarantine_size_mb); + Printf("thread_local_quarantine_size_kb=%zuK\n", + (uptr)flags()->thread_local_quarantine_size_kb); Printf("malloc_context_size=%zu\n", (uptr)common_flags()->malloc_context_size); Index: compiler-rt/trunk/lib/sanitizer_common/sanitizer_quarantine.h =================================================================== --- compiler-rt/trunk/lib/sanitizer_common/sanitizer_quarantine.h +++ compiler-rt/trunk/lib/sanitizer_common/sanitizer_quarantine.h @@ -56,6 +56,7 @@ } uptr GetSize() const { return atomic_load(&max_size_, memory_order_acquire); } + uptr GetCacheSize() const { return max_cache_size_; } void Put(Cache *c, Callback cb, Node *ptr, uptr size) { c->Enqueue(cb, ptr, size); Index: compiler-rt/trunk/test/asan/TestCases/Linux/thread_local_quarantine_size_kb.cc =================================================================== --- compiler-rt/trunk/test/asan/TestCases/Linux/thread_local_quarantine_size_kb.cc +++ compiler-rt/trunk/test/asan/TestCases/Linux/thread_local_quarantine_size_kb.cc @@ -0,0 +1,40 @@ +// Test thread_local_quarantine_size_kb + +// RUN: %clangxx_asan %s -o %t +// RUN: %env_asan_opts=thread_local_quarantine_size_kb=256:verbosity=1 %run %t 2>&1 | \ +// RUN: FileCheck %s --check-prefix=CHECK-VALUE +// RUN: %env_asan_opts=thread_local_quarantine_size_kb=64:quarantine_size_mb=64 %run %t 2>&1 | \ +// RUN: FileCheck %s --allow-empty --check-prefix=CHECK-SMALL-LOCAL-CACHE-SMALL-OVERHEAD +// RUN: %env_asan_opts=thread_local_quarantine_size_kb=0:quarantine_size_mb=64 %run %t 2>&1 | \ +// RUN: FileCheck %s --check-prefix=CHECK-NO-LOCAL-CACHE-HUGE-OVERHEAD + +#include +#include +#include +#include + +// The idea is allocate a lot of small blocks, totaling 5Mb of user memory +// total, and verify that quarantine does not incur too much memory overhead. +// There's always an overhead for red zones, shadow memory and such, but +// quarantine accounting should not significantly contribute to that. +static const int kNumAllocs = 20000; +static const int kAllocSize = 256; +static const size_t kHeapSizeLimit = 12 << 20; + +int main() { + size_t old_heap_size = __sanitizer_get_heap_size(); + for (int i = 0; i < kNumAllocs; i++) { + char *g = new char[kAllocSize]; + memset(g, -1, kAllocSize); + delete [] (g); + } + size_t new_heap_size = __sanitizer_get_heap_size(); + fprintf(stderr, "heap size: new: %zd old: %zd\n", new_heap_size, + old_heap_size); + if (new_heap_size - old_heap_size > kHeapSizeLimit) + fprintf(stderr, "Heap size limit exceeded"); +} + +// CHECK-VALUE: thread_local_quarantine_size_kb=256K +// CHECK-SMALL-LOCAL-CACHE-SMALL-OVERHEAD-NOT: Heap size limit exceeded +// CHECK-NO-LOCAL-CACHE-HUGE-OVERHEAD: Heap size limit exceeded