polymorphic_stack.cpp (5129B)
1 #include "libshit/container/polymorphic_stack.hpp" 2 3 #include "libshit/doctest.hpp" 4 #include "libshit/utils.hpp" 5 6 #include <tracy/Tracy.hpp> 7 8 #include <limits> 9 #include <vector> 10 11 #if !LIBSHIT_OS_IS_WINDOWS && !LIBSHIT_OS_IS_VITA 12 # include <unistd.h> 13 # include <sys/mman.h> 14 #endif 15 16 namespace Libshit 17 { 18 using namespace PolymorphicStackDetail; 19 TEST_SUITE_BEGIN("Libshit::PolymorphicStack"); 20 21 std::pair<void*, std::uint32_t> MallocAllocator::Alloc( 22 std::uint32_t size) noexcept 23 { 24 auto ptr = std::malloc(size); 25 TracyAllocS(ptr, size, 5); 26 return { ptr, size }; 27 } 28 29 void MallocAllocator::Free(void* ptr, std::uint32_t size) noexcept 30 { 31 TracyFreeS(ptr, 5); 32 std::free(ptr); 33 } 34 35 #if !LIBSHIT_OS_IS_WINDOWS && !LIBSHIT_OS_IS_VITA 36 37 static const std::size_t PAGE_SIZE_M1 = sysconf(_SC_PAGE_SIZE) - 1; 38 39 std::pair<void*, std::uint32_t> MmapAllocator::Alloc( 40 std::uint32_t size) noexcept 41 { 42 if (std::numeric_limits<std::uint32_t>::max() - size < PAGE_SIZE_M1) 43 return { nullptr, 0 }; 44 45 size = (size + PAGE_SIZE_M1) & ~PAGE_SIZE_M1; 46 auto ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, 47 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 48 if (ptr == MAP_FAILED) return { nullptr, 0 }; 49 50 TracyAllocS(ptr, size, 5); 51 return { ptr, size }; 52 } 53 54 void MmapAllocator::Free(void* ptr, std::uint32_t size) noexcept 55 { 56 TracyFreeS(ptr, 5); 57 munmap(ptr, size); 58 } 59 60 #endif 61 62 static bool disable_mremap [[maybe_unused]] = false; 63 #if LIBSHIT_OS_IS_LINUX 64 std::uint32_t MmapAllocator::Extend( 65 void* ptr, std::uint32_t old_size, std::uint32_t min_size, 66 std::uint32_t max_size) noexcept 67 { 68 LIBSHIT_ASSERT(max_size >= min_size); 69 if (disable_mremap) return 0; 70 if (std::numeric_limits<std::uint32_t>::max() - max_size < PAGE_SIZE_M1) 71 return 0; 72 73 max_size = (max_size + PAGE_SIZE_M1) & ~PAGE_SIZE_M1; 74 if (mremap(ptr, old_size, max_size, 0) != MAP_FAILED) return max_size; 75 min_size = (min_size + PAGE_SIZE_M1) & ~PAGE_SIZE_M1; 76 if (mremap(ptr, old_size, min_size, 0) != MAP_FAILED) return min_size; 77 return 0; 78 } 79 #endif 80 81 namespace 82 { 83 struct Base { virtual ~Base() = default; }; 84 template <std::size_t N> 85 struct X : Base { char x[N]; }; 86 } 87 88 TEST_CASE_TEMPLATE_DEFINE("PolymorphicStack", T, polymorphic_stack) 89 { 90 T stack; 91 CHECK(stack.Size() == 0); 92 CHECK(stack.Empty()); 93 94 SUBCASE("simple push/pop") 95 { 96 auto& x = stack.template Push<Base>(); 97 CHECK(stack.Size() == 1); 98 CHECK(!stack.Empty()); 99 CHECK(&stack.Back() == &x); 100 101 stack.Pop(); 102 CHECK(stack.Size() == 0); 103 CHECK(stack.Empty()); 104 } 105 106 SUBCASE("push multiple") 107 { 108 auto& a = stack.template Push<Base>(); 109 auto& b = stack.template Push<X<16>>(); 110 CHECK(reinterpret_cast<uintptr_t>(&b) == 111 reinterpret_cast<uintptr_t>(&a) + sizeof(Base) + sizeof(Head)); 112 auto& c = stack.template Push<X<32>>(); 113 CHECK(reinterpret_cast<uintptr_t>(&c) == 114 reinterpret_cast<uintptr_t>(&b) + sizeof(X<16>) + sizeof(Head)); 115 116 CHECK(&stack.Back() == &c); CHECK(stack.Size() == 3); stack.Pop(); 117 CHECK(&stack.Back() == &b); CHECK(stack.Size() == 2); stack.Pop(); 118 CHECK(&stack.Back() == &a); CHECK(stack.Size() == 1); stack.Pop(); 119 CHECK(stack.Size() == 0); CHECK(stack.Empty()); 120 } 121 122 SUBCASE("push until overflow") 123 { 124 disable_mremap = true; 125 AtScopeExit x{[]() { disable_mremap = false; }}; 126 127 auto& first = stack.template Push<X<128>>(); 128 std::vector<X<128>*> vec{&first}; 129 do 130 { 131 REQUIRE(vec.size() < 100'000); // do not eat all of the memory 132 vec.push_back(&stack.template Push<X<128>>()); 133 } 134 while (reinterpret_cast<char*>(vec.back()) == 135 reinterpret_cast<char*>(&first) + 136 (sizeof(Head) + sizeof(X<128>)) * (vec.size()-1)); 137 CCAPTURE(vec.size()); 138 139 std::size_t n = vec.size(); 140 for (auto it = vec.rbegin(); it != vec.rend(); ++it) 141 { 142 CHECK(stack.Size() == n); 143 CHECK(stack.Empty() == !n); 144 --n; 145 CHECK(&stack.Back() == *it); 146 stack.Pop(); 147 } 148 149 SUBCASE("overflow head") 150 { 151 auto& foo = stack.template Push<X<65536>>(); 152 CHECK(&stack.Back() == &foo); 153 CHECK(stack.Size() == 1); 154 } 155 SUBCASE("overflow tail") 156 { 157 auto& head = stack.template Push<Base>(); 158 auto& foo = stack.template Push<X<65536>>(); 159 CHECK(&stack.Back() == &foo); 160 CHECK(stack.Size() == 2); 161 stack.Pop(); 162 CHECK(&stack.Back() == &head); 163 } 164 } 165 } 166 167 TEST_CASE_TEMPLATE_INVOKE( 168 polymorphic_stack, PolymorphicStack<Base, MallocAllocator>); 169 #if !LIBSHIT_OS_IS_WINDOWS && !LIBSHIT_OS_IS_VITA 170 TEST_CASE_TEMPLATE_INVOKE( 171 polymorphic_stack, PolymorphicStack<Base, MmapAllocator>); 172 #endif 173 174 TEST_SUITE_END(); 175 } 176 177 TYPE_TO_STRING(Libshit::PolymorphicStack<Libshit::Base, Libshit::MallocAllocator>); 178 #if !LIBSHIT_OS_IS_WINDOWS && !LIBSHIT_OS_IS_VITA 179 TYPE_TO_STRING(Libshit::PolymorphicStack<Libshit::Base, Libshit::MmapAllocator>); 180 #endif