diff --git a/xo-alloc2/include/xo/alloc2/ArenaConfig.hpp b/xo-alloc2/include/xo/alloc2/ArenaConfig.hpp new file mode 100644 index 00000000..d96dcf05 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/ArenaConfig.hpp @@ -0,0 +1,59 @@ +/** @file ArenaConfig.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include +#include + +namespace xo { + namespace mm { + + /** @class ArenaConfig + * + * @brief configuration for a @ref DArena instance + **/ + struct ArenaConfig { + /** @defgroup mm-arenaconfig-instance-vars ArenaConfig members **/ + ///@{ + + /** optional name, for diagnostics **/ + std::string name_; + /** desired arena size -- hard max = reserved virtual memory **/ + std::size_t size_; + /** hugepage size -- using huge pages relieves some TLB pressure + * (provided you use their full extent :) + **/ + std::size_t hugepage_z_ = 2 * 1024 * 1024; + /** if non-zero, allocate extra space between allocs, and fill + * with fixed test-pattern contents. Allows for simple + * runtime arena sanitizing checks. + * Will be rounded up to multiple of @ref padding::c_alloc_alignment + **/ + std::size_t guard_z_ = 0; + /** if guard_z_ > 0, write at least that many copies + * of this guard byte following each complete allocation + **/ + std::uint8_t guard_byte_ = 0xfd; + /** if store_header_flag_ is true: mask bits for allocation size. + * remaining bits can be stolen for other purposes + * otherwise ignored + **/ + /** true to store header (8 bytes) at the beginning of each allocation. + * necessary and sufficient to allows iterating over allocs + * present in arena + **/ + bool store_header_flag_ = false; + std::uint64_t header_size_mask_ = 0xffffffff; + /** true to enable debug logging **/ + bool debug_flag_ = false; + + ///@} + }; + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end ArenaConfig.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/DArena.hpp b/xo-alloc2/include/xo/alloc2/DArena.hpp index a30f5c2d..fc38b604 100644 --- a/xo-alloc2/include/xo/alloc2/DArena.hpp +++ b/xo-alloc2/include/xo/alloc2/DArena.hpp @@ -5,49 +5,11 @@ #pragma once -#include +#include "ArenaConfig.hpp" namespace xo { namespace mm { - /** @class ArenaConfig - * - * @brief configuration for a @ref DArena instance - **/ - struct ArenaConfig { - /** @defgroup mm-arenaconfig-instance-vars ArenaConfig members **/ - ///@{ - - /** optional name, for diagnostics **/ - std::string name_; - /** desired arena size -- hard max = reserved virtual memory **/ - std::size_t size_; - /** hugepage size -- using huge pages relieves some TLB pressure - * (provided you use their full extent :) - **/ - std::size_t hugepage_z_ = 2 * 1024 * 1024; - /** true to store header (8 bytes) at the beginning of each allocation. - * necessary and sufficient to allows iterating over allocs - * present in arena - **/ - bool store_header_flag_ = false; - /** if non-zero, allocate extra space between allocs, and fill - * with fixed test-pattern contents. Allows for simple - * runtime arena sanitizing checks. - * Will be rounded up to multiple of @ref padding::c_alloc_alignment - **/ - std::size_t guard_z_ = 0; - /** if store_header_flag_ is true: mask bits for allocation size. - * remaining bits can be stolen for other purposes - * otherwise ignored - **/ - std::uint64_t header_size_mask_ = 0xffffffff; - /** true to enable debug logging **/ - bool debug_flag_ = false; - - ///@} - }; - /** @class DArena * * @brief represent arena allocator state diff --git a/xo-alloc2/include/xo/alloc2/IAllocator_Any.hpp b/xo-alloc2/include/xo/alloc2/IAllocator_Any.hpp index 0da80ae8..c58a19d8 100644 --- a/xo-alloc2/include/xo/alloc2/IAllocator_Any.hpp +++ b/xo-alloc2/include/xo/alloc2/IAllocator_Any.hpp @@ -9,9 +9,7 @@ #include namespace xo { - namespace mm { - struct IAllocator_Any; - } + namespace mm { struct IAllocator_Any; } namespace facet { template <> @@ -22,12 +20,14 @@ namespace xo { namespace mm { /** @class IAllocator_Any - * @brief Allocator implementation for variant instance. + * @brief Allocator implementation for empty variant instance. **/ struct IAllocator_Any : public AAllocator { //using Impl = IAllocator_ImplType; using size_type = std::size_t; + const AAllocator * iface() const { return std::launder(this); } + // from AAllocator int32_t _typeseq() const noexcept override { return s_typeseq; } diff --git a/xo-alloc2/include/xo/alloc2/IAllocator_DArena.hpp b/xo-alloc2/include/xo/alloc2/IAllocator_DArena.hpp index 0cfd7622..f845cf3e 100644 --- a/xo-alloc2/include/xo/alloc2/IAllocator_DArena.hpp +++ b/xo-alloc2/include/xo/alloc2/IAllocator_DArena.hpp @@ -34,7 +34,7 @@ namespace xo { enum class alloc_mode : uint8_t { standard, super, - sub, + sub_incomplete, sub_complete, }; @@ -75,10 +75,7 @@ namespace xo { /** alloc driver. shared by alloc(), super_alloc(), sub_alloc() **/ static value_type _alloc(DArena &, size_type z, - alloc_mode mode, - bool store_header_flag, - bool remember_header_flag); - + alloc_mode mode); }; // template <> diff --git a/xo-alloc2/src/alloc2/CMakeLists.txt b/xo-alloc2/src/alloc2/CMakeLists.txt index 42e98ed4..77d29762 100644 --- a/xo-alloc2/src/alloc2/CMakeLists.txt +++ b/xo-alloc2/src/alloc2/CMakeLists.txt @@ -2,10 +2,13 @@ set(SELF_LIB xo_alloc2) set(SELF_SRCS + AAllocator.cpp DArena.cpp IAllocator_Any.cpp IAllocator_DArena.cpp + + IGCObject_Any.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/xo-alloc2/src/alloc2/IAllocator_Any.cpp b/xo-alloc2/src/alloc2/IAllocator_Any.cpp index 414bd02c..2f92711c 100644 --- a/xo-alloc2/src/alloc2/IAllocator_Any.cpp +++ b/xo-alloc2/src/alloc2/IAllocator_Any.cpp @@ -20,7 +20,10 @@ namespace xo { * e.g. IAllocator_Xfer */ - std::cerr << "fatal: attempt to call uninitialized IAllocator_Any" << std::endl; + std::cerr << "fatal" + << ": attempt to call uninitialized" + << " IAllocator_Any method" + << std::endl; std::terminate(); } diff --git a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp index 412651c7..b37aab1a 100644 --- a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp +++ b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp @@ -8,6 +8,7 @@ #include "xo/indentlog/scope.hpp" #include #include +#include #include namespace xo { @@ -77,12 +78,6 @@ namespace xo { s.last_error_ = AllocatorError(error::reserve_exhausted, s.error_count_, target_z, s.committed_z_, reserved(s)); - -#ifdef OBSOLETE - throw std::runtime_error(tostr("ArenaAlloc::expand: requested size exceeds reserved size", - xtag("requested", target_z), - xtag("reserved", reserved(s)))); -#endif return false; } @@ -114,13 +109,15 @@ namespace xo { // 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]] { + if (::mprotect(commit_start, + add_commit_z, + PROT_READ | PROT_WRITE) != 0) [[unlikely]] + { ++(s.error_count_); s.last_error_ = AllocatorError(error::commit_failed, s.error_count_, @@ -136,11 +133,22 @@ namespace xo { 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_.guard_byte_, + s.config_.guard_z_); + + s.free_ += s.config_.guard_z_; + } + assert(s.committed_z_ % s.config_.hugepage_z_ == 0); assert(reinterpret_cast(s.limit_) % s.config_.hugepage_z_ == 0); return true; - } + } /*expand*/ std::byte * IAllocator_DArena::alloc(DArena & s, @@ -165,8 +173,6 @@ namespace xo { * ArenaConfig.store_header_flag_ disabled */ - bool remember_header_flag = s.config_.store_header_flag_; - return _alloc(s, req_z, alloc_mode::super); } @@ -258,17 +264,37 @@ namespace xo { #endif } - std::byte * + byte * IAllocator_DArena::_alloc(DArena & s, std::size_t req_z, - bool store_header_flag, - bool remember_header_flag) + 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_)); - /* remember_header_flag -implies-> store_header_flag */ - assert(store_header_flag || !remember_header_flag); /* * free_(pre) @@ -277,7 +303,7 @@ namespace xo { * <-------------z1---------------> * < guard >< hz >< req_z >< dz >< guard > * - * used <== +++++++++0000zzzz@@@@@@@@@@@@@@@@@ppppppp+++++++++ ==> unallocated + * used <== +++++++++0000zzzz@@@@@@@@@@@@@@@@@ppppppp+++++++++ ==> avail * * ^ ^ ^ * header mem | @@ -296,12 +322,15 @@ namespace xo { /* 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 + /* 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 (store_header_flag) + { if ((s.config_.header_size_mask_ & z0) == z0) [[likely]] { hz = sizeof(header); } else { @@ -309,7 +338,9 @@ namespace xo { ++(s.error_count_); s.last_error_ = AllocatorError(error::header_size_mask, s.error_count_, - 0 /*add_commit_z*/, s.committed_z_, reserved(s)); + 0 /*add_commit_z*/, + s.committed_z_, + reserved(s)); return nullptr; } } @@ -318,32 +349,45 @@ namespace xo { assert(padding::is_aligned(z1)); - if (expand(s, allocated(s) + z1)) [[likely]] { - if (store_header_flag) { - (*(uint64_t *)s.free_) = header; - - if (remember_header_flag) { - s.last_header_ = (uint64_t *)s.free_; - } - } - - byte * mem = s.free_ + hz; - - s.free_ += z1; - - 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))); - - return mem; - } else { - /* error already captured */ + 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_ = (uint64_t *)s.free_; + } + } + + byte * mem = s.free_ + hz; + + s.free_ += z1; + + if (store_guard) { + /* write guard bytes for overrun detection */ + ::memset(s.free_, + s.config_.guard_byte_, + s.config_.guard_z_); + + s.free_ += s.config_.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))); + + return mem; } void diff --git a/xo-alloc2/utest/arena.test.cpp b/xo-alloc2/utest/arena.test.cpp index 9178b347..69a2f0f3 100644 --- a/xo-alloc2/utest/arena.test.cpp +++ b/xo-alloc2/utest/arena.test.cpp @@ -212,6 +212,59 @@ namespace xo { REQUIRE(a1o.committed() <= a1o.reserved()); } + TEST_CASE("allocator-alloc-3", "[alloc2][Allocator]") + { + using header_type = AAllocator::header_type; + + /* typed allocator a1o, with object header + guard bytes */ + ArenaConfig cfg { .name_ = "testarena", + .size_ = 64*1024, + .guard_z_ = 8, + .guard_byte_ = 0xfd, + .store_header_flag_ = true, + /* up to 4GB */ + .header_size_mask_ = 0xffffffff, + .debug_flag_ = false, + }; + DArena arena = DArena::map(cfg); + obj a1o{&arena}; + + REQUIRE(a1o.reserved() >= cfg.size_); + REQUIRE(a1o.committed() == 0); + REQUIRE(a1o.available() == 0); + REQUIRE(a1o.allocated() == 0); + + size_t z0 = 1; + byte * m0 = a1o.alloc(1); + + REQUIRE(m0); + + // + // > < + // < guard>
< pad >< guard> + // ++++++++0000zzzzXppppppp++++++++ + // ^ ^ ^ ^ + // guard0 header m0 guard1 + // + + byte * guard0 = m0 - sizeof(header_type) - cfg.guard_z_; + header_type* header = (header_type*)(m0 - sizeof(header_type)); + size_t pad = padding::with_padding(z0) - z0; + byte * guard1 = m0 + z0 + pad; + + REQUIRE(a1o.contains(guard0)); + REQUIRE(a1o.contains(header)); + REQUIRE(((*header) & cfg.header_size_mask_) == padding::with_padding(z0)); + + REQUIRE(a1o.last_error().error_ == error::none); + REQUIRE(a1o.last_error().error_seq_ == 0); + + REQUIRE(a1o.allocated() == cfg.guard_z_ + sizeof(header_type) + z0 + pad + cfg.guard_z_); + REQUIRE(a1o.allocated() <= a1o.committed()); + REQUIRE(a1o.allocated() + a1o.available() == a1o.committed()); + REQUIRE(a1o.committed() <= a1o.reserved()); + } + TEST_CASE("allocator-fail-1", "[alloc2][AAllocator]") { /* typed allocator a1o */