[compiler-rt] [scudo] Added test fixture for cache tests. (PR #102230)

Joshua Baehring via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 6 15:51:34 PDT 2024


https://github.com/JoshuaMBa updated https://github.com/llvm/llvm-project/pull/102230

>From 03ef6f1ee6cac27c846083b77c0f338ac4c4b4e8 Mon Sep 17 00:00:00 2001
From: Joshua Baehring <jmbaehring at google.com>
Date: Tue, 6 Aug 2024 21:20:35 +0000
Subject: [PATCH] [scudo] Added test fixture for cache tests.

The test fixture simplifies some of the logic for
allocations and mmap-based allocations are separated
from the cache to allow for more direct cache tests.
Additionally, a couple of end to end tests for the
cache and the LRU algorithm are added.
---
 compiler-rt/lib/scudo/standalone/secondary.h  | 15 ++-
 .../scudo/standalone/tests/secondary_test.cpp | 98 +++++++++++++++++++
 2 files changed, 108 insertions(+), 5 deletions(-)

diff --git a/compiler-rt/lib/scudo/standalone/secondary.h b/compiler-rt/lib/scudo/standalone/secondary.h
index df7d1e63b9562..3cd8c7cc846c1 100644
--- a/compiler-rt/lib/scudo/standalone/secondary.h
+++ b/compiler-rt/lib/scudo/standalone/secondary.h
@@ -177,7 +177,11 @@ template <typename T> class NonZeroLengthArray<T, 0> {
   T &operator[](uptr UNUSED Idx) { UNREACHABLE("Unsupported!"); }
 };
 
-template <typename Config> class MapAllocatorCache {
+// The default unmap callback is simply scudo::unmap.
+// In testing, a different unmap callback is used to
+// record information about unmaps in the cache
+template <typename Config, void (*unmapCallBack)(MemMapT &) = unmap>
+class MapAllocatorCache {
 public:
   void getStats(ScopedString *Str) {
     ScopedLock L(Mutex);
@@ -245,6 +249,7 @@ template <typename Config> class MapAllocatorCache {
     const s32 Interval = atomic_load_relaxed(&ReleaseToOsIntervalMs);
     u64 Time;
     CachedBlock Entry;
+
     Entry.CommitBase = CommitBase;
     Entry.CommitSize = CommitSize;
     Entry.BlockBegin = BlockBegin;
@@ -289,7 +294,7 @@ template <typename Config> class MapAllocatorCache {
         // read Options and when we locked Mutex. We can't insert our entry into
         // the quarantine or the cache because the permissions would be wrong so
         // just unmap it.
-        unmap(Entry.MemMap);
+        unmapCallBack(Entry.MemMap);
         break;
       }
       if (Config::getQuarantineSize() && useMemoryTagging<Config>(Options)) {
@@ -320,7 +325,7 @@ template <typename Config> class MapAllocatorCache {
     } while (0);
 
     for (MemMapT &EvictMemMap : EvictionMemMaps)
-      unmap(EvictMemMap);
+      unmapCallBack(EvictMemMap);
 
     if (Interval >= 0) {
       // TODO: Add ReleaseToOS logic to LRU algorithm
@@ -422,7 +427,7 @@ template <typename Config> class MapAllocatorCache {
     for (u32 I = 0; I != Config::getQuarantineSize(); ++I) {
       if (Quarantine[I].isValid()) {
         MemMapT &MemMap = Quarantine[I].MemMap;
-        unmap(MemMap);
+        unmapCallBack(MemMap);
         Quarantine[I].invalidate();
       }
     }
@@ -516,7 +521,7 @@ template <typename Config> class MapAllocatorCache {
     }
     for (uptr I = 0; I < N; I++) {
       MemMapT &MemMap = MapInfo[I];
-      unmap(MemMap);
+      unmapCallBack(MemMap);
     }
   }
 
diff --git a/compiler-rt/lib/scudo/standalone/tests/secondary_test.cpp b/compiler-rt/lib/scudo/standalone/tests/secondary_test.cpp
index 5685a9335316d..0fd1f505afda8 100644
--- a/compiler-rt/lib/scudo/standalone/tests/secondary_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/secondary_test.cpp
@@ -265,3 +265,101 @@ TEST_F(MapAllocatorWithReleaseTest, SecondaryThreadsRace) {
   Allocator->getStats(&Str);
   Str.output();
 }
+
+struct MapAllocatorCacheTest : public Test {
+  static constexpr scudo::u32 UnmappedMarker = 0xDEADBEEF;
+
+  static void testUnmapCallback(scudo::MemMapT &MemMap) {
+    scudo::u32 *Ptr = reinterpret_cast<scudo::u32 *>(MemMap.getBase());
+    *Ptr = UnmappedMarker;
+  }
+
+  using SecondaryConfig = scudo::SecondaryConfig<TestConfig>;
+  using CacheConfig = SecondaryConfig::CacheConfig;
+  using CacheT = scudo::MapAllocatorCache<CacheConfig, testUnmapCallback>;
+
+  std::unique_ptr<CacheT> Cache = std::make_unique<CacheT>();
+
+  const scudo::uptr PageSize = scudo::getPageSizeCached();
+  // The current test allocation size is set to the minimum size
+  // needed for the scudo allocator to fall back to the secondary allocator
+  static constexpr scudo::uptr TestAllocSize =
+      CacheConfig::getDefaultMaxEntrySize();
+
+  scudo::Options Options = getOptionsForConfig<SecondaryConfig>();
+
+  void SetUp() override { Cache->init(/*ReleaseToOsInterval=*/-1); }
+
+  void TearDown() override { Cache->unmapTestOnly(); }
+
+  scudo::MemMapT allocate(scudo::uptr Size) {
+    scudo::uptr MapSize = scudo::roundUp(Size, PageSize);
+    scudo::ReservedMemoryT ReservedMemory;
+    CHECK(ReservedMemory.create(0U, MapSize, nullptr, MAP_ALLOWNOMEM));
+
+    scudo::MemMapT MemMap = ReservedMemory.dispatch(
+        ReservedMemory.getBase(), ReservedMemory.getCapacity());
+    MemMap.remap(MemMap.getBase(), MemMap.getCapacity(), "scudo:test",
+                 MAP_RESIZABLE | MAP_ALLOWNOMEM);
+    return MemMap;
+  }
+
+  void fillCacheWithSameSizeBlocks(std::vector<scudo::MemMapT> &MemMaps,
+                                   scudo::uptr NumEntries, scudo::uptr Size) {
+    for (scudo::uptr I = 0; I < NumEntries; I++) {
+      MemMaps.emplace_back(allocate(Size));
+      scudo::MemMapT &MemMap = MemMaps[I];
+      Cache->store(Options, MemMap.getBase(), MemMap.getCapacity(),
+                   MemMap.getBase(), MemMap);
+    }
+  }
+};
+
+TEST_F(MapAllocatorCacheTest, CacheOrder) {
+  std::vector<scudo::MemMapT> MemMaps;
+  Cache->setOption(scudo::Option::MaxCacheEntriesCount,
+                   CacheConfig::getEntriesArraySize());
+
+  fillCacheWithSameSizeBlocks(MemMaps, CacheConfig::getEntriesArraySize(),
+                              TestAllocSize);
+
+  // Retrieval order should be the inverse of insertion order
+  for (scudo::uptr I = CacheConfig::getEntriesArraySize(); I > 0; I--) {
+    scudo::uptr EntryHeaderPos;
+    scudo::CachedBlock Entry =
+        Cache->retrieve(TestAllocSize, PageSize, 0, EntryHeaderPos);
+    EXPECT_EQ(Entry.MemMap.getBase(), MemMaps[I - 1].getBase());
+  }
+
+  // Clean up MemMaps
+  for (scudo::MemMapT &MemMap : MemMaps)
+    unmap(MemMap);
+}
+
+TEST_F(MapAllocatorCacheTest, MemoryLeakTest) {
+  std::vector<scudo::MemMapT> MemMaps;
+  // Fill the cache above MaxEntriesCount to force an eviction
+  // The first cache entry should be evicted (because it is the oldest)
+  // due to the maximum number of entries being reached
+  fillCacheWithSameSizeBlocks(
+      MemMaps, CacheConfig::getDefaultMaxEntriesCount() + 1, TestAllocSize);
+
+  std::vector<scudo::CachedBlock> RetrievedEntries;
+
+  // First MemMap should be evicted from cache because it was the first
+  // inserted into the cache
+  for (scudo::uptr I = CacheConfig::getDefaultMaxEntriesCount(); I > 0; I--) {
+    scudo::uptr EntryHeaderPos;
+    RetrievedEntries.push_back(
+        Cache->retrieve(TestAllocSize, PageSize, 0, EntryHeaderPos));
+    EXPECT_EQ(MemMaps[I].getBase(), RetrievedEntries.back().MemMap.getBase());
+  }
+
+  // Evicted entry should be marked due to unmap callback
+  EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(MemMaps[0].getBase()),
+            UnmappedMarker);
+
+  // Clean up MemMaps
+  for (scudo::MemMapT &MemMap : MemMaps)
+    unmap(MemMap);
+}



More information about the llvm-commits mailing list