diff --git a/include/blt/std/memory.h b/include/blt/std/memory.h index 9ae3f37..06b574a 100755 --- a/include/blt/std/memory.h +++ b/include/blt/std/memory.h @@ -11,6 +11,7 @@ #include #include #include "queue.h" +#include "utility.h" #include #include #include @@ -519,7 +520,7 @@ namespace blt private: struct pointer_view { - const_pointer p; + pointer p; size_t n; }; @@ -531,13 +532,65 @@ namespace blt std::vector unallocated_blocks; }; - void allocate_block() + inline void allocate_block() { BLT_INFO("Allocating a new block of size %d", BLOCK_SIZE); - block_storage blk; - blk.data = static_cast(malloc(sizeof(T) * BLOCK_SIZE)); + auto* blk = new block_storage(); + blk->data = static_cast(malloc(sizeof(T) * BLOCK_SIZE)); blocks.push_back(blk); } + + inline pointer find_available_block(size_t n) + { + for (auto* blk : blocks) + { + size_t index = -1ull; + size_t leftover = 0; + for (auto kv : blt::enumerate(blk->unallocated_blocks)) + { + if (kv.second.n >= n) + { + index = kv.first; + leftover = kv.second.n - n; + break; + } + } + if (index != -1ull) + { + pointer_view ptr = blk->unallocated_blocks[index]; + std::iter_swap(blk->unallocated_blocks.begin() + index, blk->unallocated_blocks.end() - 1); + blk->unallocated_blocks.pop_back(); +// BLT_INFO("Found block! %d, Unallocated leftover %d", index, leftover); + if (leftover > 0) + blk->unallocated_blocks.push_back({ptr.p + n, leftover}); + return ptr.p; + } + } + return nullptr; + } + + inline std::pair getBlock(size_t n) + { + auto* blk = find_available_block(n); + if (blk != nullptr) + return {blk, 0}; + + if (blocks.back()->used + n > BLOCK_SIZE) + allocate_block(); + + auto ptr = std::pair{blocks.back()->data, blocks.back()->used}; + blocks.back()->used += n; + return ptr; + } + + inline void allocate_in_block(pointer begin, size_t n) + { + if constexpr (std::is_default_constructible_v && !std::is_trivially_default_constructible_v) + { + for (size_t i = 0; i < n; i++) + new(&begin[i]) T(); + } + } public: area_allocator() @@ -548,26 +601,14 @@ namespace blt [[nodiscard]] pointer allocate(size_t n) { if (n > BLOCK_SIZE) - { - // handle cases where they want to allocate one large huge block - // in this case we do not care about it since the allocator is meant for small object allocations throw std::runtime_error("Requested allocation is too large!"); - } - // TODO: something better - if (blocks.back().used + n > BLOCK_SIZE) - { - BLT_TRACE("Moving to a new block"); - allocate_block(); - } - auto& current_block = blocks.back(); - auto* ptr = &blocks.back().data[current_block.used]; - if constexpr (std::is_default_constructible_v && !std::is_trivially_default_constructible_v) - { - for (size_t i = 0; i < n; i++) - new(&ptr[i]) T(); - } - current_block.used += n; + auto block_info = getBlock(n); + + auto* ptr = &block_info.first[block_info.second]; + // call constructors on the objects if they require it + allocate_in_block(ptr, n); + return ptr; } @@ -575,11 +616,11 @@ namespace blt { for (size_t i = 0; i < n; i++) p[i].~T(); - for (auto& blk : blocks) + for (auto*& blk : blocks) { - if (p >= blk.data && p <= (blk.data + BLOCK_SIZE)) + if (p >= blk->data && p <= (blk->data + BLOCK_SIZE)) { - blk.unallocated_blocks.push_back({p, n}); + blk->unallocated_blocks.push_back({p, n}); break; } } @@ -587,30 +628,15 @@ namespace blt ~area_allocator() { - for (auto& blk : blocks) + for (auto*& blk : blocks) { -// for (size_t i = 0; i < blk.used; i++) -// { -// bool alreadyDeallocated = false; -// for (const auto& dealoc : blk.unallocated_blocks) -// { -// auto pos = (blk.data + i); -// if (pos >= dealoc.p && pos <= (dealoc.p + dealoc.n)) -// { -// alreadyDeallocated = true; -// break; -// } -// } -// if (!alreadyDeallocated) -// blk.data[i].~T(); -// } - // it is UB to not deallocate allocated memory. Get fucked. - free(blk.data); + free(blk->data); + delete blk; } } private: - std::vector blocks; + std::vector blocks; }; } diff --git a/tests/src/memory_test.cpp b/tests/src/memory_test.cpp index 403dcd3..9551ee2 100644 --- a/tests/src/memory_test.cpp +++ b/tests/src/memory_test.cpp @@ -22,6 +22,8 @@ #include #include #include "blt/std/utility.h" +#include +#include template blt::scoped_buffer create_scoped_buffer(size_t size) @@ -136,67 +138,133 @@ void blt::test::memory::static_vector_test() BLT_DEBUG_STREAM << '\n'; } -struct fucked_type1 -{ - private: - int T = 0; - public: - fucked_type1() = default; -}; - struct fucked_type2 { public: + static constexpr size_t initial_value = 50; int T = 0; public: fucked_type2() { - T = 50; - BLT_DEBUG("I HAVE BEEN CONSTRUCTED"); + T = initial_value; + //BLT_DEBUG("I HAVE BEEN CONSTRUCTED"); + } + + void set(int t) + { + T = t; } ~fucked_type2() { - BLT_DEBUG("I HAVE BEEN DESTRUCTED!"); + //BLT_DEBUG("I HAVE BEEN DESTRUCTED!"); } }; +#define ALLOC(alloc, amount) alloc.allocate(amount), amount + +/** + * run tests to make sure that we can actually allocate blocks of memory. + * we are using a custom type to ensure that the state is known and the example is complex enough + * if this work then it should work for any generic type + */ +template +void test_allocations_1() +{ + std::vector> types; + blt::area_allocator int_test{}; + + types.emplace_back(ALLOC(int_test, static_cast(allocator_size * 0.75))); + for (size_t i = 0; i < static_cast(allocator_size * 0.30); i++) + { + types.emplace_back(ALLOC(int_test, 1)); + auto v = std::pair{ALLOC(int_test, 1)}; + v.first->set(120); + int_test.deallocate(v.first, 1); + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); + } + + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); + + bool passed = true; + + std::unordered_set used_pointers; + + for (const auto& pair : types) + { + for (size_t i = 0; i < pair.second; i++) + { + // every value should be the initial value assigned in the constructor + // if this isn't the case there was an error. + if (pair.first[i].T != fucked_type2::initial_value) + { + BLT_WARN("We have an allocated value that isn't initial at index %d (allocated in a block of size %d at pointer %p)", i, pair.second, + pair.first); + passed = false; + break; + } + // every allocation here should be unique. + // if we have a pointer in our list which is not unique, + // we know we have an error + if (BLT_CONTAINS(used_pointers, &pair.first[i])) + { + BLT_WARN( + "We have found another pointer which was allocated as a unique block but isn't (in block %d with size %d; pointer in question: %p)", + i, pair.second, pair.first); + passed = false; + break; + } + used_pointers.insert(&pair.first[i]); + } + int_test.deallocate(pair.first, pair.second); + } + if (passed) + BLT_INFO("Test (1) with size %d passed!", allocator_size); + else + BLT_ERROR("Test (1) with size %d failed!", allocator_size); +} + void blt::test::memory::test() { + test_allocations_1(); + test_allocations_1<50>(); + test_allocations_1<4096>(); + + std::vector> types; area_allocator int_test{}; //auto arr = int_test.allocate(10); - auto arr2 = int_test.allocate(15); - blt::black_box(int_test.allocate(1)); - blt::black_box(int_test.allocate(1)); - blt::black_box(int_test.allocate(1)); - blt::black_box(int_test.allocate(1)); - blt::black_box(int_test.allocate(1)); + types.emplace_back(ALLOC(int_test, 15)); + types.emplace_back(ALLOC(int_test, 1)); + auto v = std::pair{ALLOC(int_test, 1)}; + v.first->set(120); + int_test.deallocate(v.first, 1); + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); - blt::black_box(int_test.allocate(1)); - blt::black_box(int_test.allocate(1)); - blt::black_box(int_test.allocate(1)); - blt::black_box(int_test.allocate(1)); - blt::black_box(int_test.allocate(1)); + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); + types.emplace_back(ALLOC(int_test, 1)); //blt::black_box(arr4); BLT_INFO("CUM"); -// arr3 = int_test.allocate(2); -// blt::black_box(arr3); -// arr3 = int_test.allocate(5); -// blt::black_box(arr3); -// arr3 = int_test.allocate(10); -// blt::black_box(arr3); - //auto arr3 = int_test.allocate(20); - //std::memset(arr, 0, 10); - //std::memset(arr2, 0, 15); - //std::memset(arr3, 0, 20); - - for (int i = 0; i < 15; i++) + for (const auto& pair : types) { - BLT_TRACE_STREAM << arr2[i].T << ' '; + BLT_TRACE("Pointer: %p", pair.first); + for (size_t i = 0; i < pair.second; i++) + { + BLT_TRACE_STREAM << pair.first[i].T << ' '; + } + BLT_TRACE_STREAM << '\n'; + int_test.deallocate(pair.first, pair.second); + BLT_INFO("-----------------"); } - BLT_TRACE_STREAM << "\n"; - - int_test.deallocate(arr2, 15); - BLT_INFO("-----------------"); }