blob: 38bf671508530cf14ec7697f8f93dbceba532257 [file] [log] [blame]
Dynamic Tools Team517193e2019-09-11 14:48:41 +00001//===-- 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
Dynamic Tools Team09e6d482019-11-26 18:18:14 -08009#include "tests/scudo_unit_test.h"
10
Dynamic Tools Team517193e2019-09-11 14:48:41 +000011#include "primary32.h"
12#include "primary64.h"
13#include "size_class_map.h"
14
Dynamic Tools Team517193e2019-09-11 14:48:41 +000015#include <condition_variable>
16#include <mutex>
17#include <thread>
Dynamic Tools Team09e6d482019-11-26 18:18:14 -080018#include <vector>
Dynamic Tools Team517193e2019-09-11 14:48:41 +000019
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
24template <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();
Dynamic Tools Team3e8c65b2019-10-18 20:00:32 +000050 scudo::ScopedString Str(1024);
51 Allocator->getStats(&Str);
52 Str.output();
Dynamic Tools Team517193e2019-09-11 14:48:41 +000053}
54
Peter Collingbourne6be49192020-12-15 14:26:10 -080055template <typename SizeClassMapT> struct TestConfig1 {
56 using SizeClassMap = SizeClassMapT;
57 static const scudo::uptr PrimaryRegionSizeLog = 18U;
58 static const scudo::s32 PrimaryMinReleaseToOsIntervalMs = INT32_MIN;
59 static const scudo::s32 PrimaryMaxReleaseToOsIntervalMs = INT32_MAX;
60 static const bool MaySupportMemoryTagging = false;
Kostya Kortchinskyc9369542021-02-10 10:17:18 -080061 typedef scudo::uptr PrimaryCompactPtrT;
62 static const scudo::uptr PrimaryCompactPtrScale = 0;
Peter Collingbourne6be49192020-12-15 14:26:10 -080063};
64
65template <typename SizeClassMapT> struct TestConfig2 {
66 using SizeClassMap = SizeClassMapT;
67 static const scudo::uptr PrimaryRegionSizeLog = 24U;
68 static const scudo::s32 PrimaryMinReleaseToOsIntervalMs = INT32_MIN;
69 static const scudo::s32 PrimaryMaxReleaseToOsIntervalMs = INT32_MAX;
70 static const bool MaySupportMemoryTagging = false;
Kostya Kortchinskyc9369542021-02-10 10:17:18 -080071 typedef scudo::uptr PrimaryCompactPtrT;
72 static const scudo::uptr PrimaryCompactPtrScale = 0;
Peter Collingbourne6be49192020-12-15 14:26:10 -080073};
74
75template <typename SizeClassMapT> struct TestConfig3 {
76 using SizeClassMap = SizeClassMapT;
77 static const scudo::uptr PrimaryRegionSizeLog = 24U;
78 static const scudo::s32 PrimaryMinReleaseToOsIntervalMs = INT32_MIN;
79 static const scudo::s32 PrimaryMaxReleaseToOsIntervalMs = INT32_MAX;
80 static const bool MaySupportMemoryTagging = true;
Kostya Kortchinskyc9369542021-02-10 10:17:18 -080081 typedef scudo::uptr PrimaryCompactPtrT;
82 static const scudo::uptr PrimaryCompactPtrScale = 0;
Peter Collingbourne6be49192020-12-15 14:26:10 -080083};
84
Dynamic Tools Team517193e2019-09-11 14:48:41 +000085TEST(ScudoPrimaryTest, BasicPrimary) {
86 using SizeClassMap = scudo::DefaultSizeClassMap;
Dynamic Tools Team09e6d482019-11-26 18:18:14 -080087#if !SCUDO_FUCHSIA
Peter Collingbourne6be49192020-12-15 14:26:10 -080088 testPrimary<scudo::SizeClassAllocator32<TestConfig1<SizeClassMap>>>();
Dynamic Tools Team09e6d482019-11-26 18:18:14 -080089#endif
Peter Collingbourne6be49192020-12-15 14:26:10 -080090 testPrimary<scudo::SizeClassAllocator64<TestConfig2<SizeClassMap>>>();
91 testPrimary<scudo::SizeClassAllocator64<TestConfig3<SizeClassMap>>>();
Dynamic Tools Team517193e2019-09-11 14:48:41 +000092}
93
Peter Collingbourne6be49192020-12-15 14:26:10 -080094struct SmallRegionsConfig {
95 using SizeClassMap = scudo::DefaultSizeClassMap;
96 static const scudo::uptr PrimaryRegionSizeLog = 20U;
97 static const scudo::s32 PrimaryMinReleaseToOsIntervalMs = INT32_MIN;
98 static const scudo::s32 PrimaryMaxReleaseToOsIntervalMs = INT32_MAX;
99 static const bool MaySupportMemoryTagging = false;
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800100 typedef scudo::uptr PrimaryCompactPtrT;
101 static const scudo::uptr PrimaryCompactPtrScale = 0;
Peter Collingbourne6be49192020-12-15 14:26:10 -0800102};
103
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000104// The 64-bit SizeClassAllocator can be easily OOM'd with small region sizes.
105// For the 32-bit one, it requires actually exhausting memory, so we skip it.
106TEST(ScudoPrimaryTest, Primary64OOM) {
Peter Collingbourne6be49192020-12-15 14:26:10 -0800107 using Primary = scudo::SizeClassAllocator64<SmallRegionsConfig>;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000108 using TransferBatch = Primary::CacheT::TransferBatch;
109 Primary Allocator;
110 Allocator.init(/*ReleaseToOsInterval=*/-1);
111 typename Primary::CacheT Cache;
112 scudo::GlobalStats Stats;
113 Stats.init();
114 Cache.init(&Stats, &Allocator);
115 bool AllocationFailed = false;
116 std::vector<TransferBatch *> Batches;
117 const scudo::uptr ClassId = Primary::SizeClassMap::LargestClassId;
118 const scudo::uptr Size = Primary::getSizeByClassId(ClassId);
119 for (scudo::uptr I = 0; I < 10000U; I++) {
120 TransferBatch *B = Allocator.popBatch(&Cache, ClassId);
121 if (!B) {
122 AllocationFailed = true;
123 break;
124 }
Dynamic Tools Team09e6d482019-11-26 18:18:14 -0800125 for (scudo::u32 J = 0; J < B->getCount(); J++)
Kostya Kortchinskyc9369542021-02-10 10:17:18 -0800126 memset(Allocator.decompactPtr(ClassId, B->get(J)), 'B', Size);
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000127 Batches.push_back(B);
128 }
129 while (!Batches.empty()) {
130 Allocator.pushBatch(ClassId, Batches.back());
131 Batches.pop_back();
132 }
133 Cache.destroy(nullptr);
134 Allocator.releaseToOS();
Dynamic Tools Team3e8c65b2019-10-18 20:00:32 +0000135 scudo::ScopedString Str(1024);
136 Allocator.getStats(&Str);
137 Str.output();
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000138 EXPECT_EQ(AllocationFailed, true);
139 Allocator.unmapTestOnly();
140}
141
142template <typename Primary> static void testIteratePrimary() {
143 auto Deleter = [](Primary *P) {
144 P->unmapTestOnly();
145 delete P;
146 };
147 std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter);
148 Allocator->init(/*ReleaseToOsInterval=*/-1);
149 typename Primary::CacheT Cache;
150 Cache.init(nullptr, Allocator.get());
151 std::vector<std::pair<scudo::uptr, void *>> V;
152 for (scudo::uptr I = 0; I < 64U; I++) {
153 const scudo::uptr Size = std::rand() % Primary::SizeClassMap::MaxSize;
154 const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
155 void *P = Cache.allocate(ClassId);
156 V.push_back(std::make_pair(ClassId, P));
157 }
158 scudo::uptr Found = 0;
159 auto Lambda = [V, &Found](scudo::uptr Block) {
160 for (const auto &Pair : V) {
161 if (Pair.second == reinterpret_cast<void *>(Block))
162 Found++;
163 }
164 };
165 Allocator->disable();
166 Allocator->iterateOverBlocks(Lambda);
167 Allocator->enable();
168 EXPECT_EQ(Found, V.size());
169 while (!V.empty()) {
170 auto Pair = V.back();
171 Cache.deallocate(Pair.first, Pair.second);
172 V.pop_back();
173 }
174 Cache.destroy(nullptr);
175 Allocator->releaseToOS();
Dynamic Tools Team3e8c65b2019-10-18 20:00:32 +0000176 scudo::ScopedString Str(1024);
177 Allocator->getStats(&Str);
178 Str.output();
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000179}
180
181TEST(ScudoPrimaryTest, PrimaryIterate) {
182 using SizeClassMap = scudo::DefaultSizeClassMap;
Dynamic Tools Team09e6d482019-11-26 18:18:14 -0800183#if !SCUDO_FUCHSIA
Peter Collingbourne6be49192020-12-15 14:26:10 -0800184 testIteratePrimary<scudo::SizeClassAllocator32<TestConfig1<SizeClassMap>>>();
Dynamic Tools Team09e6d482019-11-26 18:18:14 -0800185#endif
Peter Collingbourne6be49192020-12-15 14:26:10 -0800186 testIteratePrimary<scudo::SizeClassAllocator64<TestConfig2<SizeClassMap>>>();
187 testIteratePrimary<scudo::SizeClassAllocator64<TestConfig3<SizeClassMap>>>();
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000188}
189
190static std::mutex Mutex;
191static std::condition_variable Cv;
Kostya Kortchinskyc72ca562020-07-27 09:13:42 -0700192static bool Ready;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000193
194template <typename Primary> static void performAllocations(Primary *Allocator) {
Peter Collingbourne0d4ff652020-09-10 12:38:42 -0700195 static thread_local typename Primary::CacheT Cache;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000196 Cache.init(nullptr, Allocator);
197 std::vector<std::pair<scudo::uptr, void *>> V;
198 {
199 std::unique_lock<std::mutex> Lock(Mutex);
200 while (!Ready)
201 Cv.wait(Lock);
202 }
203 for (scudo::uptr I = 0; I < 256U; I++) {
204 const scudo::uptr Size = std::rand() % Primary::SizeClassMap::MaxSize / 4;
205 const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
206 void *P = Cache.allocate(ClassId);
207 if (P)
208 V.push_back(std::make_pair(ClassId, P));
209 }
210 while (!V.empty()) {
211 auto Pair = V.back();
212 Cache.deallocate(Pair.first, Pair.second);
213 V.pop_back();
214 }
215 Cache.destroy(nullptr);
216}
217
218template <typename Primary> static void testPrimaryThreaded() {
Kostya Kortchinskyc72ca562020-07-27 09:13:42 -0700219 Ready = false;
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000220 auto Deleter = [](Primary *P) {
221 P->unmapTestOnly();
222 delete P;
223 };
224 std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter);
225 Allocator->init(/*ReleaseToOsInterval=*/-1);
226 std::thread Threads[32];
227 for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
228 Threads[I] = std::thread(performAllocations<Primary>, Allocator.get());
229 {
230 std::unique_lock<std::mutex> Lock(Mutex);
231 Ready = true;
232 Cv.notify_all();
233 }
234 for (auto &T : Threads)
235 T.join();
236 Allocator->releaseToOS();
Dynamic Tools Team3e8c65b2019-10-18 20:00:32 +0000237 scudo::ScopedString Str(1024);
238 Allocator->getStats(&Str);
239 Str.output();
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000240}
241
242TEST(ScudoPrimaryTest, PrimaryThreaded) {
243 using SizeClassMap = scudo::SvelteSizeClassMap;
Dynamic Tools Team09e6d482019-11-26 18:18:14 -0800244#if !SCUDO_FUCHSIA
Peter Collingbourne6be49192020-12-15 14:26:10 -0800245 testPrimaryThreaded<scudo::SizeClassAllocator32<TestConfig1<SizeClassMap>>>();
Dynamic Tools Team09e6d482019-11-26 18:18:14 -0800246#endif
Peter Collingbourne6be49192020-12-15 14:26:10 -0800247 testPrimaryThreaded<scudo::SizeClassAllocator64<TestConfig2<SizeClassMap>>>();
248 testPrimaryThreaded<scudo::SizeClassAllocator64<TestConfig3<SizeClassMap>>>();
Dynamic Tools Team517193e2019-09-11 14:48:41 +0000249}
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000250
251// Through a simple allocation that spans two pages, verify that releaseToOS
252// actually releases some bytes (at least one page worth). This is a regression
253// test for an error in how the release criteria were computed.
254template <typename Primary> static void testReleaseToOS() {
255 auto Deleter = [](Primary *P) {
256 P->unmapTestOnly();
257 delete P;
258 };
259 std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter);
260 Allocator->init(/*ReleaseToOsInterval=*/-1);
261 typename Primary::CacheT Cache;
262 Cache.init(nullptr, Allocator.get());
263 const scudo::uptr Size = scudo::getPageSizeCached() * 2;
264 EXPECT_TRUE(Primary::canAllocate(Size));
Dynamic Tools Team3e8c65b2019-10-18 20:00:32 +0000265 const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000266 void *P = Cache.allocate(ClassId);
267 EXPECT_NE(P, nullptr);
268 Cache.deallocate(ClassId, P);
269 Cache.destroy(nullptr);
270 EXPECT_GT(Allocator->releaseToOS(), 0U);
271}
272
273TEST(ScudoPrimaryTest, ReleaseToOS) {
274 using SizeClassMap = scudo::DefaultSizeClassMap;
Dynamic Tools Team09e6d482019-11-26 18:18:14 -0800275#if !SCUDO_FUCHSIA
Peter Collingbourne6be49192020-12-15 14:26:10 -0800276 testReleaseToOS<scudo::SizeClassAllocator32<TestConfig1<SizeClassMap>>>();
Dynamic Tools Team09e6d482019-11-26 18:18:14 -0800277#endif
Peter Collingbourne6be49192020-12-15 14:26:10 -0800278 testReleaseToOS<scudo::SizeClassAllocator64<TestConfig2<SizeClassMap>>>();
279 testReleaseToOS<scudo::SizeClassAllocator64<TestConfig3<SizeClassMap>>>();
Dynamic Tools Teamc283bab2019-10-07 17:37:39 +0000280}