From 1387673f154e293f0916ac2a42a217d0101dc2f7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 7 Apr 2026 21:16:55 -0400 Subject: [PATCH] xo-gc: retire Collector.forward_inplace + mock x-gc utest W/ MockCollector supporting AGCObjectVisitor facet --- include/xo/gc/DX1Collector.hpp | 9 ++- .../xo/gc/detail/ICollector_DX1Collector.hpp | 6 -- src/gc/DX1Collector.cpp | 31 ++------- src/gc/facet/ICollector_DX1Collector.cpp | 5 -- utest/CMakeLists.txt | 12 ++++ utest/DMockCollector.cpp | 37 ++++++++++ utest/DMockCollector.hpp | 40 +++++++++++ utest/IGCObjectVisitor_DMockCollector.cpp | 44 ++++++++++++ utest/IGCObjectVisitor_DMockCollector.hpp | 68 +++++++++++++++++++ utest/MockCollector.hpp | 13 ++++ utest/gc_utest_main.cpp | 10 +-- .../idl/IGCObjectVisitor_DMockCollector.json5 | 27 ++++++++ utest/init_gc_utest.cpp | 48 +++++++++++++ utest/init_gc_utest.hpp | 34 ++++++++++ 14 files changed, 342 insertions(+), 42 deletions(-) create mode 100644 utest/DMockCollector.cpp create mode 100644 utest/DMockCollector.hpp create mode 100644 utest/IGCObjectVisitor_DMockCollector.cpp create mode 100644 utest/IGCObjectVisitor_DMockCollector.hpp create mode 100644 utest/MockCollector.hpp create mode 100644 utest/idl/IGCObjectVisitor_DMockCollector.json5 create mode 100644 utest/init_gc_utest.cpp create mode 100644 utest/init_gc_utest.hpp diff --git a/include/xo/gc/DX1Collector.hpp b/include/xo/gc/DX1Collector.hpp index aaa2b08..5eafa80 100644 --- a/include/xo/gc/DX1Collector.hpp +++ b/include/xo/gc/DX1Collector.hpp @@ -274,13 +274,18 @@ namespace xo { /** Execute gc immediately, for all generations < @p upto **/ void execute_gc(Generation upto) noexcept; +#ifdef OBSOLETE // replaced by visit_child() /** Evacuate object at @p *lhs_data to to-space. * Replace original with forwarding pointer to new location **/ void forward_inplace(AGCObject * lhs_iface, void ** lhs_data); +#endif - /** Supports the GCObjectVisitor facet. - * Synonym for forward_inplace + /** Supports GCObjectVisitor facet. + * During gc phase: + * 1. evacuate object at @p *lhs_data to to-space. + * 2. replace @p *lhs_data with forwarding pointer + * to new location. **/ void visit_child(AGCObject * lhs_iface, void ** lhs_data); diff --git a/include/xo/gc/detail/ICollector_DX1Collector.hpp b/include/xo/gc/detail/ICollector_DX1Collector.hpp index c81513f..d9b43a5 100644 --- a/include/xo/gc/detail/ICollector_DX1Collector.hpp +++ b/include/xo/gc/detail/ICollector_DX1Collector.hpp @@ -111,12 +111,6 @@ Require: gc not in progress **/ Source must be owned by this collector. Increments object age **/ static void * alloc_copy(DX1Collector & self, std::byte * src); - /** evacuate @p *lhs, that refers to state with interface @p lhs_iface, -to collector @p d's to-space. Replace *lhs_data with forwarding pointer - -Require: gc in progress - **/ - static void forward_inplace(DX1Collector & self, AGCObject * lhs_iface, void ** lhs_data); ///@} }; diff --git a/src/gc/DX1Collector.cpp b/src/gc/DX1Collector.cpp index 2863636..00bd9fb 100644 --- a/src/gc/DX1Collector.cpp +++ b/src/gc/DX1Collector.cpp @@ -584,32 +584,13 @@ namespace xo { } } - void - DX1Collector::forward_inplace(AGCObject * lhs_iface, - void ** lhs_data) - { - // TODO: streamline once GCObject refactored so that - // forward_children takes GCObjectVisitor instead of Collector - // argument. - - Generation upto = runstate_.gc_upto(); - - if (runstate_.is_running()) { - // called during collection phase - gco_store_.forward_inplace_aux(this->ref(), lhs_iface, lhs_data, upto); - } else if (runstate_.is_verify()) { - // called during verify_ok - this->_verify_aux(lhs_iface, *lhs_data); - } else { - // should be unreachable - assert(false); - } - } - void DX1Collector::visit_child(AGCObject * lhs_iface, void ** lhs_data) { + // MAYBE: adapter distinct from DX1Collector that supports GCObjectVisitor facet, + // calls DX1Collector::_verify_aux() + if (runstate_.is_running()) { Generation upto = runstate_.gc_upto(); @@ -661,17 +642,17 @@ namespace xo { auto DX1Collector::super_alloc(typeseq t, size_type z) noexcept -> value_type { - return with_facet::mkobj(new_space()).super_alloc(t, z); + return with_facet::mkobj(this->new_space()).super_alloc(t, z); } auto DX1Collector::sub_alloc(size_type z, bool complete) noexcept -> value_type { - return with_facet::mkobj(new_space()).sub_alloc(z, complete); + return with_facet::mkobj(this->new_space()).sub_alloc(z, complete); } auto DX1Collector::alloc_copy(value_type src) noexcept -> value_type { - return with_facet::mkobj(new_space()).alloc_copy(src); + return with_facet::mkobj(this->new_space()).alloc_copy(src); } bool diff --git a/src/gc/facet/ICollector_DX1Collector.cpp b/src/gc/facet/ICollector_DX1Collector.cpp index a656ca6..a20a974 100644 --- a/src/gc/facet/ICollector_DX1Collector.cpp +++ b/src/gc/facet/ICollector_DX1Collector.cpp @@ -99,11 +99,6 @@ namespace xo { { return self.alloc_copy(src); } - auto - ICollector_DX1Collector::forward_inplace(DX1Collector & self, AGCObject * lhs_iface, void ** lhs_data) -> void - { - self.forward_inplace(lhs_iface, lhs_data); - } } /*namespace mm*/ } /*namespace xo*/ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 5ffcac8..599e1ea 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -9,9 +9,21 @@ set(UTEST_SRCS DX1CollectorIterator.test.cpp GCObjectStore.test.cpp Object2.test.cpp + + DMockCollector.cpp + IGCObjectVisitor_DMockCollector.cpp + + init_gc_utest.cpp random_allocs.cpp ) +# mock collector for unit test +xo_add_genfacetimpl( + TARGET xo-gc-facetimpl-gcobjectvisitor-mockcollector + FACET_PKG xo_alloc2 + INPUT idl/IGCObjectVisitor_DMockCollector.json5 +) + if (ENABLE_TESTING) xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) xo_headeronly_dependency(${UTEST_EXE} randomgen) diff --git a/utest/DMockCollector.cpp b/utest/DMockCollector.cpp new file mode 100644 index 0000000..69f9050 --- /dev/null +++ b/utest/DMockCollector.cpp @@ -0,0 +1,37 @@ +/** @file DMockCollector.cpp + * + * @author Roland Conybeare, Apr 2026 + **/ + +#include "MockCollector.hpp" + +namespace xo { + namespace mm { + + Generation + DMockCollector::generation_of(Role r, const void * addr) const noexcept + { + return p_gco_store_->generation_of(r, addr); + } + + AllocInfo + DMockCollector::alloc_info(void * mem) const noexcept + { + return p_gco_store_->alloc_info((std::byte *)mem); + } + + void + DMockCollector::visit_child(AGCObject * lhs_iface, void ** lhs_data) + { + p_gco_store_->forward_inplace_aux(this->ref(), lhs_iface, lhs_data, upto_); + } + + std::byte * + DMockCollector::alloc_copy(void * src) noexcept { + return p_gco_store_->new_space()->alloc_copy((std::byte *)src); + } + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end DMockCollector.cpp */ diff --git a/utest/DMockCollector.hpp b/utest/DMockCollector.hpp new file mode 100644 index 0000000..ca1c0df --- /dev/null +++ b/utest/DMockCollector.hpp @@ -0,0 +1,40 @@ +/** @file DMockCollector.hpp + * + * @author Roland Conybeare, Apr 2026 + **/ + +#pragma once + +#include +#include + +namespace xo { + namespace mm { + + /** @brief Mock Collector + * + * Intended to help unit test a GCObjectSotre instance. + * Mock a Collector in collection phase for generations 0 <= g < @ref upto_. + **/ + class DMockCollector { + public: + explicit DMockCollector(GCObjectStore * gcos, Generation upto) : p_gco_store_{gcos}, upto_{upto} {} + + template + obj ref() { return obj(this); } + + Generation generation_of(Role r, const void * addr) const noexcept; + AllocInfo alloc_info(void * mem) const noexcept; + + void visit_child(AGCObject * lhs_iface, void ** lhs_data); + std::byte * alloc_copy(void * src) noexcept; + + private: + GCObjectStore * p_gco_store_ = nullptr; + Generation upto_; + }; + + } /*namespace mm*/ +} /*namespaace xo*/ + +/* end DMockCollector.hpp */ diff --git a/utest/IGCObjectVisitor_DMockCollector.cpp b/utest/IGCObjectVisitor_DMockCollector.cpp new file mode 100644 index 0000000..f447287 --- /dev/null +++ b/utest/IGCObjectVisitor_DMockCollector.cpp @@ -0,0 +1,44 @@ +/** @file IGCObjectVisitor_DMockCollector.cpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/IGCObjectVisitor_DMockCollector.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/IGCObjectVisitor_DMockCollector.json5] +**/ + +#include "./IGCObjectVisitor_DMockCollector.hpp" + +namespace xo { + namespace mm { + auto + IGCObjectVisitor_DMockCollector::alloc_info(const DMockCollector & self, void * addr) -> AllocInfo + { + return self.alloc_info(addr); + } + + auto + IGCObjectVisitor_DMockCollector::generation_of(const DMockCollector & self, Role r, const void * addr) noexcept -> Generation + { + return self.generation_of(r, addr); + } + + auto + IGCObjectVisitor_DMockCollector::alloc_copy(DMockCollector & self, std::byte * src) -> void * + { + return self.alloc_copy(src); + } + auto + IGCObjectVisitor_DMockCollector::visit_child(DMockCollector & self, AGCObject * iface, void ** pp_data) noexcept -> void + { + self.visit_child(iface, pp_data); + } + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IGCObjectVisitor_DMockCollector.cpp */ diff --git a/utest/IGCObjectVisitor_DMockCollector.hpp b/utest/IGCObjectVisitor_DMockCollector.hpp new file mode 100644 index 0000000..ac9db2b --- /dev/null +++ b/utest/IGCObjectVisitor_DMockCollector.hpp @@ -0,0 +1,68 @@ +/** @file IGCObjectVisitor_DMockCollector.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/IGCObjectVisitor_DMockCollector.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_repr.hpp.j2] + * 3. idl for facet methods + * [idl/IGCObjectVisitor_DMockCollector.json5] + **/ + +#pragma once + +#include "GCObjectVisitor.hpp" +#include "DMockCollector.hpp" + +namespace xo { namespace mm { class IGCObjectVisitor_DMockCollector; } } + +namespace xo { + namespace facet { + template <> + struct FacetImplementation + { + using ImplType = xo::mm::IGCObjectVisitor_Xfer + ; + }; + } +} + +namespace xo { + namespace mm { + /** @class IGCObjectVisitor_DMockCollector + **/ + class IGCObjectVisitor_DMockCollector { + public: + /** @defgroup mm-gcobjectvisitor-dmockcollector-type-traits **/ + ///@{ + using Copaque = xo::mm::AGCObjectVisitor::Copaque; + using Opaque = xo::mm::AGCObjectVisitor::Opaque; + ///@} + /** @defgroup mm-gcobjectvisitor-dmockcollector-methods **/ + ///@{ + // const methods + /** allocation metadata for gc-aware data at address @p gco. +@p gco must be the result of a call to collector's alloc() function **/ + static AllocInfo alloc_info(const DMockCollector & self, void * addr); + /** generation to which pointer @p addr belongs, given role @p r; +sentinel if @p addr is not owned by collector **/ + static Generation generation_of(const DMockCollector & self, Role r, const void * addr) noexcept; + + // non-const methods + /** allocate copy of source object at address @p src. +Source must be owned by this collector. +Increments object age **/ + static void * alloc_copy(DMockCollector & self, std::byte * src); + /** visit child of a gc-aware object. May update child in-place! **/ + static void visit_child(DMockCollector & self, AGCObject * iface, void ** pp_data) noexcept; + ///@} + }; + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end */ \ No newline at end of file diff --git a/utest/MockCollector.hpp b/utest/MockCollector.hpp new file mode 100644 index 0000000..4e3f3d1 --- /dev/null +++ b/utest/MockCollector.hpp @@ -0,0 +1,13 @@ +/** @file MockCollector.hpp + * + * @author Roland Conybeare, Apr 2026 + **/ + +#pragma once + +#include "DMockCollector.hpp" +#include "IGCObjectVisitor_DMockCollector.hpp" +//#include "ICollector_DMockCollector.hpp" +//#include "IAllocator_DMockCollector.hpp" + +/* end MockCollector.hpp */ diff --git a/utest/gc_utest_main.cpp b/utest/gc_utest_main.cpp index cb1a31f..5d4c4ea 100644 --- a/utest/gc_utest_main.cpp +++ b/utest/gc_utest_main.cpp @@ -1,17 +1,19 @@ /* file gc_utest_main.cpp */ -#include +#include "init_gc_utest.hpp" #include #define CATCH_CONFIG_RUNNER #include -using xo::S_gc_tag; +using xo::S_gc_utest_tag; using xo::InitSubsys; using xo::InitEvidence; -// ensure xo-gc properly initialized when Subsystem::initialize_all() runs -static InitEvidence s_init = (InitSubsys::require()); +// ensure xo-gc/utest as mock subsystem +// properly initialized when Subsystem::initialize_all() runs +// +static InitEvidence s_init = (InitSubsys::require()); int main(int argc, char* argv[]) diff --git a/utest/idl/IGCObjectVisitor_DMockCollector.json5 b/utest/idl/IGCObjectVisitor_DMockCollector.json5 new file mode 100644 index 0000000..9d104bf --- /dev/null +++ b/utest/idl/IGCObjectVisitor_DMockCollector.json5 @@ -0,0 +1,27 @@ +{ + mode: "implementation", + output_cpp_dir: ".", + output_hpp_dir: "./", + output_impl_subdir: ".", + includes: [ +// "", +// "" + ], + local_types: [ +// { +// name: "typeseq", +// doc: ["identifies a c++ type"], +// definition: "xo::reflect::typeseq" +// }, + ], + namespace1: "xo", + namespace2: "mm", + facet_idl: "idl/GCObjectVisitor.json5", + brief: "provide AGCObjectVisitor interface for DMockCollector", + using_doxygen: true, + repr: "DMockCollector", + doc: [ + "Implement AGCObjectVisitor for DMockCollector.", + "Evacuate object pointer (migrate to to-space) during collection phase" + ], +} diff --git a/utest/init_gc_utest.cpp b/utest/init_gc_utest.cpp new file mode 100644 index 0000000..a88f604 --- /dev/null +++ b/utest/init_gc_utest.cpp @@ -0,0 +1,48 @@ +/** @file init_gc_utest.cpp +* + * @author Roland Conybeare, Apr 2026 + **/ + +#include "init_gc_utest.hpp" +#include "MockCollector.hpp" +#include +#include +#include + +namespace xo { + using xo::mm::SetupGcUtest; + using xo::facet::FacetRegistry; + using xo::reflect::typeseq; + + bool + SetupGcUtest::register_facets() + { + scope log(XO_DEBUG(true)); + + FacetRegistry::register_impl(); + + log && log(xtag("DMockCollector.tseq", typeseq::id())); + + return true; + } + + void + InitSubsys::init() + { + SetupGcUtest::register_facets(); + } + + InitEvidence + InitSubsys::require() { + InitEvidence retval; + + /* recursive subsystem deps for xo-gc/utest */ + retval ^= InitSubsys::require(); + + /* xo-gc/utest/'s own initialization code */ + retval ^= Subsystem::provide("gc-utest", &init); + + return retval; + } + +} diff --git a/utest/init_gc_utest.hpp b/utest/init_gc_utest.hpp new file mode 100644 index 0000000..ccadf84 --- /dev/null +++ b/utest/init_gc_utest.hpp @@ -0,0 +1,34 @@ +/** @file init_gc_utest.hpp +* + * @author Roland Conybeare, Apr 2026 + **/ + +#pragma once + +#include + +namespace xo { + /* tag to represent xo-gc/utest/ as a mock subsystem within ordered initialization + * + * (so we can follow usual patterns for setting up facet tables) + */ + enum S_gc_utest_tag {}; + + template <> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; + + namespace mm { + + class SetupGcUtest { + public: + /** Register gc/utest (facet,impl) combinations with FacetRegistry **/ + static bool register_facets(); + }; + } /*namespace mm*/ + +} /*namespace xo*/ + +/* end init_gc_utest.hpp */