[compiler-rt] [scudo] Add unmap and eviction stats to Secondary (PR #210465)

Sadaf Ebrahimi via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 22 12:49:50 PDT 2026


https://github.com/sadafebrahimi updated https://github.com/llvm/llvm-project/pull/210465

>From 8f9def7e9a15f525c885cfe3fa3d1c4886787fff Mon Sep 17 00:00:00 2001
From: Sadaf Ebrahimi <sadafebrahimi at google.com>
Date: Fri, 17 Jul 2026 23:48:14 +0000
Subject: [PATCH] [scudo] Add unmap and eviction stats to Secondary

Track and print stats on the number of memory unmaps and cache evictions
in the MapAllocator. These statistics track total unmaps, as well as
unmaps triggered by exceeding the maximum cache entry size or the
maximum number of entries in the cache.
---
 compiler-rt/lib/scudo/standalone/secondary.h  | 23 ++++++++++++-------
 .../scudo/standalone/tests/secondary_test.cpp | 12 ++++++++++
 2 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/compiler-rt/lib/scudo/standalone/secondary.h b/compiler-rt/lib/scudo/standalone/secondary.h
index b4a5685080b03..1812188d91000 100644
--- a/compiler-rt/lib/scudo/standalone/secondary.h
+++ b/compiler-rt/lib/scudo/standalone/secondary.h
@@ -226,11 +226,12 @@ class MapAllocatorCache {
     const s32 Interval = atomic_load_relaxed(&ReleaseToOsIntervalMs);
     Str->append("Stats: MapAllocatorCache: EntriesCount: %zu, "
                 "MaxEntriesCount: %u, MaxEntrySize: %zu, ReleaseToOsSkips: "
-                "%zu, ReleaseToOsIntervalMs = %d\n",
+                "%zu, ReleaseToOsIntervalMs = %d, Unmapped (exceeded "
+                "DefaultMaxEntriesCount): %u, ",
                 LRUEntries.size(), atomic_load_relaxed(&MaxEntriesCount),
                 atomic_load_relaxed(&MaxEntrySize),
                 atomic_load_relaxed(&ReleaseToOsSkips),
-                Interval >= 0 ? Interval : -1);
+                Interval >= 0 ? Interval : -1, EvictedCount);
     Str->append("Stats: CacheRetrievalStats: SuccessRate: %u/%u "
                 "(%zu.%02zu%%)\n",
                 SuccessfulRetrieves, CallsToRetrieve, Integral, Fractional);
@@ -366,6 +367,7 @@ class MapAllocatorCache {
       while (LRUEntries.size() >= atomic_load_relaxed(&MaxEntriesCount)) {
         // Save MemMaps of evicted entries to perform unmap outside of lock
         CachedBlock *Entry = LRUEntries.back();
+        EvictedCount++;
         EvictionMemMaps.push_back(Entry->MemMap);
         remove(Entry);
       }
@@ -653,6 +655,7 @@ class MapAllocatorCache {
   atomic_s32 ReleaseToOsIntervalMs = {};
   u32 CallsToRetrieve GUARDED_BY(Mutex) = 0;
   u32 SuccessfulRetrieves GUARDED_BY(Mutex) = 0;
+  u32 EvictedCount GUARDED_BY(Mutex) = 0;
   atomic_uptr ReleaseToOsSkips = {};
 
   CachedBlock Entries[Config::getEntriesArraySize()] GUARDED_BY(Mutex) = {};
@@ -749,6 +752,7 @@ template <typename Config> class MapAllocator {
   uptr FreedBytes GUARDED_BY(Mutex) = 0;
   uptr FragmentedBytes GUARDED_BY(Mutex) = 0;
   uptr LargestSize GUARDED_BY(Mutex) = 0;
+  u32 UncacheableUnmaps GUARDED_BY(Mutex) = 0;
   u32 NumberOfAllocs GUARDED_BY(Mutex) = 0;
   u32 NumberOfFrees GUARDED_BY(Mutex) = 0;
   LocalStats Stats GUARDED_BY(Mutex);
@@ -957,6 +961,8 @@ void MapAllocator<Config>::deallocate(const Options &Options, void *Ptr)
     // Note that the `H->MemMap` is stored on the pages managed by itself. Take
     // over the ownership before unmap() so that any operation along with
     // unmap() won't touch inaccessible pages.
+    ScopedLock L(Mutex);
+    UncacheableUnmaps++;
     MemMapT MemMap = H->MemMap;
     unmap(MemMap);
   }
@@ -965,12 +971,13 @@ void MapAllocator<Config>::deallocate(const Options &Options, void *Ptr)
 template <typename Config>
 void MapAllocator<Config>::getStats(ScopedString *Str) EXCLUDES(Mutex) {
   ScopedLock L(Mutex);
-  Str->append("Stats: MapAllocator: allocated %u times (%zuK), freed %u times "
-              "(%zuK), remains %u (%zuK) max %zuM, Fragmented %zuK\n",
-              NumberOfAllocs, AllocatedBytes >> 10, NumberOfFrees,
-              FreedBytes >> 10, NumberOfAllocs - NumberOfFrees,
-              (AllocatedBytes - FreedBytes) >> 10, LargestSize >> 20,
-              FragmentedBytes >> 10);
+  Str->append(
+      "Stats: MapAllocator: allocated %u times (%zuK), freed %u times "
+      "(%zuK), remains %u (%zuK) max %zuM, Fragmented %zuK, Uncacheable unmaps "
+      "(exceeded MaxEntrySize): %u\n",
+      NumberOfAllocs, AllocatedBytes >> 10, NumberOfFrees, FreedBytes >> 10,
+      NumberOfAllocs - NumberOfFrees, (AllocatedBytes - FreedBytes) >> 10,
+      LargestSize >> 20, FragmentedBytes >> 10, UncacheableUnmaps);
   Cache.getStats(Str);
 }
 
diff --git a/compiler-rt/lib/scudo/standalone/tests/secondary_test.cpp b/compiler-rt/lib/scudo/standalone/tests/secondary_test.cpp
index e6ceb2d8b9317..5dad7bffd86bc 100644
--- a/compiler-rt/lib/scudo/standalone/tests/secondary_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/secondary_test.cpp
@@ -314,6 +314,13 @@ void testGetMappedSize(scudo::uptr Size, scudo::uptr *mapped,
 
   Info.Allocator->deallocate(Info.Options, Ptr);
 
+  // Cache is disabled therefore every deallocation is guaranteed to bypass the
+  // cache and increment UncacheableUnmaps.
+  scudo::ScopedString Str;
+  Info.Allocator->getStats(&Str);
+  EXPECT_NE(strstr(Str.data(), "Uncacheable unmaps (exceeded MaxEntrySize): 1"),
+            nullptr);
+
   *guard_page_size = Info.Allocator->getGuardPageSize();
 }
 
@@ -478,6 +485,11 @@ TEST(ScudoSecondaryTest, AllocatorCacheMemoryLeakTest) {
   // Evicted entry should be marked due to unmap callback
   EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[0].getBase()),
             UnmappedMarker);
+  scudo::ScopedString Str;
+  Info.Cache->getStats(&Str);
+  Str.output();
+  EXPECT_NE(strstr(Str.data(), "Unmapped (exceeded DefaultMaxEntriesCount): 1"),
+            nullptr);
 }
 
 TEST(ScudoSecondaryTest, AllocatorCacheOptions) {



More information about the llvm-commits mailing list