13cab2bb3Spatrick //===-- secondary_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
9d89ec533Spatrick #include "memtag.h"
103cab2bb3Spatrick #include "tests/scudo_unit_test.h"
113cab2bb3Spatrick
12d89ec533Spatrick #include "allocator_config.h"
133cab2bb3Spatrick #include "secondary.h"
143cab2bb3Spatrick
15*810390e3Srobert #include <algorithm>
163cab2bb3Spatrick #include <condition_variable>
17d89ec533Spatrick #include <memory>
183cab2bb3Spatrick #include <mutex>
193cab2bb3Spatrick #include <random>
20d89ec533Spatrick #include <stdio.h>
213cab2bb3Spatrick #include <thread>
223cab2bb3Spatrick #include <vector>
233cab2bb3Spatrick
getOptionsForConfig()24d89ec533Spatrick template <typename Config> static scudo::Options getOptionsForConfig() {
25d89ec533Spatrick if (!Config::MaySupportMemoryTagging || !scudo::archSupportsMemoryTagging() ||
26d89ec533Spatrick !scudo::systemSupportsMemoryTagging())
27d89ec533Spatrick return {};
28d89ec533Spatrick scudo::AtomicOptions AO;
29d89ec533Spatrick AO.set(scudo::OptionBit::UseMemoryTagging);
30d89ec533Spatrick return AO.load();
31d89ec533Spatrick }
32d89ec533Spatrick
testSecondaryBasic(void)33d89ec533Spatrick template <typename Config> static void testSecondaryBasic(void) {
34d89ec533Spatrick using SecondaryT = scudo::MapAllocator<Config>;
35d89ec533Spatrick scudo::Options Options = getOptionsForConfig<Config>();
36d89ec533Spatrick
373cab2bb3Spatrick scudo::GlobalStats S;
383cab2bb3Spatrick S.init();
39d89ec533Spatrick std::unique_ptr<SecondaryT> L(new SecondaryT);
403cab2bb3Spatrick L->init(&S);
413cab2bb3Spatrick const scudo::uptr Size = 1U << 16;
42d89ec533Spatrick void *P = L->allocate(Options, Size);
433cab2bb3Spatrick EXPECT_NE(P, nullptr);
443cab2bb3Spatrick memset(P, 'A', Size);
453cab2bb3Spatrick EXPECT_GE(SecondaryT::getBlockSize(P), Size);
46d89ec533Spatrick L->deallocate(Options, P);
47d89ec533Spatrick
481f9cb04fSpatrick // If the Secondary can't cache that pointer, it will be unmapped.
49d89ec533Spatrick if (!L->canCache(Size)) {
50d89ec533Spatrick EXPECT_DEATH(
51d89ec533Spatrick {
52d89ec533Spatrick // Repeat few time to avoid missing crash if it's mmaped by unrelated
53d89ec533Spatrick // code.
54d89ec533Spatrick for (int i = 0; i < 10; ++i) {
55d89ec533Spatrick P = L->allocate(Options, Size);
56d89ec533Spatrick L->deallocate(Options, P);
57d89ec533Spatrick memset(P, 'A', Size);
58d89ec533Spatrick }
59d89ec533Spatrick },
60d89ec533Spatrick "");
61d89ec533Spatrick }
623cab2bb3Spatrick
633cab2bb3Spatrick const scudo::uptr Align = 1U << 16;
64d89ec533Spatrick P = L->allocate(Options, Size + Align, Align);
653cab2bb3Spatrick EXPECT_NE(P, nullptr);
663cab2bb3Spatrick void *AlignedP = reinterpret_cast<void *>(
673cab2bb3Spatrick scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
683cab2bb3Spatrick memset(AlignedP, 'A', Size);
69d89ec533Spatrick L->deallocate(Options, P);
703cab2bb3Spatrick
713cab2bb3Spatrick std::vector<void *> V;
723cab2bb3Spatrick for (scudo::uptr I = 0; I < 32U; I++)
73d89ec533Spatrick V.push_back(L->allocate(Options, Size));
743cab2bb3Spatrick std::shuffle(V.begin(), V.end(), std::mt19937(std::random_device()()));
753cab2bb3Spatrick while (!V.empty()) {
76d89ec533Spatrick L->deallocate(Options, V.back());
773cab2bb3Spatrick V.pop_back();
783cab2bb3Spatrick }
79d89ec533Spatrick scudo::ScopedString Str;
803cab2bb3Spatrick L->getStats(&Str);
813cab2bb3Spatrick Str.output();
82d89ec533Spatrick L->unmapTestOnly();
833cab2bb3Spatrick }
843cab2bb3Spatrick
85d89ec533Spatrick struct NoCacheConfig {
86d89ec533Spatrick typedef scudo::MapAllocatorNoCache SecondaryCache;
87d89ec533Spatrick static const bool MaySupportMemoryTagging = false;
88d89ec533Spatrick };
89d89ec533Spatrick
90d89ec533Spatrick struct TestConfig {
91d89ec533Spatrick typedef scudo::MapAllocatorCache<TestConfig> SecondaryCache;
92d89ec533Spatrick static const bool MaySupportMemoryTagging = false;
93d89ec533Spatrick static const scudo::u32 SecondaryCacheEntriesArraySize = 128U;
94d89ec533Spatrick static const scudo::u32 SecondaryCacheQuarantineSize = 0U;
95d89ec533Spatrick static const scudo::u32 SecondaryCacheDefaultMaxEntriesCount = 64U;
96d89ec533Spatrick static const scudo::uptr SecondaryCacheDefaultMaxEntrySize = 1UL << 20;
97d89ec533Spatrick static const scudo::s32 SecondaryCacheMinReleaseToOsIntervalMs = INT32_MIN;
98d89ec533Spatrick static const scudo::s32 SecondaryCacheMaxReleaseToOsIntervalMs = INT32_MAX;
99d89ec533Spatrick };
100d89ec533Spatrick
TEST(ScudoSecondaryTest,SecondaryBasic)1013cab2bb3Spatrick TEST(ScudoSecondaryTest, SecondaryBasic) {
102d89ec533Spatrick testSecondaryBasic<NoCacheConfig>();
103d89ec533Spatrick testSecondaryBasic<scudo::DefaultConfig>();
104d89ec533Spatrick testSecondaryBasic<TestConfig>();
1053cab2bb3Spatrick }
1063cab2bb3Spatrick
107d89ec533Spatrick struct MapAllocatorTest : public Test {
108d89ec533Spatrick using Config = scudo::DefaultConfig;
109d89ec533Spatrick using LargeAllocator = scudo::MapAllocator<Config>;
110d89ec533Spatrick
SetUpMapAllocatorTest111d89ec533Spatrick void SetUp() override { Allocator->init(nullptr); }
112d89ec533Spatrick
TearDownMapAllocatorTest113d89ec533Spatrick void TearDown() override { Allocator->unmapTestOnly(); }
114d89ec533Spatrick
115d89ec533Spatrick std::unique_ptr<LargeAllocator> Allocator =
116d89ec533Spatrick std::make_unique<LargeAllocator>();
117d89ec533Spatrick scudo::Options Options = getOptionsForConfig<Config>();
118d89ec533Spatrick };
1193cab2bb3Spatrick
1203cab2bb3Spatrick // This exercises a variety of combinations of size and alignment for the
1213cab2bb3Spatrick // MapAllocator. The size computation done here mimic the ones done by the
1223cab2bb3Spatrick // combined allocator.
TEST_F(MapAllocatorTest,SecondaryCombinations)123d89ec533Spatrick TEST_F(MapAllocatorTest, SecondaryCombinations) {
1243cab2bb3Spatrick constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16);
1253cab2bb3Spatrick constexpr scudo::uptr HeaderSize = scudo::roundUpTo(8, MinAlign);
1263cab2bb3Spatrick for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) {
1273cab2bb3Spatrick for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16;
1283cab2bb3Spatrick AlignLog++) {
1293cab2bb3Spatrick const scudo::uptr Align = 1U << AlignLog;
1303cab2bb3Spatrick for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) {
1313cab2bb3Spatrick if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0)
1323cab2bb3Spatrick continue;
1333cab2bb3Spatrick const scudo::uptr UserSize =
1343cab2bb3Spatrick scudo::roundUpTo((1U << SizeLog) + Delta, MinAlign);
1353cab2bb3Spatrick const scudo::uptr Size =
1363cab2bb3Spatrick HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0);
137d89ec533Spatrick void *P = Allocator->allocate(Options, Size, Align);
1383cab2bb3Spatrick EXPECT_NE(P, nullptr);
1393cab2bb3Spatrick void *AlignedP = reinterpret_cast<void *>(
1403cab2bb3Spatrick scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
1413cab2bb3Spatrick memset(AlignedP, 0xff, UserSize);
142d89ec533Spatrick Allocator->deallocate(Options, P);
1433cab2bb3Spatrick }
1443cab2bb3Spatrick }
1453cab2bb3Spatrick }
146d89ec533Spatrick scudo::ScopedString Str;
147d89ec533Spatrick Allocator->getStats(&Str);
1483cab2bb3Spatrick Str.output();
1493cab2bb3Spatrick }
1503cab2bb3Spatrick
TEST_F(MapAllocatorTest,SecondaryIterate)151d89ec533Spatrick TEST_F(MapAllocatorTest, SecondaryIterate) {
1523cab2bb3Spatrick std::vector<void *> V;
1533cab2bb3Spatrick const scudo::uptr PageSize = scudo::getPageSizeCached();
1543cab2bb3Spatrick for (scudo::uptr I = 0; I < 32U; I++)
155d89ec533Spatrick V.push_back(Allocator->allocate(Options, (std::rand() % 16) * PageSize));
156*810390e3Srobert auto Lambda = [&V](scudo::uptr Block) {
1573cab2bb3Spatrick EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)),
1583cab2bb3Spatrick V.end());
1593cab2bb3Spatrick };
160d89ec533Spatrick Allocator->disable();
161d89ec533Spatrick Allocator->iterateOverBlocks(Lambda);
162d89ec533Spatrick Allocator->enable();
1633cab2bb3Spatrick while (!V.empty()) {
164d89ec533Spatrick Allocator->deallocate(Options, V.back());
1653cab2bb3Spatrick V.pop_back();
1663cab2bb3Spatrick }
167d89ec533Spatrick scudo::ScopedString Str;
168d89ec533Spatrick Allocator->getStats(&Str);
1693cab2bb3Spatrick Str.output();
1703cab2bb3Spatrick }
1713cab2bb3Spatrick
TEST_F(MapAllocatorTest,SecondaryOptions)172d89ec533Spatrick TEST_F(MapAllocatorTest, SecondaryOptions) {
173d89ec533Spatrick // Attempt to set a maximum number of entries higher than the array size.
174d89ec533Spatrick EXPECT_FALSE(
175d89ec533Spatrick Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4096U));
176d89ec533Spatrick // A negative number will be cast to a scudo::u32, and fail.
177d89ec533Spatrick EXPECT_FALSE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, -1));
178d89ec533Spatrick if (Allocator->canCache(0U)) {
179d89ec533Spatrick // Various valid combinations.
180d89ec533Spatrick EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
181d89ec533Spatrick EXPECT_TRUE(
182d89ec533Spatrick Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
183d89ec533Spatrick EXPECT_TRUE(Allocator->canCache(1UL << 18));
184d89ec533Spatrick EXPECT_TRUE(
185d89ec533Spatrick Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 17));
186d89ec533Spatrick EXPECT_FALSE(Allocator->canCache(1UL << 18));
187d89ec533Spatrick EXPECT_TRUE(Allocator->canCache(1UL << 16));
188d89ec533Spatrick EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 0U));
189d89ec533Spatrick EXPECT_FALSE(Allocator->canCache(1UL << 16));
190d89ec533Spatrick EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
191d89ec533Spatrick EXPECT_TRUE(
192d89ec533Spatrick Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
193d89ec533Spatrick EXPECT_TRUE(Allocator->canCache(1UL << 16));
194d89ec533Spatrick }
195d89ec533Spatrick }
1963cab2bb3Spatrick
197d89ec533Spatrick struct MapAllocatorWithReleaseTest : public MapAllocatorTest {
SetUpMapAllocatorWithReleaseTest198d89ec533Spatrick void SetUp() override { Allocator->init(nullptr, /*ReleaseToOsInterval=*/0); }
199d89ec533Spatrick
performAllocationsMapAllocatorWithReleaseTest200d89ec533Spatrick void performAllocations() {
2013cab2bb3Spatrick std::vector<void *> V;
2023cab2bb3Spatrick const scudo::uptr PageSize = scudo::getPageSizeCached();
2033cab2bb3Spatrick {
2043cab2bb3Spatrick std::unique_lock<std::mutex> Lock(Mutex);
2053cab2bb3Spatrick while (!Ready)
2063cab2bb3Spatrick Cv.wait(Lock);
2073cab2bb3Spatrick }
2081f9cb04fSpatrick for (scudo::uptr I = 0; I < 128U; I++) {
2091f9cb04fSpatrick // Deallocate 75% of the blocks.
2101f9cb04fSpatrick const bool Deallocate = (rand() & 3) != 0;
211d89ec533Spatrick void *P = Allocator->allocate(Options, (std::rand() % 16) * PageSize);
2121f9cb04fSpatrick if (Deallocate)
213d89ec533Spatrick Allocator->deallocate(Options, P);
2141f9cb04fSpatrick else
2151f9cb04fSpatrick V.push_back(P);
2161f9cb04fSpatrick }
2173cab2bb3Spatrick while (!V.empty()) {
218d89ec533Spatrick Allocator->deallocate(Options, V.back());
2193cab2bb3Spatrick V.pop_back();
2203cab2bb3Spatrick }
2213cab2bb3Spatrick }
2223cab2bb3Spatrick
223d89ec533Spatrick std::mutex Mutex;
224d89ec533Spatrick std::condition_variable Cv;
225d89ec533Spatrick bool Ready = false;
226d89ec533Spatrick };
227d89ec533Spatrick
TEST_F(MapAllocatorWithReleaseTest,SecondaryThreadsRace)228d89ec533Spatrick TEST_F(MapAllocatorWithReleaseTest, SecondaryThreadsRace) {
2291f9cb04fSpatrick std::thread Threads[16];
2301f9cb04fSpatrick for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
231d89ec533Spatrick Threads[I] =
232d89ec533Spatrick std::thread(&MapAllocatorWithReleaseTest::performAllocations, this);
2333cab2bb3Spatrick {
2343cab2bb3Spatrick std::unique_lock<std::mutex> Lock(Mutex);
2353cab2bb3Spatrick Ready = true;
2363cab2bb3Spatrick Cv.notify_all();
2373cab2bb3Spatrick }
2383cab2bb3Spatrick for (auto &T : Threads)
2393cab2bb3Spatrick T.join();
240d89ec533Spatrick scudo::ScopedString Str;
241d89ec533Spatrick Allocator->getStats(&Str);
2423cab2bb3Spatrick Str.output();
2433cab2bb3Spatrick }
244