From f56b01e7b610ac9513beb6177c8bc7116a7170ed Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 7 May 2026 23:44:32 -0400 Subject: [PATCH] xo-gc stack: fix mutation setup + xo-reader2 utest --- include/xo/alloc2/CollectorTypeRegistry.hpp | 6 +- include/xo/alloc2/GCObjectConversion.hpp | 31 ++++-- include/xo/alloc2/alloc/AAllocator.hpp | 10 ++ include/xo/alloc2/alloc/RAllocator.hpp | 1 + include/xo/alloc2/alloc/RAllocator_aux.hpp | 26 +++-- src/alloc2/CMakeLists.txt | 1 + src/alloc2/GCObjectConversion.cpp | 28 ++++++ utest/CMakeLists.txt | 2 + utest/Generation.test.cpp | 42 ++++++++ utest/arena.test.cpp | 10 +- utest/dp.test.cpp | 100 ++++++++++++++++++++ 11 files changed, 234 insertions(+), 23 deletions(-) create mode 100644 src/alloc2/GCObjectConversion.cpp create mode 100644 utest/Generation.test.cpp create mode 100644 utest/dp.test.cpp diff --git a/include/xo/alloc2/CollectorTypeRegistry.hpp b/include/xo/alloc2/CollectorTypeRegistry.hpp index 2b3bd4b..2515f48 100644 --- a/include/xo/alloc2/CollectorTypeRegistry.hpp +++ b/include/xo/alloc2/CollectorTypeRegistry.hpp @@ -12,6 +12,7 @@ namespace xo { namespace mm { + /** @class CollectorTypeRegistry * * @brief Runtime registry for gc-aware types @@ -68,7 +69,8 @@ namespace xo { /** initialization steps for a new Collector instance **/ std::vector init_seq_v_; }; - } -} + + } /*namespace mm*/ +} /*namespace xo*/ /* end CollectorTypeRegistry.hpp */ diff --git a/include/xo/alloc2/GCObjectConversion.hpp b/include/xo/alloc2/GCObjectConversion.hpp index 59ec12f..e7c0679 100644 --- a/include/xo/alloc2/GCObjectConversion.hpp +++ b/include/xo/alloc2/GCObjectConversion.hpp @@ -13,13 +13,27 @@ namespace xo { namespace scm { + class GCObjectConversionUtil { + public: + using AGCObject = xo::mm::AGCObject; + using typeseq = xo::reflect::typeseq; + + /** helper method fro GCObjectConversion<..>::from_gco() + * on conversion failure + **/ + static void _from_gco_fail_aux(obj gco, + typeseq tseq, + scope * p_log); + }; + /** @brief compile-time conversion obj <-> T * * Specialize for each T that participates in conversion. * Methods here aren't implemented **/ template - struct GCObjectConversion { + class GCObjectConversion { + public: using AGCObject = xo::mm::AGCObject; using AAllocator = xo::mm::AAllocator; @@ -73,6 +87,13 @@ namespace xo { } } + /** Several use cases here: + * 1. runtime polymorphism + * obj v(DArray::make(..)); + * // from_gco() doesn't know v repr + * auto gc = GCObjectConversion::from_gco(mm, v); + * + **/ static obj from_gco(obj, obj gco) { scope log(XO_DEBUG(false)); @@ -92,14 +113,10 @@ namespace xo { auto retval = obj::from(gco); if (!retval) { - log.retroactively_enable(); - - log && log(xtag("gco.tseq", gco._typeseq())); - log && log(xtag("DRepr.tseq", reflect::typeseq::id())); + GCObjectConversionUtil::_from_gco_fail_aux + (gco, reflect::typeseq::id(), &log); } - assert(retval); - return retval; } } else { diff --git a/include/xo/alloc2/alloc/AAllocator.hpp b/include/xo/alloc2/alloc/AAllocator.hpp index fdf5783..9d0966c 100644 --- a/include/xo/alloc2/alloc/AAllocator.hpp +++ b/include/xo/alloc2/alloc/AAllocator.hpp @@ -64,6 +64,16 @@ namespace xo { /** @defgroup mm-allocator-methods Allocator methods **/ ///@{ + /** An uninitialized AAllocator instance will have zero vtable pointer + * (per {linux,osx} abi). + * Use case for this is narrow. + * We go to some lengths to avoid null vtable pointers. + * For example obj will have non-null vtable (via IFacet_Any) + * with all methods terminating. + **/ + bool _has_null_vptr() const noexcept { + return (*reinterpret_cast(this) == nullptr); + } /** RTTI: unique id# for actual runtime data representation **/ virtual typeseq _typeseq() const noexcept = 0; /** destroy instance @p d. Calls c++ destructor for actual runtime type. diff --git a/include/xo/alloc2/alloc/RAllocator.hpp b/include/xo/alloc2/alloc/RAllocator.hpp index f15dffc..b44d0a2 100644 --- a/include/xo/alloc2/alloc/RAllocator.hpp +++ b/include/xo/alloc2/alloc/RAllocator.hpp @@ -52,6 +52,7 @@ namespace xo { return nullptr; } + bool _has_null_vptr() const noexcept { return O::iface()->_has_null_vptr(); } typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); } void _drop() const noexcept { O::iface()->_drop(O::data()); } std::string_view name() const noexcept { return O::iface()->name(O::data()); } diff --git a/include/xo/alloc2/alloc/RAllocator_aux.hpp b/include/xo/alloc2/alloc/RAllocator_aux.hpp index c83a1dd..835b45f 100644 --- a/include/xo/alloc2/alloc/RAllocator_aux.hpp +++ b/include/xo/alloc2/alloc/RAllocator_aux.hpp @@ -33,9 +33,14 @@ namespace xo { obj * p_lhs, obj rhs) noexcept { - this->barrier_assign_aux(parent, - p_lhs->iface(), p_lhs->opaque_data_addr(), - rhs.iface(), rhs.opaque_data()); + if (this->data()) { + this->barrier_assign_aux(parent, + p_lhs->iface(), p_lhs->opaque_data_addr(), + rhs.iface(), rhs.opaque_data()); + } else { + // special case: for null allocator want no write-barrier + *p_lhs = rhs; + } } template @@ -48,11 +53,16 @@ namespace xo { // need to get AGCObject i/face that goes with DRepr. obj rhs_gco(rhs_data); - this->barrier_assign_aux(parent, - nullptr /*not needed*/, - lhs_data, - rhs_gco.iface(), - rhs_data); + if (this->data()) { + this->barrier_assign_aux(parent, + nullptr /*not needed*/, + lhs_data, + rhs_gco.iface(), + rhs_data); + } else { + // special case: for null allocator want no write-barrier + *lhs_data = rhs_data; + } } } /*namespace mm*/ diff --git a/src/alloc2/CMakeLists.txt b/src/alloc2/CMakeLists.txt index 7929371..8972e54 100644 --- a/src/alloc2/CMakeLists.txt +++ b/src/alloc2/CMakeLists.txt @@ -7,6 +7,7 @@ set(SELF_SRCS SetupAlloc2.cpp CollectorTypeRegistry.cpp + GCObjectConversion.cpp facet/ICollector_Any.cpp diff --git a/src/alloc2/GCObjectConversion.cpp b/src/alloc2/GCObjectConversion.cpp new file mode 100644 index 0000000..6df96b4 --- /dev/null +++ b/src/alloc2/GCObjectConversion.cpp @@ -0,0 +1,28 @@ +/** @file GCObjectConversion.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include "GCObjectConversion.hpp" + +namespace xo { + using xo::reflect::typeseq; + + namespace scm { + + void + GCObjectConversionUtil::_from_gco_fail_aux(obj gco, + typeseq tseq, + scope * p_log) + { + p_log->retroactively_enable(); + if (p_log) { + (*p_log)(xtag("gco.tseq", gco._typeseq())); + (*p_log)(xtag("DRepr.tseq", tseq)); + } + } + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end GCObjectConversion.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index eacd19f..3382224 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -9,6 +9,8 @@ set(UTEST_SRCS DArenaIterator.test.cpp # Collector.test.cpp # DX1CollectorIterator.test.cpp + Generation.test.cpp + dp.test.cpp random_allocs.cpp ) diff --git a/utest/Generation.test.cpp b/utest/Generation.test.cpp new file mode 100644 index 0000000..43d6438 --- /dev/null +++ b/utest/Generation.test.cpp @@ -0,0 +1,42 @@ +/** @file Generation.test.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include "Generation.hpp" +#include + +namespace xo { + using xo::mm::Generation; + using xo::mm::c_max_generation; + + namespace ut { + + TEST_CASE("Generation-1", "[Generation]") + { + REQUIRE(Generation::nursery() == 0); + REQUIRE(Generation::g0() == 0); + REQUIRE(Generation::g1() == 1); + REQUIRE(Generation::sentinel() == c_max_generation); + + REQUIRE(Generation::g0().is_sentinel() == false); + REQUIRE(Generation::g1().is_sentinel() == false); + REQUIRE(Generation::sentinel().is_sentinel()); + + REQUIRE(Generation::g0() != Generation::sentinel()); + REQUIRE(Generation::g1() != Generation::sentinel()); + + REQUIRE(!(Generation::g0() > Generation::g1())); + REQUIRE(Generation::g1() > Generation::g0()); + + auto g = Generation::g0(); + ++g; + REQUIRE(g == Generation::g1()); + ++g; + REQUIRE(g > Generation::g1()); + } + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end Generation.hpp */ diff --git a/utest/arena.test.cpp b/utest/arena.test.cpp index 47ff454..0beb66d 100644 --- a/utest/arena.test.cpp +++ b/utest/arena.test.cpp @@ -3,11 +3,10 @@ * @author Roland Conybeare, Dec 2025 **/ -#include "xo/alloc2/Allocator.hpp" -#include "xo/alloc2/Arena.hpp" -//#include "xo/alloc2/arena/IAllocator_DArena.hpp" -#include "xo/arena/print.hpp" -#include "xo/arena/padding.hpp" +#include +#include +#include +#include #include #include #include @@ -150,7 +149,6 @@ namespace xo { .size_ = 64*1024, .debug_flag_ = false }; DArena arena = DArena::map(cfg); - //obj a1o{&arena}; auto a1o = with_facet::mkobj(&arena); REQUIRE(a1o.reserved() >= cfg.size_); diff --git a/utest/dp.test.cpp b/utest/dp.test.cpp new file mode 100644 index 0000000..e507b73 --- /dev/null +++ b/utest/dp.test.cpp @@ -0,0 +1,100 @@ +/** @file dp.test.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include "dp.hpp" +#include +#include +#include + +namespace xo { + using xo::mm::AAllocator; + using xo::mm::DArena; + using xo::mm::ArenaConfig; + + namespace { + class Foo { + public: + explicit Foo(uint32_t * p_counter) : p_counter_{p_counter} {} + + static constexpr bool is_gc_eligible() { return false; } + + ~Foo() { + ++(*p_counter_); + } + + private: + uint32_t * p_counter_ = nullptr; + }; + } + + namespace ut { + + TEST_CASE("dp-1", "[dp]") + { + //ArenaConfig cfg { .name_ = "testarena", .size_ = 1024 }; + //DArena arena = DArena::map(cfg); + //auto mm = obj(&arena); + + uint32_t counter = 0; + Foo foo(&counter); + + REQUIRE(counter == 0); + { + dp foo_dp(&foo); + + REQUIRE(foo_dp); + REQUIRE(foo_dp.data()); + REQUIRE(foo_dp.is_gc_eligible() == false); + + // foo_dp dtor runs here, increments counter + } + REQUIRE(counter == 1); + } + + TEST_CASE("dp-2", "[dp]") + { + uint32_t counter = 0; + Foo foo(&counter); + + REQUIRE(counter == 0); + { + dp foo_dp(&foo); + + REQUIRE(foo_dp); + REQUIRE(foo_dp.data() == &foo); + + foo_dp.release(); + + REQUIRE(!foo_dp); + REQUIRE(!foo_dp.data()); + + // foo_dp dtor runs here, increments counter + } + REQUIRE(counter == 0); + } + + TEST_CASE("dp-DArena", "[dp][DArena]") + { + ArenaConfig cfg { .name_ = "testarena", .size_ = 1024 }; + DArena arena = DArena::map(cfg); + //auto mm = obj(&arena); + + REQUIRE(arena.reserved() > 0); + REQUIRE(arena.is_mapped()); + { + dp arena_dp(&arena); + + REQUIRE(arena_dp); + REQUIRE(arena_dp.data() == &arena); + } + + REQUIRE(arena.reserved() == 0); + REQUIRE(!arena.is_mapped()); + } + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end dp.test.cpp */