summaryrefslogtreecommitdiffstats
path: root/compiler-rt/lib/scudo/standalone/tests/primary_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'compiler-rt/lib/scudo/standalone/tests/primary_test.cpp')
-rw-r--r--compiler-rt/lib/scudo/standalone/tests/primary_test.cpp190
1 files changed, 190 insertions, 0 deletions
diff --git a/compiler-rt/lib/scudo/standalone/tests/primary_test.cpp b/compiler-rt/lib/scudo/standalone/tests/primary_test.cpp
new file mode 100644
index 00000000000..329a4c11953
--- /dev/null
+++ b/compiler-rt/lib/scudo/standalone/tests/primary_test.cpp
@@ -0,0 +1,190 @@
+//===-- primary_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 "primary32.h"
+#include "primary64.h"
+#include "size_class_map.h"
+
+#include "gtest/gtest.h"
+
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+// Note that with small enough regions, the SizeClassAllocator64 also works on
+// 32-bit architectures. It's not something we want to encourage, but we still
+// should ensure the tests pass.
+
+template <typename Primary> static void testPrimary() {
+ const scudo::uptr NumberOfAllocations = 32U;
+ auto Deleter = [](Primary *P) {
+ P->unmapTestOnly();
+ delete P;
+ };
+ std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter);
+ Allocator->init(/*ReleaseToOsInterval=*/-1);
+ typename Primary::CacheT Cache;
+ Cache.init(nullptr, Allocator.get());
+ for (scudo::uptr I = 0; I <= 16U; I++) {
+ const scudo::uptr Size = 1UL << I;
+ if (!Primary::canAllocate(Size))
+ continue;
+ const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
+ void *Pointers[NumberOfAllocations];
+ for (scudo::uptr J = 0; J < NumberOfAllocations; J++) {
+ void *P = Cache.allocate(ClassId);
+ memset(P, 'B', Size);
+ Pointers[J] = P;
+ }
+ for (scudo::uptr J = 0; J < NumberOfAllocations; J++)
+ Cache.deallocate(ClassId, Pointers[J]);
+ }
+ Cache.destroy(nullptr);
+ Allocator->releaseToOS();
+ Allocator->printStats();
+}
+
+TEST(ScudoPrimaryTest, BasicPrimary) {
+ using SizeClassMap = scudo::DefaultSizeClassMap;
+ testPrimary<scudo::SizeClassAllocator32<SizeClassMap, 18U>>();
+ testPrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U>>();
+}
+
+// The 64-bit SizeClassAllocator can be easily OOM'd with small region sizes.
+// For the 32-bit one, it requires actually exhausting memory, so we skip it.
+TEST(ScudoPrimaryTest, Primary64OOM) {
+ using Primary = scudo::SizeClassAllocator64<scudo::DefaultSizeClassMap, 20U>;
+ using TransferBatch = Primary::CacheT::TransferBatch;
+ Primary Allocator;
+ Allocator.init(/*ReleaseToOsInterval=*/-1);
+ typename Primary::CacheT Cache;
+ scudo::GlobalStats Stats;
+ Stats.init();
+ Cache.init(&Stats, &Allocator);
+ bool AllocationFailed = false;
+ std::vector<TransferBatch *> Batches;
+ const scudo::uptr ClassId = Primary::SizeClassMap::LargestClassId;
+ const scudo::uptr Size = Primary::getSizeByClassId(ClassId);
+ for (scudo::uptr I = 0; I < 10000U; I++) {
+ TransferBatch *B = Allocator.popBatch(&Cache, ClassId);
+ if (!B) {
+ AllocationFailed = true;
+ break;
+ }
+ for (scudo::uptr J = 0; J < B->getCount(); J++)
+ memset(B->get(J), 'B', Size);
+ Batches.push_back(B);
+ }
+ while (!Batches.empty()) {
+ Allocator.pushBatch(ClassId, Batches.back());
+ Batches.pop_back();
+ }
+ Cache.destroy(nullptr);
+ Allocator.releaseToOS();
+ Allocator.printStats();
+ EXPECT_EQ(AllocationFailed, true);
+ Allocator.unmapTestOnly();
+}
+
+template <typename Primary> static void testIteratePrimary() {
+ auto Deleter = [](Primary *P) {
+ P->unmapTestOnly();
+ delete P;
+ };
+ std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter);
+ Allocator->init(/*ReleaseToOsInterval=*/-1);
+ typename Primary::CacheT Cache;
+ Cache.init(nullptr, Allocator.get());
+ std::vector<std::pair<scudo::uptr, void *>> V;
+ for (scudo::uptr I = 0; I < 64U; I++) {
+ const scudo::uptr Size = std::rand() % Primary::SizeClassMap::MaxSize;
+ const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
+ void *P = Cache.allocate(ClassId);
+ V.push_back(std::make_pair(ClassId, P));
+ }
+ scudo::uptr Found = 0;
+ auto Lambda = [V, &Found](scudo::uptr Block) {
+ for (const auto &Pair : V) {
+ if (Pair.second == reinterpret_cast<void *>(Block))
+ Found++;
+ }
+ };
+ Allocator->disable();
+ Allocator->iterateOverBlocks(Lambda);
+ Allocator->enable();
+ EXPECT_EQ(Found, V.size());
+ while (!V.empty()) {
+ auto Pair = V.back();
+ Cache.deallocate(Pair.first, Pair.second);
+ V.pop_back();
+ }
+ Cache.destroy(nullptr);
+ Allocator->releaseToOS();
+ Allocator->printStats();
+}
+
+TEST(ScudoPrimaryTest, PrimaryIterate) {
+ using SizeClassMap = scudo::DefaultSizeClassMap;
+ testIteratePrimary<scudo::SizeClassAllocator32<SizeClassMap, 18U>>();
+ testIteratePrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U>>();
+}
+
+static std::mutex Mutex;
+static std::condition_variable Cv;
+static bool Ready = false;
+
+template <typename Primary> static void performAllocations(Primary *Allocator) {
+ static THREADLOCAL typename Primary::CacheT Cache;
+ Cache.init(nullptr, Allocator);
+ std::vector<std::pair<scudo::uptr, void *>> V;
+ {
+ std::unique_lock<std::mutex> Lock(Mutex);
+ while (!Ready)
+ Cv.wait(Lock);
+ }
+ for (scudo::uptr I = 0; I < 256U; I++) {
+ const scudo::uptr Size = std::rand() % Primary::SizeClassMap::MaxSize / 4;
+ const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
+ void *P = Cache.allocate(ClassId);
+ if (P)
+ V.push_back(std::make_pair(ClassId, P));
+ }
+ while (!V.empty()) {
+ auto Pair = V.back();
+ Cache.deallocate(Pair.first, Pair.second);
+ V.pop_back();
+ }
+ Cache.destroy(nullptr);
+}
+
+template <typename Primary> static void testPrimaryThreaded() {
+ auto Deleter = [](Primary *P) {
+ P->unmapTestOnly();
+ delete P;
+ };
+ std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter);
+ Allocator->init(/*ReleaseToOsInterval=*/-1);
+ std::thread Threads[32];
+ for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
+ Threads[I] = std::thread(performAllocations<Primary>, Allocator.get());
+ {
+ std::unique_lock<std::mutex> Lock(Mutex);
+ Ready = true;
+ Cv.notify_all();
+ }
+ for (auto &T : Threads)
+ T.join();
+ Allocator->releaseToOS();
+ Allocator->printStats();
+}
+
+TEST(ScudoPrimaryTest, PrimaryThreaded) {
+ using SizeClassMap = scudo::SvelteSizeClassMap;
+ testPrimaryThreaded<scudo::SizeClassAllocator32<SizeClassMap, 18U>>();
+ testPrimaryThreaded<scudo::SizeClassAllocator64<SizeClassMap, 24U>>();
+}
OpenPOWER on IntegriCloud