From 5314c12242262dd5575df260c0fce9a3c2e229d3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Dec 2025 18:54:01 -0500 Subject: [PATCH] xo-alloc2: refactor: consolidate alloc methods into DArena --- xo-alloc2/include/xo/alloc2/arena/DArena.hpp | 40 +++ .../xo/alloc2/arena/IAllocator_DArena.hpp | 9 +- xo-alloc2/src/alloc2/DArena.cpp | 242 ++++++++++++++++++ xo-alloc2/src/alloc2/IAllocator_DArena.cpp | 231 +---------------- 4 files changed, 289 insertions(+), 233 deletions(-) diff --git a/xo-alloc2/include/xo/alloc2/arena/DArena.hpp b/xo-alloc2/include/xo/alloc2/arena/DArena.hpp index 5d4ceed3..580c6958b 100644 --- a/xo-alloc2/include/xo/alloc2/arena/DArena.hpp +++ b/xo-alloc2/include/xo/alloc2/arena/DArena.hpp @@ -44,6 +44,14 @@ namespace xo { /** @brief type for allocation header (if enabled) **/ using header_type = AllocHeader; //std::uint64_t; + /** @brief mode argument for @ref _alloc **/ + enum class alloc_mode : uint8_t { + standard, + super, + sub_incomplete, + sub_complete, + }; + ///@} /** @defgroup mm-arena-ctors arena constructors and destructors **/ @@ -107,10 +115,42 @@ namespace xo { **/ AllocInfo alloc_info(value_type mem) const noexcept; + /** allocate at least @p z bytes of memory. + * Return nullptr and capture error if unable to satisfy request. + * May expand committed memory, as long as resulting committed size + * is no larger than reserved size + **/ + value_type alloc(size_type z); + + /** when store_header_flag enabled: + * like alloc(), but combine memory consumed by this alloc + * plus following consecutive sub_alloc()'s into a single header. + * otherwise equivalent to alloc() + **/ + value_type super_alloc(size_type z); + + /** when store_header_flag enabled: + * follow preceding super_alloc() by one or more sub_allocs(). + * accumulate total allocated size (including padding) into + * single header. All sub_allocs() except the last must set + * @p complete_flag to false. The last sub_alloc() must set + * @p complete_flag to true. + **/ + value_type sub_alloc(size_type z, bool complete_flag); + /** capture error information: advance error count + set last_error **/ void capture_error(error err, size_type target_z = 0) const; + /** alloc driver. shared by alloc(), super_alloc(), sub_alloc() **/ + value_type _alloc(std::size_t req_z, alloc_mode mode); + + /** expand committed space in arena @p d + * to size at least @p z + * In practice will round up to a multiple of @ref page_z_. + **/ + bool expand(size_type z) noexcept; + /** discard all allocated memory, return to empty state * Promise: * - committed memory unchanged diff --git a/xo-alloc2/include/xo/alloc2/arena/IAllocator_DArena.hpp b/xo-alloc2/include/xo/alloc2/arena/IAllocator_DArena.hpp index 1fcb064d..fab85651 100644 --- a/xo-alloc2/include/xo/alloc2/arena/IAllocator_DArena.hpp +++ b/xo-alloc2/include/xo/alloc2/arena/IAllocator_DArena.hpp @@ -34,13 +34,6 @@ namespace xo { using range_type = std::pair, obj>; - enum class alloc_mode : uint8_t { - standard, - super, - sub_incomplete, - sub_complete, - }; - static std::string_view name(const DArena &) noexcept; static size_type reserved(const DArena &) noexcept; static size_type size(const DArena &) noexcept; @@ -84,7 +77,7 @@ namespace xo { /** alloc driver. shared by alloc(), super_alloc(), sub_alloc() **/ static value_type _alloc(DArena &, size_type z, - alloc_mode mode); + DArena::alloc_mode mode); }; // template <> diff --git a/xo-alloc2/src/alloc2/DArena.cpp b/xo-alloc2/src/alloc2/DArena.cpp index 782a16ce..6a0b40ed 100644 --- a/xo-alloc2/src/alloc2/DArena.cpp +++ b/xo-alloc2/src/alloc2/DArena.cpp @@ -7,6 +7,7 @@ #include "arena/DArena.hpp" #include "arena/DArenaIterator.hpp" #include "xo/alloc2/padding.hpp" +#include "xo/indentlog/scope.hpp" #include "xo/indentlog/print/tag.hpp" #include #include // for ::munmap() @@ -289,6 +290,47 @@ namespace xo { return DArenaIterator::end(this); } + std::byte * + DArena::alloc(std::size_t req_z) + { + /* - primary allocation path: + * exactly 1 header per alloc() call. + * - store_header_flag follows configuration + */ + + return _alloc(req_z, alloc_mode::standard); + } + + std::byte * + DArena::super_alloc(std::size_t req_z) + { + /* - (uncommon) pattern for parent alloc immediately followed by + * zero-or-more susidiary allocs, all sharing a single header. + * - collapses into alloc() behavior when + * ArenaConfig.store_header_flag_ disabled + */ + + return _alloc(req_z, + alloc_mode::super); + } + + std::byte * + DArena::sub_alloc(std::size_t req_z, + bool complete_flag) + { + /* - (uncommon) pattern for subsidiary allocs: + * that piggyback onto preceding super_alloc() + * - collapses into alloc() behavior when + * ArenaConfig.store_header_flag_ disabled + */ + + return _alloc(req_z, + (complete_flag + ? alloc_mode::sub_complete + : alloc_mode::sub_incomplete)); + + } + void DArena::capture_error(error err, size_type target_z) const @@ -303,6 +345,206 @@ namespace xo { reserved()); } + byte * + DArena::_alloc(std::size_t req_z, alloc_mode mode) + { + scope log(XO_DEBUG(config_.debug_flag_)); + + /* + * sub_complete + * sub_incomplete | + * standard super | | + * v v v v + */ + std::array store_header_v = {{ true, true, false, false }}; + std::array retain_header_v = {{ false, true, false, false }}; + std::array store_guard_v = {{ true, false, false, true }}; + + /* -> write header at free_ */ + bool store_header_flag = false; + /* -> stash last_header_*/ + bool retain_header_flag = false; + /* -> write guard bytes */ + bool store_guard = false; + + if (config_.store_header_flag_) { + store_header_flag = store_header_v[(int)mode]; + retain_header_flag = retain_header_v[(int)mode]; + store_guard = store_guard_v[(int)mode]; + } + + assert(padding::is_aligned((size_t)free_)); + + /* + * free_(pre) + * v + * + * <-------------z1---------------> + * < guard >< hz >< req_z >< dz >< guard > + * + * used <== +++++++++0000zzzz@@@@@@@@@@@@@@@@@ppppppp+++++++++ ==> avail + * + * ^ ^ ^ + * header mem | + * ^ | + * last_header_ free_(post) + * + * [+] guard after each allocation, for simple sanitize checks + * [0] unused header bits (avail to application) + * [z] record allocation size + * [@] new allocated memory + * [p] padding (to uintptr_t alignment) + */ + + /* non-zero if header feature enabled */ + size_t hz = 0; + /* dz: pad req_z to alignment size (multiple of 8 bytes, probably) */ + size_t dz = padding::alloc_padding(req_z); + size_t z0 = req_z + dz; + /* if non-zero: + * will store padded alloc size at the beginning of each allocation + * reminder: + * important to store padded size for correct arena iteration + */ + uint64_t header = req_z + dz; + + if (store_header_flag) + { + if (config_.header_.is_size_enabled()) [[likely]] { + hz = sizeof(header); + } else { + /* req_z doesn't fit in configured header_size_mask bits */ + capture_error(error::header_size_mask); + return nullptr; + } + } + + size_t z1 = hz + z0; + + assert(padding::is_aligned(z1)); + + if (!this->expand(this->allocated() + z1)) [[unlikely]] { + /* (error state already captured) */ + return nullptr; + } + + if (store_header_flag) { + /* capturing header */ + *(uint64_t *)free_ = header; + + if (retain_header_flag) { + /* and rembering for subsequent + * sub_alloc() + */ + last_header_ = (AllocHeader *)free_; + } + } + + byte * mem = free_ + hz; + + this->free_ += z1; + + if (store_guard) { + /* write guard bytes for overrun detection */ + ::memset(free_, + config_.header_.guard_byte_, + config_.header_.guard_z_); + + this->free_ += config_.header_.guard_z_; + } + + log && log(xtag("self", config_.name_), + xtag("hz", hz), + xtag("z0", req_z), + xtag("+pad", dz), + xtag("z1", z1), + xtag("size", this->committed()), + xtag("avail", this->available())); + log && log(xtag("mem", mem), + xtag("free", free_)); + + return mem; + } + + bool + DArena::expand(size_t target_z) noexcept + { + scope log(XO_DEBUG(config_.debug_flag_), + xtag("target_z", target_z), + xtag("committed_z", committed_z_)); + + if (target_z <= committed_z_) [[likely]] { + log && log("trivial success, offset within committed range", + xtag("target_z", target_z), + xtag("committed_z", committed_z_)); + return true; + } + + if (lo_ + target_z > hi_) [[unlikely]] { + this->capture_error(error::reserve_exhausted, target_z); + return false; + } + + /* + * pre: + * + * _______________................................... + * ^ ^ ^ + * lo limit hi + * + * < committed_z > + * <----------target_z-----------> + * > <- z: 0 <= z < hugepage_z + * <---------aligned_target_z---------> + * <--- add_commit_z --> + * + * post: + * ____________________________________.............. + * ^ ^ ^ + * lo limit hi + * + */ + + std::size_t aligned_target_z = padding::with_padding(target_z, config_.hugepage_z_); + std::byte * commit_start = limit_; // = lo_ + committed_z_; + std::size_t add_commit_z = aligned_target_z - committed_z_; + + assert(limit_ == lo_ + committed_z_); + + // log && log(xtag("aligned_offset_z", aligned_offset_z), + // xtag("add_commit_z", add_commit_z)); + // log && log("expand committed range", + // xtag("commit_start", commit_start), + // xtag("add_commit_z", add_commit_z), + // xtag("commit_end", commit_start + add_commit_z)); + + if (::mprotect(commit_start, + add_commit_z, + PROT_READ | PROT_WRITE) != 0) [[unlikely]] + { + capture_error(error::commit_failed, add_commit_z); + return false; + } + + committed_z_ = aligned_target_z; + limit_ = lo_ + committed_z_; + + if (commit_start == lo_) [[unlikely]] { + /* first expand() for this allocator - start with guard_z_ bytes */ + + ::memset(free_, + config_.header_.guard_byte_, + config_.header_.guard_z_); + + free_ += config_.header_.guard_z_; + } + + assert(committed_z_ % config_.hugepage_z_ == 0); + assert(reinterpret_cast(limit_) % config_.hugepage_z_ == 0); + + return true; + } /*expand*/ + void DArena::clear() noexcept { diff --git a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp index 4636dbe1..2e5af3c5 100644 --- a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp +++ b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp @@ -70,14 +70,6 @@ namespace xo { return s.alloc_info(mem); } - void dummy(const DArena & s) { - byte * begin_mem = nullptr; - DArenaIterator * ix = new (begin_mem) DArenaIterator(&s, DArenaIterator::begin_header(&s)); - obj ix_vt{ix}; - - - } - auto IAllocator_DArena::alloc_range(const DArena & s, DArena & ialloc) noexcept -> range_type @@ -102,107 +94,21 @@ namespace xo { bool IAllocator_DArena::expand(DArena & s, size_t target_z) noexcept { - scope log(XO_DEBUG(s.config_.debug_flag_), - xtag("target_z", target_z), - xtag("committed_z", s.committed_z_)); - - if (target_z <= s.committed_z_) [[likely]] { - log && log("trivial success, offset within committed range", - xtag("target_z", target_z), - xtag("committed_z", s.committed_z_)); - return true; - } - - if (s.lo_ + target_z > s.hi_) [[unlikely]] { - s.capture_error(error::reserve_exhausted, target_z); - return false; - } - - /* - * pre: - * - * _______________................................... - * ^ ^ ^ - * lo limit hi - * - * < committed_z > - * <----------target_z-----------> - * > <- z: 0 <= z < hugepage_z - * <---------aligned_target_z---------> - * <--- add_commit_z --> - * - * post: - * ____________________________________.............. - * ^ ^ ^ - * lo limit hi - * - */ - - std::size_t aligned_target_z = padding::with_padding(target_z, s.config_.hugepage_z_); - std::byte * commit_start = s.limit_; // = s.lo_ + s.committed_z_; - std::size_t add_commit_z = aligned_target_z - s.committed_z_; - - assert(s.limit_ == s.lo_ + s.committed_z_); - - // log && log(xtag("aligned_offset_z", aligned_offset_z), - // xtag("add_commit_z", add_commit_z)); - // log && log("expand committed range", - // xtag("commit_start", commit_start), - // xtag("add_commit_z", add_commit_z), - // xtag("commit_end", commit_start + add_commit_z)); - - if (::mprotect(commit_start, - add_commit_z, - PROT_READ | PROT_WRITE) != 0) [[unlikely]] - { - s.capture_error(error::commit_failed, add_commit_z); - return false; - } - - s.committed_z_ = aligned_target_z; - s.limit_ = s.lo_ + s.committed_z_; - - if (commit_start == s.lo_) [[unlikely]] { - /* first expand() for this allocator - start with guard_z_ bytes */ - - ::memset(s.free_, - s.config_.header_.guard_byte_, - s.config_.header_.guard_z_); - - s.free_ += s.config_.header_.guard_z_; - } - - assert(s.committed_z_ % s.config_.hugepage_z_ == 0); - assert(reinterpret_cast(s.limit_) % s.config_.hugepage_z_ == 0); - - return true; + return s.expand(target_z); } /*expand*/ std::byte * IAllocator_DArena::alloc(DArena & s, std::size_t req_z) { - /* - primary allocation path: - * exactly 1 header per alloc() call. - * - store_header_flag follows configuration - */ - - return _alloc(s, req_z, - alloc_mode::standard); + return s.alloc(req_z); } std::byte * IAllocator_DArena::super_alloc(DArena & s, std::size_t req_z) { - /* - (uncommon) pattern for parent alloc immediately followed by - * zero-or-more susidiary allocs, all sharing a single header. - * - collapses into alloc() behavior when - * ArenaConfig.store_header_flag_ disabled - */ - - return _alloc(s, req_z, - alloc_mode::super); + return s.super_alloc(req_z); } std::byte * @@ -210,140 +116,15 @@ namespace xo { std::size_t req_z, bool complete_flag) { - /* - (uncommon) pattern for subsidiary allocs: - * that piggyback onto preceding super_alloc() - * - collapses into alloc() behavior when - * ArenaConfig.store_header_flag_ disabled - */ - - return _alloc(s, req_z, - (complete_flag - ? alloc_mode::sub_complete - : alloc_mode::sub_incomplete)); - + return s.sub_alloc(req_z, complete_flag); } byte * IAllocator_DArena::_alloc(DArena & s, std::size_t req_z, - alloc_mode mode) + DArena::alloc_mode mode) { - scope log(XO_DEBUG(s.config_.debug_flag_)); - - /* - * sub_complete - * sub_incomplete | - * standard super | | - * v v v v - */ - std::array store_header_v = {{ true, true, false, false }}; - std::array retain_header_v = {{ false, true, false, false }}; - std::array store_guard_v = {{ true, false, false, true }}; - - /* -> write header at s.free_ */ - bool store_header_flag = false; - /* -> stash s.last_header_*/ - bool retain_header_flag = false; - /* -> write guard bytes */ - bool store_guard = false; - - if (s.config_.store_header_flag_) { - store_header_flag = store_header_v[(int)mode]; - retain_header_flag = retain_header_v[(int)mode]; - store_guard = store_guard_v[(int)mode]; - } - - assert(padding::is_aligned((size_t)s.free_)); - - /* - * free_(pre) - * v - * - * <-------------z1---------------> - * < guard >< hz >< req_z >< dz >< guard > - * - * used <== +++++++++0000zzzz@@@@@@@@@@@@@@@@@ppppppp+++++++++ ==> avail - * - * ^ ^ ^ - * header mem | - * ^ | - * last_header_ free_(post) - * - * [+] guard after each allocation, for simple sanitize checks - * [0] unused header bits (avail to application) - * [z] record allocation size - * [@] new allocated memory - * [p] padding (to uintptr_t alignment) - */ - - /* non-zero if header feature enabled */ - size_t hz = 0; - /* dz: pad req_z to alignment size (multiple of 8 bytes, probably) */ - size_t dz = padding::alloc_padding(req_z); - size_t z0 = req_z + dz; - /* if non-zero: - * will store padded alloc size at the beginning of each allocation - * reminder: - * important to store padded size for correct arena iteration - */ - uint64_t header = req_z + dz; - - if (store_header_flag) - { - if (s.config_.header_.is_size_enabled()) [[likely]] { - hz = sizeof(header); - } else { - /* req_z doesn't fit in configured header_size_mask bits */ - s.capture_error(error::header_size_mask); - return nullptr; - } - } - - size_t z1 = hz + z0; - - assert(padding::is_aligned(z1)); - - if (!expand(s, allocated(s) + z1)) [[unlikely]] { - /* (error state already captured) */ - return nullptr; - } - - if (store_header_flag) { - /* capturing header */ - *(uint64_t *)s.free_ = header; - - if (retain_header_flag) { - /* and rembering for subsequent - * sub_alloc() - */ - s.last_header_ = (AllocHeader *)s.free_; - } - } - - byte * mem = s.free_ + hz; - - s.free_ += z1; - - if (store_guard) { - /* write guard bytes for overrun detection */ - ::memset(s.free_, - s.config_.header_.guard_byte_, - s.config_.header_.guard_z_); - - s.free_ += s.config_.header_.guard_z_; - } - - log && log(xtag("self", s.config_.name_), - xtag("hz", hz), - xtag("z0", req_z), - xtag("+pad", dz), - xtag("z1", z1), - xtag("size", size(s)), - xtag("avail", available(s))); - log && log(xtag("mem", mem), - xtag("free", s.free_)); - - return mem; + return s._alloc(req_z, mode); } void