diff --git a/include/xo/gc/DX1Collector.hpp b/include/xo/gc/DX1Collector.hpp index e8b930f..349fa51 100644 --- a/include/xo/gc/DX1Collector.hpp +++ b/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/src/gc/DX1Collector.cpp b/src/gc/DX1Collector.cpp index 076f348..7ea9798 100644 --- a/src/gc/DX1Collector.cpp +++ b/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/src/gc/GCObjectStore.cpp b/src/gc/GCObjectStore.cpp index fb29873..c8eac5d 100644 --- a/src/gc/GCObjectStore.cpp +++ b/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/src/gc/MutationLogStore.cpp b/src/gc/MutationLogStore.cpp index 82a7d78..0719423 100644 --- a/src/gc/MutationLogStore.cpp +++ b/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/utest/CMakeLists.txt b/utest/CMakeLists.txt index 4f55580..401371f 100644 --- a/utest/CMakeLists.txt +++ b/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/utest/Collector.test.cpp b/utest/Collector.test.cpp index 117f4eb..ceb277b 100644 --- a/utest/Collector.test.cpp +++ b/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/utest/GCObjectConversion.test.cpp b/utest/GCObjectConversion.test.cpp new file mode 100644 index 0000000..1b09407 --- /dev/null +++ b/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/utest/MutationLogStore.test.cpp b/utest/MutationLogStore.test.cpp index 40e5f25..f37ea67 100644 --- a/utest/MutationLogStore.test.cpp +++ b/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/utest/X1Collector.test.cpp b/utest/X1Collector.test.cpp index 3bd4d4e..421c463 100644 --- a/utest/X1Collector.test.cpp +++ b/utest/X1Collector.test.cpp @@ -13,7 +13,6 @@ #include #include #include -//#include "list/IGCObject_DList.hpp" #include //#include