#pragma once #include "gp_config.hpp" #include "gp/functional/bind_front.hpp" #include "gp/algorithms/tmp_manip.hpp" #include "gp/utils/allocators/dummy.hpp" #include "gp/containers/array.hpp" #include "gp/containers/buffer.hpp" #include "gp/math/integral.hpp" #include "gp/utils/allocators/allocator.hpp" #include namespace gp{ /** * @brief An allocator that uses the buddy algorithm to divide the space into allocatable memory. * * This is not resilient to memory fragmentation, but the smallest memory unit should always be allocatable unless the memory is actually full. * * @tparam max_msb The log2 rounded up of the maximum space you expect to address in bytes * @tparam align The smallest size of memory you expect the allocator to allocate */ template class buddy : public allocator { /** * @brief an allocation tree node, made into an easily packable struct */ struct twig { bool used : 1; bool used_children : 1; twig(uint8_t src) noexcept { used = 1 & src; used_children = 2 & src; } operator uint8_t() noexcept { return 1 * used + 2 * used_children; } }; /** * @brief a structure to pack 4 twig within */ struct bundle { uint8_t a : 2; uint8_t b : 2; uint8_t c : 2; uint8_t d : 2; bundle() noexcept { a = 0; b = 0; c = 0; d = 0; } }; /** * @brief a meta allocator to allocate the memory used and deallocate it later */ gp::optional> allocator_v; /** * @brief The actual buffer where data is stored * */ gp::buffer data; const size_t max_depth; const size_t twig_explore_length; /** * @brief The depth of the tree required to allocate */ static constexpr size_t max_theoric_depth = max_msb - gp::math::msb(align); /** * @brief The actual number of twigs to support the specified depth */ static constexpr size_t required_twigs = math::pow2(max_theoric_depth + 1) - 1; /** * @brief ((max allocatable size - min allocatable size) ** 2 - 1) / 4 twigs in a bundle **/ static constexpr size_t span_size = required_twigs / 4 + (required_twigs % 4 != 0); /** * @brief The array of twigs (in bundles) aka the metadata */ gp::array stack; /** * This code has been manually checked and will always return. * If you find a case where it doesn't, please file an issue. **/ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreturn-type" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wreturn-type" /** * @brief Get the value of the twig at the specified index * * @param idx * @return twig A copy of the specified twig */ twig get_twig(size_t idx) const noexcept { auto far = idx / 4; auto local = idx % 4; switch(local) { case 0: return stack[far].a; case 1: return stack[far].b; case 2: return stack[far].c; case 3: return stack[far].d; } } #pragma GCC diagnostic pop #pragma clang diagnostic pop /** * @brief Set the twig at the specified index to the value of the heredescribed twig * * @param idx * @param v */ void set_twig(size_t idx, twig v) noexcept { auto far = idx / 4; auto local = idx % 4; auto& group = stack[far]; switch(local) { case 0: group.a = v; return; case 1: group.b = v; return; case 2: group.c = v; return; case 3: group.d = v; return; } } /** * @brief Deduces the depth to explore to allocate the specified size * * @param sz * @return size_t */ constexpr size_t size_to_depth(size_t sz) noexcept { size_t pow2 = gp::math::msb(sz) - gp::math::msb(align); return gp::clamp( (size_t)0 , max_depth - pow2, max_depth ); } /** * @brief Deduces the size from the specified depth * * @param depth * @return size_t */ constexpr size_t depth_to_size(size_t depth) noexcept { return 1 << (max_depth - depth + gp::math::msb(align)); } /** * @brief Get the left child index from the specified index * * @param index * @return size_t */ constexpr size_t get_left(size_t index) const noexcept { return ((index + 1) << 1) - 1; } /** * @brief Get the right child index from the specified index * * @param index * @return size_t */ constexpr size_t get_right(size_t index) const noexcept{ return ((index + 1) << 1); } /** * @brief Recursively applies a function to the children of a given node * * @param index the starting node * @param func the function to apply downwards */ template void all_under(size_t index, function func) { size_t left = get_left(index); size_t right = get_right(index); all_under(left, func); all_under(right, func); func(left); func(right); } /** * @brief Recursively applies a function to the chain of parents of the given node * * @param index the starting node * @param func the function to apply upwards */ template void all_over(size_t index, function func) { if(index != 0) { size_t parent = ((index + 1) >> 1) - 1; func(parent); if(parent != 0) all_over(parent, func); } } /** * @brief Recursively checks if any child matches a given predicate * * @param index The parent * @param func The predicate * @return true if the predicate matched once * @return false if the predicate never matched */ template bool is_any_child(size_t index, function func) const { size_t left = get_left(index); size_t right = get_right(index); if(left < twig_explore_length && right < twig_explore_length) { if(func(left)) return true; if(func(right)) return true; if(is_any_child(left, func)) return true; if(is_any_child(right, func)) return true; } return false; } /** * @brief a constant used to specify that no twig has been found */ static constexpr size_t no_twig = -1; /** * @brief Finds a twig that represent free memory * * It's free real estate! * * @param depth the depth to allocate * @param root the root to look from * @param explored the current depth we sit at * @return size_t */ size_t find_free_twig(size_t depth, size_t root = 0, size_t explored = 0) const { auto v = get_twig(root); if(depth == explored) { if(v.used || v.used_children) { return no_twig; } else { return root; } } else { if(v.used) { return no_twig; } ++explored; auto ret = find_free_twig(depth, get_right(root), explored); if(ret != no_twig) { return ret; } ret = find_free_twig(depth, get_left(root), explored); if(ret != no_twig) { return ret; } } return no_twig; } /** * @brief Recursively looks for the highest filled nodewith the given offset * * @param offset The offset to look for * @param root the root to look from * @param explored the explored depth * @return size_t */ size_t find_used_twig(size_t offset, size_t root = 0, size_t explored = 0) { auto v = get_twig(root); if(v.used && offset == 0) { return root; } ++explored; if(explored > max_depth) return no_twig; size_t cut = (1 << (max_depth + gp::math::log2(align))) >> explored; if(offset >= cut) { return find_used_twig(offset-cut, get_right(root), explored); } else { return find_used_twig(offset, get_left(root), explored); } } static bool empty_node(const buddy* me, size_t node) { gp_config::assertion(node < me->twig_explore_length, "bad emptyness test"); auto p = me->get_twig(node); return !( p.used | p.used_children ); } public: /** * @brief Construct a new empty buddy object */ buddy() : data(gp::buffer(nullptr,nullptr)) , max_depth(0) , twig_explore_length(1 << max_depth) {} /** * @brief Construct a new buddy object from another allocator * * @param sz the size to allocate * @param allocator_p the source of the allocated memory */ buddy(size_t sz, allocator& allocator_p) : allocator_v(allocator_p) , data(nullptr,nullptr) , max_depth(gp::math::msb(sz)-gp::math::msb(align)) , twig_explore_length(1 << max_depth) { if(sz!=0 && (sz & (sz - 1)) == 0) { auto v=allocator_v.value().get().allocate(sz); if(v!=nullptr) { if((reinterpret_cast(v) % align) ==0) { data=gp::buffer(reinterpret_cast(v),reinterpret_cast(v)+sz); } else { allocator_v.value().get().deallocate(v); } } } } /** * @brief Construct a new buddy object from a memory location * * @param pos the location of the memory to manage * @param sz the size of the span to manage */ buddy(char* pos, size_t sz) : data(pos,pos+sz) , max_depth(gp::math::msb(sz)-gp::math::msb(align)) , twig_explore_length(1 << max_depth) { } /** * @brief Allocates memory * * @param sz The size of memory to allocate * @return void* the allocated memory OR nullptr */ virtual void* allocate(size_t sz) { auto depth = size_to_depth(sz); auto index = find_free_twig(depth); if(index == no_twig) { return nullptr; } auto pot = reinterpret_cast( (index - (1 << depth) + 1)*depth_to_size(depth) + reinterpret_cast(&*data.begin()) ); if(!data.contains(pot)) { return nullptr; } all_over(index, [&](size_t idx){ auto t = get_twig(idx); t.used_children = true; set_twig(idx, t); }); auto t = get_twig(index); t.used = true; set_twig(index, t); return pot; } /** * @brief Tries to reallocate (UNIMPLEMENTED) * * @return false */ virtual bool try_reallocate(void*, size_t) { return false; } /** * @brief Tries to deallocate the given memory * * @param ptr the pointer to the memory to deallocate * @return true if everything went fine * @return false if deallocation failed */ virtual bool deallocate(void* ptr) { if(data.contains((char*)ptr)) { size_t integral_offset = reinterpret_cast(ptr) - reinterpret_cast(&*data.begin()); auto index = find_used_twig(integral_offset); if(index == no_twig) { return false; } twig v = get_twig(index); v.used = false; v.used_children = false; set_twig(index, v); all_over(index, [&](size_t idx){ auto l = get_twig(get_left(idx)); auto r = get_twig(get_right(idx)); // And now for the tricky bit: // Sets the "used_children" bit if any of the collected twigs has a bit set set_twig(idx, 2*(l.used | l.used_children | r.used | r.used_children)); }); return true; } return false; } /** * @brief Checks if anything is still allocated in * * @return true if the allocator is completely empty * @return false if anything is still allocated in there */ bool empty() const noexcept { const buddy* addr = (buddy*)this; const auto prepred = not_fn(&buddy::empty_node); const auto pred = bind_front(prepred, addr); return empty_node(addr, 0) && !is_any_child(0, pred); } /** * @brief Destroy the buddy object, will wrink out the space if it was created with an allocator */ virtual ~buddy() { if(allocator_v.has_value()) { allocator_v.value().get().deallocate(data.begin().data); } } }; }