diff --git a/include/blt/std/allocator.h b/include/blt/std/allocator.h new file mode 100644 index 0000000..655ca32 --- /dev/null +++ b/include/blt/std/allocator.h @@ -0,0 +1,252 @@ +/* + * + * Copyright (C) 2023 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_ALLOCATOR_H + +#include +#include +#include +#include +#include + +namespace blt +{ + template + class area_allocator + { + public: + using type = T; + using value_type = type; + using pointer = type*; + using const_pointer = const type*; + using void_pointer = void*; + using const_void_pointer = const void*; + using reference = value_type&; + using const_reference = const value_type&; + using size_type = size_t; + using difference_type = size_t; + using propagate_on_container_move_assignment = std::false_type; + template + struct rebind + { + typedef std::allocator other; + }; + private: + /** + * Stores a view to a region of memory that has been deallocated + * This is a non-owning reference to the memory block + * + * pointer p is the pointer to the beginning of the block of memory + * size_t n is the number of elements that this block can hold + */ + struct pointer_view + { + pointer p; + size_t n; + }; + + /** + * Stores the actual data for allocated blocks. Since we would like to be able to allocate an arbitrary number of items + * we need a way of storing that data. The block storage holds an owning pointer to a region of memory with used elements + * Only up to used has to have their destructors called, which should be handled by the deallocate function + * it is UB to not deallocate memory allocated by this allocator + * + * an internal vector is used to store the regions of memory which have been deallocated. the allocate function will search for + * free blocks with sufficient size in order to maximize memory usage. In the future more advanced methods should be used + * for both faster access to deallocated blocks of sufficient size and to ensure coherent memory. + */ + struct block_storage + { + pointer data; + size_t used = 0; + // TODO: b-tree? + std::vector unallocated_blocks; + }; + + /** + * Stores an index to a pointer_view along with the amount of memory leftover after the allocation + * it also stores the block being allocated to in question. The new inserted leftover should start at old_ptr + size + */ + struct block_view + { + block_storage* blk; + size_t index; + size_t leftover; + + block_view(block_storage* blk, size_t index, size_t leftover): blk(blk), index(index), leftover(leftover) + {} + }; + + /** + * Allocate a new block of memory and push it to the back of blocks. + */ + inline void allocate_block() + { + //BLT_INFO("Allocating a new block of size %d", BLOCK_SIZE); + auto* blk = new block_storage(); + blk->data = static_cast(malloc(sizeof(T) * BLOCK_SIZE)); + blocks.push_back(blk); + } + + /** + * Searches for a free block inside the block storage with sufficient space and returns an optional view to it + * The optional will be empty if no open block can be found. + */ + inline std::optional search_for_block(block_storage* blk, size_t n) + { + for (auto kv : blt::enumerate(blk->unallocated_blocks)) + { + if (kv.second.n >= n) + return block_view{blk, kv.first, kv.second.n - n}; + } + return {}; + } + + /** + * removes the block of memory from the unallocated_blocks storage in the underlying block, inserting a new unallocated block if + * there was any leftover. Returns a pointer to the beginning of the new block. + */ + inline pointer swap_pop_resize_if(const block_view& view, size_t n) + { + pointer_view ptr = view.blk->unallocated_blocks[view.index]; + std::iter_swap(view.blk->unallocated_blocks.begin() + view.index, view.blk->unallocated_blocks.end() - 1); + view.blk->unallocated_blocks.pop_back(); + if (view.leftover > 0) + view.blk->unallocated_blocks.push_back({ptr.p + n, view.leftover}); + return ptr.p; + } + + /** + * Finds the next available unallocated block of memory, or empty if there is none which meet size requirements + */ + inline std::optional find_available_block(size_t n) + { + for (auto* blk : blocks) + { + if (auto view = search_for_block(blk, n)) + return swap_pop_resize_if(view.value(), n); + } + return {}; + } + + /** + * returns a pointer to a block of memory along with an offset into that pointer that the requested block can be found at + */ + inline std::pair getBlock(size_t n) + { + if (auto blk = find_available_block(n)) + return {blk.value(), 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; + } + + /** + * Calls the constructor on elements if they require construction, otherwise constructor will not be called and this function is useless + */ + 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() + { + allocate_block(); + } + + [[nodiscard]] pointer allocate(size_t n) + { + if (n > BLOCK_SIZE) + throw std::runtime_error("Requested allocation is too large!"); + + 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; + } + + void deallocate(pointer p, size_t n) noexcept + { + for (size_t i = 0; i < n; i++) + p[i].~T(); + for (auto*& blk : blocks) + { + if (p >= blk->data && p <= (blk->data + BLOCK_SIZE)) + { + blk->unallocated_blocks.push_back({p, n}); + break; + } + } + } + + template + inline void construct(U* p, Args&& ... args) + { + ::new((void*) p) U(std::forward(args)...); + } + + template + inline void destroy(U* p) + { + p->~U(); + } + + inline size_t max_size() const + { + return std::numeric_limits::max(); + } + + inline const_pointer address(const value_type& val) + { + return std::addressof(val); + } + + inline pointer address(value_type& val) + { + return std::addressof(val); + } + + ~area_allocator() + { + for (auto*& blk : blocks) + { + free(blk->data); + delete blk; + } + } + + private: + std::vector blocks; + }; +} + +#define BLT_ALLOCATOR_H + +#endif //BLT_ALLOCATOR_H diff --git a/include/blt/std/memory.h b/include/blt/std/memory.h index 4b7ae75..a3e4969 100755 --- a/include/blt/std/memory.h +++ b/include/blt/std/memory.h @@ -7,105 +7,21 @@ #ifndef BLT_TESTS_MEMORY_H #define BLT_TESTS_MEMORY_H +#include #include #include #include #include "queue.h" #include "utility.h" #include -#include #include #include -#include #include #include -#include -#include -#include - -#if defined(__clang__) || defined(__llvm__) || defined(__GNUC__) || defined(__GNUG__) - - #include - - #define SWAP16(val) bswap_16(val) - #define SWAP32(val) bswap_32(val) - #define SWAP64(val) bswap_64(val) -#if __cplusplus >= 202002L - - #include - - #define ENDIAN_LOOKUP(little_endian) (std::endian::native == std::endian::little && !little_endian) || \ - (std::endian::native == std::endian::big && little_endian) -#else - #define ENDIAN_LOOKUP(little_endian) !little_endian -#endif -#elif defined(_MSC_VER) - #include - #define SWAP16(val) _byteswap_ushort(val) - #define SWAP32(val) _byteswap_ulong(val) - #define SWAP64(val) _byteswap_uint64(val) - #define ENDIAN_LOOKUP(little_endian) !little_endian -#endif namespace blt { - namespace mem - { - // Used to grab the byte-data of any T element. Defaults to Big Endian, however can be configured to use little endian - template - inline static int toBytes(const T& in, BYTE_TYPE* out) - { - if constexpr (!(std::is_same_v || std::is_same_v)) - static_assert("Must provide a signed/unsigned int8 type"); - std::memcpy(out, (void*) &in, sizeof(T)); - - if constexpr (ENDIAN_LOOKUP(little_endian)) - { - // TODO: this but better. - for (size_t i = 0; i < sizeof(T) / 2; i++) - std::swap(out[i], out[sizeof(T) - 1 - i]); - } - - return 0; - } - - // Used to cast the binary data of any T object, into a T object. Assumes data is in big ending (configurable) - template - inline static int fromBytes(const BYTE_TYPE* in, T& out) - { - if constexpr (!(std::is_same_v || std::is_same_v)) - static_assert("Must provide a signed/unsigned int8 type"); - - std::array data; - std::memcpy(data.data(), in, sizeof(T)); - - if constexpr (ENDIAN_LOOKUP(little_endian)) - { - // if we need to swap find the best way to do so - if constexpr (std::is_same_v || std::is_same_v) - out = SWAP16(*reinterpret_cast(data.data())); - else if constexpr (std::is_same_v || std::is_same_v) - out = SWAP32(*reinterpret_cast(data.data())); - else if constexpr (std::is_same_v || std::is_same_v) - out = SWAP64(*reinterpret_cast(data.data())); - else - { - std::reverse(data.begin(), data.end()); - out = *reinterpret_cast(data.data()); - } - } - - return 0; - } - - template - inline static int fromBytes(const BYTE_TYPE* in, T* out) - { - return fromBytes(in, *out); - } - } - template struct ptr_iterator { @@ -510,226 +426,6 @@ namespace blt } }; - template - class area_allocator - { - public: - using type = T; - using value_type = type; - using pointer = type*; - using const_pointer = const type*; - using void_pointer = void*; - using const_void_pointer = const void*; - using reference = value_type&; - using const_reference = const value_type&; - using size_type = size_t; - using difference_type = size_t; - using propagate_on_container_move_assignment = std::false_type; - template - struct rebind - { - typedef std::allocator other; - }; - private: - /** - * Stores a view to a region of memory that has been deallocated - * This is a non-owning reference to the memory block - * - * pointer p is the pointer to the beginning of the block of memory - * size_t n is the number of elements that this block can hold - */ - struct pointer_view - { - pointer p; - size_t n; - }; - - /** - * Stores the actual data for allocated blocks. Since we would like to be able to allocate an arbitrary number of items - * we need a way of storing that data. The block storage holds an owning pointer to a region of memory with used elements - * Only up to used has to have their destructors called, which should be handled by the deallocate function - * it is UB to not deallocate memory allocated by this allocator - * - * an internal vector is used to store the regions of memory which have been deallocated. the allocate function will search for - * free blocks with sufficient size in order to maximize memory usage. In the future more advanced methods should be used - * for both faster access to deallocated blocks of sufficient size and to ensure coherent memory. - */ - struct block_storage - { - pointer data; - size_t used = 0; - // TODO: b-tree? - std::vector unallocated_blocks; - }; - - /** - * Stores an index to a pointer_view along with the amount of memory leftover after the allocation - * it also stores the block being allocated to in question. The new inserted leftover should start at old_ptr + size - */ - struct block_view - { - block_storage* blk; - size_t index; - size_t leftover; - - block_view(block_storage* blk, size_t index, size_t leftover): blk(blk), index(index), leftover(leftover) - {} - }; - - /** - * Allocate a new block of memory and push it to the back of blocks. - */ - inline void allocate_block() - { - //BLT_INFO("Allocating a new block of size %d", BLOCK_SIZE); - auto* blk = new block_storage(); - blk->data = static_cast(malloc(sizeof(T) * BLOCK_SIZE)); - blocks.push_back(blk); - } - - /** - * Searches for a free block inside the block storage with sufficient space and returns an optional view to it - * The optional will be empty if no open block can be found. - */ - inline std::optional search_for_block(block_storage* blk, size_t n) - { - for (auto kv : blt::enumerate(blk->unallocated_blocks)) - { - if (kv.second.n >= n) - return block_view{blk, kv.first, kv.second.n - n}; - } - return {}; - } - - /** - * removes the block of memory from the unallocated_blocks storage in the underlying block, inserting a new unallocated block if - * there was any leftover. Returns a pointer to the beginning of the new block. - */ - inline pointer swap_pop_resize_if(const block_view& view, size_t n) - { - pointer_view ptr = view.blk->unallocated_blocks[view.index]; - std::iter_swap(view.blk->unallocated_blocks.begin() + view.index, view.blk->unallocated_blocks.end() - 1); - view.blk->unallocated_blocks.pop_back(); - if (view.leftover > 0) - view.blk->unallocated_blocks.push_back({ptr.p + n, view.leftover}); - return ptr.p; - } - - /** - * Finds the next available unallocated block of memory, or empty if there is none which meet size requirements - */ - inline std::optional find_available_block(size_t n) - { - for (auto* blk : blocks) - { - if (auto view = search_for_block(blk, n)) - return swap_pop_resize_if(view.value(), n); - } - return {}; - } - - /** - * returns a pointer to a block of memory along with an offset into that pointer that the requested block can be found at - */ - inline std::pair getBlock(size_t n) - { - if (auto blk = find_available_block(n)) - return {blk.value(), 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; - } - - /** - * Calls the constructor on elements if they require construction, otherwise constructor will not be called and this function is useless - */ - 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() - { - allocate_block(); - } - - [[nodiscard]] pointer allocate(size_t n) - { - if (n > BLOCK_SIZE) - throw std::runtime_error("Requested allocation is too large!"); - - 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; - } - - void deallocate(pointer p, size_t n) noexcept - { - for (size_t i = 0; i < n; i++) - p[i].~T(); - for (auto*& blk : blocks) - { - if (p >= blk->data && p <= (blk->data + BLOCK_SIZE)) - { - blk->unallocated_blocks.push_back({p, n}); - break; - } - } - } - - template - inline void construct(U* p, Args&&... args) - { - ::new((void*) p) U(std::forward(args)...); - } - - template - inline void destroy(U* p) - { - p->~U(); - } - - inline size_t max_size() const - { - return std::numeric_limits::max(); - } - - inline const_pointer address(const value_type& val) - { - return std::addressof(val); - } - - inline pointer address(value_type& val) - { - return std::addressof(val); - } - - ~area_allocator() - { - for (auto*& blk : blocks) - { - free(blk->data); - delete blk; - } - } - - private: - std::vector blocks; - }; - } #endif //BLT_TESTS_MEMORY_H diff --git a/include/blt/std/memory_util.h b/include/blt/std/memory_util.h new file mode 100644 index 0000000..b7e2e8b --- /dev/null +++ b/include/blt/std/memory_util.h @@ -0,0 +1,114 @@ +/* + * + * Copyright (C) 2023 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_MEMORY_UTIL_H +#define BLT_MEMORY_UTIL_H + +#include +#include +#include +#include + +#if defined(__clang__) || defined(__llvm__) || defined(__GNUC__) || defined(__GNUG__) + + #include + + #define SWAP16(val) bswap_16(val) + #define SWAP32(val) bswap_32(val) + #define SWAP64(val) bswap_64(val) + #if __cplusplus >= 202002L + + #include + + #define ENDIAN_LOOKUP(little_endian) (std::endian::native == std::endian::little && !little_endian) || \ + (std::endian::native == std::endian::big && little_endian) + #else + #define ENDIAN_LOOKUP(little_endian) !little_endian + #endif +#elif defined(_MSC_VER) + #include + #define SWAP16(val) _byteswap_ushort(val) + #define SWAP32(val) _byteswap_ulong(val) + #define SWAP64(val) _byteswap_uint64(val) + #define ENDIAN_LOOKUP(little_endian) !little_endian +#endif + +namespace blt::mem +{ + // Used to grab the byte-data of any T element. Defaults to Big Endian, however can be configured to use little endian + template + inline static int toBytes(const T& in, BYTE_TYPE* out) + { + if constexpr (!(std::is_same_v || std::is_same_v)) + static_assert("Must provide a signed/unsigned int8 type"); + std::memcpy(out, (void*) &in, sizeof(T)); + + if constexpr (ENDIAN_LOOKUP(little_endian)) + { + // TODO: this but better. + for (size_t i = 0; i < sizeof(T) / 2; i++) + std::swap(out[i], out[sizeof(T) - 1 - i]); + } + + return 0; + } + + // Used to cast the binary data of any T object, into a T object. Assumes data is in big ending (configurable) + template + inline static int fromBytes(const BYTE_TYPE* in, T& out) + { + if constexpr (!(std::is_same_v || std::is_same_v)) + static_assert("Must provide a signed/unsigned int8 type"); + + std::array data; + std::memcpy(data.data(), in, sizeof(T)); + + if constexpr (ENDIAN_LOOKUP(little_endian)) + { + // if we need to swap find the best way to do so + if constexpr (std::is_same_v || std::is_same_v) + out = SWAP16(*reinterpret_cast(data.data())); + else if constexpr (std::is_same_v || std::is_same_v) + out = SWAP32(*reinterpret_cast(data.data())); + else if constexpr (std::is_same_v || std::is_same_v) + out = SWAP64(*reinterpret_cast(data.data())); + else + { + std::reverse(data.begin(), data.end()); + out = *reinterpret_cast(data.data()); + } + } + + return 0; + } + + template + inline static int fromBytes(const BYTE_TYPE* in, T* out) + { + return fromBytes(in, *out); + } + + inline static size_t next_byte_allocation(size_t prev_size, size_t default_allocation_block = 8192) + { + if (prev_size < default_allocation_block) + return prev_size * 2; + return prev_size + default_allocation_block; + } +} + +#endif //BLT_MEMORY_UTIL_H diff --git a/include/blt/std/queue.h b/include/blt/std/queue.h index 5cb27c2..19df841 100755 --- a/include/blt/std/queue.h +++ b/include/blt/std/queue.h @@ -7,6 +7,8 @@ #ifndef BLT_QUEUE_H #define BLT_QUEUE_H +#include + /** * */ @@ -32,7 +34,7 @@ namespace blt */ void expand() { - int new_size = m_size * 2; + int new_size = blt::mem::next_byte_allocation(m_size); auto tempData = new T[new_size]; for (int i = 0; i < m_insertIndex; i++) tempData[i] = m_data[i]; @@ -102,7 +104,7 @@ namespace blt */ void expand() { - int new_size = m_size * 2; + int new_size = blt::mem::next_byte_allocation(m_size); int removed_size = m_size - m_headIndex; auto tempData = new T[new_size]; // only copy data from where we've removed onward diff --git a/tests/src/memory_test.cpp b/tests/src/memory_test.cpp index a555439..9aac4d5 100644 --- a/tests/src/memory_test.cpp +++ b/tests/src/memory_test.cpp @@ -14,6 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include #include #include