From bf27314688652fda5caee14e29f587ac4c6b358d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Dec 2025 20:16:46 -0500 Subject: [PATCH] xo-alloc2: DX1CollectorIterator infra [WIP] --- include/xo/alloc2/alloc/AAllocIterator.hpp | 12 +- include/xo/alloc2/alloc/AllocError.hpp | 8 + include/xo/alloc2/alloc/AllocHeader.hpp | 14 -- include/xo/alloc2/alloc/AllocInfo.hpp | 8 +- .../xo/alloc2/alloc/IAllocIterator_Any.hpp | 5 +- .../xo/alloc2/alloc/IAllocIterator_Xfer.hpp | 9 +- include/xo/alloc2/alloc/RAllocIterator.hpp | 2 +- include/xo/alloc2/arena/ArenaConfig.hpp | 7 +- include/xo/alloc2/arena/DArena.hpp | 13 +- include/xo/alloc2/arena/DArenaIterator.hpp | 102 +++++++++ .../arena/IAllocIterator_DArenaIterator.hpp | 36 ++++ include/xo/alloc2/cmpresult.hpp | 59 ++++++ include/xo/alloc2/gc/DX1Collector.hpp | 11 + include/xo/alloc2/gc/DX1CollectorIterator.hpp | 90 ++++++++ .../IAllocIterator_DX1CollectorIterator.hpp | 36 ++++ include/xo/alloc2/gc/generation.hpp | 3 +- src/alloc2/AllocInfo.cpp | 13 +- src/alloc2/CMakeLists.txt | 4 + src/alloc2/DArena.cpp | 43 ++-- src/alloc2/DArenaIterator.cpp | 134 ++++++++++++ src/alloc2/DX1Collector.cpp | 56 ++--- src/alloc2/DX1CollectorIterator.cpp | 88 ++++++++ src/alloc2/IAllocIterator_DArenaIterator.cpp | 43 ++++ .../IAllocIterator_DX1CollectorIterator.cpp | 41 ++++ src/alloc2/IAllocator_DArena.cpp | 93 +------- utest/AllocIterator.test.cpp | 199 ++++++++++++++++++ utest/CMakeLists.txt | 5 +- utest/DX1CollectorIterator.test.cpp | 64 ++++++ utest/arena.test.cpp | 6 +- utest/random_allocs.cpp | 13 ++ 30 files changed, 1041 insertions(+), 176 deletions(-) create mode 100644 include/xo/alloc2/arena/DArenaIterator.hpp create mode 100644 include/xo/alloc2/arena/IAllocIterator_DArenaIterator.hpp create mode 100644 include/xo/alloc2/cmpresult.hpp create mode 100644 include/xo/alloc2/gc/DX1CollectorIterator.hpp create mode 100644 include/xo/alloc2/gc/IAllocIterator_DX1CollectorIterator.hpp create mode 100644 src/alloc2/DArenaIterator.cpp create mode 100644 src/alloc2/DX1CollectorIterator.cpp create mode 100644 src/alloc2/IAllocIterator_DArenaIterator.cpp create mode 100644 src/alloc2/IAllocIterator_DX1CollectorIterator.cpp create mode 100644 utest/AllocIterator.test.cpp create mode 100644 utest/DX1CollectorIterator.test.cpp diff --git a/include/xo/alloc2/alloc/AAllocIterator.hpp b/include/xo/alloc2/alloc/AAllocIterator.hpp index c548493..6ce08d1 100644 --- a/include/xo/alloc2/alloc/AAllocIterator.hpp +++ b/include/xo/alloc2/alloc/AAllocIterator.hpp @@ -6,6 +6,7 @@ #pragma once #include "alloc/AllocInfo.hpp" +#include "cmpresult.hpp" #include namespace xo { @@ -19,7 +20,8 @@ namespace xo { /** @class AAllocIterator * @brief Abstract facet for iterating over allocs * - * Iterator refers to an AllocInfo instance. + * Iterator refers to an AllocInfo instance + * Only supporting forward-allocator. **/ struct AAllocIterator { using obj_AAllocIterator = xo::facet::obj; @@ -29,13 +31,11 @@ namespace xo { /** retrieve AllocInfo for current iterator position **/ virtual AllocInfo deref(Copaque d) const noexcept = 0; - /** compare alloc iterators @p d and @p other for equality **/ - virtual int compare(Copaque d, - const obj_AAllocIterator & other) const noexcept = 0; + /** compare alloc iterators @p d and @p other **/ + virtual cmpresult compare(Copaque d, + const obj_AAllocIterator & other) const noexcept = 0; /** advance iterator to next position **/ virtual void next(Opaque d) const noexcept = 0; - /** retreat iterator to previous position **/ - virtual void prev(Opaque d) const noexcept = 0; }; } /*namespace mm*/ } /*namespace xo*/ diff --git a/include/xo/alloc2/alloc/AllocError.hpp b/include/xo/alloc2/alloc/AllocError.hpp index 5264d93..7d09764 100644 --- a/include/xo/alloc2/alloc/AllocError.hpp +++ b/include/xo/alloc2/alloc/AllocError.hpp @@ -29,6 +29,14 @@ namespace xo { alloc_info_disabled, /** attempt to call alloc_info for address not owned by allocator **/ alloc_info_address, + /** for example: alloc iteration not supported in arenas with + * AllocConfig.store_header_flag_ = false + **/ + alloc_iterator_not_supported, + /** attempt to deref an iterator that does not refer to an alloc **/ + alloc_iterator_deref, + /** attempt to advance an iterator that does not refer to an alloc **/ + alloc_iterator_next, }; struct AllocError { diff --git a/include/xo/alloc2/alloc/AllocHeader.hpp b/include/xo/alloc2/alloc/AllocHeader.hpp index 7544f36..ec99f59 100644 --- a/include/xo/alloc2/alloc/AllocHeader.hpp +++ b/include/xo/alloc2/alloc/AllocHeader.hpp @@ -17,20 +17,6 @@ namespace xo { explicit AllocHeader(repr_type x) : repr_{x} {} -#ifdef OBSOLETE - std::uint32_t tseq(const AllocHeaderConfig & cfg) const noexcept { - return cfg.tseq(repr_); - } - - std::uint32_t age(const AllocHeaderConfig & cfg) const noexcept { - return cfg.age(repr_); - } - - size_type size(const AllocHeaderConfig & cfg) const noexcept { - return cfg.size(repr_); - } -#endif - repr_type repr_; }; diff --git a/include/xo/alloc2/alloc/AllocInfo.hpp b/include/xo/alloc2/alloc/AllocInfo.hpp index 28a1bbd..09846ba 100644 --- a/include/xo/alloc2/alloc/AllocInfo.hpp +++ b/include/xo/alloc2/alloc/AllocInfo.hpp @@ -31,7 +31,11 @@ namespace xo { p_guard_hi_{p_guard_hi} {} /** error when alloc-header not configured **/ - static AllocInfo error_not_configured(AllocHeaderConfig * p_cfg) { + static AllocInfo error_not_configured(const AllocHeaderConfig * p_cfg) { + return AllocInfo(p_cfg, nullptr, nullptr, nullptr); + } + /** error on deref empty iterator **/ + static AllocInfo error_invalid_iterator(const AllocHeaderConfig * p_cfg) { return AllocInfo(p_cfg, nullptr, nullptr, nullptr); } @@ -46,6 +50,8 @@ namespace xo { std::uint32_t age() const noexcept { return p_config_->age (*p_header_); } /** Allocation size (including allocator-supplied padding) **/ size_type size() const noexcept { return p_config_->size(*p_header_); } + /** Payload for this allocation. This is the memory available to application **/ + span_type payload() const noexcept; /** Guard bytes immediately following allocation **/ span_type guard_hi() const noexcept; /** Number of guard bytes **/ diff --git a/include/xo/alloc2/alloc/IAllocIterator_Any.hpp b/include/xo/alloc2/alloc/IAllocIterator_Any.hpp index 388c5aa..9765194 100644 --- a/include/xo/alloc2/alloc/IAllocIterator_Any.hpp +++ b/include/xo/alloc2/alloc/IAllocIterator_Any.hpp @@ -27,12 +27,11 @@ namespace xo { // const methods [[noreturn]] AllocInfo deref(Copaque) const noexcept override { _fatal(); } - [[noreturn]] int compare(Copaque, - const obj_AAllocIterator &) const noexcept override { _fatal(); } + [[noreturn]] cmpresult compare(Copaque, + const obj_AAllocIterator &) const noexcept override { _fatal(); } // non-const methods [[noreturn]] void next(Opaque) const noexcept override { _fatal(); } - [[noreturn]] void prev(Opaque) const noexcept override { _fatal(); } private: [[noreturn]] static void _fatal(); diff --git a/include/xo/alloc2/alloc/IAllocIterator_Xfer.hpp b/include/xo/alloc2/alloc/IAllocIterator_Xfer.hpp index 604b4f0..9aa8c33 100644 --- a/include/xo/alloc2/alloc/IAllocIterator_Xfer.hpp +++ b/include/xo/alloc2/alloc/IAllocIterator_Xfer.hpp @@ -3,6 +3,8 @@ * @author Roland Conybeare, Dec 2025 **/ +#pragma once + #include "AAllocIterator.hpp" namespace xo { @@ -27,15 +29,14 @@ namespace xo { int32_t _typeseq() const noexcept override { return s_typeseq; } AllocInfo deref(Copaque d) const noexcept override { return I::deref(_dcast(d)); } - int compare(Copaque d, - const obj_AAllocIterator & other) + cmpresult compare(Copaque d, + const obj_AAllocIterator & other) const noexcept override { return I::compare(_dcast(d), other); } // non-const methods - void next(Opaque d) const noexcept override { I::prev(_dcast(d)); } - void prev(Opaque d) const noexcept override { I::next(_dcast(d)); } + void next(Opaque d) const noexcept override { I::next(_dcast(d)); } private: using I = Impl; diff --git a/include/xo/alloc2/alloc/RAllocIterator.hpp b/include/xo/alloc2/alloc/RAllocIterator.hpp index 9a986f6..fd5f983 100644 --- a/include/xo/alloc2/alloc/RAllocIterator.hpp +++ b/include/xo/alloc2/alloc/RAllocIterator.hpp @@ -4,7 +4,7 @@ **/ #include "AAllocIterator.hpp" -#include //#include namespace xo { diff --git a/include/xo/alloc2/arena/ArenaConfig.hpp b/include/xo/alloc2/arena/ArenaConfig.hpp index c467f60..dcac455 100644 --- a/include/xo/alloc2/arena/ArenaConfig.hpp +++ b/include/xo/alloc2/arena/ArenaConfig.hpp @@ -5,7 +5,7 @@ #pragma once -#include "alloc/AllocHeader.hpp" +#include "alloc/AllocHeaderConfig.hpp" #include "alloc/AllocError.hpp" #include #include @@ -34,11 +34,6 @@ namespace xo { * present in arena **/ bool store_header_flag_ = false; -#ifdef OBSOLETE - /** number of bits to represent allocation size **/ - std::uint64_t header_size_bits_ = 32; - std::uint64_t header_size_mask_ = (1ul << header_size_bits_) - 1; -#endif /** configuration for per-alloc header **/ AllocHeaderConfig header_; /** true to enable debug logging **/ diff --git a/include/xo/alloc2/arena/DArena.hpp b/include/xo/alloc2/arena/DArena.hpp index 8be33af..5d4ceed 100644 --- a/include/xo/alloc2/arena/DArena.hpp +++ b/include/xo/alloc2/arena/DArena.hpp @@ -6,9 +6,11 @@ #pragma once #include "ArenaConfig.hpp" +#include "alloc/AllocInfo.hpp" namespace xo { namespace mm { + struct DArenaIterator; // see DArenaIterator.hpp /** @class DArena * @@ -85,6 +87,11 @@ namespace xo { /** true if arena is mapped i.e. has a reserved address range **/ bool is_mapped() const noexcept { return (lo_ != nullptr) && (hi_ != nullptr); } + /** @ret iterator pointing to the first allocation in this arena **/ + DArenaIterator begin() const noexcept; + /** @ret iterator pointing to just after the last allocation in this arena **/ + DArenaIterator end() const noexcept; + /** get header from allocated object address **/ header_type * obj2hdr(void * obj) noexcept; @@ -98,7 +105,11 @@ namespace xo { * * Note: non-const, may stash error details **/ - AllocInfo alloc_info(value_type mem) noexcept; + AllocInfo alloc_info(value_type mem) const noexcept; + + /** capture error information: advance error count + set last_error **/ + void capture_error(error err, + size_type target_z = 0) const; /** discard all allocated memory, return to empty state * Promise: diff --git a/include/xo/alloc2/arena/DArenaIterator.hpp b/include/xo/alloc2/arena/DArenaIterator.hpp new file mode 100644 index 0000000..5a237c4 --- /dev/null +++ b/include/xo/alloc2/arena/DArenaIterator.hpp @@ -0,0 +1,102 @@ +/** @file DArenaIterator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "alloc/AllocInfo.hpp" +#include "alloc/AllocHeader.hpp" +#include "cmpresult.hpp" + +namespace xo { + namespace mm { + struct DArena; + + /** @class DArenaIterator + * @brief Representation for alloc iterator over arena + * + * Map showing an arena allocation: + * + * @pre + * + * <-------------z1---------------> + * < guard >< hz >< req_z >< dz >< guard > + * + * +++++++++0000zzzz@@@@@@@@@@@@@@@@@ppppppp+++++++++ + * + * ^ ^ ^ + * header mem header + * ^ (next alloc) + * DArenaIterator::pos_ + * + * guard [+] guard before+after each allocation, for simple sanitize checks + * header [0] alloc header (non-size bits) + * [z] alloc header (size bits) + * mem [@] app-requested memory, including padding [p] + * dz [p] padding (to uintptr_t alignment. req_z+dz recorded in header) + * free_ DArena::free_ just after guard bytes for last allocation + * + * @endpre + **/ + struct DArenaIterator { + DArenaIterator() = default; + DArenaIterator(const DArena * arena, + AllocHeader * pos) : arena_{arena}, + pos_{pos} {} + + /** Create iterator in invalid state **/ + static DArenaIterator invalid() { return DArenaIterator(); } + + /** Create iterator pointing to the beginning of @p arena + * Iterator cannot modify memory, but can capture + * an iterator error in @p *arena + **/ + static DArenaIterator begin(const DArena * arena); + /** Create iterator pointing to the end of @p arena + * Iterator cannot modify memory, but can capture + * an iterator error in @p *arena + **/ + static DArenaIterator end(const DArena * arena); + + /** A valid iterator can be compared, at least with itself + * It can be dereferenced if is also non-empty + **/ + bool is_valid() const noexcept { return (arena_ != nullptr) && (pos_ != nullptr); } + bool is_invalid() const noexcept { return !is_valid(); } + + /** fetch contents at current iterator position **/ + AllocInfo deref() const noexcept; + /** compare two iterators. To be comparable, + * iterators must refer to the same arena + **/ + cmpresult compare(const DArenaIterator & other) const noexcept; + /** advance iterator to next allocation **/ + void next() noexcept; + + std::byte * pos_as_byte() const { return (std::byte *)pos_; } + + /** *ix synonym for ix.deref() **/ + AllocInfo operator*() const noexcept { return this->deref(); } + /** ++ix synonym for ix.next() **/ + DArenaIterator & operator++() noexcept { this->next(); return *this; } + + /** iterator visits allocations from this arena **/ + const DArena * arena_ = nullptr; + /** current iterator position **/ + AllocHeader * pos_ = nullptr; + }; + + inline bool + operator==(const DArenaIterator & x, const DArenaIterator & y) { + return x.compare(y).is_equal(); + } + + inline bool + operator!=(const DArenaIterator & x, const DArenaIterator & y) { + return !x.compare(y).is_equal(); + } + } /*namespace mm*/ +} /*namespace xo*/ + +/* end DArenaIterator.hpp */ diff --git a/include/xo/alloc2/arena/IAllocIterator_DArenaIterator.hpp b/include/xo/alloc2/arena/IAllocIterator_DArenaIterator.hpp new file mode 100644 index 0000000..dfe8400 --- /dev/null +++ b/include/xo/alloc2/arena/IAllocIterator_DArenaIterator.hpp @@ -0,0 +1,36 @@ +/** @file IAllocIterator_DArenaIterator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "alloc/IAllocIterator_Xfer.hpp" +#include "arena/DArenaIterator.hpp" + +namespace xo { + namespace mm { struct IAllocIterator_DArenaIterator; } + + namespace facet { + template <> + struct FacetImplementation { + using ImplType = xo::mm::IAllocIterator_Xfer; + }; + } + + namespace mm { + /** @class IAllocIterator_DArena + * @brief alloc iteration for the DArena allocator + **/ + struct IAllocIterator_DArenaIterator { + static AllocInfo deref(const DArenaIterator &) noexcept; + static cmpresult compare(const DArenaIterator &, + const obj & other) noexcept; + static void next(DArenaIterator &) noexcept; + }; + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocIterator_DArenaIterator.cpp */ diff --git a/include/xo/alloc2/cmpresult.hpp b/include/xo/alloc2/cmpresult.hpp new file mode 100644 index 0000000..9fab433 --- /dev/null +++ b/include/xo/alloc2/cmpresult.hpp @@ -0,0 +1,59 @@ +/** @file cmpresult.hpp +* + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include +#include + +namespace xo { + namespace mm { + enum class comparison : int32_t { + invalid = -1, + comparable = 0, + incomparable = 1, + }; + + struct cmpresult { + cmpresult() : err_{comparison::invalid}, cmp_{0} {} + cmpresult(comparison err, std::int16_t cmp) : err_{err}, cmp_{cmp} {} + + static cmpresult incomparable() { return cmpresult(comparison::incomparable, 0); } + static cmpresult lesser() { return cmpresult(comparison::comparable, -1); } + static cmpresult equal() { return cmpresult(comparison::comparable, 0); } + static cmpresult greater() { return cmpresult(comparison::comparable, +1); } + + template + static cmpresult from_cmp(T && x, T && y) { + if (x < y) + return cmpresult::lesser(); + else if (x == y) + return cmpresult::equal(); + else + return cmpresult::greater(); + } + + + bool is_equal() const { return (err_ == comparison::comparable) && (cmp_ == 0); } + + /* -1 -> invalid (sentinel) + * 0 -> comparable + * 1 -> incomparable (e.g. iterators from different arenas) + */ + comparison err_ = comparison::invalid; + /* <0 -> lesser; 0 -> equal, >0 -> greater */ + std::int16_t cmp_ = 0; + }; + + inline std::ostream & operator<<(std::ostream & os, + const cmpresult & x) + { + os << ""; + } + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end cmpresult.hpp */ diff --git a/include/xo/alloc2/gc/DX1Collector.hpp b/include/xo/alloc2/gc/DX1Collector.hpp index 79fef9e..a637c99 100644 --- a/include/xo/alloc2/gc/DX1Collector.hpp +++ b/include/xo/alloc2/gc/DX1Collector.hpp @@ -145,6 +145,8 @@ namespace xo { generation gc_upto_; }; + struct DX1CollectorIterator; + // ----- DX1Collector ----- struct DX1Collector { @@ -212,6 +214,15 @@ namespace xo { /** Retreive bookkeeping info for allocation at @p mem. **/ AllocInfo alloc_info(value_type mem) noexcept; + // ----- iteration ----- + + /** alloc iterator at begin position **/ + DX1CollectorIterator begin() const noexcept; + /** alloc iterator at end position + * (valid, but cannot be dereferenced) + **/ + DX1CollectorIterator end() const noexcept; + // ----- book-keeping ----- /** reverse to-space and from-space roles for generation g **/ diff --git a/include/xo/alloc2/gc/DX1CollectorIterator.hpp b/include/xo/alloc2/gc/DX1CollectorIterator.hpp new file mode 100644 index 0000000..abd0d48 --- /dev/null +++ b/include/xo/alloc2/gc/DX1CollectorIterator.hpp @@ -0,0 +1,90 @@ +/** @file DX1CollectorIterator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "alloc/AllocInfo.hpp" +#include "gc/generation.hpp" +#include "arena/DArenaIterator.hpp" +#include "cmpresult.hpp" + +namespace xo { + namespace mm { + struct DX1Collector; + + /** @class DX1CollectorIterator + * @brief Representation for alloc iterator over X1 collector + * + * Will iterate across all allocs in all generations + **/ + struct DX1CollectorIterator { + DX1CollectorIterator() = default; + DX1CollectorIterator(const DX1Collector * gc, + generation gen_ix, + generation gen_hi, + DArenaIterator arena_ix, + DArenaIterator arena_hi); + + /** Invalid iterator. Does not compare equal to anything, including itself **/ + static DX1CollectorIterator invalid() { return DX1CollectorIterator(); } + /** Create iterator pointing to the beginning of @p gc. + * Iterator cannot modify payload memory + **/ + static DX1CollectorIterator begin(DX1Collector * gc); + /** Create iterator pointing to the end of @p gc. + * Iterator cannot modify payload memory. + **/ + static DX1CollectorIterator end(DX1Collector * gc); + + /** true if iterator is invalid. invalid iterators are not comparable **/ + bool is_valid() const noexcept { return (gc_ != nullptr); } + bool is_invalid() const noexcept { return !is_valid(); } + + /** fetch contents at current iterator position **/ + AllocInfo deref() const noexcept; + /** compare two iterators. To be comparable, + * iterators must refer to the same collector + **/ + cmpresult compare(const DX1CollectorIterator & other) const noexcept; + /** advance iterator to next allocation **/ + void next() noexcept; + + /** for *ix synonym for ix.deref() **/ + AllocInfo operator*() const noexcept { return this->deref(); } + + private: + /** if non-empty, normalize to state with arena_ix_ != arena_hi_ **/ + void normalize() noexcept; + + private: + /** Iterator visits allocations from this collector **/ + const DX1Collector * gc_ = nullptr; + /** Iterating over generations in [@p gen_ix_, @p gen_hi_). + * Current position is within arena for @p gen_ix_ to-space, + * Provided @p gen_ix_ < @p gen_hi_ + **/ + generation gen_ix_; + generation gen_hi_; + /** Iterating over allocs in [@p arena_ix_, @p arena_hi_). + * Current position is at @p arena_ix_ + **/ + DArenaIterator arena_ix_; + DArenaIterator arena_hi_; + }; + + inline bool + operator==(const DX1CollectorIterator & x, const DX1CollectorIterator & y) { + return x.compare(y).is_equal(); + } + + inline bool + operator!=(const DX1CollectorIterator & x, const DX1CollectorIterator & y) { + return !x.compare(y).is_equal(); + } + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end DX1CollectorIterator.hpp */ diff --git a/include/xo/alloc2/gc/IAllocIterator_DX1CollectorIterator.hpp b/include/xo/alloc2/gc/IAllocIterator_DX1CollectorIterator.hpp new file mode 100644 index 0000000..686f9fc --- /dev/null +++ b/include/xo/alloc2/gc/IAllocIterator_DX1CollectorIterator.hpp @@ -0,0 +1,36 @@ +/** @file IAllocIterator_DX1Collector.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "alloc/IAllocIterator_Xfer.hpp" +#include "gc/DX1CollectorIterator.hpp" + +namespace xo { + namespace mm { struct IAllocIterator_DX1CollectorIterator; } + + namespace facet { + template <> + struct FacetImplementation { + using ImplType = xo::mm::IAllocIterator_Xfer; + }; + } + + namespace mm { + /** @class IAllocIterator_DX1Collector + * @brief alloc iteration for the DX1Collector allocator + **/ + struct IAllocIterator_DX1CollectorIterator { + static AllocInfo deref(const DX1CollectorIterator &) noexcept; + static cmpresult compare(const DX1CollectorIterator &, + const obj & other) noexcept; + static void next(DX1CollectorIterator &) noexcept; + }; + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocIterator_DX1Collector.hpp */ diff --git a/include/xo/alloc2/gc/generation.hpp b/include/xo/alloc2/gc/generation.hpp index 2bf4fee..12b36d0 100644 --- a/include/xo/alloc2/gc/generation.hpp +++ b/include/xo/alloc2/gc/generation.hpp @@ -19,6 +19,7 @@ namespace xo { struct generation { using value_type = std::uint32_t; + constexpr generation() = default; explicit constexpr generation(value_type x) : value_{x} {} static generation nursery() { return generation{0}; } @@ -27,7 +28,7 @@ namespace xo { generation & operator++() { ++value_; return *this; } - std::uint32_t value_; + std::uint32_t value_ = 0; }; } } diff --git a/src/alloc2/AllocInfo.cpp b/src/alloc2/AllocInfo.cpp index c0749ba..76e899d 100644 --- a/src/alloc2/AllocInfo.cpp +++ b/src/alloc2/AllocInfo.cpp @@ -7,7 +7,6 @@ namespace xo { namespace mm { - auto AllocInfo::guard_lo() const noexcept -> span_type { @@ -18,6 +17,18 @@ namespace xo { p_guard_lo_ + p_config_->guard_z_); } + auto + AllocInfo::payload() const noexcept -> span_type + { + if (!p_header_) + return span_type(nullptr, nullptr); + + byte * lo = (byte *)(p_header_ + 1); + size_type z = this->size(); + + return span_type(lo, lo+z); + } + auto AllocInfo::guard_hi() const noexcept -> span_type { diff --git a/src/alloc2/CMakeLists.txt b/src/alloc2/CMakeLists.txt index 0b94e80..7dde13f 100644 --- a/src/alloc2/CMakeLists.txt +++ b/src/alloc2/CMakeLists.txt @@ -11,13 +11,17 @@ set(SELF_SRCS IAllocator_DArena.cpp IAllocIterator_Any.cpp + DArenaIterator.cpp + IAllocIterator_DArenaIterator.cpp ICollector_Any.cpp IGCObject_Any.cpp IAllocator_DX1Collector.cpp ICollector_DX1Collector.cpp + IAllocIterator_DX1CollectorIterator.cpp DX1Collector.cpp + DX1CollectorIterator.cpp ) diff --git a/src/alloc2/DArena.cpp b/src/alloc2/DArena.cpp index fe55281..782a16c 100644 --- a/src/alloc2/DArena.cpp +++ b/src/alloc2/DArena.cpp @@ -5,6 +5,7 @@ #include "alloc/AAllocator.hpp" #include "arena/DArena.hpp" +#include "arena/DArenaIterator.hpp" #include "xo/alloc2/padding.hpp" #include "xo/indentlog/print/tag.hpp" #include @@ -249,15 +250,10 @@ namespace xo { } AllocInfo - DArena::alloc_info(value_type mem) noexcept + DArena::alloc_info(value_type mem) const noexcept { if (!config_.store_header_flag_) [[unlikely]] { - ++(error_count_); - last_error_ = AllocError(error::alloc_info_disabled, - error_count_, - 0 /*add_commit_z*/, - committed_z_, - this->reserved()); + this->capture_error(error::alloc_info_disabled); return AllocInfo::error_not_configured(&config_.header_); } @@ -265,12 +261,7 @@ namespace xo { byte * header_mem = mem - sizeof(AllocHeader); if (!this->contains(header_mem)) { - ++(error_count_); - last_error_ = AllocError(error::alloc_info_address, - error_count_, - 0 /*add_commit_z*/, - committed_z_, - this->reserved()); + this->capture_error(error::alloc_info_address); } AllocHeader * header = (AllocHeader *)header_mem; @@ -286,6 +277,32 @@ namespace xo { guard_hi); } + DArenaIterator + DArena::begin() const noexcept + { + return DArenaIterator::begin(this); + } + + DArenaIterator + DArena::end() const noexcept + { + return DArenaIterator::end(this); + } + + void + DArena::capture_error(error err, + size_type target_z) const + { + DArena * self = const_cast(this); + + ++(self->error_count_); + self->last_error_ = AllocError(err, + error_count_, + target_z, + committed_z_, + reserved()); + } + void DArena::clear() noexcept { diff --git a/src/alloc2/DArenaIterator.cpp b/src/alloc2/DArenaIterator.cpp new file mode 100644 index 0000000..2a3e852 --- /dev/null +++ b/src/alloc2/DArenaIterator.cpp @@ -0,0 +1,134 @@ +/** @file DArenaIterator.cpp +* + * @author Roland Conybeare, Dec 2025 + **/ + +#include "arena/DArenaIterator.hpp" +#include "arena/DArena.hpp" +#include +#include +#include + +namespace xo { + using std::byte; + + namespace mm { + DArenaIterator + DArenaIterator::begin(const DArena * arena) + { + constexpr bool c_debug_flag = false; + scope log(XO_DEBUG(c_debug_flag)); + + assert(arena); + + if (arena->config_.store_header_flag_ == false) { + arena->capture_error(error::alloc_iterator_not_supported); + + return DArenaIterator::invalid(); + } + + byte * begin_byte = arena->lo_; + AllocHeader * begin_hdr = (AllocHeader *)begin_byte; + + log && log(xtag("begin_hdr", begin_hdr)); + + return DArenaIterator(arena, begin_hdr); + } + + DArenaIterator + DArenaIterator::end(const DArena * arena) + { + constexpr bool c_debug_flag = false; + scope log(XO_DEBUG(c_debug_flag)); + + assert(arena); + + if (arena->config_.store_header_flag_ == false) { + arena->capture_error(error::alloc_iterator_not_supported); + + return DArenaIterator::invalid(); + } + + byte * end_byte = arena->free_; + AllocHeader * end_hdr = (AllocHeader *)end_byte; + + log && log(xtag("end_hdr", end_hdr)); + + return DArenaIterator(arena, end_hdr); + } + + AllocInfo + DArenaIterator::deref() const noexcept + { + constexpr bool c_debug_flag = false; + scope log(XO_DEBUG(c_debug_flag)); + + bool contains_flag = arena_->contains(this->pos_as_byte()); + bool bounds_flag = (this->pos_as_byte() < arena_->free_); + + log && log(xtag("contains_flag", contains_flag), + xtag("bounds_flag", bounds_flag)); + + if (!contains_flag || !bounds_flag) { + arena_->capture_error(error::alloc_iterator_deref); + + return AllocInfo::error_invalid_iterator(&(arena_->config_.header_)); + } + + /* iterator points to beginning of header. + * memory given to application start immediately followed header + */ + + byte * mem = (byte *)(pos_ + 1); + + return arena_->alloc_info(mem); + } + + cmpresult + DArenaIterator::compare(const DArenaIterator & other_ix) const noexcept + { + if (is_invalid() || (arena_ != other_ix.arena_)) + return cmpresult::incomparable(); + + if (pos_ < other_ix.pos_) { + return cmpresult::lesser(); + } else if(pos_ == other_ix.pos_) { + return cmpresult::equal(); + } else { + return cmpresult::greater(); + } + } + + void + DArenaIterator::next() noexcept + { + constexpr bool c_debug_flag = false; + scope log(XO_DEBUG(c_debug_flag)); + + bool contains_flag = arena_->contains(this->pos_as_byte()); + bool bounds_flag = (this->pos_as_byte() < arena_->free_); + + log && log(xtag("contains_flag", contains_flag), + xtag("bounds_flag", bounds_flag)); + + if (!contains_flag || !bounds_flag) { + arena_->capture_error(error::alloc_iterator_next); + return; + } + + size_t mem_z = arena_->config_.header_.size(*pos_); + size_t guard_z = arena_->config_.header_.guard_z_; + + byte * next_as_byte = ((byte *)pos_ + sizeof(AllocHeader) + mem_z + guard_z); + /* next == ix.arena_free_ --> iterator is at end of allocator */ + assert(next_as_byte <= arena_->free_); + + AllocHeader * next = (AllocHeader *)next_as_byte; + + this->pos_ = next; + } + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end DArenaIterator.cpp */ diff --git a/src/alloc2/DX1Collector.cpp b/src/alloc2/DX1Collector.cpp index ab3bab2..1ac6cdf 100644 --- a/src/alloc2/DX1Collector.cpp +++ b/src/alloc2/DX1Collector.cpp @@ -6,6 +6,7 @@ #include "Allocator.hpp" #include "arena/IAllocator_DArena.hpp" #include "gc/DX1Collector.hpp" +#include "gc/DX1CollectorIterator.hpp" #include "gc/generation.hpp" #include "gc/object_age.hpp" #include @@ -24,23 +25,6 @@ namespace xo { } #endif -#ifdef OBSOLETE - constexpr std::uint64_t - CollectorConfig::gen_shift() const { - return arena_config_.header_size_bits_; - } - - constexpr std::uint64_t - CollectorConfig::gen_mask_unshifted() const { - return (1ul << gen_bits_) - 1; - } - - constexpr std::uint64_t - CollectorConfig::gen_mask_shifted() const { - return gen_mask_unshifted() << arena_config_.header_size_bits_; - } -#endif - #ifdef NOT_USING constexpr std::uint64_t CollectorConfig::tseq_mult() const { @@ -48,23 +32,6 @@ namespace xo { } #endif -#ifdef OBSOLETE - constexpr std::uint64_t - CollectorConfig::tseq_shift() const { - return gen_bits_ + arena_config_.header_size_bits_; - } - - constexpr std::uint64_t - CollectorConfig::tseq_mask_unshifted() const { - return (1ul << tseq_bits_) - 1; - } - - constexpr std::uint64_t - CollectorConfig::tseq_mask_shifted() const { - return tseq_mask_unshifted() << (gen_bits_ + arena_config_.header_size_bits_); - } -#endif - // ----- GCRunState ----- GCRunState::GCRunState(generation gc_upto) @@ -257,6 +224,27 @@ namespace xo { return this->new_space()->alloc_info(mem); } + DX1CollectorIterator + DX1Collector::begin() const noexcept + { + return DX1CollectorIterator(this, + generation{0}, + generation{config_.n_generation_}, + DArenaIterator(), + DArenaIterator()); + } + + DX1CollectorIterator + DX1Collector::end() const noexcept { + generation gen_hi = generation{config_.n_generation_}; + + return DX1CollectorIterator(this, + gen_hi, + gen_hi, + DArenaIterator(), + DArenaIterator()); + } + void DX1Collector::reverse_roles(generation g) noexcept { assert(g < config_.n_generation_); diff --git a/src/alloc2/DX1CollectorIterator.cpp b/src/alloc2/DX1CollectorIterator.cpp new file mode 100644 index 0000000..c8377ca --- /dev/null +++ b/src/alloc2/DX1CollectorIterator.cpp @@ -0,0 +1,88 @@ +/** @file DX1CollectorIterator.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "gc/DX1CollectorIterator.hpp" +#include "gc/DX1Collector.hpp" +#include +#include + +namespace xo { + namespace mm { + DX1CollectorIterator::DX1CollectorIterator(const DX1Collector * gc, + generation gen_ix, + generation gen_hi, + DArenaIterator arena_ix, + DArenaIterator arena_hi) : gc_{gc}, + gen_ix_{gen_ix}, + gen_hi_{gen_hi}, + arena_ix_{arena_ix}, + arena_hi_{arena_hi} + { + this->normalize(); + } + + void + DX1CollectorIterator::normalize() noexcept + { + /* normalize: find lowest generation with non-empty to-space */ + + for (; gen_ix_ < gen_hi_; ++gen_ix_) { + const DArena * arena + = gc_->get_space(role::to_space(), gen_ix_); + + arena_ix_ = arena->begin(); + arena_hi_ = arena->end(); + + if (arena_ix_ != arena_hi_) { + // normalization achieved! + break; + } + } + } + + AllocInfo + DX1CollectorIterator::deref() const noexcept + { + return arena_ix_.deref(); + } + + cmpresult + DX1CollectorIterator::compare(const DX1CollectorIterator & other_ix) const noexcept + { + scope log(XO_DEBUG(true), + xtag("is_valid", is_valid()), + xtag("other_ix.is_valid", other_ix.is_valid()) ); + + if (is_invalid() || (gc_ != other_ix.gc_)) { + log && log("incomparable!"); + return cmpresult::incomparable(); + } + + if (gen_ix_ != other_ix.gen_ix_) { + log && log(xtag("gen", gen_ix_), xtag("other.gen", other_ix.gen_ix_)); + + /* same collector, different arenas -> compare based on gen# */ + + return cmpresult::from_cmp(gen_ix_, other_ix.gen_ix_); + } + + /* both iterators refer to the same arena, + * so can compare their arena iterators directly + */ + return arena_ix_.compare(other_ix.arena_ix_); + } + + void + DX1CollectorIterator::next() noexcept + { + if (arena_ix_ != arena_hi_) { + ++arena_ix_; + this->normalize(); + } + } + } /*namespace mm*/ +} /*namespace xo*/ + +/* end DX1CollectorIterator.cpp */ diff --git a/src/alloc2/IAllocIterator_DArenaIterator.cpp b/src/alloc2/IAllocIterator_DArenaIterator.cpp new file mode 100644 index 0000000..4d929de --- /dev/null +++ b/src/alloc2/IAllocIterator_DArenaIterator.cpp @@ -0,0 +1,43 @@ +/** @file IAllocIterator_DArenaIterator.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "arena/IAllocIterator_DArenaIterator.hpp" +#include "AllocIterator.hpp" +#include + +namespace xo { + using std::byte; + + namespace mm { + AllocInfo + IAllocIterator_DArenaIterator::deref(const DArenaIterator & ix) noexcept + { + return ix.deref(); + } + + cmpresult + IAllocIterator_DArenaIterator::compare(const DArenaIterator & ix, + const obj & other_arg) noexcept + { + /* downcast from variant */ + auto other = obj::from(other_arg); + + if (!other) + return cmpresult::incomparable(); + + DArenaIterator & other_ix = *other; + + return ix.compare(other_ix); + } + + void + IAllocIterator_DArenaIterator::next(DArenaIterator & ix) noexcept + { + ix.next(); + } + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocIterator_DArenaIterator.cpp */ diff --git a/src/alloc2/IAllocIterator_DX1CollectorIterator.cpp b/src/alloc2/IAllocIterator_DX1CollectorIterator.cpp new file mode 100644 index 0000000..2cb22e3 --- /dev/null +++ b/src/alloc2/IAllocIterator_DX1CollectorIterator.cpp @@ -0,0 +1,41 @@ +/** @file IAllocIterator_DX1CollectorIterator.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "gc/IAllocIterator_DX1CollectorIterator.hpp" +#include "AllocIterator.hpp" +//#include + +namespace xo { + namespace mm { + AllocInfo + IAllocIterator_DX1CollectorIterator::deref(const DX1CollectorIterator & ix) noexcept + { + return ix.deref(); + } + + cmpresult + IAllocIterator_DX1CollectorIterator::compare(const DX1CollectorIterator & ix, + const obj & other_arg) noexcept + { + /* downcast from variant */ + auto other = obj::from(other_arg); + + if (!other) + return cmpresult::incomparable(); + + DX1CollectorIterator & other_ix = *other; + + return ix.compare(other_ix); + } + + void + IAllocIterator_DX1CollectorIterator::next(DX1CollectorIterator & ix) noexcept + { + ix.next(); + } + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocIterator_DX1CollectorIterator.cpp */ diff --git a/src/alloc2/IAllocator_DArena.cpp b/src/alloc2/IAllocator_DArena.cpp index af49b25..d023749 100644 --- a/src/alloc2/IAllocator_DArena.cpp +++ b/src/alloc2/IAllocator_DArena.cpp @@ -80,10 +80,7 @@ namespace xo { } if (s.lo_ + target_z > s.hi_) [[unlikely]] { - ++(s.error_count_); - s.last_error_ = AllocError(error::reserve_exhausted, - s.error_count_, - target_z, s.committed_z_, reserved(s)); + s.capture_error(error::reserve_exhausted, target_z); return false; } @@ -124,15 +121,7 @@ namespace xo { add_commit_z, PROT_READ | PROT_WRITE) != 0) [[unlikely]] { - ++(s.error_count_); - s.last_error_ = AllocError(error::commit_failed, - s.error_count_, - add_commit_z, s.committed_z_, reserved(s)); -#ifdef OBSOLETE - throw std::runtime_error(tostr("ArenaAlloc::expand: commit failure", - xtag("committed_z", s.committed_z_), - xtag("add_commit_z", add_commit_z))); -#endif + s.capture_error(error::commit_failed, add_commit_z); return false; } @@ -198,75 +187,6 @@ namespace xo { ? alloc_mode::sub_complete : alloc_mode::sub_incomplete)); -#ifdef OBSOLETE - if ((req_z == 0) && complete_flag) [[unlikely]] { - /** use zero req_z with complete_flag to clear s.last_header_ **/ - - if (s.config_.store_header_flag_) { - if (!s.last_header_) [[unlikely]] { - ++(s.error_count_); - s.last_error_ = AllocError(error::orphan_sub_alloc, - s.error_count_, - 0 /*add_commit_z*/, s.committed_z_, reserved(s)); - } else { - s.last_header_ = nullptr; - } - } - - return nullptr; - } - - byte * free0 = s.free_; - byte * mem = _alloc(s, req_z, - complete_flag ? alloc_mode::sub_complete : alloc_mode::sub, - false /*!store_header_flag*/, - false /*!remember_header_flag*/); - - if (!mem) [[unlikely]] { - /* error already captured */ - return nullptr; - } - - byte * free1 = s.free_; - /* used: accounting for padding applied to req_z */ - size_t z0 = (free1 - free0); - - assert(z0 > 0); - - if (s.config_.store_header_flag_) { - if (!s.last_header_) [[unlikely]] { - ++(s.error_count_); - s.last_error_ = AllocError(error::orphan_sub_alloc, - s.error_count_, - 0 /*add_commit_z*/, s.committed_z_, reserved(s)); - return nullptr; - } - - /* s.last_header_ holds aggregate size of preceding super_alloc - * (+ any sub-alloc's). - * - * Accumulate allocation size - */ - uint64_t header = *s.last_header_; - - if ((header & s.config_.header_size_mask_ & z0) != z0) [[unlikely]] { - /* cumulative alloc size doesn't fit in configured header_size_mask bits */ - ++(s.error_count_); - s.last_error_ = AllocError(error::header_size_mask, - s.error_count_, - 0 /*add_commit_z*/, s.committed_z_, reserved(s)); - return nullptr; - } - - *s.last_header_ = ((header & ~s.config_.header_size_mask_) | z0); - - if (complete_flag) { - s.last_header_ = nullptr; - } - } - - return mem; -#endif } byte * @@ -340,12 +260,7 @@ namespace xo { hz = sizeof(header); } else { /* req_z doesn't fit in configured header_size_mask bits */ - ++(s.error_count_); - s.last_error_ = AllocError(error::header_size_mask, - s.error_count_, - 0 /*add_commit_z*/, - s.committed_z_, - reserved(s)); + s.capture_error(error::header_size_mask); return nullptr; } } @@ -391,6 +306,8 @@ namespace xo { xtag("z1", z1), xtag("size", size(s)), xtag("avail", available(s))); + log && log(xtag("mem", mem), + xtag("free", s.free_)); return mem; } diff --git a/utest/AllocIterator.test.cpp b/utest/AllocIterator.test.cpp new file mode 100644 index 0000000..d941f03 --- /dev/null +++ b/utest/AllocIterator.test.cpp @@ -0,0 +1,199 @@ +/** @file AllocIterator.test.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "Allocator.hpp" +#include "AllocIterator.hpp" +#include "arena/IAllocator_DArena.hpp" +#include "arena/IAllocIterator_DArenaIterator.hpp" +#include "padding.hpp" +#include + +namespace xo { + using xo::mm::AAllocator; + + using xo::mm::AllocInfo; + + using xo::mm::AAllocIterator; + using xo::mm::IAllocIterator_Any; + using xo::mm::IAllocIterator_Xfer; + using xo::mm::IAllocIterator_DArenaIterator; + using xo::mm::DArenaIterator; + + using xo::mm::ArenaConfig; + using xo::mm::DArena; + + using xo::mm::padding; + using xo::mm::error; + + using std::byte; + + namespace ut { + TEST_CASE("IAllocIterator_Xfer_DArenaIterator", "[alloc2]") + { + /* verify IAllocIterator_Xfer is constructible + satisfies concept checks */ + IAllocIterator_Xfer xfer; + REQUIRE(IAllocIterator_Xfer::_valid); + } + + TEST_CASE("IAllocIterator_Any", "[alloc2]") + { + /* verify IAllocIterator_Any is constructible + satisfies concept checks */ + IAllocIterator_Any any; + REQUIRE(IAllocIterator_Any::_valid); + } + + TEST_CASE("obj_IAllocIterator", "[alloc2]") + { + /* verify variant obj constructible */ + obj obj_any; + REQUIRE(obj_any.iface()); + REQUIRE(obj_any.data() == nullptr); + } + + TEST_CASE("IAllocIterator-disabled-1", "[alloc2]") + { + /* verify iteration over empty arena */ + /* typed allocator a1o */ + ArenaConfig cfg { .name_ = "testarena", + .size_ = 64*1024, + .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); + + DArenaIterator ix = arena.begin(); + /* iteration not supported since we did not set + * ArenaConfig.store_header_flag_ + */ + REQUIRE(ix.is_invalid()); + REQUIRE(ix != ix); + REQUIRE(arena.error_count_ == 1); + REQUIRE(arena.last_error_.error_seq_ == 1); + REQUIRE(arena.last_error_.error_ == error::alloc_iterator_not_supported); + + DArenaIterator end_ix = arena.end(); + /* iteration not supported since we did not set + * ArenaConfig.store_header_flag_ + */ + REQUIRE(end_ix.is_invalid()); + REQUIRE(end_ix != end_ix); + REQUIRE(arena.error_count_ == 2); + REQUIRE(arena.last_error_.error_seq_ == 2); + REQUIRE(arena.last_error_.error_ == error::alloc_iterator_not_supported); + } + + TEST_CASE("IAllocIterator-emptyarena", "[alloc2]") + { + /* verify iteration over empty arena */ + /* typed allocator a1o */ + ArenaConfig cfg { .name_ = "testarena", + .size_ = 64*1024, + .store_header_flag_ = true, + .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); + + DArenaIterator ix = arena.begin(); + DArenaIterator end_ix = arena.end(); + + REQUIRE(ix.is_valid()); + REQUIRE(end_ix.is_valid()); + + /* iteration not supported since we did not set */ + + /* arena is empty, so begin==end */ + REQUIRE(ix == end_ix); + + REQUIRE(arena.error_count_ == 0); + + /* empty iterator cannot be dereferenced */ + { + AllocInfo bad_info = *ix; + REQUIRE(!bad_info.is_valid()); + + REQUIRE(arena.error_count_ == 1); + REQUIRE(arena.last_error_.error_seq_ == 1); + REQUIRE(arena.last_error_.error_ == error::alloc_iterator_deref); + } + + /* empty iterator cannot be advanced */ + { + ix.next(); + + REQUIRE(arena.error_count_ == 2); + REQUIRE(arena.last_error_.error_seq_ == 2); + REQUIRE(arena.last_error_.error_ == error::alloc_iterator_next); + } + } + + TEST_CASE("IAllocIterator-singlearena", "[alloc2]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 64*1024, + .store_header_flag_ = true, + .debug_flag_ = false }; + DArena arena = DArena::map(cfg); + obj a1o{&arena}; + + REQUIRE(arena.error_count_ == 0); + REQUIRE(a1o.reserved() >= cfg.size_); + REQUIRE(a1o.committed() == 0); + REQUIRE(a1o.available() == 0); + REQUIRE(a1o.allocated() == 0); + + /* arbitrary alloc size */ + size_t req_z = 13; + + byte * mem = a1o.alloc(req_z); + REQUIRE(arena.error_count_ == 0); + REQUIRE(mem != nullptr); + + DArenaIterator ix = arena.begin(); + DArenaIterator end_ix = arena.end(); + + REQUIRE(ix.is_valid()); + REQUIRE(end_ix.is_valid()); + + /* arena is non-empty, so begin!=end */ + REQUIRE (ix != end_ix); + + REQUIRE(arena.error_count_ == 0); + + /* valid iterator can be dereferenced */ + { + AllocInfo info = *ix; + + REQUIRE(arena.error_count_ == 0); + REQUIRE(info.is_valid()); + REQUIRE(info.size() == padding::with_padding(req_z)); + + auto [payload_lo, payload_hi] = info.payload(); + + REQUIRE(payload_lo == mem); + REQUIRE(payload_hi == mem + info.size()); + } + + /* valid iterator can be advanced */ + { + ix.next(); + + REQUIRE(arena.error_count_ == 0); + REQUIRE(ix == end_ix); + } + } + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end AllocIterator.test.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 51f929a..4460e2c 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -4,9 +4,11 @@ set(UTEST_EXE utest.alloc2) set(UTEST_SRCS alloc2_utest_main.cpp - arena.test.cpp objectmodel.test.cpp + arena.test.cpp + AllocIterator.test.cpp Collector.test.cpp + DX1CollectorIterator.test.cpp random_allocs.cpp ) @@ -19,5 +21,4 @@ if (ENABLE_TESTING) xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) endif() - # end CMakeLists.txt diff --git a/utest/DX1CollectorIterator.test.cpp b/utest/DX1CollectorIterator.test.cpp new file mode 100644 index 0000000..8ef6be5 --- /dev/null +++ b/utest/DX1CollectorIterator.test.cpp @@ -0,0 +1,64 @@ +/** @file DX1CollectorIterator.test.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "Allocator.hpp" +#include "AllocIterator.hpp" +#include "gc/DX1CollectorIterator.hpp" +#include "gc/IAllocator_DX1Collector.hpp" +#include "gc/IAllocIterator_DX1CollectorIterator.hpp" +#include "arena/ArenaConfig.hpp" +#include "padding.hpp" +#include + +namespace xo { + using xo::mm::AAllocIterator; + using xo::mm::IAllocIterator_Any; + using xo::mm::IAllocIterator_Xfer; + using xo::mm::IAllocIterator_DX1CollectorIterator; + using xo::mm::DX1Collector; + using xo::mm::DX1CollectorIterator; + using xo::mm::CollectorConfig; + using xo::mm::ArenaConfig; + using xo::mm::AllocHeaderConfig; + + namespace ut { + TEST_CASE("IAllocIterator_Xfer_DX1CollectorIterator", "[alloc2]") + { + /* verify IAllocIterator_Xfer is constructible + satisfies concept checks */ + IAllocIterator_Xfer xfer; + REQUIRE(IAllocIterator_Xfer::_valid); + } + + TEST_CASE("DX1CollectorIterator", "[alloc2][gc][DX1Collector]") + { + ArenaConfig arena_cfg = { .name_ = "_test_unused", + .size_ = 4*1024*1024, + .store_header_flag_ = true, + .header_ = AllocHeaderConfig(0 /*guard_z*/, + 0xfd /*guard_byte*/, + 0 /*tseq_bits*/, + 0 /*age_bits*/, + 16 /*size_bits*/), }; + CollectorConfig cfg = { .arena_config_ = arena_cfg, + .n_generation_ = 2, + .gc_trigger_v_ = {{64*1024, 1024*1024, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0}} }; + + DX1Collector gc = DX1Collector{cfg}; + + auto ix = gc.begin(); + auto end_ix = gc.end(); + + REQUIRE(ix.is_valid()); + REQUIRE(end_ix.is_valid()); + + REQUIRE(ix == end_ix); + } + } /*namespace ut*/ +} /*namespace xo*/ + +/* end DX1CollectorIterator.test.cpp */ diff --git a/utest/arena.test.cpp b/utest/arena.test.cpp index 34d8fe0..6826dd3 100644 --- a/utest/arena.test.cpp +++ b/utest/arena.test.cpp @@ -269,7 +269,11 @@ namespace xo { REQUIRE(a1o.last_error().error_ == error::none); REQUIRE(a1o.last_error().error_seq_ == 0); - REQUIRE(a1o.allocated() == cfg.header_.guard_z_ + sizeof(header_type) + z0 + pad + cfg.header_.guard_z_); + REQUIRE(a1o.allocated() == (cfg.header_.guard_z_ + + sizeof(header_type) + + z0 + + pad + + cfg.header_.guard_z_)); REQUIRE(a1o.allocated() <= a1o.committed()); REQUIRE(a1o.allocated() + a1o.available() == a1o.committed()); REQUIRE(a1o.committed() <= a1o.reserved()); diff --git a/utest/random_allocs.cpp b/utest/random_allocs.cpp index a480310..c2775a5 100644 --- a/utest/random_allocs.cpp +++ b/utest/random_allocs.cpp @@ -162,6 +162,19 @@ namespace utest { info.guard_hi().second == nullptr); } + +#ifdef NOT_YET // to verify iteration here, need iterator support in AAllocator + + /* verify iteration visits all the allocs, exactly once */ + { + auto alloc_map = allocs_by_lo_map; + + for (AllocInfo info : mm) { + } + + } +#endif + } return true;