From 1fd5d544f2bfb8dfca5c25c1b653cb3e359cbdaa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 15 Dec 2025 22:43:21 -0500 Subject: [PATCH] xo-alloc2 : work on X1Collector unit test [WIP] --- include/xo/alloc2/arena/DArena.hpp | 38 ++++-- include/xo/alloc2/gc/DX1Collector.hpp | 69 ++++++++-- .../xo/alloc2/gc/IAllocator_DX1Collector.hpp | 13 +- include/xo/alloc2/gc/generation.hpp | 2 + include/xo/alloc2/gc/role.hpp | 6 + src/alloc2/DArena.cpp | 8 +- src/alloc2/DX1Collector.cpp | 126 ++++++++++++++++-- src/alloc2/IAllocator_DArena.cpp | 2 +- src/alloc2/IAllocator_DX1Collector.cpp | 92 +++++++++++++ src/alloc2/ICollector_DX1Collector.cpp | 2 + utest/CMakeLists.txt | 3 + utest/Collector.test.cpp | 74 +++++++++- utest/random_allocs.cpp | 103 ++++++++++++++ utest/random_allocs.hpp | 45 +++++++ 14 files changed, 542 insertions(+), 41 deletions(-) create mode 100644 utest/random_allocs.cpp create mode 100644 utest/random_allocs.hpp diff --git a/include/xo/alloc2/arena/DArena.hpp b/include/xo/alloc2/arena/DArena.hpp index 2a874927..c12f3e1b 100644 --- a/include/xo/alloc2/arena/DArena.hpp +++ b/include/xo/alloc2/arena/DArena.hpp @@ -10,6 +10,17 @@ namespace xo { namespace mm { + /** **/ + struct AllocHeader { + using repr_type = std::uint64_t; + + explicit AllocHeader(repr_type x) : repr_{x} {} + + repr_type repr_; + }; + + static_assert(sizeof(AllocHeader) == sizeof(AllocHeader::repr_type)); + /** @class DArena * * @brief represent arena allocator state @@ -35,8 +46,10 @@ namespace xo { /** @brief an amount of memory **/ using size_type = std::size_t; + /** @brief allocation pointer; use for allocation results **/ + using value_type = std::byte*; /** @brief a contiguous memory range **/ - using range_type = std::pair; + using range_type = std::pair; /** @brief type for allocation header (if enabled) **/ using header_type = std::uint64_t; @@ -51,7 +64,7 @@ namespace xo { /** null ctor **/ DArena() = default; /** ctor from already-mapped (but not committed) address range **/ - DArena(const ArenaConfig & cfg, size_type page_z, std::byte * lo, std::byte * hi); + DArena(const ArenaConfig & cfg, size_type page_z, value_type lo, value_type hi); /** DArena is not copyable **/ DArena(const DArena & other) = delete; /** move ctor **/ @@ -67,12 +80,12 @@ namespace xo { /** @defgroup mm-arena-methods **/ ///@{ - size_type reserved() const { return hi_ - lo_; } - size_type allocated() const { return free_ - lo_; } - size_type committed() const { return committed_z_; } - size_type available() const { return limit_ - free_; } + size_type reserved() const noexcept { return hi_ - lo_; } + size_type allocated() const noexcept { return free_ - lo_; } + size_type committed() const noexcept { return committed_z_; } + size_type available() const noexcept { return limit_ - free_; } - bool contains(void * addr) const { return (lo_ <= addr) && (addr < hi_); } + bool contains(const void * addr) const noexcept { return (lo_ <= addr) && (addr < hi_); } /** obtain uncommitted contiguous memory range comprising * a whole multiple of @p hugepage_z bytes, of at least size @p req_z, @@ -81,10 +94,17 @@ namespace xo { static range_type map_aligned_range(size_type req_z, size_type hugepage_z); /** true if arena is mapped i.e. has a reserved address range **/ - bool is_mapped() const { return (lo_ != nullptr) && (hi_ != nullptr); } + bool is_mapped() const noexcept { return (lo_ != nullptr) && (hi_ != nullptr); } /** get header from allocated object address **/ - header_type * obj2hdr(void * obj); + header_type * obj2hdr(void * obj) noexcept; + + /** discard all allocated memory, return to empty state + * Promise: + * - committed memory unchanged + * - available memory = committed memory + **/ + void clear() noexcept; ///@} diff --git a/include/xo/alloc2/gc/DX1Collector.hpp b/include/xo/alloc2/gc/DX1Collector.hpp index b8740ee7..ce802f4e 100644 --- a/include/xo/alloc2/gc/DX1Collector.hpp +++ b/include/xo/alloc2/gc/DX1Collector.hpp @@ -64,6 +64,12 @@ namespace xo { constexpr std::uint64_t tseq_mask_unshifted() const; constexpr std::uint64_t tseq_mask_shifted() const; + public: + // ----- Instance Variables ----- + + /** optional name, for diagnostics **/ + std::string name_; + /** Configuration for collector spaces. * Will have at least {nursery,tenured} x {from,to} spaces. * Not using name_ member. @@ -135,32 +141,75 @@ namespace xo { // ----- DX1Collector ----- struct DX1Collector { + using size_type = DArena::size_type; + using value_type = DArena::value_type; using header_type = DArena::header_type; explicit DX1Collector(const CollectorConfig & cfg); - const DArena * get_space(role r, generation g) const { return space_[r][g]; } - DArena * get_space(role r, generation g) { return space_[r][g]; } - DArena * from_space(generation g) { return get_space(role::from_space(), g); } - DArena * to_space(generation g) { return get_space(role::to_space(), g); } + const DArena * get_space(role r, generation g) const noexcept { return space_[r][g]; } + DArena * get_space(role r, generation g) noexcept { return space_[r][g]; } + DArena * from_space(generation g) noexcept { return get_space(role::from_space(), g); } + DArena * to_space(generation g) noexcept { return get_space(role::to_space(), g); } + DArena * new_space() noexcept { return to_space(generation{0}); } + + /** total reserved memory in bytes, across all {role, generation} **/ + size_type reserved_total() const noexcept; + /** total size in bytes (same as committed_total()) **/ + size_type size_total() const noexcept; + /** total committed memory in bytes, across all {role, generation} **/ + size_type committed_total() const noexcept; + /** total available memory in bytes, across all {role, generation} **/ + size_type available_total() const noexcept; + /** total allocated memory in bytes, across all {role, generation} **/ + size_type allocated_total() const noexcept; /** true iff address @p addr allocated from this collector * in role @p r (according to current GC state) **/ - bool contains(role r, void * addr) const; + bool contains(role r, const void * addr) const noexcept; + + /** return details from last error (will be in gen0 to-space) **/ + AllocatorError last_error() const noexcept; /** get allocation size from header **/ - std::size_t header2size(header_type hdr) const; + std::size_t header2size(header_type hdr) const noexcept; /** get generation counter from alloc header **/ - generation header2gen(header_type hdr) const; + generation header2gen(header_type hdr) const noexcept; /** get tseq from alloc header **/ - uint32_t header2tseq(header_type hdr) const; + uint32_t header2tseq(header_type hdr) const noexcept; /** true iff original alloc has been replaced by a forwarding pointer **/ - bool is_forwarding_header(header_type hdr) const; + bool is_forwarding_header(header_type hdr) const noexcept; + + // ----- allocation ----- + + /** simple allocation. new allocs always in gen0 to-space **/ + value_type alloc(size_type z) noexcept; + /** compound allocation. To be followed immediately by: + * 1. zero or more calls to sub_alloc(zi, complete=false), then + * 2. exactly one call to sub_alloc(zi, complete=true) + * all the allocs in a compound allocation share the same + * allocation header. End state is equivalent to a single + * allocation with size z + sum(zi). + * New allocs always in gen0 to-space + **/ + value_type super_alloc(size_type z) noexcept; + /** sub-allocation with preceding compound allocation. + * New allocs always in gen0 to-space + **/ + value_type sub_alloc(size_type z, bool complete) noexcept; + /** expand gen0 committed size to at least @p z. + **/ + bool expand(size_type z) noexcept; + + // ----- book-keeping ----- /** reverse to-space and from-space roles for generation g **/ - void reverse_roles(generation g); + void reverse_roles(generation g) noexcept; + + /** discard all allocated memory **/ + void clear() noexcept; public: /** garbage collector configuration **/ diff --git a/include/xo/alloc2/gc/IAllocator_DX1Collector.hpp b/include/xo/alloc2/gc/IAllocator_DX1Collector.hpp index 138cce88..cea8ae41 100644 --- a/include/xo/alloc2/gc/IAllocator_DX1Collector.hpp +++ b/include/xo/alloc2/gc/IAllocator_DX1Collector.hpp @@ -5,8 +5,7 @@ #pragma once -#include "alloc2/alloc/Allocator.hpp" -#include "alloc2/alloc/IAllocator_Xfer.hpp" +#include "Allocator.hpp" #include "DX1Collector.hpp" namespace xo { @@ -38,6 +37,7 @@ namespace xo { static std::string_view name(const DX1Collector &) noexcept; /** reserved memory across all {roles, generations} **/ static size_type reserved(const DX1Collector &) noexcept; + /** 'size'. synonym for committed size **/ static size_type size(const DX1Collector &) noexcept; /** committed size accross all {roles, generations} **/ static size_type committed(const DX1Collector &) noexcept; @@ -50,14 +50,13 @@ namespace xo { /** report last error, if any, for collector @p d **/ static AllocatorError last_error(const DX1Collector &) noexcept; + /** always alloc in gen0 to-space **/ + static value_type alloc(DX1Collector & d, size_type z) noexcept; + static value_type super_alloc(DX1Collector & d, size_type z) noexcept; + 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; - /** always alloc in gen0 to-space **// - static value_type alloc(DX1Collector & d, size_type z); - static value_type super_alloc(DX1Collector & d, size_type z); - static value_type sub_alloc(DX1Collector & d, size_type z, bool complete); - /** reset to empty state; clears all generations **/ static void clear(DX1Collector & d); /** invoke destructor **/ diff --git a/include/xo/alloc2/gc/generation.hpp b/include/xo/alloc2/gc/generation.hpp index 70e9c85a..2bf4feef 100644 --- a/include/xo/alloc2/gc/generation.hpp +++ b/include/xo/alloc2/gc/generation.hpp @@ -21,6 +21,8 @@ namespace xo { explicit constexpr generation(value_type x) : value_{x} {} + static generation nursery() { return generation{0}; } + constexpr operator value_type() const { return value_; } generation & operator++() { ++value_; return *this; } diff --git a/include/xo/alloc2/gc/role.hpp b/include/xo/alloc2/gc/role.hpp index 0c41e39e..58fc72a2 100644 --- a/include/xo/alloc2/gc/role.hpp +++ b/include/xo/alloc2/gc/role.hpp @@ -5,6 +5,7 @@ #pragma once +#include #include namespace xo { @@ -19,6 +20,11 @@ namespace xo { static constexpr role to_space() { return role{0}; } static constexpr role from_space() { return role{1}; } + static constexpr std::array all() { return {{to_space(), from_space()}}; } + + static constexpr role begin() { return role{0}; } + static constexpr role end() { return role{2}; } + operator value_type() const { return role_; } value_type role_ = 0; diff --git a/src/alloc2/DArena.cpp b/src/alloc2/DArena.cpp index 6aacd0f3..202a723d 100644 --- a/src/alloc2/DArena.cpp +++ b/src/alloc2/DArena.cpp @@ -240,14 +240,18 @@ namespace xo { } DArena::header_type * - DArena::obj2hdr(void * obj) + DArena::obj2hdr(void * obj) noexcept { assert(config_.store_header_flag_); return (header_type *)((byte *)obj - sizeof(header_type)); } - + void + DArena::clear() noexcept + { + this->free_ = lo_; + } } } /*namespace xo*/ diff --git a/src/alloc2/DX1Collector.cpp b/src/alloc2/DX1Collector.cpp index 0b165aeb..51b516e9 100644 --- a/src/alloc2/DX1Collector.cpp +++ b/src/alloc2/DX1Collector.cpp @@ -3,13 +3,18 @@ * @author Roland Conybeare, Dec 2025 **/ +#include "Allocator.hpp" +#include "arena/IAllocator_DArena.hpp" #include "gc/DX1Collector.hpp" #include "gc/generation.hpp" -#include <_types/_uint32_t.h> +#include #include #include namespace xo { + using xo::mm::AAllocator; + using xo::facet::with_facet; + namespace mm { #ifdef NOT_USING constexpr std::uint64_t @@ -75,6 +80,8 @@ namespace xo { // ----- DX1Collector ----- + using size_type = xo::mm::DX1Collector::size_type; + DX1Collector::DX1Collector(const CollectorConfig & cfg) : config_{cfg} { assert(config_.arena_config_.header_size_bits_ + config_.gen_bits_ + config_.tseq_bits_ <= 64); @@ -94,18 +101,81 @@ namespace xo { } bool - DX1Collector::contains(role r, void * addr) const + DX1Collector::contains(role r, const void * addr) const noexcept { for (generation gi{0}; gi < config_.n_generation_; ++gi) { - if (get_space(r, gi)->contains(addr)) + const DArena * arena = get_space(r, gi); + + if (arena->contains(addr)) return true; } return false; } - std::size_t - DX1Collector::header2size(header_type hdr) const + AllocatorError + DX1Collector::last_error() const noexcept + { + // TODO: + // need to adjust here if runtime errors + // encountered during gc. + + return get_space(role::to_space(), generation::nursery())->last_error_; + } + + namespace { + size_type + accumulate_total_aux(const DX1Collector & d, + size_t (DArena::* get_stat_fn)() const) noexcept + { + size_t z = 0; + + for (role ri : role::all()) { + for (generation gj{0}; gj < d.config_.n_generation_; ++gj) { + const DArena * arena = d.get_space(ri, gj); + + assert(arena); + + z += (arena->*get_stat_fn)(); + } + } + + return z; + } + } + + size_type + DX1Collector::reserved_total() const noexcept + { + return accumulate_total_aux(*this, &DArena::reserved); + } + + size_type + DX1Collector::size_total() const noexcept + { + return committed_total(); + } + + size_type + DX1Collector::committed_total() const noexcept + { + return accumulate_total_aux(*this, &DArena::committed); + } + + size_type + DX1Collector::available_total() const noexcept + { + return accumulate_total_aux(*this, &DArena::available); + } + + size_type + DX1Collector::allocated_total() const noexcept + { + return accumulate_total_aux(*this, &DArena::allocated); + } + + size_type + DX1Collector::header2size(header_type hdr) const noexcept { uint32_t z = (hdr & config_.arena_config_.header_size_mask_); @@ -113,7 +183,7 @@ namespace xo { } generation - DX1Collector::header2gen(header_type hdr) const + DX1Collector::header2gen(header_type hdr) const noexcept { uint32_t g = (hdr & config_.gen_mask_shifted()) >> config_.gen_shift(); @@ -123,7 +193,7 @@ namespace xo { } uint32_t - DX1Collector::header2tseq(header_type hdr) const + DX1Collector::header2tseq(header_type hdr) const noexcept { uint32_t tseq = (hdr & config_.tseq_mask_shifted()) >> config_.tseq_shift(); @@ -131,18 +201,56 @@ namespace xo { } bool - DX1Collector::is_forwarding_header(header_type hdr) const + DX1Collector::is_forwarding_header(header_type hdr) const noexcept { /** all 1 bits to flag forwarding pointer **/ return header2tseq(hdr) == config_.tseq_mask_shifted(); } + auto + DX1Collector::alloc(size_type z) noexcept -> value_type + { + return with_facet::mkobj(new_space()).alloc(z); + } + + auto + DX1Collector::super_alloc(size_type z) noexcept -> value_type { + return with_facet::mkobj(new_space()).super_alloc(z); + } + + auto + DX1Collector::sub_alloc(size_type z, bool complete) noexcept -> value_type { + return with_facet::mkobj(new_space()).sub_alloc(z, complete); + } + + bool + DX1Collector::expand(size_type z) noexcept + { + if (with_facet::mkobj(to_space(generation{0})).expand(z)) + return with_facet::mkobj(from_space(generation{0})).expand(z); + + return false; + } + void - DX1Collector::reverse_roles(generation g) { + DX1Collector::reverse_roles(generation g) noexcept { assert(g < config_.n_generation_); std::swap(space_[0][g], space_[1][g]); } + + void + DX1Collector::clear() noexcept { + for (role ri : role::all()) { + for (generation gj{0}; gj < config_.n_generation_; ++gj) { + DArena * arena = this->get_space(ri, gj); + + assert(arena); + + arena->clear(); + } + } + } } /*namespace mm*/ } /*namespace xo*/ diff --git a/src/alloc2/IAllocator_DArena.cpp b/src/alloc2/IAllocator_DArena.cpp index f4254b39..cc61c8c5 100644 --- a/src/alloc2/IAllocator_DArena.cpp +++ b/src/alloc2/IAllocator_DArena.cpp @@ -393,7 +393,7 @@ namespace xo { void IAllocator_DArena::clear(DArena & s) { - s.free_ = s.lo_; + s.clear(); //s.checkpoint_ = s.lo_; } diff --git a/src/alloc2/IAllocator_DX1Collector.cpp b/src/alloc2/IAllocator_DX1Collector.cpp index bc84e0a3..36f7cedf 100644 --- a/src/alloc2/IAllocator_DX1Collector.cpp +++ b/src/alloc2/IAllocator_DX1Collector.cpp @@ -1,10 +1,102 @@ /** @file IAllocator_DX1Collector.cpp * * @author Roland Conybeare, Dec 2025 + * + * See also ICollector_DX1Collector.cpp for collector facet **/ +#include "gc/IAllocator_DX1Collector.hpp" + namespace xo { + using std::size_t; + namespace mm { + using value_type = IAllocator_DX1Collector::value_type; + + std::string_view + IAllocator_DX1Collector::name(const DX1Collector & d) noexcept + { + return d.config_.name_; + } + + auto + IAllocator_DX1Collector::reserved(const DX1Collector & d) noexcept -> size_type + { + return d.reserved_total(); + } + + auto + IAllocator_DX1Collector::size(const DX1Collector & d) noexcept -> size_type + { + return d.size_total(); + } + + auto + IAllocator_DX1Collector::committed(const DX1Collector & d) noexcept -> size_type + { + return d.committed_total(); + } + + auto + IAllocator_DX1Collector::available(const DX1Collector & d) noexcept -> size_type + { + return d.available_total(); + } + + auto + IAllocator_DX1Collector::allocated(const DX1Collector & d) noexcept -> size_type + { + return d.allocated_total(); + } + + bool + IAllocator_DX1Collector::contains(const DX1Collector & d, const void * addr) noexcept + { + return d.contains(role::to_space(), addr); + } + + AllocatorError + IAllocator_DX1Collector::last_error(const DX1Collector & d) noexcept + { + return d.last_error(); + } + + auto + IAllocator_DX1Collector::alloc(DX1Collector & d, size_type z) noexcept -> value_type + { + return d.alloc(z); + } + + auto + IAllocator_DX1Collector::super_alloc(DX1Collector & d, size_type z) noexcept -> value_type + { + return d.super_alloc(z); + } + + auto + IAllocator_DX1Collector::sub_alloc(DX1Collector & d, size_type z, bool complete) noexcept -> value_type + { + return d.sub_alloc(z, complete); + } + + bool + IAllocator_DX1Collector::expand(DX1Collector & d, size_type z) noexcept + { + return d.expand(z); + } + + void + IAllocator_DX1Collector::clear(DX1Collector & d) + { + d.clear(); + } + + void + IAllocator_DX1Collector::destruct_data(DX1Collector & d) + { + d.~DX1Collector(); + } + } /*namespace mm*/ } /*namespace xo*/ diff --git a/src/alloc2/ICollector_DX1Collector.cpp b/src/alloc2/ICollector_DX1Collector.cpp index 81962975..2d8adaca 100644 --- a/src/alloc2/ICollector_DX1Collector.cpp +++ b/src/alloc2/ICollector_DX1Collector.cpp @@ -1,6 +1,8 @@ /** @file ICollector_DX1Collector.cpp * * @author Roland Conybeare, Dec 2025 + * + * See also IAllocator_DX1Collector.cpp for allocator facet **/ #include "gc/ICollector_DX1Collector.hpp" diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index a4b976b0..51f929a9 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -7,11 +7,14 @@ set(UTEST_SRCS arena.test.cpp objectmodel.test.cpp Collector.test.cpp + random_allocs.cpp ) if (ENABLE_TESTING) xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) + xo_headeronly_dependency(${UTEST_EXE} randomgen) xo_self_dependency(${UTEST_EXE} xo_alloc2) + xo_headeronly_dependency(${UTEST_EXE} indentlog) xo_headeronly_dependency(${UTEST_EXE} xo_facet) xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) endif() diff --git a/utest/Collector.test.cpp b/utest/Collector.test.cpp index c1fbca53..861c425b 100644 --- a/utest/Collector.test.cpp +++ b/utest/Collector.test.cpp @@ -7,24 +7,36 @@ * see xo-object2/utest **/ +#include "Allocator.hpp" #include "Collector.hpp" +#include "random_allocs.hpp" #include "gc/ICollector_DX1Collector.hpp" +#include "gc/IAllocator_DX1Collector.hpp" //#include "gc/DX1Collector.hpp" +#include +#include #include +#include #include namespace xo { + using xo::mm::AAllocator; using xo::mm::ACollector; using xo::mm::CollectorConfig; using xo::mm::DX1Collector; using xo::mm::ArenaConfig; using xo::mm::generation; using xo::mm::c_max_generation; + using xo::facet::with_facet; namespace ut { // checklist - // - obj constructible [ ] - // - obj truthy [ ] + // - obj constructible [ ] + // - obj truthy [ ] + // - obj constructible [ ] + // + // - obj constructible [ ] + // - obj allocation TEST_CASE("collector-any-null", "[alloc2][gc][ACollector]") { @@ -95,12 +107,68 @@ namespace xo { DX1Collector gc = DX1Collector{cfg}; - /* typed collector */ + /* typed collector -- repr known at compile time */ obj x1(&gc); REQUIRE(x1.iface()); REQUIRE(x1.data()); + } + TEST_CASE("collector-x1-facet-mkobj", "[alloc2][gc]") + { + ArenaConfig arena_cfg = { .name_ = "_test_unused", + .size_ = 4*1024*1024, + .store_header_flag_ = true, + .header_size_mask_ = 0x0000ffff, }; + 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}; + + /* typed collector -- repr inferred at compile time */ + auto x1 = with_facet::mkobj(&gc); + + REQUIRE(x1.iface()); + REQUIRE(x1.data()); + } + + TEST_CASE("collector-x1-alloc", "[alloc2][gc]") + { + ArenaConfig arena_cfg = { .name_ = "_test_unused", + .size_ = 4*1024*1024, + .store_header_flag_ = true, + .header_size_mask_ = 0x0000ffff, }; + + /* collector with one generation collapses to a non-generational copying collector */ + CollectorConfig cfg = { .arena_config_ = arena_cfg, + .n_generation_ = 1, + .gc_trigger_v_ = {{64*1024, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0}} }; + + DX1Collector x1state = DX1Collector{cfg}; + + /* typed collector */ + auto x1gc = with_facet::mkobj(&x1state); + auto x1alloc = with_facet::mkobj(&x1state); + + REQUIRE(x1gc.iface()); + REQUIRE(x1gc.data()); + + REQUIRE(x1alloc.iface()); + REQUIRE(x1alloc.data()); + + rng::Seed seed; + std::cerr << "ratio: seed=" << seed << std::endl; + + auto rng = rng::xoshiro256ss(seed); + + utest::AllocUtil::random_allocs(25, true, &rng, x1alloc); } } } diff --git a/utest/random_allocs.cpp b/utest/random_allocs.cpp new file mode 100644 index 00000000..a38e6acc --- /dev/null +++ b/utest/random_allocs.cpp @@ -0,0 +1,103 @@ +/** @file random_allocs.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "random_allocs.hpp" +#include +#include +#include +#include + +namespace utest { + using xo::rng::xoshiro256ss; + using xo::facet::obj; + using xo::scope; + using xo::xtag; + using std::uint32_t; + using std::byte; + + /* 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} {} + + byte * lo() const { return lo_; } + byte * hi() const { return lo_ + z_; } + + byte * lo_ = nullptr; + size_t z_ = 0; + }; + + bool + AllocUtil::random_allocs(uint32_t n_alloc, + bool catch_flag, + xoshiro256ss * p_rgen, + obj mm) + { + scope log(XO_DEBUG(catch_flag), xtag("n-alloc", n_alloc)); + + /* track allocs. verify: + * - allocs are non-overlapping + * - allocs have valid alloc header + * - allocs surrounded by guard bytes + * + * allocs sorted on AllocInfo::lo + */ + std::map allocs_by_lo_map; + /* allocs sorted on AllocInfo::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}; + + double si = ngen(*p_rgen); + double zi = ::pow(2.0, si); + std::size_t z = ::ceil(zi); + + bool ok_flag = true; + + std::byte * mem = mm.alloc(z); + log && log(xtag("i_alloc", i_alloc), + xtag("si", si), + xtag("zi", zi), + xtag("mem", mem)); + log && log(xtag("used", mm.allocated()), + xtag("avail", mm.available()), + xtag("commit", mm.committed()), + xtag("resv", mm.reserved())); + + + REQUIRE_ORFAIL(ok_flag, catch_flag, mem != nullptr); + REQUIRE_ORFAIL(ok_flag, catch_flag, mm.contains(mem)); + REQUIRE_ORFAIL(ok_flag, catch_flag, mm.last_error().error_seq_ == 0); + REQUIRE_ORFAIL(ok_flag, catch_flag, mm.last_error().error_ == xo::mm::error::none); + + { + auto ix = allocs_by_lo_map.lower_bound(mem); + if (ix != allocs_by_lo_map.end()) { + REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->first > mem + z)); + } + } + + { + auto ix = allocs_by_hi_map.upper_bound(mem); + if (ix != allocs_by_hi_map.end()) { + --ix; + REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->first < mem)); + } + } + + allocs_by_lo_map[mem] = AllocInfo(mem, z); + allocs_by_hi_map[mem + z] = &(allocs_by_lo_map[mem]); + + + } + + return true; + } +} + +/* end random_allocs.cpp */ diff --git a/utest/random_allocs.hpp b/utest/random_allocs.hpp new file mode 100644 index 00000000..ab0afc4e --- /dev/null +++ b/utest/random_allocs.hpp @@ -0,0 +1,45 @@ +/** @file random_allocs.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "Allocator.hpp" +#include +#include + +namespace utest { + +/* note: trivial REQUIRE() call in else branch bc we still want + * catch2 to count assertions when verification succeeds + */ +# define REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr) \ + if (catch_flag) { \ + REQUIRE((expr)); \ + } else { \ + REQUIRE(true); \ + ok_flag &= (expr); \ + } + +# define REQUIRE_ORFAIL(ok_flag, catch_flag, expr) \ + REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr); \ + if (!ok_flag) \ + return ok_flag + + + + struct AllocUtil { + using AAllocator = xo::mm::AAllocator; + + /** generate a random sequence of allocations. + * verify allocator behavior + **/ + static bool random_allocs(std::uint32_t n_alloc, + bool catch_flag, + xo::rng::xoshiro256ss * p_rgen, + xo::facet::obj alloc); + }; +} + +/* end random_allocs.hpp */