blob: bbc24ea8d5f7af2195f49c421e3cf39bcee4160f [file] [log] [blame]
/** @file
MemArena unit tests.
@section license License
Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements. See the NOTICE file distributed with this work for additional information regarding
copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with the License. You may
obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the specific language governing permissions and
limitations under the License.
*/
#include <array>
#include <string_view>
#include <string>
#include <map>
#include <set>
#include <random>
#include "swoc/MemArena.h"
#include "swoc/TextView.h"
#include "catch.hpp"
using swoc::MemSpan;
using swoc::MemArena;
using swoc::FixedArena;
using std::string_view;
using swoc::TextView;
using namespace std::literals;
static constexpr std::string_view CHARS{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/."};
std::uniform_int_distribution<short> char_gen{0, short(CHARS.size() - 1)};
std::minstd_rand randu;
namespace
{
TextView
localize(MemArena &arena, TextView const &view)
{
auto span = arena.alloc(view.size()).rebind<char>();
memcpy(span, view);
return span;
}
} // namespace
TEST_CASE("MemArena generic", "[libswoc][MemArena]")
{
swoc::MemArena arena{64};
REQUIRE(arena.size() == 0);
REQUIRE(arena.reserved_size() == 0);
arena.alloc(0);
REQUIRE(arena.size() == 0);
REQUIRE(arena.reserved_size() >= 64);
REQUIRE(arena.remaining() >= 64);
swoc::MemSpan span1 = arena.alloc(32);
REQUIRE(span1.size() == 32);
REQUIRE(arena.remaining() >= 32);
swoc::MemSpan span2 = arena.alloc(32);
REQUIRE(span2.size() == 32);
REQUIRE(span1.data() != span2.data());
REQUIRE(arena.size() == 64);
auto extent{arena.reserved_size()};
span1 = arena.alloc(128);
REQUIRE(extent < arena.reserved_size());
arena.clear();
arena.alloc(17);
span1 = arena.alloc(16, 8);
REQUIRE((uintptr_t(span1.data()) & 0x7) == 0);
REQUIRE(span1.size() == 16);
span2 = arena.alloc(16, 16);
REQUIRE((uintptr_t(span2.data()) & 0xF) == 0);
REQUIRE(span2.size() == 16);
REQUIRE(span2.data() >= span1.data_end());
}
TEST_CASE("MemArena freeze and thaw", "[libswoc][MemArena]")
{
MemArena arena;
MemSpan span1{arena.alloc(1024)};
REQUIRE(span1.size() == 1024);
REQUIRE(arena.size() == 1024);
REQUIRE(arena.reserved_size() >= 1024);
arena.freeze();
REQUIRE(arena.size() == 0);
REQUIRE(arena.allocated_size() == 1024);
REQUIRE(arena.reserved_size() >= 1024);
arena.thaw();
REQUIRE(arena.size() == 0);
REQUIRE(arena.allocated_size() == 0);
REQUIRE(arena.reserved_size() == 0);
span1 = arena.alloc(1024);
arena.freeze();
auto extent{arena.reserved_size()};
arena.alloc(512);
REQUIRE(arena.reserved_size() > extent); // new extent should be bigger.
arena.thaw();
REQUIRE(arena.size() == 512);
REQUIRE(arena.reserved_size() >= 1024);
arena.clear();
REQUIRE(arena.size() == 0);
REQUIRE(arena.reserved_size() == 0);
span1 = arena.alloc(262144);
arena.freeze();
extent = arena.reserved_size();
arena.alloc(512);
REQUIRE(arena.reserved_size() > extent); // new extent should be bigger.
arena.thaw();
REQUIRE(arena.size() == 512);
REQUIRE(arena.reserved_size() >= 262144);
arena.clear();
span1 = arena.alloc(262144);
extent = arena.reserved_size();
arena.freeze();
for (int i = 0; i < 262144 / 512; ++i)
arena.alloc(512);
REQUIRE(arena.reserved_size() > extent); // Bigger while frozen memory is still around.
arena.thaw();
REQUIRE(arena.size() == 262144);
REQUIRE(arena.reserved_size() == extent); // should be identical to before freeze.
arena.alloc(512);
arena.alloc(768);
arena.freeze(32000);
arena.thaw();
arena.alloc(0);
REQUIRE(arena.reserved_size() >= 32000);
REQUIRE(arena.reserved_size() < 2 * 32000);
}
TEST_CASE("MemArena helper", "[libswoc][MemArena]")
{
struct Thing {
int ten{10};
std::string name{"name"};
Thing() {}
Thing(int x) : ten(x) {}
Thing(std::string const &s) : name(s) {}
Thing(int x, std::string_view s) : ten(x), name(s) {}
Thing(std::string const &s, int x) : ten(x), name(s) {}
};
swoc::MemArena arena{256};
REQUIRE(arena.size() == 0);
swoc::MemSpan s = arena.alloc(56).rebind<char>();
REQUIRE(arena.size() == 56);
REQUIRE(arena.remaining() >= 200);
void *ptr = s.begin();
REQUIRE(arena.contains((char *)ptr));
REQUIRE(arena.contains((char *)ptr + 100)); // even though span isn't this large, this pointer should still be in arena
REQUIRE(!arena.contains((char *)ptr + 300));
REQUIRE(!arena.contains((char *)ptr - 1));
arena.freeze(128);
REQUIRE(arena.contains((char *)ptr));
REQUIRE(arena.contains((char *)ptr + 100));
swoc::MemSpan s2 = arena.alloc(10).rebind<char>();
void *ptr2 = s2.begin();
REQUIRE(arena.contains((char *)ptr));
REQUIRE(arena.contains((char *)ptr2));
REQUIRE(arena.allocated_size() == 56 + 10);
arena.thaw();
REQUIRE(!arena.contains((char *)ptr));
REQUIRE(arena.contains((char *)ptr2));
Thing *thing_one{arena.make<Thing>()};
REQUIRE(thing_one->ten == 10);
REQUIRE(thing_one->name == "name");
thing_one = arena.make<Thing>(17, "bob"sv);
REQUIRE(thing_one->name == "bob");
REQUIRE(thing_one->ten == 17);
thing_one = arena.make<Thing>("Dave", 137);
REQUIRE(thing_one->name == "Dave");
REQUIRE(thing_one->ten == 137);
thing_one = arena.make<Thing>(9999);
REQUIRE(thing_one->ten == 9999);
REQUIRE(thing_one->name == "name");
thing_one = arena.make<Thing>("Persia");
REQUIRE(thing_one->ten == 10);
REQUIRE(thing_one->name == "Persia");
}
TEST_CASE("MemArena large alloc", "[libswoc][MemArena]")
{
swoc::MemArena arena;
auto s = arena.alloc(4000);
REQUIRE(s.size() == 4000);
decltype(s) s_a[10];
s_a[0] = arena.alloc(100);
s_a[1] = arena.alloc(200);
s_a[2] = arena.alloc(300);
s_a[3] = arena.alloc(400);
s_a[4] = arena.alloc(500);
s_a[5] = arena.alloc(600);
s_a[6] = arena.alloc(700);
s_a[7] = arena.alloc(800);
s_a[8] = arena.alloc(900);
s_a[9] = arena.alloc(1000);
// ensure none of the spans have any overlap in memory.
for (int i = 0; i < 10; ++i) {
s = s_a[i];
for (int j = i + 1; j < 10; ++j) {
REQUIRE(s_a[i].data() != s_a[j].data());
}
}
}
TEST_CASE("MemArena block allocation", "[libswoc][MemArena]")
{
swoc::MemArena arena{64};
swoc::MemSpan s = arena.alloc(32).rebind<char>();
swoc::MemSpan s2 = arena.alloc(16).rebind<char>();
swoc::MemSpan s3 = arena.alloc(16).rebind<char>();
REQUIRE(s.size() == 32);
REQUIRE(arena.allocated_size() == 64);
REQUIRE(arena.contains((char *)s.begin()));
REQUIRE(arena.contains((char *)s2.begin()));
REQUIRE(arena.contains((char *)s3.begin()));
REQUIRE((char *)s.begin() + 32 == (char *)s2.begin());
REQUIRE((char *)s.begin() + 48 == (char *)s3.begin());
REQUIRE((char *)s2.begin() + 16 == (char *)s3.begin());
REQUIRE(s.end() == s2.begin());
REQUIRE(s2.end() == s3.begin());
REQUIRE((char *)s.begin() + 64 == s3.end());
}
TEST_CASE("MemArena full blocks", "[libswoc][MemArena]")
{
// couple of large allocations - should be exactly sized in the generation.
size_t init_size = 32000;
swoc::MemArena arena(init_size);
MemSpan m1{arena.alloc(init_size - 64).rebind<uint8_t>()};
MemSpan m2{arena.alloc(32000).rebind<unsigned char>()};
MemSpan m3{arena.alloc(64000).rebind<char>()};
REQUIRE(arena.remaining() >= 64);
REQUIRE(arena.reserved_size() > 32000 + 64000 + init_size);
REQUIRE(arena.reserved_size() < 2 * (32000 + 64000 + init_size));
// Let's see if that memory is really there.
memset(m1, 0xa5);
memset(m2, 0xc2);
memset(m3, 0x56);
REQUIRE(std::all_of(m1.begin(), m1.end(), [](uint8_t c) { return 0xa5 == c; }));
REQUIRE(std::all_of(m2.begin(), m2.end(), [](unsigned char c) { return 0xc2 == c; }));
REQUIRE(std::all_of(m3.begin(), m3.end(), [](char c) { return 0x56 == c; }));
}
TEST_CASE("MemArena esoterica", "[libswoc][MemArena]")
{
MemArena a1;
MemSpan<char> span;
{
MemArena alpha{1020};
alpha.alloc(1);
REQUIRE(alpha.remaining() >= 1019);
}
{
MemArena alpha{4092};
alpha.alloc(1);
REQUIRE(alpha.remaining() >= 4091);
}
{
MemArena alpha{4096};
alpha.alloc(1);
REQUIRE(alpha.remaining() >= 4095);
}
{
MemArena a2{512};
span = a2.alloc(128).rebind<char>();
REQUIRE(a2.contains(span.data()));
a1 = std::move(a2);
}
REQUIRE(a1.contains(span.data()));
REQUIRE(a1.remaining() >= 384);
{
MemArena *arena = MemArena::construct_self_contained();
arena->~MemArena();
}
{
MemArena *arena = MemArena::construct_self_contained();
MemArena::destroyer(arena);
}
{
std::unique_ptr<MemArena, void (*)(MemArena *)> arena(MemArena::construct_self_contained(),
[](MemArena *arena) -> void { arena->~MemArena(); });
static constexpr unsigned MAX = 512;
std::uniform_int_distribution<unsigned> length_gen{6, MAX};
char buffer[MAX];
for (unsigned i = 0; i < 50; ++i) {
auto n = length_gen(randu);
for (unsigned k = 0; k < n; ++k) {
buffer[k] = CHARS[char_gen(randu)];
}
localize(*arena, {buffer, n});
}
// Really, at this point just make sure there's no memory corruption on destruction.
}
{ // as previous but delay construction. Use internal functor instead of a lambda.
std::unique_ptr<MemArena, void (*)(MemArena*)> arena(nullptr, MemArena::destroyer);
arena.reset(MemArena::construct_self_contained());
static constexpr unsigned MAX = 512;
std::uniform_int_distribution<unsigned> length_gen{6, MAX};
char buffer[MAX];
for (unsigned i = 0; i < 50; ++i) {
auto n = length_gen(randu);
for (unsigned k = 0; k < n; ++k) {
buffer[k] = CHARS[char_gen(randu)];
}
localize(*arena, {buffer, n});
}
// Really, at this point just make sure there's no memory corruption on destruction.
}
{ // Construct immediately in the unique pointer.
MemArena::unique_ptr arena(MemArena::construct_self_contained(), MemArena::destroyer);
static constexpr unsigned MAX = 512;
std::uniform_int_distribution<unsigned> length_gen{6, MAX};
char buffer[MAX];
for (unsigned i = 0; i < 50; ++i) {
auto n = length_gen(randu);
for (unsigned k = 0; k < n; ++k) {
buffer[k] = CHARS[char_gen(randu)];
}
localize(*arena, {buffer, n});
}
// Really, at this point just make sure there's no memory corruption on destruction.
}
{ // as previous but delay construction. Use destroy_at instead of a lambda.
MemArena::unique_ptr arena(nullptr, MemArena::destroyer);
arena.reset(MemArena::construct_self_contained());
}
{ // And what if the arena is never constructed?
struct Thing {
int x;
std::unique_ptr<MemArena, void (*)(MemArena *)> arena{nullptr, std::destroy_at<MemArena>};
} thing;
thing.x = 56; // force access to instance.
}
}
// --- temporary allocation
TEST_CASE("MemArena temporary", "[libswoc][MemArena][tmp]")
{
MemArena arena;
std::vector<std::string_view> strings;
static constexpr short MAX{8000};
static constexpr int N{100};
std::uniform_int_distribution<unsigned> length_gen{100, MAX};
std::array<char, MAX> url;
REQUIRE(arena.remaining() == 0);
int i;
unsigned max{0};
for (i = 0; i < N; ++i) {
auto n = length_gen(randu);
max = std::max(max, n);
arena.require(n);
auto span = arena.remnant().rebind<char>();
if (span.size() < n)
break;
for (auto j = n; j > 0; --j) {
span[j - 1] = url[j - 1] = CHARS[char_gen(randu)];
}
if (string_view{span.data(), n} != string_view{url.data(), n})
break;
}
REQUIRE(i == N); // did all the loops.
REQUIRE(arena.size() == 0); // nothing actually allocated.
// Hard to get a good value, but shouldn't be more than twice.
REQUIRE(arena.reserved_size() < 2 * MAX);
// Should be able to allocate at least the longest string without increasing the reserve size.
unsigned rsize = arena.reserved_size();
auto count = max;
std::uniform_int_distribution<unsigned> alloc_size{32, 128};
while (count >= 128) { // at least the max distribution value
auto k = alloc_size(randu);
arena.alloc(k);
count -= k;
}
REQUIRE(arena.reserved_size() == rsize);
// Check for switching full blocks - calculate something like the total free space
// and then try to allocate most of it without increasing the reserved size.
count = rsize - (max - count);
while (count >= 128) {
auto k = alloc_size(randu);
arena.alloc(k);
count -= k;
}
REQUIRE(arena.reserved_size() == rsize);
}
TEST_CASE("FixedArena", "[libswoc][FixedArena]") {
struct Thing {
int x = 0;
std::string name;
};
MemArena arena;
FixedArena<Thing> fa{arena};
[[maybe_unused]] Thing * one = fa.make();
Thing * two = fa.make();
two->x = 17;
two->name = "Bob";
fa.destroy(two);
Thing * three = fa.make();
REQUIRE(three == two); // reused instance.
REQUIRE(three->x == 0); // but reconstructed.
REQUIRE(three->name.empty() == true);
fa.destroy(three);
std::array<Thing*, 17> things;
for ( auto & ptr : things ) {
ptr = fa.make();
}
two = things[things.size() - 1];
for ( auto & ptr : things) {
fa.destroy(ptr);
}
three = fa.make();
REQUIRE(two == three);
};
// RHEL 7 compatibility - std::pmr::string isn't available even though the header exists unless
// _GLIBCXX_USE_CXX11_ABI is defined and non-zero. It appears to always be defined for the RHEL
// toolsets, so if undefined that's OK.
#if __has_include(<memory_resource>) && ( !defined(_GLIBCXX_USE_CXX11_ABI) || _GLIBCXX_USE_CXX11_ABI)
struct PMR {
bool* _flag;
PMR(bool& flag) : _flag(&flag) {}
PMR(PMR && that) : _flag(that._flag) {
that._flag = nullptr;
}
~PMR() { if (_flag) *_flag = true; }
};
// External container using a MemArena.
TEST_CASE("PMR 1", "[libswoc][arena][pmr]") {
static const std::pmr::string BRAVO { "bravo bravo bravo bravo"}; // avoid small string opt.
using C = std::pmr::map<std::pmr::string, PMR>;
bool flags[3] = { false, false, false };
MemArena arena;
{
C c{&arena};
REQUIRE(arena.size() == 0);
c.insert(C::value_type{"alpha", PMR(flags[0])});
c.insert(C::value_type{BRAVO, PMR(flags[1])});
c.insert(C::value_type{"charlie", PMR(flags[2])});
REQUIRE(arena.size() > 0);
auto spot = c.find(BRAVO);
REQUIRE(spot != c.end());
REQUIRE(arena.contains(&*spot));
REQUIRE(arena.contains(spot->first.data()));
}
// Check the map was destructed.
REQUIRE(flags[0] == true);
REQUIRE(flags[1] == true);
REQUIRE(flags[2] == true);
}
// Container inside MemArena, using the MemArena.
TEST_CASE("PMR 2", "[libswoc][arena][pmr]") {
using C = std::pmr::map<std::pmr::string, PMR>;
bool flags[3] = { false, false, false };
{
static const std::pmr::string BRAVO { "bravo bravo bravo bravo"}; // avoid small string opt.
MemArena arena;
REQUIRE(arena.size() == 0);
C *c = arena.make<C>(&arena);
auto base = arena.size();
REQUIRE(base > 0);
c->insert(C::value_type{"alpha", PMR(flags[0])});
c->insert(C::value_type{BRAVO, PMR(flags[1])});
c->insert(C::value_type{"charlie", PMR(flags[2])});
REQUIRE(arena.size() > base);
auto spot = c->find(BRAVO);
REQUIRE(spot != c->end());
REQUIRE(arena.contains(&*spot));
REQUIRE(arena.contains(spot->first.data()));
}
// Check the map was not destructed.
REQUIRE(flags[0] == false);
REQUIRE(flags[1] == false);
REQUIRE(flags[2] == false);
}
// Container inside MemArena, using the MemArena.
TEST_CASE("PMR 3", "[libswoc][arena][pmr]") {
using C = std::pmr::set<std::pmr::string>;
MemArena arena;
REQUIRE(arena.size() == 0);
C *c = arena.make<C>(&arena);
auto base = arena.size();
REQUIRE(base > 0);
c->insert("alpha");
c->insert("bravo");
c->insert("charlie");
c->insert("delta");
c->insert("foxtrot");
c->insert("golf");
REQUIRE(arena.size() > base);
c->erase("charlie");
c->erase("delta");
c->erase("alpha");
// This includes all of the strings.
auto pre = arena.allocated_size();
arena.freeze();
// Copy the set into the arena.
C *gc = arena.make<C>(&arena);
*gc = *c;
auto frozen = arena.allocated_size();
REQUIRE(frozen > pre);
// Sparse set should be in the frozen memory, and discarded.
arena.thaw();
auto post = arena.allocated_size();
REQUIRE(frozen > post);
REQUIRE(pre > post);
}
TEST_CASE("MemArena static", "[libswoc][MemArena][static]")
{
static constexpr size_t SIZE = 2048;
std::byte buffer[SIZE];
MemArena arena{{buffer, SIZE}};
REQUIRE(arena.remaining() > 0);
REQUIRE(arena.remaining() < SIZE);
REQUIRE(arena.size() == 0);
// Allocate something and make sure it's in the static area.
auto span = arena.alloc(1024);
REQUIRE(true == (buffer <= span.data() && span.data() < buffer + SIZE));
span = arena.remnant(); // require the remnant to still be in the buffer.
REQUIRE(true == (buffer <= span.data() && span.data() < buffer + SIZE));
// This can't fit, must be somewhere other than the buffer.
span = arena.alloc(SIZE);
REQUIRE(false == (buffer <= span.data() && span.data() < buffer + SIZE));
MemArena arena2{std::move(arena)};
REQUIRE(arena2.size() > 0);
arena2.freeze();
arena2.thaw();
REQUIRE(arena.size() == 0);
REQUIRE(arena2.size() == 0);
// Now let @a arena2 destruct.
}
#endif // has memory_resource header.