xref: /openbsd-src/gnu/llvm/compiler-rt/lib/scudo/standalone/tests/tsd_test.cpp (revision 810390e339a5425391477d5d41c78d7cab2424ac)
13cab2bb3Spatrick //===-- tsd_test.cpp --------------------------------------------*- C++ -*-===//
23cab2bb3Spatrick //
33cab2bb3Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43cab2bb3Spatrick // See https://llvm.org/LICENSE.txt for license information.
53cab2bb3Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63cab2bb3Spatrick //
73cab2bb3Spatrick //===----------------------------------------------------------------------===//
83cab2bb3Spatrick 
93cab2bb3Spatrick #include "tests/scudo_unit_test.h"
103cab2bb3Spatrick 
113cab2bb3Spatrick #include "tsd_exclusive.h"
123cab2bb3Spatrick #include "tsd_shared.h"
133cab2bb3Spatrick 
14d89ec533Spatrick #include <stdlib.h>
15d89ec533Spatrick 
163cab2bb3Spatrick #include <condition_variable>
173cab2bb3Spatrick #include <mutex>
18d89ec533Spatrick #include <set>
193cab2bb3Spatrick #include <thread>
203cab2bb3Spatrick 
213cab2bb3Spatrick // We mock out an allocator with a TSD registry, mostly using empty stubs. The
223cab2bb3Spatrick // cache contains a single volatile uptr, to be able to test that several
233cab2bb3Spatrick // concurrent threads will not access or modify the same cache at the same time.
243cab2bb3Spatrick template <class Config> class MockAllocator {
253cab2bb3Spatrick public:
263cab2bb3Spatrick   using ThisT = MockAllocator<Config>;
273cab2bb3Spatrick   using TSDRegistryT = typename Config::template TSDRegistryT<ThisT>;
28*810390e3Srobert   using CacheT = struct MockCache {
29*810390e3Srobert     volatile scudo::uptr Canary;
30*810390e3Srobert   };
313cab2bb3Spatrick   using QuarantineCacheT = struct MockQuarantine {};
323cab2bb3Spatrick 
init()33d89ec533Spatrick   void init() {
343cab2bb3Spatrick     // This should only be called once by the registry.
353cab2bb3Spatrick     EXPECT_FALSE(Initialized);
363cab2bb3Spatrick     Initialized = true;
373cab2bb3Spatrick   }
383cab2bb3Spatrick 
unmapTestOnly()39d89ec533Spatrick   void unmapTestOnly() { TSDRegistry.unmapTestOnly(this); }
initCache(CacheT * Cache)40d89ec533Spatrick   void initCache(CacheT *Cache) { *Cache = {}; }
commitBack(scudo::TSD<MockAllocator> * TSD)413cab2bb3Spatrick   void commitBack(scudo::TSD<MockAllocator> *TSD) {}
getTSDRegistry()423cab2bb3Spatrick   TSDRegistryT *getTSDRegistry() { return &TSDRegistry; }
callPostInitCallback()433cab2bb3Spatrick   void callPostInitCallback() {}
443cab2bb3Spatrick 
isInitialized()453cab2bb3Spatrick   bool isInitialized() { return Initialized; }
463cab2bb3Spatrick 
operator new(size_t Size)47d89ec533Spatrick   void *operator new(size_t Size) {
48d89ec533Spatrick     void *P = nullptr;
49d89ec533Spatrick     EXPECT_EQ(0, posix_memalign(&P, alignof(ThisT), Size));
50d89ec533Spatrick     return P;
51d89ec533Spatrick   }
operator delete(void * P)52d89ec533Spatrick   void operator delete(void *P) { free(P); }
53d89ec533Spatrick 
543cab2bb3Spatrick private:
55d89ec533Spatrick   bool Initialized = false;
563cab2bb3Spatrick   TSDRegistryT TSDRegistry;
573cab2bb3Spatrick };
583cab2bb3Spatrick 
593cab2bb3Spatrick struct OneCache {
603cab2bb3Spatrick   template <class Allocator>
61d89ec533Spatrick   using TSDRegistryT = scudo::TSDRegistrySharedT<Allocator, 1U, 1U>;
623cab2bb3Spatrick };
633cab2bb3Spatrick 
643cab2bb3Spatrick struct SharedCaches {
653cab2bb3Spatrick   template <class Allocator>
66d89ec533Spatrick   using TSDRegistryT = scudo::TSDRegistrySharedT<Allocator, 16U, 8U>;
673cab2bb3Spatrick };
683cab2bb3Spatrick 
693cab2bb3Spatrick struct ExclusiveCaches {
703cab2bb3Spatrick   template <class Allocator>
713cab2bb3Spatrick   using TSDRegistryT = scudo::TSDRegistryExT<Allocator>;
723cab2bb3Spatrick };
733cab2bb3Spatrick 
TEST(ScudoTSDTest,TSDRegistryInit)743cab2bb3Spatrick TEST(ScudoTSDTest, TSDRegistryInit) {
753cab2bb3Spatrick   using AllocatorT = MockAllocator<OneCache>;
763cab2bb3Spatrick   auto Deleter = [](AllocatorT *A) {
773cab2bb3Spatrick     A->unmapTestOnly();
783cab2bb3Spatrick     delete A;
793cab2bb3Spatrick   };
803cab2bb3Spatrick   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
813cab2bb3Spatrick                                                            Deleter);
823cab2bb3Spatrick   EXPECT_FALSE(Allocator->isInitialized());
833cab2bb3Spatrick 
843cab2bb3Spatrick   auto Registry = Allocator->getTSDRegistry();
85d89ec533Spatrick   Registry->init(Allocator.get());
863cab2bb3Spatrick   EXPECT_TRUE(Allocator->isInitialized());
873cab2bb3Spatrick }
883cab2bb3Spatrick 
testRegistry()893cab2bb3Spatrick template <class AllocatorT> static void testRegistry() {
903cab2bb3Spatrick   auto Deleter = [](AllocatorT *A) {
913cab2bb3Spatrick     A->unmapTestOnly();
923cab2bb3Spatrick     delete A;
933cab2bb3Spatrick   };
943cab2bb3Spatrick   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
953cab2bb3Spatrick                                                            Deleter);
963cab2bb3Spatrick   EXPECT_FALSE(Allocator->isInitialized());
973cab2bb3Spatrick 
983cab2bb3Spatrick   auto Registry = Allocator->getTSDRegistry();
993cab2bb3Spatrick   Registry->initThreadMaybe(Allocator.get(), /*MinimalInit=*/true);
1003cab2bb3Spatrick   EXPECT_TRUE(Allocator->isInitialized());
1013cab2bb3Spatrick 
1023cab2bb3Spatrick   bool UnlockRequired;
1033cab2bb3Spatrick   auto TSD = Registry->getTSDAndLock(&UnlockRequired);
1043cab2bb3Spatrick   EXPECT_NE(TSD, nullptr);
1053cab2bb3Spatrick   EXPECT_EQ(TSD->Cache.Canary, 0U);
1063cab2bb3Spatrick   if (UnlockRequired)
1073cab2bb3Spatrick     TSD->unlock();
1083cab2bb3Spatrick 
1093cab2bb3Spatrick   Registry->initThreadMaybe(Allocator.get(), /*MinimalInit=*/false);
1103cab2bb3Spatrick   TSD = Registry->getTSDAndLock(&UnlockRequired);
1113cab2bb3Spatrick   EXPECT_NE(TSD, nullptr);
1123cab2bb3Spatrick   EXPECT_EQ(TSD->Cache.Canary, 0U);
1133cab2bb3Spatrick   memset(&TSD->Cache, 0x42, sizeof(TSD->Cache));
1143cab2bb3Spatrick   if (UnlockRequired)
1153cab2bb3Spatrick     TSD->unlock();
1163cab2bb3Spatrick }
1173cab2bb3Spatrick 
TEST(ScudoTSDTest,TSDRegistryBasic)1183cab2bb3Spatrick TEST(ScudoTSDTest, TSDRegistryBasic) {
1193cab2bb3Spatrick   testRegistry<MockAllocator<OneCache>>();
1203cab2bb3Spatrick   testRegistry<MockAllocator<SharedCaches>>();
1213cab2bb3Spatrick #if !SCUDO_FUCHSIA
1223cab2bb3Spatrick   testRegistry<MockAllocator<ExclusiveCaches>>();
1233cab2bb3Spatrick #endif
1243cab2bb3Spatrick }
1253cab2bb3Spatrick 
1263cab2bb3Spatrick static std::mutex Mutex;
1273cab2bb3Spatrick static std::condition_variable Cv;
128d89ec533Spatrick static bool Ready;
1293cab2bb3Spatrick 
stressCache(AllocatorT * Allocator)1303cab2bb3Spatrick template <typename AllocatorT> static void stressCache(AllocatorT *Allocator) {
1313cab2bb3Spatrick   auto Registry = Allocator->getTSDRegistry();
1323cab2bb3Spatrick   {
1333cab2bb3Spatrick     std::unique_lock<std::mutex> Lock(Mutex);
1343cab2bb3Spatrick     while (!Ready)
1353cab2bb3Spatrick       Cv.wait(Lock);
1363cab2bb3Spatrick   }
1373cab2bb3Spatrick   Registry->initThreadMaybe(Allocator, /*MinimalInit=*/false);
1383cab2bb3Spatrick   bool UnlockRequired;
1393cab2bb3Spatrick   auto TSD = Registry->getTSDAndLock(&UnlockRequired);
1403cab2bb3Spatrick   EXPECT_NE(TSD, nullptr);
1413cab2bb3Spatrick   // For an exclusive TSD, the cache should be empty. We cannot guarantee the
1423cab2bb3Spatrick   // same for a shared TSD.
1433cab2bb3Spatrick   if (!UnlockRequired)
1443cab2bb3Spatrick     EXPECT_EQ(TSD->Cache.Canary, 0U);
1453cab2bb3Spatrick   // Transform the thread id to a uptr to use it as canary.
1463cab2bb3Spatrick   const scudo::uptr Canary = static_cast<scudo::uptr>(
1473cab2bb3Spatrick       std::hash<std::thread::id>{}(std::this_thread::get_id()));
1483cab2bb3Spatrick   TSD->Cache.Canary = Canary;
1493cab2bb3Spatrick   // Loop a few times to make sure that a concurrent thread isn't modifying it.
1503cab2bb3Spatrick   for (scudo::uptr I = 0; I < 4096U; I++)
1513cab2bb3Spatrick     EXPECT_EQ(TSD->Cache.Canary, Canary);
1523cab2bb3Spatrick   if (UnlockRequired)
1533cab2bb3Spatrick     TSD->unlock();
1543cab2bb3Spatrick }
1553cab2bb3Spatrick 
testRegistryThreaded()1563cab2bb3Spatrick template <class AllocatorT> static void testRegistryThreaded() {
157d89ec533Spatrick   Ready = false;
1583cab2bb3Spatrick   auto Deleter = [](AllocatorT *A) {
1593cab2bb3Spatrick     A->unmapTestOnly();
1603cab2bb3Spatrick     delete A;
1613cab2bb3Spatrick   };
1623cab2bb3Spatrick   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
1633cab2bb3Spatrick                                                            Deleter);
1643cab2bb3Spatrick   std::thread Threads[32];
1653cab2bb3Spatrick   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
1663cab2bb3Spatrick     Threads[I] = std::thread(stressCache<AllocatorT>, Allocator.get());
1673cab2bb3Spatrick   {
1683cab2bb3Spatrick     std::unique_lock<std::mutex> Lock(Mutex);
1693cab2bb3Spatrick     Ready = true;
1703cab2bb3Spatrick     Cv.notify_all();
1713cab2bb3Spatrick   }
1723cab2bb3Spatrick   for (auto &T : Threads)
1733cab2bb3Spatrick     T.join();
1743cab2bb3Spatrick }
1753cab2bb3Spatrick 
TEST(ScudoTSDTest,TSDRegistryThreaded)1763cab2bb3Spatrick TEST(ScudoTSDTest, TSDRegistryThreaded) {
1773cab2bb3Spatrick   testRegistryThreaded<MockAllocator<OneCache>>();
1783cab2bb3Spatrick   testRegistryThreaded<MockAllocator<SharedCaches>>();
1793cab2bb3Spatrick #if !SCUDO_FUCHSIA
1803cab2bb3Spatrick   testRegistryThreaded<MockAllocator<ExclusiveCaches>>();
1813cab2bb3Spatrick #endif
1823cab2bb3Spatrick }
183d89ec533Spatrick 
184d89ec533Spatrick static std::set<void *> Pointers;
185d89ec533Spatrick 
stressSharedRegistry(MockAllocator<SharedCaches> * Allocator)186d89ec533Spatrick static void stressSharedRegistry(MockAllocator<SharedCaches> *Allocator) {
187d89ec533Spatrick   std::set<void *> Set;
188d89ec533Spatrick   auto Registry = Allocator->getTSDRegistry();
189d89ec533Spatrick   {
190d89ec533Spatrick     std::unique_lock<std::mutex> Lock(Mutex);
191d89ec533Spatrick     while (!Ready)
192d89ec533Spatrick       Cv.wait(Lock);
193d89ec533Spatrick   }
194d89ec533Spatrick   Registry->initThreadMaybe(Allocator, /*MinimalInit=*/false);
195d89ec533Spatrick   bool UnlockRequired;
196d89ec533Spatrick   for (scudo::uptr I = 0; I < 4096U; I++) {
197d89ec533Spatrick     auto TSD = Registry->getTSDAndLock(&UnlockRequired);
198d89ec533Spatrick     EXPECT_NE(TSD, nullptr);
199d89ec533Spatrick     Set.insert(reinterpret_cast<void *>(TSD));
200d89ec533Spatrick     if (UnlockRequired)
201d89ec533Spatrick       TSD->unlock();
202d89ec533Spatrick   }
203d89ec533Spatrick   {
204d89ec533Spatrick     std::unique_lock<std::mutex> Lock(Mutex);
205d89ec533Spatrick     Pointers.insert(Set.begin(), Set.end());
206d89ec533Spatrick   }
207d89ec533Spatrick }
208d89ec533Spatrick 
TEST(ScudoTSDTest,TSDRegistryTSDsCount)209d89ec533Spatrick TEST(ScudoTSDTest, TSDRegistryTSDsCount) {
210d89ec533Spatrick   Ready = false;
211d89ec533Spatrick   Pointers.clear();
212d89ec533Spatrick   using AllocatorT = MockAllocator<SharedCaches>;
213d89ec533Spatrick   auto Deleter = [](AllocatorT *A) {
214d89ec533Spatrick     A->unmapTestOnly();
215d89ec533Spatrick     delete A;
216d89ec533Spatrick   };
217d89ec533Spatrick   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
218d89ec533Spatrick                                                            Deleter);
219d89ec533Spatrick   // We attempt to use as many TSDs as the shared cache offers by creating a
220d89ec533Spatrick   // decent amount of threads that will be run concurrently and attempt to get
221d89ec533Spatrick   // and lock TSDs. We put them all in a set and count the number of entries
222d89ec533Spatrick   // after we are done.
223d89ec533Spatrick   std::thread Threads[32];
224d89ec533Spatrick   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
225d89ec533Spatrick     Threads[I] = std::thread(stressSharedRegistry, Allocator.get());
226d89ec533Spatrick   {
227d89ec533Spatrick     std::unique_lock<std::mutex> Lock(Mutex);
228d89ec533Spatrick     Ready = true;
229d89ec533Spatrick     Cv.notify_all();
230d89ec533Spatrick   }
231d89ec533Spatrick   for (auto &T : Threads)
232d89ec533Spatrick     T.join();
233d89ec533Spatrick   // The initial number of TSDs we get will be the minimum of the default count
234d89ec533Spatrick   // and the number of CPUs.
235d89ec533Spatrick   EXPECT_LE(Pointers.size(), 8U);
236d89ec533Spatrick   Pointers.clear();
237d89ec533Spatrick   auto Registry = Allocator->getTSDRegistry();
238d89ec533Spatrick   // Increase the number of TSDs to 16.
239d89ec533Spatrick   Registry->setOption(scudo::Option::MaxTSDsCount, 16);
240d89ec533Spatrick   Ready = false;
241d89ec533Spatrick   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
242d89ec533Spatrick     Threads[I] = std::thread(stressSharedRegistry, Allocator.get());
243d89ec533Spatrick   {
244d89ec533Spatrick     std::unique_lock<std::mutex> Lock(Mutex);
245d89ec533Spatrick     Ready = true;
246d89ec533Spatrick     Cv.notify_all();
247d89ec533Spatrick   }
248d89ec533Spatrick   for (auto &T : Threads)
249d89ec533Spatrick     T.join();
250d89ec533Spatrick   // We should get 16 distinct TSDs back.
251d89ec533Spatrick   EXPECT_EQ(Pointers.size(), 16U);
252d89ec533Spatrick }
253