diff --git a/xo-alloc2/include/xo/alloc2/CollectorTypeRegistry.hpp b/xo-alloc2/include/xo/alloc2/CollectorTypeRegistry.hpp index 2b3bd4b7..2515f486 100644 --- a/xo-alloc2/include/xo/alloc2/CollectorTypeRegistry.hpp +++ b/xo-alloc2/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/xo-alloc2/include/xo/alloc2/GCObjectConversion.hpp b/xo-alloc2/include/xo/alloc2/GCObjectConversion.hpp index 59ec12fa..e7c06797 100644 --- a/xo-alloc2/include/xo/alloc2/GCObjectConversion.hpp +++ b/xo-alloc2/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/xo-alloc2/include/xo/alloc2/alloc/AAllocator.hpp b/xo-alloc2/include/xo/alloc2/alloc/AAllocator.hpp index fdf57831..9d0966c7 100644 --- a/xo-alloc2/include/xo/alloc2/alloc/AAllocator.hpp +++ b/xo-alloc2/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/xo-alloc2/include/xo/alloc2/alloc/RAllocator.hpp b/xo-alloc2/include/xo/alloc2/alloc/RAllocator.hpp index f15dffca..b44d0a24 100644 --- a/xo-alloc2/include/xo/alloc2/alloc/RAllocator.hpp +++ b/xo-alloc2/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/xo-alloc2/include/xo/alloc2/alloc/RAllocator_aux.hpp b/xo-alloc2/include/xo/alloc2/alloc/RAllocator_aux.hpp index c83a1dda..835b45fb 100644 --- a/xo-alloc2/include/xo/alloc2/alloc/RAllocator_aux.hpp +++ b/xo-alloc2/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/xo-alloc2/src/alloc2/CMakeLists.txt b/xo-alloc2/src/alloc2/CMakeLists.txt index 7929371a..8972e546 100644 --- a/xo-alloc2/src/alloc2/CMakeLists.txt +++ b/xo-alloc2/src/alloc2/CMakeLists.txt @@ -7,6 +7,7 @@ set(SELF_SRCS SetupAlloc2.cpp CollectorTypeRegistry.cpp + GCObjectConversion.cpp facet/ICollector_Any.cpp diff --git a/xo-alloc2/src/alloc2/GCObjectConversion.cpp b/xo-alloc2/src/alloc2/GCObjectConversion.cpp new file mode 100644 index 00000000..6df96b4b --- /dev/null +++ b/xo-alloc2/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/xo-alloc2/utest/CMakeLists.txt b/xo-alloc2/utest/CMakeLists.txt index eacd19fd..33822241 100644 --- a/xo-alloc2/utest/CMakeLists.txt +++ b/xo-alloc2/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/xo-alloc2/utest/Generation.test.cpp b/xo-alloc2/utest/Generation.test.cpp new file mode 100644 index 00000000..43d64389 --- /dev/null +++ b/xo-alloc2/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/xo-alloc2/utest/arena.test.cpp b/xo-alloc2/utest/arena.test.cpp index 47ff4548..0beb66d8 100644 --- a/xo-alloc2/utest/arena.test.cpp +++ b/xo-alloc2/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/xo-alloc2/utest/dp.test.cpp b/xo-alloc2/utest/dp.test.cpp new file mode 100644 index 00000000..e507b730 --- /dev/null +++ b/xo-alloc2/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 */ diff --git a/xo-gc/include/xo/gc/DX1Collector.hpp b/xo-gc/include/xo/gc/DX1Collector.hpp index e8b930fe..349fa51c 100644 --- a/xo-gc/include/xo/gc/DX1Collector.hpp +++ b/xo-gc/include/xo/gc/DX1Collector.hpp @@ -57,6 +57,24 @@ namespace xo { Generation gc_upto_; }; + /** @class GcStatistics + **/ + struct GCStatistics { + public: + GCStatistics() = default; + explicit GCStatistics(uint32_t n_gc) : n_gc_{n_gc} {}; + + uint32_t n_gc() const noexcept { return n_gc_; } + + void include_gc() { + ++n_gc_; + } + + private: + /** count #gc **/ + uint32_t n_gc_ = 0; + }; + struct DX1CollectorIterator; /** @brief GC root struct @@ -116,6 +134,8 @@ namespace xo { std::string_view name() const noexcept { return config_.name_; } GCRunState runstate() const noexcept { return runstate_; } + const GCStatistics & gc_stats() const noexcept { return gc_stats_; } + const ObjectTypeTable * get_object_types() const noexcept { return gco_store_.get_object_types(); } const RootSet * get_root_set() const noexcept { return &root_set_; } const DArena * get_space(Role r, Generation g) const noexcept { return gco_store_.get_space(r, g); } @@ -405,6 +425,9 @@ namespace xo { **/ MutationLogStore mlog_store_; + /** counters collected across GC phases **/ + GCStatistics gc_stats_; + /** counters collected during @ref verify_ok call **/ X1VerifyStats verify_stats_; }; diff --git a/xo-gc/src/gc/DX1Collector.cpp b/xo-gc/src/gc/DX1Collector.cpp index 076f3480..7ea97982 100644 --- a/xo-gc/src/gc/DX1Collector.cpp +++ b/xo-gc/src/gc/DX1Collector.cpp @@ -528,6 +528,9 @@ namespace xo { log && log("step 5 : cleanup"); this->_cleanup_phase(upto); + log && log("step 6 : update gc statistics"); + gc_stats_.include_gc(); + if (config_.sanitize_flag_) { log && log("step 5b : verify"); bool ok = this->verify_ok(); @@ -550,7 +553,7 @@ namespace xo { void DX1Collector::_swap_roles(Generation upto) noexcept { - scope log(XO_DEBUG(true), xtag("upto", upto)); + scope log(XO_DEBUG(config_.debug_flag_), xtag("upto", upto)); gco_store_.swap_roles(upto); mlog_store_.swap_roles(upto); @@ -559,7 +562,7 @@ namespace xo { void DX1Collector::_cleanup_phase(Generation upto) { - scope log(XO_DEBUG(true), xtag("upto", upto)); + scope log(XO_DEBUG(config_.debug_flag_), xtag("upto", upto)); this->gco_store_.cleanup_phase(upto, config_.sanitize_flag_); this->runstate_ = GCRunState::idle(); @@ -568,7 +571,7 @@ namespace xo { void DX1Collector::_copy_roots(Generation upto) noexcept { - scope log(XO_DEBUG(true)); + scope log(XO_DEBUG(config_.debug_flag_)); for (RootSet::size_type i = 0, n = root_set_.size(); i < n; ++i) { GCRoot & slot = root_set_[i]; diff --git a/xo-gc/src/gc/GCObjectStore.cpp b/xo-gc/src/gc/GCObjectStore.cpp index fb29873e..c8eac5d6 100644 --- a/xo-gc/src/gc/GCObjectStore.cpp +++ b/xo-gc/src/gc/GCObjectStore.cpp @@ -705,7 +705,7 @@ namespace xo { void GCObjectStore::_verify_aux(AGCObject * iface, void * data) { - scope log(XO_DEBUG(config_.debug_flag_)); + scope log(XO_DEBUG(false)); (void)iface; @@ -719,6 +719,7 @@ namespace xo { if (!g2.is_sentinel()) { // verify failure - live pointer still refers to from-space + log.retroactively_enable(); print_backtrace_dwarf(true /*demangle*/); ++(p_verify_stats_->n_from_); @@ -1020,13 +1021,17 @@ namespace xo { AGCObject * iface, void * from_src) { - scope log(XO_DEBUG(config_.debug_flag_)); + scope log(XO_DEBUG(config_.debug_flag_), + xtag("iface", iface), + xtag("from_src", from_src)); + + assert(!iface->_has_null_vptr()); AllocInfo info = this->alloc_info((std::byte *)from_src); void * to_dest = iface->gco_shallow_move(from_src, gc); - log && log(xtag("from_src", from_src), xtag("to_dest", to_dest)); + log && log(xtag("to_dest", to_dest)); log && log(xtag("tseq", info.tseq()), xtag("tname", TypeRegistry::id2name(typeseq(info.tseq()))), xtag("age", info.age()), diff --git a/xo-gc/src/gc/MutationLogStore.cpp b/xo-gc/src/gc/MutationLogStore.cpp index 82a7d782..0719423a 100644 --- a/xo-gc/src/gc/MutationLogStore.cpp +++ b/xo-gc/src/gc/MutationLogStore.cpp @@ -160,8 +160,14 @@ namespace xo { assert(rhs_iface); assert(rhs_data); - if (lhs_iface) - *lhs_iface = *rhs_iface; + if (lhs_iface) { + // memcpy (not assignment): lhs_iface points to AGCObject storage + // whose vptr was set at construction (e.g. IGCObject_Any from + // a default-constructed obj). Polymorphic copy-assignment + // copies AGCObject's data members but NOT the vptr, so it would + // leave the slot dispatching to the wrong (often fatal) iface. + ::memcpy((void *)lhs_iface, (void *)rhs_iface, sizeof(AGCObject)); + } *lhs_addr = rhs_data; @@ -195,6 +201,13 @@ namespace xo { return; } + if (dest_g + 1 == config_.n_generation_) { + log && log(xtag("msg", "noop because dest in last gen")); + + // don't need mlog entry to final gen + return; + } + if (src_g < dest_g) { log && log(xtag("msg", "noop because src gen younger than dest gen")); @@ -259,7 +272,7 @@ namespace xo { void MutationLogStore::swap_roles(Generation upto) noexcept { - scope log(XO_DEBUG(true), xtag("upto", upto)); + scope log(XO_DEBUG(config_.debug_flag_), xtag("upto", upto)); for (Generation g = Generation{0}; g < upto; ++g) { log && log("swap roles", xtag("g", g)); diff --git a/xo-gc/utest/CMakeLists.txt b/xo-gc/utest/CMakeLists.txt index 4f555807..401371f6 100644 --- a/xo-gc/utest/CMakeLists.txt +++ b/xo-gc/utest/CMakeLists.txt @@ -9,6 +9,7 @@ set(UTEST_SRCS DX1CollectorIterator.test.cpp MutationLogStore.test.cpp GCObjectStore.test.cpp + GCObjectConversion.test.cpp Object2.test.cpp DMockCollector.cpp diff --git a/xo-gc/utest/Collector.test.cpp b/xo-gc/utest/Collector.test.cpp index 117f4ebb..ceb277b4 100644 --- a/xo-gc/utest/Collector.test.cpp +++ b/xo-gc/utest/Collector.test.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ namespace xo { using xo::scm::DList; using xo::scm::DArray; using xo::scm::DInteger; + using xo::mm::CollectorTypeRegistry; using xo::mm::AAllocator; using xo::mm::ACollector; using xo::mm::AGCObject; @@ -33,9 +35,11 @@ namespace xo { using xo::mm::Role; using xo::mm::ArenaConfig; using xo::mm::AllocHeaderConfig; + using xo::mm::AllocHeader; using xo::mm::Generation; using xo::mm::c_max_generation; using xo::facet::with_facet; + using xo::reflect::typeseq; using xo::scope; namespace ut { @@ -321,7 +325,11 @@ namespace xo { .with_n_survive(tc.n_survive_) .with_size(tc.gc_halfspace_z_) .with_debug_flag(tc.debug_flag_)) - {} + { + auto gc = obj(&gc_); + + CollectorTypeRegistry::instance().install_types(gc); + } # define nil nullptr # define T true @@ -332,11 +340,11 @@ namespace xo { * debug_flag * object_type_z | * gc_halfspace_z | | - * n_survive | | | - * n_gen | | | | - * v v v v v + * n_survive | | | + * n_gen | | | | + * v v v v v **/ - Testcase(1, 2, 16 * 1024, 128, F), + Testcase(1, 2, 16 * 1024, 128, T), }; # undef T @@ -345,10 +353,23 @@ namespace xo { } /*namespace*/ // full collector test. + // + // PLAN: + // eventually: make generative + // + // Setup ( + // 1. gc_utest_main.cpp Subsystem::initialize_all() + // invokes per-module plugin init. Gets types registered + // with FacetRegistry, CollectorTypeRegistry etc. + // 2. per-utest collector setup (fixture) + // calls CollectorTypeRegistry::instance().install_types(gc) + // to establish the set of types that collector knows. + // TEST_CASE("collector-x1-gc", "[alloc2][gc]") { - scope log(XO_DEBUG(true), - "DX1Collector gc test"); + const auto & testname = Catch::getResultCapture().getCurrentTestName(); + + scope log(XO_DEBUG(true), xtag("test", testname)); //std::uint64_t seed = 7988747704879432247ul; //random_seed(&seed); @@ -372,7 +393,17 @@ namespace xo { auto mm = x1.ref(); auto gc = mm.to_facet(); - Generation g1{1}; + + REQUIRE(mm._typeseq() == typeseq::id()); + REQUIRE(gc._typeseq() == typeseq::id()); + + Generation g0 = Generation::g0(); + + REQUIRE(mm.allocated() == tc.object_type_z_); + REQUIRE(gc.allocated(g0, Role::to_space()) == 0); + REQUIRE(gc.allocated(g0, Role::from_space()) == 0); + + Generation g1 = Generation::g1(); { auto roots = DArray::_empty(mm, 1)->ref(); REQUIRE(mm->contains_allocated(Role::to_space(), roots.data())); @@ -383,12 +414,32 @@ namespace xo { auto x1_gco = obj(x1); auto l1 = DList::cons(mm, x1, DList::_nil()); -#ifdef NOT_YET - REQUIRE(roots->push_back(l1)); + REQUIRE(l1._typeseq() == typeseq::id()); + REQUIRE(roots->push_back(mm, l1)); REQUIRE(mm->contains_allocated(Role::to_space(), x1.data())); REQUIRE(mm->contains_allocated(Role::to_space(), l1.data())); - gc->add_gc_root_poly(&(*roots.operator->())[0]); + REQUIRE(roots->at(0) == l1); + REQUIRE(roots->at(0)._typeseq() == typeseq::id()); + + // z: total allocated so far + // 3x 8-byte header + // sizeof(DInteger) + // sizeof(DList) + // sizeof(DArray(1)) + // + auto z = (3 * sizeof(AllocHeader) + + sizeof(DInteger) + + sizeof(DList) + + sizeof(DArray) + sizeof(obj)); + { + REQUIRE(z == 80); + REQUIRE(mm.allocated() == tc.object_type_z_ + z); + REQUIRE(gc.allocated(g0, Role::to_space()) == z); + REQUIRE(gc.allocated(g1, Role::to_space()) == 0); + REQUIRE(gc.allocated(g0, Role::from_space()) == 0); + REQUIRE(gc.allocated(g1, Role::from_space()) == 0); + } gc->request_gc(g1); // 1st GC @@ -398,9 +449,20 @@ namespace xo { // l1 target got moved, og locn now relabeled from-space REQUIRE(mm->contains(Role::from_space(), l1.data())); REQUIRE(!mm->contains_allocated(Role::from_space(), l1.data())); -#endif + + REQUIRE(mm.allocated() == tc.object_type_z_ + z); + REQUIRE(gc.allocated(g0, Role::to_space()) == z); + REQUIRE(gc.allocated(g1, Role::to_space()) == 0); + REQUIRE(gc.allocated(g0, Role::from_space()) == 0); + REQUIRE(gc.allocated(g1, Role::from_space()) == 0); } + + // NOTE: if this fails: + // look for preceding GCObjectStore::lookup_type out-of-bounds. + // May need to add to CollectorTypeRegistry + // REQUIRE(mm->contains_allocated(Role::to_space(), roots.data())); + } } diff --git a/xo-gc/utest/GCObjectConversion.test.cpp b/xo-gc/utest/GCObjectConversion.test.cpp new file mode 100644 index 00000000..1b094070 --- /dev/null +++ b/xo-gc/utest/GCObjectConversion.test.cpp @@ -0,0 +1,95 @@ +/** @file GCObjectConversion.test.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include "GCObjectConversion.hpp" +#include +#include +#include +#include +#include +#include + +namespace xo { + //using xo::scm::ASequence; + using xo::scm::ListOps; + using xo::scm::DArray; + using xo::scm::DList; + using xo::scm::DInteger; + using xo::scm::GCObjectConversion; + using xo::mm::AGCObject; + using xo::mm::ArenaConfig; + using xo::mm::AAllocator; + using xo::mm::DArena; + using xo::facet::obj; + + namespace ut { + + TEST_CASE("GCObjectConversion-1", "[GCObjectConversion]") + { + scope log(XO_DEBUG(true), "GCObjectConversion-1"); + + ArenaConfig cfg { + .name_ = "testarena", + .size_ = 128 + }; + DArena arena = DArena::map(cfg); + auto mm = obj(&arena); + auto v1 = DArray::empty(mm, 3); + + REQUIRE(v1); + REQUIRE(v1->size() == 0); + + { + obj v1_seq + = GCObjectConversion>::from_gco(mm /*not used*/, v1); + + REQUIRE(v1_seq); + REQUIRE(v1_seq == v1); + REQUIRE(v1_seq->size() == 0); + } + + { + obj l1_seq + = GCObjectConversion>::from_gco(mm /*not used*/, v1); + + REQUIRE(!l1_seq); + } + } + + TEST_CASE("GCObjectConversion-2", "[GCObjectConversion]") + { + scope log(XO_DEBUG(true), "GCObjectConversion-2"); + + ArenaConfig cfg { + .name_ = "testarena", + .size_ = 128 + }; + DArena arena = DArena::map(cfg); + auto mm = obj(&arena); + auto l1 = ListOps::cons(mm, DInteger::box(mm, 42), ListOps::nil()); + + REQUIRE(l1); + REQUIRE(l1->size() == 1); + + { + // will fail; source is DArena + obj l1_seq + = GCObjectConversion>::from_gco(mm /*not used*/, l1); + + REQUIRE(l1_seq); + } + + { + obj v1_seq + = GCObjectConversion>::from_gco(mm /*not used*/, l1); + + REQUIRE(!v1_seq); + } + } + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end GCObjectConversion.cpp */ diff --git a/xo-gc/utest/MutationLogStore.test.cpp b/xo-gc/utest/MutationLogStore.test.cpp index 40e5f25e..f37ea67b 100644 --- a/xo-gc/utest/MutationLogStore.test.cpp +++ b/xo-gc/utest/MutationLogStore.test.cpp @@ -341,7 +341,7 @@ namespace ut { Testcase(2, 1, 16 * 1024, 8 * 128, T, seq_2, 128, T, c_fixed, 3, 0, 0, 0, 0, F), Testcase(2, 2, 16 * 1024, 8 * 128, T, seq_3, 128, T, c_fixed, 4, 0, 0, 0, 0, F), Testcase(2, 2, 16 * 1024, 8 * 128, T, seq_4, 128, T, c_fixed, 4, 0, 0, 0, 0, F), - Testcase(2, 2, 16 * 1024, 8 * 128, T, seq_5, 128, T, c_fixed, 4, 0, 0, 0, 0, T), + Testcase(2, 2, 16 * 1024, 8 * 128, T, seq_5, 128, T, c_fixed, 4, 0, 0, 0, 0, F), }; # undef T diff --git a/xo-gc/utest/X1Collector.test.cpp b/xo-gc/utest/X1Collector.test.cpp index 3bd4d4ee..421c4639 100644 --- a/xo-gc/utest/X1Collector.test.cpp +++ b/xo-gc/utest/X1Collector.test.cpp @@ -13,7 +13,6 @@ #include #include #include -//#include "list/IGCObject_DList.hpp" #include //#include diff --git a/xo-object2/include/xo/object2/DArray.hpp b/xo-object2/include/xo/object2/DArray.hpp index 64d7f52c..5f571333 100644 --- a/xo-object2/include/xo/object2/DArray.hpp +++ b/xo-object2/include/xo/object2/DArray.hpp @@ -76,6 +76,11 @@ namespace xo { static DArray * _empty(obj mm, size_type cap); + /** ofp version of _empty(mm,cap) **/ + template + static obj empty(obj mm, + size_type cap); + /** create copy of @p src using memory from @p mm * with capacity for @p new_cap elements **/ @@ -198,6 +203,15 @@ namespace xo { ///@} }; + template + obj + DArray::empty(obj mm, DArray::size_type cap) + { + DArray * retval = _empty(mm, cap); + + return obj(retval); + } + template requires (std::convertible_to> && ...) DArray * diff --git a/xo-object2/src/object2/DArray.cpp b/xo-object2/src/object2/DArray.cpp index 2a5c590e..050f757d 100644 --- a/xo-object2/src/object2/DArray.cpp +++ b/xo-object2/src/object2/DArray.cpp @@ -84,7 +84,8 @@ namespace xo { } bool - DArray::push_back(obj mm, obj elt) noexcept + DArray::push_back(obj mm, + obj elt) noexcept { if (size_ >= capacity_) { return false; diff --git a/xo-object2/utest/DArray.test.cpp b/xo-object2/utest/DArray.test.cpp index 89353608..15da135e 100644 --- a/xo-object2/utest/DArray.test.cpp +++ b/xo-object2/utest/DArray.test.cpp @@ -63,6 +63,9 @@ namespace xo { auto alloc = with_facet::mkobj(&arena); obj null_mm; + REQUIRE(!null_mm.data()); + REQUIRE(!null_mm._has_null_vptr()); // any + DArray * arr = DArray::_empty(alloc, 16); REQUIRE(arr != nullptr); REQUIRE(arr->capacity() == 16); diff --git a/xo-reader2/src/reader2/DDefineSsm.cpp b/xo-reader2/src/reader2/DDefineSsm.cpp index 0e38f93d..59388820 100644 --- a/xo-reader2/src/reader2/DDefineSsm.cpp +++ b/xo-reader2/src/reader2/DDefineSsm.cpp @@ -466,7 +466,7 @@ namespace xo { DDefineSsm::on_parsed_typedescr(TypeDescr td, ParserStateMachine * p_psm) { - scope log(XO_DEBUG(true), xtag("td", td)); + scope log(XO_DEBUG(p_psm->debug_flag()), xtag("td", td)); if (defstate_ == defexprstatetype::def_3) { this->defstate_ = defexprstatetype::def_4;