• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===-- primary_test.cpp ----------------------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "tests/scudo_unit_test.h"
10 
11 #include "primary32.h"
12 #include "primary64.h"
13 #include "size_class_map.h"
14 
15 #include <condition_variable>
16 #include <mutex>
17 #include <thread>
18 #include <vector>
19 
20 // Note that with small enough regions, the SizeClassAllocator64 also works on
21 // 32-bit architectures. It's not something we want to encourage, but we still
22 // should ensure the tests pass.
23 
testPrimary()24 template <typename Primary> static void testPrimary() {
25   const scudo::uptr NumberOfAllocations = 32U;
26   auto Deleter = [](Primary *P) {
27     P->unmapTestOnly();
28     delete P;
29   };
30   std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter);
31   Allocator->init(/*ReleaseToOsInterval=*/-1);
32   typename Primary::CacheT Cache;
33   Cache.init(nullptr, Allocator.get());
34   for (scudo::uptr I = 0; I <= 16U; I++) {
35     const scudo::uptr Size = 1UL << I;
36     if (!Primary::canAllocate(Size))
37       continue;
38     const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
39     void *Pointers[NumberOfAllocations];
40     for (scudo::uptr J = 0; J < NumberOfAllocations; J++) {
41       void *P = Cache.allocate(ClassId);
42       memset(P, 'B', Size);
43       Pointers[J] = P;
44     }
45     for (scudo::uptr J = 0; J < NumberOfAllocations; J++)
46       Cache.deallocate(ClassId, Pointers[J]);
47   }
48   Cache.destroy(nullptr);
49   Allocator->releaseToOS();
50   scudo::ScopedString Str(1024);
51   Allocator->getStats(&Str);
52   Str.output();
53 }
54 
TEST(ScudoPrimaryTest,BasicPrimary)55 TEST(ScudoPrimaryTest, BasicPrimary) {
56   using SizeClassMap = scudo::DefaultSizeClassMap;
57 #if !SCUDO_FUCHSIA
58   testPrimary<scudo::SizeClassAllocator32<SizeClassMap, 18U>>();
59 #endif
60   testPrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U>>();
61   testPrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U, INT32_MIN,
62                                           INT32_MAX, true>>();
63 }
64 
65 // The 64-bit SizeClassAllocator can be easily OOM'd with small region sizes.
66 // For the 32-bit one, it requires actually exhausting memory, so we skip it.
TEST(ScudoPrimaryTest,Primary64OOM)67 TEST(ScudoPrimaryTest, Primary64OOM) {
68   using Primary = scudo::SizeClassAllocator64<scudo::DefaultSizeClassMap, 20U>;
69   using TransferBatch = Primary::CacheT::TransferBatch;
70   Primary Allocator;
71   Allocator.init(/*ReleaseToOsInterval=*/-1);
72   typename Primary::CacheT Cache;
73   scudo::GlobalStats Stats;
74   Stats.init();
75   Cache.init(&Stats, &Allocator);
76   bool AllocationFailed = false;
77   std::vector<TransferBatch *> Batches;
78   const scudo::uptr ClassId = Primary::SizeClassMap::LargestClassId;
79   const scudo::uptr Size = Primary::getSizeByClassId(ClassId);
80   for (scudo::uptr I = 0; I < 10000U; I++) {
81     TransferBatch *B = Allocator.popBatch(&Cache, ClassId);
82     if (!B) {
83       AllocationFailed = true;
84       break;
85     }
86     for (scudo::u32 J = 0; J < B->getCount(); J++)
87       memset(B->get(J), 'B', Size);
88     Batches.push_back(B);
89   }
90   while (!Batches.empty()) {
91     Allocator.pushBatch(ClassId, Batches.back());
92     Batches.pop_back();
93   }
94   Cache.destroy(nullptr);
95   Allocator.releaseToOS();
96   scudo::ScopedString Str(1024);
97   Allocator.getStats(&Str);
98   Str.output();
99   EXPECT_EQ(AllocationFailed, true);
100   Allocator.unmapTestOnly();
101 }
102 
testIteratePrimary()103 template <typename Primary> static void testIteratePrimary() {
104   auto Deleter = [](Primary *P) {
105     P->unmapTestOnly();
106     delete P;
107   };
108   std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter);
109   Allocator->init(/*ReleaseToOsInterval=*/-1);
110   typename Primary::CacheT Cache;
111   Cache.init(nullptr, Allocator.get());
112   std::vector<std::pair<scudo::uptr, void *>> V;
113   for (scudo::uptr I = 0; I < 64U; I++) {
114     const scudo::uptr Size = std::rand() % Primary::SizeClassMap::MaxSize;
115     const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
116     void *P = Cache.allocate(ClassId);
117     V.push_back(std::make_pair(ClassId, P));
118   }
119   scudo::uptr Found = 0;
120   auto Lambda = [V, &Found](scudo::uptr Block) {
121     for (const auto &Pair : V) {
122       if (Pair.second == reinterpret_cast<void *>(Block))
123         Found++;
124     }
125   };
126   Allocator->disable();
127   Allocator->iterateOverBlocks(Lambda);
128   Allocator->enable();
129   EXPECT_EQ(Found, V.size());
130   while (!V.empty()) {
131     auto Pair = V.back();
132     Cache.deallocate(Pair.first, Pair.second);
133     V.pop_back();
134   }
135   Cache.destroy(nullptr);
136   Allocator->releaseToOS();
137   scudo::ScopedString Str(1024);
138   Allocator->getStats(&Str);
139   Str.output();
140 }
141 
TEST(ScudoPrimaryTest,PrimaryIterate)142 TEST(ScudoPrimaryTest, PrimaryIterate) {
143   using SizeClassMap = scudo::DefaultSizeClassMap;
144 #if !SCUDO_FUCHSIA
145   testIteratePrimary<scudo::SizeClassAllocator32<SizeClassMap, 18U>>();
146 #endif
147   testIteratePrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U>>();
148   testIteratePrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U, INT32_MIN,
149                                                  INT32_MAX, true>>();
150 }
151 
152 static std::mutex Mutex;
153 static std::condition_variable Cv;
154 static bool Ready;
155 
performAllocations(Primary * Allocator)156 template <typename Primary> static void performAllocations(Primary *Allocator) {
157   static thread_local typename Primary::CacheT Cache;
158   Cache.init(nullptr, Allocator);
159   std::vector<std::pair<scudo::uptr, void *>> V;
160   {
161     std::unique_lock<std::mutex> Lock(Mutex);
162     while (!Ready)
163       Cv.wait(Lock);
164   }
165   for (scudo::uptr I = 0; I < 256U; I++) {
166     const scudo::uptr Size = std::rand() % Primary::SizeClassMap::MaxSize / 4;
167     const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
168     void *P = Cache.allocate(ClassId);
169     if (P)
170       V.push_back(std::make_pair(ClassId, P));
171   }
172   while (!V.empty()) {
173     auto Pair = V.back();
174     Cache.deallocate(Pair.first, Pair.second);
175     V.pop_back();
176   }
177   Cache.destroy(nullptr);
178 }
179 
testPrimaryThreaded()180 template <typename Primary> static void testPrimaryThreaded() {
181   Ready = false;
182   auto Deleter = [](Primary *P) {
183     P->unmapTestOnly();
184     delete P;
185   };
186   std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter);
187   Allocator->init(/*ReleaseToOsInterval=*/-1);
188   std::thread Threads[32];
189   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
190     Threads[I] = std::thread(performAllocations<Primary>, Allocator.get());
191   {
192     std::unique_lock<std::mutex> Lock(Mutex);
193     Ready = true;
194     Cv.notify_all();
195   }
196   for (auto &T : Threads)
197     T.join();
198   Allocator->releaseToOS();
199   scudo::ScopedString Str(1024);
200   Allocator->getStats(&Str);
201   Str.output();
202 }
203 
TEST(ScudoPrimaryTest,PrimaryThreaded)204 TEST(ScudoPrimaryTest, PrimaryThreaded) {
205   using SizeClassMap = scudo::SvelteSizeClassMap;
206 #if !SCUDO_FUCHSIA
207   testPrimaryThreaded<scudo::SizeClassAllocator32<SizeClassMap, 18U>>();
208 #endif
209   testPrimaryThreaded<scudo::SizeClassAllocator64<SizeClassMap, 24U>>();
210   testPrimaryThreaded<scudo::SizeClassAllocator64<SizeClassMap, 24U, INT32_MIN,
211                                                   INT32_MAX, true>>();
212 }
213 
214 // Through a simple allocation that spans two pages, verify that releaseToOS
215 // actually releases some bytes (at least one page worth). This is a regression
216 // test for an error in how the release criteria were computed.
testReleaseToOS()217 template <typename Primary> static void testReleaseToOS() {
218   auto Deleter = [](Primary *P) {
219     P->unmapTestOnly();
220     delete P;
221   };
222   std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter);
223   Allocator->init(/*ReleaseToOsInterval=*/-1);
224   typename Primary::CacheT Cache;
225   Cache.init(nullptr, Allocator.get());
226   const scudo::uptr Size = scudo::getPageSizeCached() * 2;
227   EXPECT_TRUE(Primary::canAllocate(Size));
228   const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
229   void *P = Cache.allocate(ClassId);
230   EXPECT_NE(P, nullptr);
231   Cache.deallocate(ClassId, P);
232   Cache.destroy(nullptr);
233   EXPECT_GT(Allocator->releaseToOS(), 0U);
234 }
235 
TEST(ScudoPrimaryTest,ReleaseToOS)236 TEST(ScudoPrimaryTest, ReleaseToOS) {
237   using SizeClassMap = scudo::DefaultSizeClassMap;
238 #if !SCUDO_FUCHSIA
239   testReleaseToOS<scudo::SizeClassAllocator32<SizeClassMap, 18U>>();
240 #endif
241   testReleaseToOS<scudo::SizeClassAllocator64<SizeClassMap, 24U>>();
242   testReleaseToOS<scudo::SizeClassAllocator64<SizeClassMap, 24U, INT32_MIN,
243                                               INT32_MAX, true>>();
244 }
245