You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
7.0 KiB
208 lines
7.0 KiB
//===-- secondary_test.cpp --------------------------------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "tests/scudo_unit_test.h"
|
|
|
|
#include "allocator_config.h"
|
|
#include "secondary.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <condition_variable>
|
|
#include <mutex>
|
|
#include <random>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
template <typename Config> static void testSecondaryBasic(void) {
|
|
using SecondaryT = scudo::MapAllocator<Config>;
|
|
|
|
scudo::GlobalStats S;
|
|
S.init();
|
|
std::unique_ptr<SecondaryT> L(new SecondaryT);
|
|
L->init(&S);
|
|
const scudo::uptr Size = 1U << 16;
|
|
void *P = L->allocate(scudo::Options{}, Size);
|
|
EXPECT_NE(P, nullptr);
|
|
memset(P, 'A', Size);
|
|
EXPECT_GE(SecondaryT::getBlockSize(P), Size);
|
|
L->deallocate(scudo::Options{}, P);
|
|
// If the Secondary can't cache that pointer, it will be unmapped.
|
|
if (!L->canCache(Size))
|
|
EXPECT_DEATH(memset(P, 'A', Size), "");
|
|
|
|
const scudo::uptr Align = 1U << 16;
|
|
P = L->allocate(scudo::Options{}, Size + Align, Align);
|
|
EXPECT_NE(P, nullptr);
|
|
void *AlignedP = reinterpret_cast<void *>(
|
|
scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
|
|
memset(AlignedP, 'A', Size);
|
|
L->deallocate(scudo::Options{}, P);
|
|
|
|
std::vector<void *> V;
|
|
for (scudo::uptr I = 0; I < 32U; I++)
|
|
V.push_back(L->allocate(scudo::Options{}, Size));
|
|
std::shuffle(V.begin(), V.end(), std::mt19937(std::random_device()()));
|
|
while (!V.empty()) {
|
|
L->deallocate(scudo::Options{}, V.back());
|
|
V.pop_back();
|
|
}
|
|
scudo::ScopedString Str(1024);
|
|
L->getStats(&Str);
|
|
Str.output();
|
|
}
|
|
|
|
struct NoCacheConfig {
|
|
typedef scudo::MapAllocatorNoCache SecondaryCache;
|
|
static const bool MaySupportMemoryTagging = false;
|
|
};
|
|
|
|
struct TestConfig {
|
|
typedef scudo::MapAllocatorCache<TestConfig> SecondaryCache;
|
|
static const bool MaySupportMemoryTagging = false;
|
|
static const scudo::u32 SecondaryCacheEntriesArraySize = 128U;
|
|
static const scudo::u32 SecondaryCacheQuarantineSize = 0U;
|
|
static const scudo::u32 SecondaryCacheDefaultMaxEntriesCount = 64U;
|
|
static const scudo::uptr SecondaryCacheDefaultMaxEntrySize = 1UL << 20;
|
|
static const scudo::s32 SecondaryCacheMinReleaseToOsIntervalMs = INT32_MIN;
|
|
static const scudo::s32 SecondaryCacheMaxReleaseToOsIntervalMs = INT32_MAX;
|
|
};
|
|
|
|
TEST(ScudoSecondaryTest, SecondaryBasic) {
|
|
testSecondaryBasic<NoCacheConfig>();
|
|
testSecondaryBasic<scudo::DefaultConfig>();
|
|
testSecondaryBasic<TestConfig>();
|
|
}
|
|
|
|
using LargeAllocator = scudo::MapAllocator<scudo::DefaultConfig>;
|
|
|
|
// This exercises a variety of combinations of size and alignment for the
|
|
// MapAllocator. The size computation done here mimic the ones done by the
|
|
// combined allocator.
|
|
TEST(ScudoSecondaryTest, SecondaryCombinations) {
|
|
constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16);
|
|
constexpr scudo::uptr HeaderSize = scudo::roundUpTo(8, MinAlign);
|
|
std::unique_ptr<LargeAllocator> L(new LargeAllocator);
|
|
L->init(nullptr);
|
|
for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) {
|
|
for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16;
|
|
AlignLog++) {
|
|
const scudo::uptr Align = 1U << AlignLog;
|
|
for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) {
|
|
if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0)
|
|
continue;
|
|
const scudo::uptr UserSize =
|
|
scudo::roundUpTo((1U << SizeLog) + Delta, MinAlign);
|
|
const scudo::uptr Size =
|
|
HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0);
|
|
void *P = L->allocate(scudo::Options{}, Size, Align);
|
|
EXPECT_NE(P, nullptr);
|
|
void *AlignedP = reinterpret_cast<void *>(
|
|
scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
|
|
memset(AlignedP, 0xff, UserSize);
|
|
L->deallocate(scudo::Options{}, P);
|
|
}
|
|
}
|
|
}
|
|
scudo::ScopedString Str(1024);
|
|
L->getStats(&Str);
|
|
Str.output();
|
|
}
|
|
|
|
TEST(ScudoSecondaryTest, SecondaryIterate) {
|
|
std::unique_ptr<LargeAllocator> L(new LargeAllocator);
|
|
L->init(nullptr);
|
|
std::vector<void *> V;
|
|
const scudo::uptr PageSize = scudo::getPageSizeCached();
|
|
for (scudo::uptr I = 0; I < 32U; I++)
|
|
V.push_back(L->allocate(scudo::Options{}, (std::rand() % 16) * PageSize));
|
|
auto Lambda = [V](scudo::uptr Block) {
|
|
EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)),
|
|
V.end());
|
|
};
|
|
L->disable();
|
|
L->iterateOverBlocks(Lambda);
|
|
L->enable();
|
|
while (!V.empty()) {
|
|
L->deallocate(scudo::Options{}, V.back());
|
|
V.pop_back();
|
|
}
|
|
scudo::ScopedString Str(1024);
|
|
L->getStats(&Str);
|
|
Str.output();
|
|
}
|
|
|
|
TEST(ScudoSecondaryTest, SecondaryOptions) {
|
|
std::unique_ptr<LargeAllocator> L(new LargeAllocator);
|
|
L->init(nullptr);
|
|
// Attempt to set a maximum number of entries higher than the array size.
|
|
EXPECT_FALSE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4096U));
|
|
// A negative number will be cast to a scudo::u32, and fail.
|
|
EXPECT_FALSE(L->setOption(scudo::Option::MaxCacheEntriesCount, -1));
|
|
if (L->canCache(0U)) {
|
|
// Various valid combinations.
|
|
EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
|
|
EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
|
|
EXPECT_TRUE(L->canCache(1UL << 18));
|
|
EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 17));
|
|
EXPECT_FALSE(L->canCache(1UL << 18));
|
|
EXPECT_TRUE(L->canCache(1UL << 16));
|
|
EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 0U));
|
|
EXPECT_FALSE(L->canCache(1UL << 16));
|
|
EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
|
|
EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
|
|
EXPECT_TRUE(L->canCache(1UL << 16));
|
|
}
|
|
}
|
|
|
|
static std::mutex Mutex;
|
|
static std::condition_variable Cv;
|
|
static bool Ready;
|
|
|
|
static void performAllocations(LargeAllocator *L) {
|
|
std::vector<void *> V;
|
|
const scudo::uptr PageSize = scudo::getPageSizeCached();
|
|
{
|
|
std::unique_lock<std::mutex> Lock(Mutex);
|
|
while (!Ready)
|
|
Cv.wait(Lock);
|
|
}
|
|
for (scudo::uptr I = 0; I < 128U; I++) {
|
|
// Deallocate 75% of the blocks.
|
|
const bool Deallocate = (rand() & 3) != 0;
|
|
void *P = L->allocate(scudo::Options{}, (std::rand() % 16) * PageSize);
|
|
if (Deallocate)
|
|
L->deallocate(scudo::Options{}, P);
|
|
else
|
|
V.push_back(P);
|
|
}
|
|
while (!V.empty()) {
|
|
L->deallocate(scudo::Options{}, V.back());
|
|
V.pop_back();
|
|
}
|
|
}
|
|
|
|
TEST(ScudoSecondaryTest, SecondaryThreadsRace) {
|
|
Ready = false;
|
|
std::unique_ptr<LargeAllocator> L(new LargeAllocator);
|
|
L->init(nullptr, /*ReleaseToOsInterval=*/0);
|
|
std::thread Threads[16];
|
|
for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
|
|
Threads[I] = std::thread(performAllocations, L.get());
|
|
{
|
|
std::unique_lock<std::mutex> Lock(Mutex);
|
|
Ready = true;
|
|
Cv.notify_all();
|
|
}
|
|
for (auto &T : Threads)
|
|
T.join();
|
|
scudo::ScopedString Str(1024);
|
|
L->getStats(&Str);
|
|
Str.output();
|
|
}
|