Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / compiler-rt / lib / scudo / standalone / tests / secondary_test.cpp
blob18d2e187fa3ce25448f85a6e77ee40625c31cd48
1 //===-- secondary_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 //===----------------------------------------------------------------------===//
9 #include "memtag.h"
10 #include "tests/scudo_unit_test.h"
12 #include "allocator_config.h"
13 #include "secondary.h"
15 #include <algorithm>
16 #include <condition_variable>
17 #include <memory>
18 #include <mutex>
19 #include <random>
20 #include <stdio.h>
21 #include <thread>
22 #include <vector>
24 template <typename Config> static scudo::Options getOptionsForConfig() {
25 if (!Config::MaySupportMemoryTagging || !scudo::archSupportsMemoryTagging() ||
26 !scudo::systemSupportsMemoryTagging())
27 return {};
28 scudo::AtomicOptions AO;
29 AO.set(scudo::OptionBit::UseMemoryTagging);
30 return AO.load();
33 template <typename Config> static void testSecondaryBasic(void) {
34 using SecondaryT = scudo::MapAllocator<Config>;
35 scudo::Options Options = getOptionsForConfig<Config>();
37 scudo::GlobalStats S;
38 S.init();
39 std::unique_ptr<SecondaryT> L(new SecondaryT);
40 L->init(&S);
41 const scudo::uptr Size = 1U << 16;
42 void *P = L->allocate(Options, Size);
43 EXPECT_NE(P, nullptr);
44 memset(P, 'A', Size);
45 EXPECT_GE(SecondaryT::getBlockSize(P), Size);
46 L->deallocate(Options, P);
48 // If the Secondary can't cache that pointer, it will be unmapped.
49 if (!L->canCache(Size)) {
50 EXPECT_DEATH(
52 // Repeat few time to avoid missing crash if it's mmaped by unrelated
53 // code.
54 for (int i = 0; i < 10; ++i) {
55 P = L->allocate(Options, Size);
56 L->deallocate(Options, P);
57 memset(P, 'A', Size);
60 "");
63 const scudo::uptr Align = 1U << 16;
64 P = L->allocate(Options, Size + Align, Align);
65 EXPECT_NE(P, nullptr);
66 void *AlignedP = reinterpret_cast<void *>(
67 scudo::roundUp(reinterpret_cast<scudo::uptr>(P), Align));
68 memset(AlignedP, 'A', Size);
69 L->deallocate(Options, P);
71 std::vector<void *> V;
72 for (scudo::uptr I = 0; I < 32U; I++)
73 V.push_back(L->allocate(Options, Size));
74 std::shuffle(V.begin(), V.end(), std::mt19937(std::random_device()()));
75 while (!V.empty()) {
76 L->deallocate(Options, V.back());
77 V.pop_back();
79 scudo::ScopedString Str;
80 L->getStats(&Str);
81 Str.output();
82 L->unmapTestOnly();
85 struct NoCacheConfig {
86 static const bool MaySupportMemoryTagging = false;
87 struct Secondary {
88 template <typename Config>
89 using CacheT = scudo::MapAllocatorNoCache<Config>;
93 struct TestConfig {
94 static const bool MaySupportMemoryTagging = false;
95 struct Secondary {
96 struct Cache {
97 static const scudo::u32 EntriesArraySize = 128U;
98 static const scudo::u32 QuarantineSize = 0U;
99 static const scudo::u32 DefaultMaxEntriesCount = 64U;
100 static const scudo::uptr DefaultMaxEntrySize = 1UL << 20;
101 static const scudo::s32 MinReleaseToOsIntervalMs = INT32_MIN;
102 static const scudo::s32 MaxReleaseToOsIntervalMs = INT32_MAX;
105 template <typename Config> using CacheT = scudo::MapAllocatorCache<Config>;
109 TEST(ScudoSecondaryTest, SecondaryBasic) {
110 testSecondaryBasic<NoCacheConfig>();
111 testSecondaryBasic<scudo::DefaultConfig>();
112 testSecondaryBasic<TestConfig>();
115 struct MapAllocatorTest : public Test {
116 using Config = scudo::DefaultConfig;
117 using LargeAllocator = scudo::MapAllocator<Config>;
119 void SetUp() override { Allocator->init(nullptr); }
121 void TearDown() override { Allocator->unmapTestOnly(); }
123 std::unique_ptr<LargeAllocator> Allocator =
124 std::make_unique<LargeAllocator>();
125 scudo::Options Options = getOptionsForConfig<Config>();
128 // This exercises a variety of combinations of size and alignment for the
129 // MapAllocator. The size computation done here mimic the ones done by the
130 // combined allocator.
131 TEST_F(MapAllocatorTest, SecondaryCombinations) {
132 constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16);
133 constexpr scudo::uptr HeaderSize = scudo::roundUp(8, MinAlign);
134 for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) {
135 for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16;
136 AlignLog++) {
137 const scudo::uptr Align = 1U << AlignLog;
138 for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) {
139 if ((1LL << SizeLog) + Delta <= 0)
140 continue;
141 const scudo::uptr UserSize = scudo::roundUp(
142 static_cast<scudo::uptr>((1LL << SizeLog) + Delta), MinAlign);
143 const scudo::uptr Size =
144 HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0);
145 void *P = Allocator->allocate(Options, Size, Align);
146 EXPECT_NE(P, nullptr);
147 void *AlignedP = reinterpret_cast<void *>(
148 scudo::roundUp(reinterpret_cast<scudo::uptr>(P), Align));
149 memset(AlignedP, 0xff, UserSize);
150 Allocator->deallocate(Options, P);
154 scudo::ScopedString Str;
155 Allocator->getStats(&Str);
156 Str.output();
159 TEST_F(MapAllocatorTest, SecondaryIterate) {
160 std::vector<void *> V;
161 const scudo::uptr PageSize = scudo::getPageSizeCached();
162 for (scudo::uptr I = 0; I < 32U; I++)
163 V.push_back(Allocator->allocate(
164 Options, (static_cast<scudo::uptr>(std::rand()) % 16U) * PageSize));
165 auto Lambda = [&V](scudo::uptr Block) {
166 EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)),
167 V.end());
169 Allocator->disable();
170 Allocator->iterateOverBlocks(Lambda);
171 Allocator->enable();
172 while (!V.empty()) {
173 Allocator->deallocate(Options, V.back());
174 V.pop_back();
176 scudo::ScopedString Str;
177 Allocator->getStats(&Str);
178 Str.output();
181 TEST_F(MapAllocatorTest, SecondaryOptions) {
182 // Attempt to set a maximum number of entries higher than the array size.
183 EXPECT_FALSE(
184 Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4096U));
185 // A negative number will be cast to a scudo::u32, and fail.
186 EXPECT_FALSE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, -1));
187 if (Allocator->canCache(0U)) {
188 // Various valid combinations.
189 EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
190 EXPECT_TRUE(
191 Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
192 EXPECT_TRUE(Allocator->canCache(1UL << 18));
193 EXPECT_TRUE(
194 Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 17));
195 EXPECT_FALSE(Allocator->canCache(1UL << 18));
196 EXPECT_TRUE(Allocator->canCache(1UL << 16));
197 EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 0U));
198 EXPECT_FALSE(Allocator->canCache(1UL << 16));
199 EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
200 EXPECT_TRUE(
201 Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
202 EXPECT_TRUE(Allocator->canCache(1UL << 16));
206 struct MapAllocatorWithReleaseTest : public MapAllocatorTest {
207 void SetUp() override { Allocator->init(nullptr, /*ReleaseToOsInterval=*/0); }
209 void performAllocations() {
210 std::vector<void *> V;
211 const scudo::uptr PageSize = scudo::getPageSizeCached();
213 std::unique_lock<std::mutex> Lock(Mutex);
214 while (!Ready)
215 Cv.wait(Lock);
217 for (scudo::uptr I = 0; I < 128U; I++) {
218 // Deallocate 75% of the blocks.
219 const bool Deallocate = (std::rand() & 3) != 0;
220 void *P = Allocator->allocate(
221 Options, (static_cast<scudo::uptr>(std::rand()) % 16U) * PageSize);
222 if (Deallocate)
223 Allocator->deallocate(Options, P);
224 else
225 V.push_back(P);
227 while (!V.empty()) {
228 Allocator->deallocate(Options, V.back());
229 V.pop_back();
233 std::mutex Mutex;
234 std::condition_variable Cv;
235 bool Ready = false;
238 TEST_F(MapAllocatorWithReleaseTest, SecondaryThreadsRace) {
239 std::thread Threads[16];
240 for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
241 Threads[I] =
242 std::thread(&MapAllocatorWithReleaseTest::performAllocations, this);
244 std::unique_lock<std::mutex> Lock(Mutex);
245 Ready = true;
246 Cv.notify_all();
248 for (auto &T : Threads)
249 T.join();
250 scudo::ScopedString Str;
251 Allocator->getStats(&Str);
252 Str.output();