diff --git a/CMakeLists.txt b/CMakeLists.txt index d3faf98..f1c6de4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.5) include(cmake/color.cmake) -set(BLT_VERSION 0.14.7) +set(BLT_VERSION 0.14.8) set(BLT_TEST_VERSION 0.0.1) set(BLT_TARGET BLT) diff --git a/include/blt/std/allocator.h b/include/blt/std/allocator.h index f7c51b3..907fd2b 100644 --- a/include/blt/std/allocator.h +++ b/include/blt/std/allocator.h @@ -540,14 +540,36 @@ namespace blt } }; + // size of 2mb in bytes inline constexpr blt::size_t BLT_2MB_SIZE = 4096 * 512; + /** + * blt::bump_allocator. Allocates blocks of BLOCK_SIZE with zero reuse. When all objects from a block are fully deallocated the block will be freed + * @tparam BLOCK_SIZE size of block to use. recommended to be multiple of page size or huge page size. + * @tparam USE_HUGE allocate using mmap and huge pages. If this fails it will use mmap to allocate normally. defaults to off because linux has parent huge pages. + * @tparam HUGE_PAGE_SIZE size the system allows huge pages to be. defaults to 2mb + * @tparam WARN_ON_FAIL print warning messages if allocating huge pages fail + */ template class bump_allocator { - // power of two + // ensure power of two static_assert(((BLOCK_SIZE & (BLOCK_SIZE - 1)) == 0) && "Must be a power of two!"); + public: + /** + * convert any pointer back into a pointer its block + */ + template + static inline auto to_block(T* p) + { + return reinterpret_cast(reinterpret_cast(p) & static_cast(~(BLOCK_SIZE - 1))); + } + private: + /** + * Logging function used for handling mmap errors. call after a failed mmap call. + * @param LOG_FUNC function to log with, must be a BLT_*_STREAM + */ template static void handle_mmap_error(LOG_FUNC func = BLT_ERROR_STREAM) { @@ -623,11 +645,18 @@ namespace blt } }; - static constexpr blt::size_t BASE_REMAINDER = BLOCK_SIZE - sizeof(typename block::block_metadata_t); + // remaining space inside the block after accounting for the metadata + static constexpr blt::size_t BLOCK_REMAINDER = BLOCK_SIZE - sizeof(typename block::block_metadata_t); block* base = nullptr; block* head = nullptr; + /** + * Handles the allocation of the bytes for the block. + * This function will either use mmap to allocate huge pages if requested + * or use std::align_alloc to create an aligned allocation + * @return pointer to a constructed block + */ block* allocate_block() { block* buffer; @@ -675,23 +704,43 @@ namespace blt return buffer; } + /** + * Allocates a new block and pushes it to the front of the linked listed + */ void allocate_forward() { auto* block = allocate_block(); + if (head == nullptr) + { + base = head = block; + return; + } block->metadata.prev = head; head->metadata.next = block; head = block; } + /** + * handles the actual allocation and alignment of memory + * @param bytes number of bytes to allocate + * @param alignment alignment required + * @return aligned pointer + */ void* allocate_bytes(blt::size_t bytes, blt::size_t alignment) { - blt::size_t remaining_bytes = BASE_REMAINDER - static_cast(head->metadata.offset - head->buffer); + blt::size_t remaining_bytes = BLOCK_REMAINDER - static_cast(head->metadata.offset - head->buffer); auto pointer = static_cast(head->metadata.offset); return std::align(alignment, bytes, pointer, remaining_bytes); } + /** + * allocate an object starting from the next available address + * @tparam T type to allocate for + * @param count number of elements to allocate + * @return nullptr if the object could not be allocated, pointer to the object if it could, pointer to the start if count != 1 + */ template - T* allocate_back(blt::size_t count) + T* allocate_object(blt::size_t count) { blt::size_t bytes = sizeof(T) * count; const auto aligned_address = allocate_bytes(bytes, alignof(T)); @@ -703,7 +752,11 @@ namespace blt return static_cast(aligned_address); } - inline void del(block* p) + /** + * Frees a block + * @param p pointer to the block to free + */ + inline void delete_block(block* p) { if constexpr (USE_HUGE) { @@ -715,63 +768,67 @@ namespace blt } else free(p); } - - template - inline T* attempt_allocation(FUNC f, blt::size_t count) - { - T* ptr = allocate_back(count); - if (ptr == nullptr) - f(); - return ptr; - } - public: - bump_allocator() - { - base = head = allocate_block(); - }; + bump_allocator() = default; /** * Takes an unused size parameter. Purely used for compatibility with the old bump_allocator */ - explicit bump_allocator(blt::size_t): bump_allocator() + explicit bump_allocator(blt::size_t) {} + /** + * Allocate bytes for a type + * @tparam T type to allocate + * @param count number of elements to allocate for + * @throws std::bad_alloc + * @return aligned pointer to the beginning of the allocated memory + */ template [[nodiscard]] T* allocate(blt::size_t count = 1) { - if constexpr (sizeof(T) > BASE_REMAINDER) + if constexpr (sizeof(T) > BLOCK_REMAINDER) throw std::bad_alloc(); - auto* ptr = attempt_allocation([this]() { allocate_forward(); }, count); + T* ptr = allocate_object(count); + if (ptr != nullptr) + return ptr; + allocate_forward(); + ptr = allocate_object(count); if (ptr == nullptr) - return attempt_allocation([]() { throw std::bad_alloc(); }, count); + throw std::bad_alloc(); return ptr; } + /** + * Deallocate a pointer, does not call the destructor + * @tparam T type of pointer + * @param p pointer to deallocate + */ template void deallocate(T* p) { if (p == nullptr) return; - auto* blk = reinterpret_cast(reinterpret_cast(p) & static_cast(~(BLOCK_SIZE - 1))); + auto blk = to_block(p); if (--blk->metadata.allocated_objects == 0) { if (blk == base) - base = allocate_block(); + base = head = nullptr; if (blk->metadata.prev != nullptr) blk->metadata.prev->metadata.next = blk->metadata.next; - del(blk); + delete_block(blk); } } - template - auto* blk(T* p) - { - return reinterpret_cast(reinterpret_cast(p) & static_cast(~(BLOCK_SIZE - 1))); - } - + /** + * allocate a type then call its constructor with arguments + * @tparam T type to construct + * @tparam Args type of args to construct with + * @param args args to construct with + * @return aligned pointer to the constructed type + */ template [[nodiscard]] T* emplace(Args&& ... args) { @@ -779,6 +836,14 @@ namespace blt return new(allocated_memory) T{std::forward(args)...}; } + /** + * allocate an array of count T with argument(s) args and call T's constructor + * @tparam T class to construct + * @tparam Args argument types to supply to construction + * @param count size of the array to allocate in number of elements. Note calling this with count = 0 is equivalent to calling emplace + * @param args the args to supply to construction + * @return aligned pointer to the beginning of the array of T + */ template [[nodiscard]] T* emplace_many(blt::size_t count, Args&& ... args) { @@ -790,17 +855,45 @@ namespace blt return allocated_memory; } + /** + * Used to construct a class U with parameters Args + * @tparam U class to construct + * @tparam Args args to use + * @param p pointer to non-constructed memory + * @param args list of arguments to build the class with + */ template inline void construct(U* p, Args&& ... args) { ::new((void*) p) U(std::forward(args)...); } + /** + * Call the destructor for class U with pointer p + * @tparam U class to call destructor on, this will not do anything if the type is std::trivially_destructible + * @param p + */ template inline void destroy(U* p) { - if (p != nullptr) - p->~U(); + if constexpr (std::is_trivially_destructible_v) + { + if (p != nullptr) + p->~U(); + } + } + + /** + * Calls destroy on pointer p + * Then calls deallocate on p + * @tparam U class to destroy + * @param p pointer to deallocate + */ + template + inline void destruct(U* p) + { + destroy(p); + deallocate(p); } ~bump_allocator() @@ -809,7 +902,7 @@ namespace blt while (next != nullptr) { auto* after = next->metadata.next; - del(next); + delete_block(next); next = after; } }