From ef8ec32a2dff01c3308195ff33ee071ba51d89ba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Dec 2025 16:44:44 -0500 Subject: [PATCH] xo-alloc2: + Allocator::alloc_info() Also extend unit test --- .../include/xo/alloc2/alloc/AAllocator.hpp | 9 +++++- .../include/xo/alloc2/alloc/AllocHeader.hpp | 13 -------- .../xo/alloc2/alloc/AllocatorError.hpp | 6 ++++ .../xo/alloc2/alloc/IAllocator_Any.hpp | 13 ++++---- .../xo/alloc2/alloc/IAllocator_Xfer.hpp | 3 ++ .../include/xo/alloc2/alloc/RAllocator.hpp | 25 ++++++++-------- xo-alloc2/include/xo/alloc2/arena/DArena.hpp | 12 ++++++++ .../xo/alloc2/arena/IAllocator_DArena.hpp | 2 ++ .../include/xo/alloc2/gc/DX1Collector.hpp | 2 ++ .../xo/alloc2/gc/IAllocator_DX1Collector.hpp | 2 ++ xo-alloc2/src/alloc2/DArena.cpp | 28 +++++++++++++++++ xo-alloc2/src/alloc2/DX1Collector.cpp | 18 +++++++++++ xo-alloc2/src/alloc2/IAllocator_DArena.cpp | 30 +++++++++++++++++++ .../src/alloc2/IAllocator_DX1Collector.cpp | 6 ++++ xo-alloc2/utest/random_allocs.cpp | 27 ++++++++++++----- 15 files changed, 156 insertions(+), 40 deletions(-) diff --git a/xo-alloc2/include/xo/alloc2/alloc/AAllocator.hpp b/xo-alloc2/include/xo/alloc2/alloc/AAllocator.hpp index b553f138..b6e269a3 100644 --- a/xo-alloc2/include/xo/alloc2/alloc/AAllocator.hpp +++ b/xo-alloc2/include/xo/alloc2/alloc/AAllocator.hpp @@ -6,6 +6,7 @@ #pragma once #include "AllocatorError.hpp" +#include "AllocInfo.hpp" #include "xo/facet/facet_implementation.hpp" #include "xo/facet/typeseq.hpp" #include @@ -72,9 +73,15 @@ namespace xo { /** true iff allocator @p d is responsible for memory at address @p p. **/ virtual bool contains(Copaque d, const void * p) const noexcept = 0; - /** report last error **/ virtual AllocatorError last_error(Copaque d) const noexcept = 0; + /** fetch alloc info: given memory @p mem previously obtained + * from {@ref alloc, @ref super_alloc}, get {tseq, age, size} details + * for that allocation. + * + * Non-const @p d because may stash error details + **/ + virtual AllocInfo alloc_info(Opaque d, value_type mem) const noexcept = 0; /** expand committed space in arena @p d * to size at least @p z diff --git a/xo-alloc2/include/xo/alloc2/alloc/AllocHeader.hpp b/xo-alloc2/include/xo/alloc2/alloc/AllocHeader.hpp index 663c443e..7d6b4ec6 100644 --- a/xo-alloc2/include/xo/alloc2/alloc/AllocHeader.hpp +++ b/xo-alloc2/include/xo/alloc2/alloc/AllocHeader.hpp @@ -148,19 +148,6 @@ namespace xo { std::uint8_t size_bits_ = 32; }; - struct AllocInfo { - using size_type = AllocHeader::size_type; - - AllocInfo(const AllocHeaderConfig * p_cfg, const AllocHeader * p_hdr) - : p_config_{p_cfg}, p_header_{p_hdr} {} - - std::uint32_t tseq() const noexcept { return p_config_->tseq(*p_header_); } - std::uint32_t age() const noexcept { return p_config_->age (*p_header_); } - size_type size() const noexcept { return p_config_->size(*p_header_); } - - const AllocHeaderConfig * p_config_ = nullptr; - const AllocHeader * p_header_ = nullptr; - }; } } diff --git a/xo-alloc2/include/xo/alloc2/alloc/AllocatorError.hpp b/xo-alloc2/include/xo/alloc2/alloc/AllocatorError.hpp index 43299ac7..cb80b484 100644 --- a/xo-alloc2/include/xo/alloc2/alloc/AllocatorError.hpp +++ b/xo-alloc2/include/xo/alloc2/alloc/AllocatorError.hpp @@ -23,6 +23,12 @@ namespace xo { header_size_mask, /** sub_alloc not preceded by super alloc (or another sub_alloc) **/ orphan_sub_alloc, + /** attempt to call alloc_info for allocator with alloc header feature disabled + * (e.g. @ref see ArenaConfig::store_header_flag_) + **/ + alloc_info_disabled, + /** attempt to call alloc_info for address not owned by allocator **/ + alloc_info_address, }; struct AllocatorError { diff --git a/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Any.hpp b/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Any.hpp index 24414fb9..dae19cca 100644 --- a/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Any.hpp +++ b/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Any.hpp @@ -42,12 +42,13 @@ namespace xo { [[noreturn]] AllocatorError last_error(Copaque) const noexcept override { _fatal(); } // non-const methods - [[noreturn]] bool expand(Opaque, std::size_t) const noexcept override { _fatal(); } - [[noreturn]] value_type alloc(Opaque, std::size_t) const override { _fatal(); } - [[noreturn]] value_type super_alloc(Opaque, std::size_t) const override { _fatal(); } - [[noreturn]] value_type sub_alloc(Opaque, std::size_t, bool) const override { _fatal(); } - [[noreturn]] void clear(Opaque) const override { _fatal(); } - [[noreturn]] void destruct_data(Opaque) const override { _fatal(); } + [[noreturn]] AllocInfo alloc_info(Opaque, value_type) const noexcept override { _fatal(); } + [[noreturn]] bool expand(Opaque, std::size_t) const noexcept override { _fatal(); } + [[noreturn]] value_type alloc(Opaque, std::size_t) const override { _fatal(); } + [[noreturn]] value_type super_alloc(Opaque, std::size_t) const override { _fatal(); } + [[noreturn]] value_type sub_alloc(Opaque, std::size_t, bool) const override { _fatal(); } + [[noreturn]] void clear(Opaque) const override { _fatal(); } + [[noreturn]] void destruct_data(Opaque) const override { _fatal(); } private: [[noreturn]] static void _fatal(); diff --git a/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Xfer.hpp b/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Xfer.hpp index c8ad8187..7224f6d1 100644 --- a/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Xfer.hpp +++ b/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Xfer.hpp @@ -42,6 +42,9 @@ namespace xo { // non-const methods + AllocInfo alloc_info(Opaque d, value_type mem) const noexcept override { + return I::alloc_info(_dcast(d), mem); + } bool expand(Opaque d, std::size_t z) const noexcept override { return I::expand(_dcast(d), z); } value_type alloc(Opaque d, diff --git a/xo-alloc2/include/xo/alloc2/alloc/RAllocator.hpp b/xo-alloc2/include/xo/alloc2/alloc/RAllocator.hpp index 6d0b4a5e..cca2f7da 100644 --- a/xo-alloc2/include/xo/alloc2/alloc/RAllocator.hpp +++ b/xo-alloc2/include/xo/alloc2/alloc/RAllocator.hpp @@ -25,22 +25,23 @@ namespace xo { RAllocator() {} RAllocator(Object::DataPtr data) : Object{std::move(data)} {} - int32_t _typeseq() const noexcept { return O::iface()->_typeseq(); } - std::string_view name() const noexcept { return O::iface()->name(O::data()); } - size_type reserved() const noexcept { return O::iface()->reserved(O::data()); } - size_type size() const noexcept { return O::iface()->size(O::data()); } - size_type committed() const noexcept { return O::iface()->committed(O::data()); } - size_type available() const noexcept { return O::iface()->available(O::data()); } - size_type allocated() const noexcept { return O::iface()->allocated(O::data()); } - bool contains(const void * p) const noexcept { return O::iface()->contains(O::data(), p); } + int32_t _typeseq() const noexcept { return O::iface()->_typeseq(); } + std::string_view name() const noexcept { return O::iface()->name(O::data()); } + size_type reserved() const noexcept { return O::iface()->reserved(O::data()); } + size_type size() const noexcept { return O::iface()->size(O::data()); } + size_type committed() const noexcept { return O::iface()->committed(O::data()); } + size_type available() const noexcept { return O::iface()->available(O::data()); } + size_type allocated() const noexcept { return O::iface()->allocated(O::data()); } + bool contains(const void * p) const noexcept { return O::iface()->contains(O::data(), p); } AllocatorError last_error() const noexcept { return O::iface()->last_error(O::data()); } - bool expand(size_type z) { return O::iface()->expand(O::data(), z); } - value_type alloc(size_type z) noexcept { return O::iface()->alloc(O::data(), z); } + value_type alloc(size_type z) noexcept { return O::iface()->alloc(O::data(), z); } value_type super_alloc(size_type z) noexcept { return O::iface()->super_alloc(O::data(), z); } - value_type sub_alloc(size_type z, - bool complete_flag) noexcept { return O::iface()->sub_alloc(O::data(), + value_type sub_alloc(size_type z, + bool complete_flag) noexcept { return O::iface()->sub_alloc(O::data(), z, complete_flag); } + bool expand(size_type z) { return O::iface()->expand(O::data(), z); } + AllocInfo alloc_info(value_type mem) { return O::iface()->alloc_info(O::data(), mem); } static bool _valid; }; diff --git a/xo-alloc2/include/xo/alloc2/arena/DArena.hpp b/xo-alloc2/include/xo/alloc2/arena/DArena.hpp index dc95b0d7..39eb1b5a 100644 --- a/xo-alloc2/include/xo/alloc2/arena/DArena.hpp +++ b/xo-alloc2/include/xo/alloc2/arena/DArena.hpp @@ -88,6 +88,18 @@ namespace xo { /** get header from allocated object address **/ header_type * obj2hdr(void * obj) noexcept; + /** report alloc book-keeping info for allocation at @p mem + * + * Require: + * 1. @p mem is address returned by allocation on this arena + * i.e. by @ref IAllocator_DArena::alloc() or @ref IAllocator_DArena::alloc_super() + * 2. @p mem has not been invalidated since it was allocated + * i.e. by call to @ref DArena::clear + * + * Note: non-const, may stash error details + **/ + AllocInfo alloc_info(value_type mem) 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 39d77c21..36311bfa 100644 --- a/xo-alloc2/include/xo/alloc2/arena/IAllocator_DArena.hpp +++ b/xo-alloc2/include/xo/alloc2/arena/IAllocator_DArena.hpp @@ -45,6 +45,8 @@ namespace xo { static bool contains(const DArena &, const void * p) noexcept; static AllocatorError last_error(const DArena &) noexcept; + /** retrieve allocation bookkeeping info for @p mem from arena @p d **/ + static AllocInfo alloc_info(DArena &, value_type mem) noexcept; /** 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_. diff --git a/xo-alloc2/include/xo/alloc2/gc/DX1Collector.hpp b/xo-alloc2/include/xo/alloc2/gc/DX1Collector.hpp index aed77e98..5a98c54f 100644 --- a/xo-alloc2/include/xo/alloc2/gc/DX1Collector.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/DX1Collector.hpp @@ -209,6 +209,8 @@ namespace xo { /** expand gen0 committed size to at least @p z. **/ bool expand(size_type z) noexcept; + /** Retreive bookkeeping info for allocation at @p mem. **/ + AllocInfo alloc_info(value_type mem) noexcept; // ----- book-keeping ----- diff --git a/xo-alloc2/include/xo/alloc2/gc/IAllocator_DX1Collector.hpp b/xo-alloc2/include/xo/alloc2/gc/IAllocator_DX1Collector.hpp index cea8ae41..b8cf8232 100644 --- a/xo-alloc2/include/xo/alloc2/gc/IAllocator_DX1Collector.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/IAllocator_DX1Collector.hpp @@ -56,6 +56,8 @@ namespace xo { static value_type sub_alloc(DX1Collector & d, size_type z, bool complete) noexcept; /** expand gen0 spaces (both from-space and to-space) **/ static bool expand(DX1Collector & d, size_type z) noexcept; + /** fetch allocation bookkeeping info **/ + static AllocInfo alloc_info(DX1Collector & d, value_type mem) noexcept; /** reset to empty state; clears all generations **/ static void clear(DX1Collector & d); diff --git a/xo-alloc2/src/alloc2/DArena.cpp b/xo-alloc2/src/alloc2/DArena.cpp index 202a723d..74b404cc 100644 --- a/xo-alloc2/src/alloc2/DArena.cpp +++ b/xo-alloc2/src/alloc2/DArena.cpp @@ -247,6 +247,34 @@ namespace xo { return (header_type *)((byte *)obj - sizeof(header_type)); } + AllocInfo + DArena::alloc_info(value_type mem) noexcept + { + if (!config_.store_header_flag_) [[unlikely]] { + ++(error_count_); + last_error_ = AllocatorError(error::alloc_info_disabled, + error_count_, + 0 /*add_commit_z*/, + committed_z_, + this->reserved()); + + return AllocInfo::error_not_configured(&config_.header_); + } + + byte * header_mem = mem - sizeof(AllocHeader); + + if (!this->contains(header_mem)) { + ++(error_count_); + last_error_ = AllocatorError(error::alloc_info_address, + error_count_, + 0 /*add_commit_z*/, + committed_z_, + this->reserved()); + } + + return AllocInfo(&config_.header_, (AllocHeader *)header_mem); + } + void DArena::clear() noexcept { diff --git a/xo-alloc2/src/alloc2/DX1Collector.cpp b/xo-alloc2/src/alloc2/DX1Collector.cpp index 5d573e8b..c9f53b0d 100644 --- a/xo-alloc2/src/alloc2/DX1Collector.cpp +++ b/xo-alloc2/src/alloc2/DX1Collector.cpp @@ -239,6 +239,24 @@ namespace xo { return false; } + AllocInfo + DX1Collector::alloc_info(value_type mem) noexcept { + for (role ri : role::all()) { + for (generation gj{0}; gj < config_.n_generation_; ++gj) { + DArena * arena = this->get_space(ri, gj); + + assert(arena); + + if (arena->contains(mem)) { + return arena->alloc_info(mem); + } + } + } + + // deliberately attempt on nursery to-space, to capture error info + return sentinel + return this->new_space()->alloc_info(mem); + } + void DX1Collector::reverse_roles(generation g) noexcept { assert(g < config_.n_generation_); diff --git a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp index 82b9cc8e..04499e0d 100644 --- a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp +++ b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp @@ -59,6 +59,36 @@ namespace xo { return s.last_error_; } + AllocInfo + IAllocator_DArena::alloc_info(DArena & s, value_type mem) noexcept + { + return s.alloc_info(mem); + + if (!s.config_.store_header_flag_) [[unlikely]] { + ++(s.error_count_); + s.last_error_ = AllocatorError(error::alloc_info_disabled, + s.error_count_, + 0 /*add_commit_z*/, + s.committed_z_, + reserved(s)); + + return AllocInfo::error_not_configured(&s.config_.header_); + } + + byte * header_mem = mem - sizeof(AllocHeader); + + if (!s.contains(header_mem)) { + ++(s.error_count_); + s.last_error_ = AllocatorError(error::alloc_info_address, + s.error_count_, + 0 /*add_commit_z*/, + s.committed_z_, + reserved(s)); + } + + return AllocInfo(&s.config_.header_, (AllocHeader*)header_mem); + } + bool IAllocator_DArena::expand(DArena & s, size_t target_z) noexcept { diff --git a/xo-alloc2/src/alloc2/IAllocator_DX1Collector.cpp b/xo-alloc2/src/alloc2/IAllocator_DX1Collector.cpp index 36f7cedf..f26fc094 100644 --- a/xo-alloc2/src/alloc2/IAllocator_DX1Collector.cpp +++ b/xo-alloc2/src/alloc2/IAllocator_DX1Collector.cpp @@ -85,6 +85,12 @@ namespace xo { return d.expand(z); } + AllocInfo + IAllocator_DX1Collector::alloc_info(DX1Collector & d, value_type mem) noexcept + { + return d.alloc_info(mem); + } + void IAllocator_DX1Collector::clear(DX1Collector & d) { diff --git a/xo-alloc2/utest/random_allocs.cpp b/xo-alloc2/utest/random_allocs.cpp index a38e6acc..58580ff1 100644 --- a/xo-alloc2/utest/random_allocs.cpp +++ b/xo-alloc2/utest/random_allocs.cpp @@ -4,12 +4,15 @@ **/ #include "random_allocs.hpp" +#include "padding.hpp" #include #include #include #include namespace utest { + using xo::mm::AllocInfo; + using xo::mm::padding; using xo::rng::xoshiro256ss; using xo::facet::obj; using xo::scope; @@ -20,9 +23,9 @@ namespace utest { /* remember an allocation result. * application owns memory in [lo, lo+z) */ - struct AllocInfo { - AllocInfo() = default; - AllocInfo(byte * lo, size_t z) : lo_{lo}, z_{z} {} + struct Alloc { + Alloc() = default; + Alloc(byte * lo, size_t z) : lo_{lo}, z_{z} {} byte * lo() const { return lo_; } byte * hi() const { return lo_ + z_; } @@ -44,11 +47,11 @@ namespace utest { * - allocs have valid alloc header * - allocs surrounded by guard bytes * - * allocs sorted on AllocInfo::lo + * allocs sorted on Alloc::lo */ - std::map allocs_by_lo_map; - /* allocs sorted on AllocInfo::hi */ - std::map allocs_by_hi_map; + std::map allocs_by_lo_map; + /* allocs sorted on Alloc::hi */ + std::map allocs_by_hi_map; for (uint32_t i_alloc = 0; i_alloc < n_alloc; ++i_alloc) { std::normal_distribution ngen{5.0, 1.5}; @@ -90,10 +93,18 @@ namespace utest { } } - allocs_by_lo_map[mem] = AllocInfo(mem, z); + allocs_by_lo_map[mem] = Alloc(mem, z); allocs_by_hi_map[mem + z] = &(allocs_by_lo_map[mem]); + /* verify we can recover alloc info */ + AllocInfo info = mm.alloc_info(mem); + REQUIRE_ORFAIL(ok_flag, catch_flag, info.is_valid()); + REQUIRE_ORFAIL(ok_flag, catch_flag, info.size() == padding::with_padding(z)); + /* age isn't configured -> 0 = sentinel */ + REQUIRE_ORFAIL(ok_flag, catch_flag, info.age() == 0); + /* tseq isn't confrigured -> 0 = sentinel */ + REQUIRE_ORFAIL(ok_flag, catch_flag, info.tseq() == 0); } return true;