From c5da97dea028c0f6f6b8ba917bc13ffa71b1b640 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 29 Mar 2026 13:44:19 -0400 Subject: [PATCH] xo-gc stack: + request-gc-statistics() primitive 1. xo-gc now depends on xo-object2. 2. use genfacet for ICollector_DX1Collector 3. moves xo-gc utest previously in xo-object2 to more natural location in xo-gc/ --- CMakeLists.txt | 13 + cmake/xo_gcConfig.cmake.in | 1 + idl/ICollector_DX1Collector.json5 | 24 ++ include/xo/gc/DX1Collector.hpp | 31 +- .../xo/gc/detail/ICollector_DX1Collector.hpp | 118 ++++--- src/gc/CMakeLists.txt | 4 +- src/gc/DX1Collector.cpp | 120 ++++++- src/gc/IAllocator_DX1Collector.cpp | 8 +- src/gc/ICollector_DX1Collector.cpp | 118 ------- src/gc/facet/ICollector_DX1Collector.cpp | 88 ++++++ src/gc/init_gc.cpp | 2 + utest/CMakeLists.txt | 2 + utest/Object2.test.cpp | 134 ++++++++ utest/X1Collector.test.cpp | 297 ++++++++++++++++++ 14 files changed, 790 insertions(+), 170 deletions(-) create mode 100644 idl/ICollector_DX1Collector.json5 delete mode 100644 src/gc/ICollector_DX1Collector.cpp create mode 100644 src/gc/facet/ICollector_DX1Collector.cpp create mode 100644 utest/Object2.test.cpp create mode 100644 utest/X1Collector.test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2edb0ac..bacf4f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,19 @@ add_definitions(${PROJECT_CXX_FLAGS}) # ---------------------------------------------------------------- +# note: manual target; generated code committed to git +xo_add_genfacetimpl( + TARGET xo-gc-facetimpl-collector-x1collector + FACET_PKG xo_alloc2 + INPUT idl/ICollector_DX1Collector.json5 +) + +# ---------------------------------------------------------------- + +xo_add_genfacet_all(xo-gc-genfacet-all) + +# ---------------------------------------------------------------- + # must complete definition of expression lib before configuring examples add_subdirectory(src/gc) add_subdirectory(utest) diff --git a/cmake/xo_gcConfig.cmake.in b/cmake/xo_gcConfig.cmake.in index 548eb73..29f8748 100644 --- a/cmake/xo_gcConfig.cmake.in +++ b/cmake/xo_gcConfig.cmake.in @@ -1,6 +1,7 @@ @PACKAGE_INIT@ include(CMakeFindDependencyMacro) +find_dependency(xo_object2) find_dependency(xo_alloc2) find_dependency(xo_facet) find_dependency(subsys) diff --git a/idl/ICollector_DX1Collector.json5 b/idl/ICollector_DX1Collector.json5 new file mode 100644 index 0000000..9124288 --- /dev/null +++ b/idl/ICollector_DX1Collector.json5 @@ -0,0 +1,24 @@ +{ + mode: "implementation", + output_cpp_dir: "src/gc/facet", + output_hpp_dir: "include/xo/gc", + output_impl_subdir: "detail", + includes: [ +// "", +// "" + ], + local_types: [ + { + name: "typeseq", + doc: ["identifies a c++ type"], + definition: "xo::reflect::typeseq" + }, + ], + namespace1: "xo", + namespace2: "mm", + facet_idl: "idl/Collector.json5", + brief: "provide ACollector interface for DX1Collector", + using_doxygen: true, + repr: "DX1Collector", + doc: [ "implement ACollector for DX1Collector" ], +} diff --git a/include/xo/gc/DX1Collector.hpp b/include/xo/gc/DX1Collector.hpp index c72b802..d593461 100644 --- a/include/xo/gc/DX1Collector.hpp +++ b/include/xo/gc/DX1Collector.hpp @@ -153,15 +153,38 @@ namespace xo { // ----- basic statistics ----- /** total reserved memory in bytes, across all {role, generation} **/ - size_type reserved_total() const noexcept; + size_type reserved() 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; + size_type committed() const noexcept; /** total available memory in bytes, across all {role, generation} **/ - size_type available_total() const noexcept; + size_type available() const noexcept; /** total allocated memory in bytes, across all {role, generation} **/ - size_type allocated_total() const noexcept; + size_type allocated() const noexcept; + + /** total number of mutation log entries **/ + size_type mutation_log_entries() const noexcept; + + /** memory allocated for generation @p g in role @p r **/ + size_type allocated(Generation g, role r) const noexcept; + /** memory committed for generation @p g in role @p r **/ + size_type committed(Generation g, role r) const noexcept; + /** memory (virtual addresses) reserved for generation @p g in role @p r **/ + size_type reserved(Generation g, role r) const noexcept; + + // ----- full statistics ----- + + /** Report gc statistics as a dictionary. + * Providing for the same of making GC statistics visible to schematika programs + * + * @p mm allocate stats dictionary from this allocator. May be the same as this collector. + * @p error_mm Allocator for last-report error reporting when out-of-memory. + * @p p_output on exit @p *p_output contains stats dictionary + **/ + bool report_statistics(obj mm, + obj error_mm, + obj * p_output) const noexcept; // ----- queries ----- diff --git a/include/xo/gc/detail/ICollector_DX1Collector.hpp b/include/xo/gc/detail/ICollector_DX1Collector.hpp index fe7bb01..a9c8e01 100644 --- a/include/xo/gc/detail/ICollector_DX1Collector.hpp +++ b/include/xo/gc/detail/ICollector_DX1Collector.hpp @@ -1,63 +1,107 @@ /** @file ICollector_DX1Collector.hpp -* - * @author Roland Conybeare, Dec 2025 + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/ICollector_DX1Collector.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_repr.hpp.j2] + * 3. idl for facet methods + * [idl/ICollector_DX1Collector.json5] **/ #pragma once -#include -#include +#include "Collector.hpp" #include "DX1Collector.hpp" -namespace xo { - namespace mm { struct ICollector_DX1Collector; } +namespace xo { namespace mm { class ICollector_DX1Collector; } } +namespace xo { namespace facet { template <> struct FacetImplementation { - using ImplType = xo::mm::ICollector_Xfer; + using ImplType = xo::mm::ICollector_Xfer + ; }; } +} +namespace xo { namespace mm { - /* changes here coordinate with - * ACollector ACollector.hpp - * ICollector_Any ICollector_Any.hpp - * ICollector_Xfer ICollector_Xfer.hpp - * RCollector RCollector.hpp - */ - struct ICollector_DX1Collector { - using size_type = std::size_t; - using header_type = DArena::header_type; - using typeseq = xo::facet::typeseq; + /** @class ICollector_DX1Collector + **/ + class ICollector_DX1Collector { + public: + /** @defgroup mm-collector-dx1collector-type-traits **/ + ///@{ + using size_type = xo::mm::ACollector::size_type; + using Copaque = xo::mm::ACollector::Copaque; + using Opaque = xo::mm::ACollector::Opaque; + using typeseq = xo::reflect::typeseq; + ///@} + /** @defgroup mm-collector-dx1collector-methods **/ + ///@{ + // const methods + /** memory in use for this collector **/ + static size_type allocated(const DX1Collector & self, Generation g, role r) noexcept; + /** memory committed for this collector **/ + static size_type committed(const DX1Collector & self, Generation g, role r) noexcept; + /** address space reserved for this collector **/ + static size_type reserved(const DX1Collector & self, Generation g, role r) noexcept; + /** true if gc responsible for data at @p addr, and data belongs to role @p r **/ + static bool contains(const DX1Collector & self, role r, const void * addr) noexcept; + /** true iff gc-aware object of type @p tseq is installed in this collector **/ + static bool is_type_installed(const DX1Collector & self, typeseq tseq) noexcept; + /** Report gc statistics, at discretion of collector implementation. +Creates dictionary using memory from @p report_mm. +If unable to comply (e.g. oom), return runtime error allocated from @p error_mm. +Avoiding obj return type to avoid #include cycle **/ + static bool report_statistics(const DX1Collector & self, obj report_mm, obj error_mm, obj * output) noexcept; - static bool check_move_policy(const DX1Collector & d, - header_type alloc_hdr, - void * object_data); + // non-const methods + /** install interface @p iface for representation with typeseq @p tseq +in collector @p d. - // todo: available() +The type AGCObject_Any here is misleading. +Will have been replaced by an instance of + @c AGCObject_Xfer for some @c DFoo +in which case calls through @c std::launder(&iface) +will properly act on @c DFoo. - static size_type allocated(const DX1Collector & d, Generation g, role r); - static size_type reserved(const DX1Collector & d, Generation g, role r); - static size_type committed(const DX1Collector & d, Generation g, role r); - static bool is_type_installed(const DX1Collector & d, typeseq tseq); - static bool contains(const DX1Collector & d, role r, const void * addr); +Return false if installation fails (e.g. memory exhausted) **/ + static bool install_type(DX1Collector & self, const AGCObject & iface); + /** add gc root with address @p p_root. gc will preserve subgraph at this address **/ + static void add_gc_root_poly(DX1Collector & self, obj * p_root); + /** remove gc root with address @p p_root. Reverse effect of prior add_gc_root_poly call **/ + static void remove_gc_root_poly(DX1Collector & self, obj * p_root); + /** Request immediate collection. + 1. if collection is enabled, immediately collect all generations + up to (but not including) g + 2. may nevertheless escalate to older generations, + depending on collector state. + 3. if collection is currently disabled, + collection will trigger the next time gc is enabled. + **/ + static void request_gc(DX1Collector & self, Generation upto); + /** Assign pointer @p p_lhs to destination @p rhs, within parent allocation @p parent - static bool install_type(DX1Collector & d, const AGCObject & iface); - static void add_gc_root_poly(DX1Collector & d, obj * p_root); - static void remove_gc_root_poly(DX1Collector & d, obj * p_root); - static void request_gc(DX1Collector & d, Generation upto); - static void assign_member(DX1Collector & d, void * parent, - obj * p_lhs, obj & rhs); - static void forward_inplace(DX1Collector & d, AGCObject * lhs_iface, void ** lhs_data); +Require: gc not in progress **/ + static void assign_member(DX1Collector & self, void * parent, obj * p_lhs, obj & rhs); + /** 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 - static int32_t s_typeseq; - static bool _valid; +Require: gc in progress + **/ + static void forward_inplace(DX1Collector & self, AGCObject * lhs_iface, void ** lhs_data); + ///@} }; + } /*namespace mm*/ } /*namespace xo*/ -/* end ICollector_DX1_Collector.hpp */ +/* end */ \ No newline at end of file diff --git a/src/gc/CMakeLists.txt b/src/gc/CMakeLists.txt index eee6864..2dec873 100644 --- a/src/gc/CMakeLists.txt +++ b/src/gc/CMakeLists.txt @@ -7,10 +7,11 @@ set(SELF_SRCS SetupGc.cpp IAllocator_DX1Collector.cpp - ICollector_DX1Collector.cpp IAllocIterator_DX1CollectorIterator.cpp DX1Collector.cpp + facet/ICollector_DX1Collector.cpp + DX1CollectorIterator.cpp X1CollectorConfig.cpp @@ -20,6 +21,7 @@ set(SELF_SRCS xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # note: deps here must also appear in cmake/xo_alloc2Config.cmake.in +xo_dependency(${SELF_LIB} xo_object2) xo_dependency(${SELF_LIB} xo_alloc2) xo_dependency(${SELF_LIB} xo_facet) xo_dependency(${SELF_LIB} subsys) diff --git a/src/gc/DX1Collector.cpp b/src/gc/DX1Collector.cpp index bdb73ff..a668b8d 100644 --- a/src/gc/DX1Collector.cpp +++ b/src/gc/DX1Collector.cpp @@ -4,10 +4,12 @@ **/ #include "X1Collector.hpp" +#include +#include +#include #include #include #include -#include #include #include "object_age.hpp" #include @@ -18,6 +20,11 @@ #include // for ::getpagesize() namespace xo { + // for report_statistics() + using xo::scm::DDictionary; + using xo::scm::DString; + using xo::scm::DInteger; + using xo::mm::AAllocator; using xo::facet::TypeRegistry; using xo::facet::typeseq; @@ -165,6 +172,10 @@ namespace xo { this->space_[role::to_space()][igen] = nullptr; this->space_[role::from_space()][igen] = nullptr; } + + if (config_.n_generation_ == 2) { + assert(this->get_space(role::to_space(), Generation{2}) == nullptr); + } } void @@ -251,7 +262,7 @@ namespace xo { } size_type - DX1Collector::reserved_total() const noexcept + DX1Collector::reserved() const noexcept { return accumulate_total_aux(*this, &DArena::reserved); } @@ -259,27 +270,124 @@ namespace xo { size_type DX1Collector::size_total() const noexcept { - return committed_total(); + return this->committed(); } size_type - DX1Collector::committed_total() const noexcept + DX1Collector::committed() const noexcept { return accumulate_total_aux(*this, &DArena::committed); } size_type - DX1Collector::available_total() const noexcept + DX1Collector::available() const noexcept { return accumulate_total_aux(*this, &DArena::available); } size_type - DX1Collector::allocated_total() const noexcept + DX1Collector::allocated() const noexcept { return accumulate_total_aux(*this, &DArena::allocated); } + size_type + DX1Collector::mutation_log_entries() const noexcept + { + size_type z = 0; + + for (Generation gj{0}; gj + 1 < config_.n_generation_; ++gj) { + z += mlog_[role::to_space()][gj]->size(); + } + + return z; + } + + namespace { + size_type + stat_helper(const DX1Collector & d, + size_type (DArena::* getter)() const, + Generation g, + role r) + { + const DArena * arena = d.get_space(r, g); + + if (arena) [[likely]] + return (arena->*getter)(); + + return 0; + } + } + + size_type + DX1Collector::allocated(Generation g, role r) const noexcept + { + return stat_helper(*this, &DArena::allocated, g, r); + } + + size_type + DX1Collector::committed(Generation g, role r) const noexcept + { + return stat_helper(*this, &DArena::committed, g, r); + } + + size_type + DX1Collector::reserved(Generation g, role r) const noexcept + { + return stat_helper(*this, &DArena::reserved, g, r); + } + + // editor bait: report-gc-statistics + bool + DX1Collector::report_statistics(obj mm, + obj error_mm, + obj * p_output) const noexcept + { + (void)error_mm; + + DDictionary * rpt = DDictionary::make(mm); + + bool ok = true; + + // note: totals taken across both roles and generations, + // so counts both from-space and to-space + // + ok &= rpt->upsert_cstr(mm, "n-generation", DInteger::box(mm, config_.n_generation_)); + ok &= rpt->upsert_cstr(mm, "n-survive-threshold", DInteger::box(mm, config_.n_survive_threshold_)); + ok &= rpt->upsert_cstr(mm, "allocated", DInteger::box(mm, this->allocated())); + ok &= rpt->upsert_cstr(mm, "committed", DInteger::box(mm, this->committed())); + ok &= rpt->upsert_cstr(mm, "reserved", DInteger::box(mm, this->reserved())); + ok &= rpt->upsert_cstr(mm, "n-mlog-entry", DInteger::box(mm, this->mutation_log_entries())); + + // per-(generation,role) info + { + for (Generation gi{0}; gi < config_.n_generation_; ++gi) { + for (role rj : role::all()) { + const DArena * arena = this->get_space(rj, gi); + DDictionary * arena_d = DDictionary::make(mm); + + auto lo = reinterpret_cast(arena->lo_); + auto free = reinterpret_cast(arena->free_); + auto limit = reinterpret_cast(arena->limit_); + auto hi = reinterpret_cast(arena->hi_); + + ok &= arena_d->upsert_cstr(mm, "lo", DInteger::box(mm, lo)); + ok &= arena_d->upsert_cstr(mm, "d-free", DInteger::box(mm, free - lo)); + ok &= arena_d->upsert_cstr(mm, "d-limit", DInteger::box(mm, limit - lo)); + ok &= arena_d->upsert_cstr(mm, "d-hi", DInteger::box(mm, hi - lo)); + + const DString * key = DString::from_str(mm, arena->config_.name_); + + rpt->upsert(mm, std::make_pair(key, obj(arena_d))); + } + } + } + + *p_output = obj(rpt); + + return ok; + } + size_type DX1Collector::header2size(header_type hdr) const noexcept { diff --git a/src/gc/IAllocator_DX1Collector.cpp b/src/gc/IAllocator_DX1Collector.cpp index 039e84d..37ed497 100644 --- a/src/gc/IAllocator_DX1Collector.cpp +++ b/src/gc/IAllocator_DX1Collector.cpp @@ -29,7 +29,7 @@ namespace xo { auto IAllocator_DX1Collector::reserved(const DX1Collector & d) noexcept -> size_type { - return d.reserved_total(); + return d.reserved(); } auto @@ -41,19 +41,19 @@ namespace xo { auto IAllocator_DX1Collector::committed(const DX1Collector & d) noexcept -> size_type { - return d.committed_total(); + return d.committed(); } auto IAllocator_DX1Collector::available(const DX1Collector & d) noexcept -> size_type { - return d.available_total(); + return d.available(); } auto IAllocator_DX1Collector::allocated(const DX1Collector & d) noexcept -> size_type { - return d.allocated_total(); + return d.allocated(); } void diff --git a/src/gc/ICollector_DX1Collector.cpp b/src/gc/ICollector_DX1Collector.cpp deleted file mode 100644 index 5503d42..0000000 --- a/src/gc/ICollector_DX1Collector.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/** @file ICollector_DX1Collector.cpp - * - * @author Roland Conybeare, Dec 2025 - * - * See also IAllocator_DX1Collector.cpp for allocator facet - **/ - -#include "xo/gc/detail/ICollector_DX1Collector.hpp" -#include "GCObject.hpp" - -namespace xo { - namespace mm { - using size_type = ICollector_DX1Collector::size_type; - using std::byte; - - namespace { - size_type - stat_helper(const DX1Collector & d, - size_type (DArena::* getter)() const, - Generation g, - role r) - { - const DArena * arena = d.get_space(r, g); - - if (arena) [[likely]] - return (arena->*getter)(); - - return 0; - } - } - - size_type - ICollector_DX1Collector::reserved(const DX1Collector & d, Generation g, role r) - { - return stat_helper(d, &DArena::reserved, g, r); - } - - size_type - ICollector_DX1Collector::allocated(const DX1Collector & d, Generation g, role r) - { - return stat_helper(d, &DArena::allocated, g, r); - } - - size_type - ICollector_DX1Collector::committed(const DX1Collector & d, Generation g, role r) - { - return stat_helper(d, &DArena::committed, g, r); - } - - bool - ICollector_DX1Collector::contains(const DX1Collector & d, role r, const void * addr) - { - return d.contains(r, addr); - } - - bool - ICollector_DX1Collector::is_type_installed(const DX1Collector & d, typeseq tseq) - { - return d.is_type_installed(tseq); - } - - bool - ICollector_DX1Collector::install_type(DX1Collector & d, - const AGCObject & iface) - { - return d.install_type(iface); - } - - void - ICollector_DX1Collector::add_gc_root_poly(DX1Collector & d, - obj * p_root) - { - d.add_gc_root_poly(p_root); - } - - void - ICollector_DX1Collector::remove_gc_root_poly(DX1Collector & d, - obj * p_root) - { - d.remove_gc_root_poly(p_root); - } - - void - ICollector_DX1Collector::request_gc(DX1Collector & d, - Generation upto) - { - d.request_gc(upto); - } - - void - ICollector_DX1Collector::assign_member(DX1Collector & d, - void * parent, - obj * p_lhs, - obj & rhs) - { - d.assign_member(parent, p_lhs, rhs); - } - - void - ICollector_DX1Collector::forward_inplace(DX1Collector & d, - AGCObject * lhs_iface, - void ** lhs_data) - { - d.forward_inplace(lhs_iface, lhs_data); - } - - bool - ICollector_DX1Collector::check_move_policy(const DX1Collector & d, - header_type alloc_hdr, - void * object_data) - { - return d.check_move_policy(alloc_hdr, object_data); - } - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end ICollector_DX1Collector.cpp */ diff --git a/src/gc/facet/ICollector_DX1Collector.cpp b/src/gc/facet/ICollector_DX1Collector.cpp new file mode 100644 index 0000000..10b4add --- /dev/null +++ b/src/gc/facet/ICollector_DX1Collector.cpp @@ -0,0 +1,88 @@ +/** @file ICollector_DX1Collector.cpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/ICollector_DX1Collector.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/ICollector_DX1Collector.json5] +**/ + +#include "detail/ICollector_DX1Collector.hpp" + +namespace xo { + namespace mm { + auto + ICollector_DX1Collector::allocated(const DX1Collector & self, Generation g, role r) noexcept -> size_type + { + return self.allocated(g, r); + } + + auto + ICollector_DX1Collector::committed(const DX1Collector & self, Generation g, role r) noexcept -> size_type + { + return self.committed(g, r); + } + + auto + ICollector_DX1Collector::reserved(const DX1Collector & self, Generation g, role r) noexcept -> size_type + { + return self.reserved(g, r); + } + + auto + ICollector_DX1Collector::contains(const DX1Collector & self, role r, const void * addr) noexcept -> bool + { + return self.contains(r, addr); + } + + auto + ICollector_DX1Collector::is_type_installed(const DX1Collector & self, typeseq tseq) noexcept -> bool + { + return self.is_type_installed(tseq); + } + + auto + ICollector_DX1Collector::report_statistics(const DX1Collector & self, obj report_mm, obj error_mm, obj * output) noexcept -> bool + { + return self.report_statistics(report_mm, error_mm, output); + } + + auto + ICollector_DX1Collector::install_type(DX1Collector & self, const AGCObject & iface) -> bool + { + return self.install_type(iface); + } + auto + ICollector_DX1Collector::add_gc_root_poly(DX1Collector & self, obj * p_root) -> void + { + self.add_gc_root_poly(p_root); + } + auto + ICollector_DX1Collector::remove_gc_root_poly(DX1Collector & self, obj * p_root) -> void + { + self.remove_gc_root_poly(p_root); + } + auto + ICollector_DX1Collector::request_gc(DX1Collector & self, Generation upto) -> void + { + self.request_gc(upto); + } + auto + ICollector_DX1Collector::assign_member(DX1Collector & self, void * parent, obj * p_lhs, obj & rhs) -> void + { + self.assign_member(parent, p_lhs, rhs); + } + 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*/ + +/* end ICollector_DX1Collector.cpp */ diff --git a/src/gc/init_gc.cpp b/src/gc/init_gc.cpp index 8747229..40dd700 100644 --- a/src/gc/init_gc.cpp +++ b/src/gc/init_gc.cpp @@ -5,6 +5,7 @@ #include "init_gc.hpp" #include "SetupGc.hpp" +#include #include namespace xo { @@ -21,6 +22,7 @@ namespace xo { InitEvidence retval; /* recursive subsystem deps for xo-gc/ */ + retval ^= InitSubsys::require(); retval ^= InitSubsys::require(); /* xo-gc/'s own initialization code */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index c48dbdb..0f3c967 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -5,7 +5,9 @@ set(UTEST_EXE utest.gc) set(UTEST_SRCS gc_utest_main.cpp Collector.test.cpp + X1Collector.test.cpp DX1CollectorIterator.test.cpp + Object2.test.cpp random_allocs.cpp ) diff --git a/utest/Object2.test.cpp b/utest/Object2.test.cpp new file mode 100644 index 0000000..fa0baec --- /dev/null +++ b/utest/Object2.test.cpp @@ -0,0 +1,134 @@ +/** @file Object2.test.cpp + * + * Tests that target xo-object2/ features, but also rely on xo-gc/ + * + * @author Roland Conybeare, Mar 2026 + **/ + +#include +#include +#include +#include +#include +#include + +namespace ut { + using xo::scm::SetupObject2; + using xo::scm::ListOps; + using xo::scm::DList; + using xo::scm::DInteger; + using xo::scm::DString; + using xo::mm::AAllocator; + using xo::mm::ACollector; + using xo::mm::AGCObject; + using xo::mm::X1CollectorConfig; + using xo::mm::DX1Collector; + using xo::mm::ArenaConfig; + using xo::print::APrintable; + using xo::facet::FacetRegistry; + using xo::facet::with_facet; + using xo::facet::obj; + using xo::facet::typeseq; + using xo::print::ppstate_standalone; + using xo::print::ppconfig; + using xo::scope; + using xo::xtag; + using std::string; + + namespace { + struct testcase_pp { + explicit testcase_pp(size_t gc_z, size_t gc_threshold, int z, const std::string & expected) + : gc_gen_size_{gc_z}, gc_trigger_threshold_{gc_threshold}, expected_{expected} { + for (int i = 0; i < z; ++i) { + list_.push_back(1000 + 197 * i); + } + } + + size_t gc_gen_size_ = 0; + size_t gc_trigger_threshold_ = 0; + std::vector list_; + std::string expected_; + }; + + std::vector + s_testcase_v = { + testcase_pp(16384, 8192, 0, "()"), + testcase_pp(16384, 8192, 1, "(01000)"), + testcase_pp(16384, 8192, 2, "(01000 1197)"), + testcase_pp(16384, 8192, 5, "(01000 1197 01394 1591 01788)"), + testcase_pp(16384, 8192, 10, "(01000 1197 01394 1591 01788 1985 02182 2379 02576 2773)"), + testcase_pp(16384, 8192, 20, "(...)"), + }; + } + + TEST_CASE("printable1", "[pp][x1][list]") + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + bool ok = SetupObject2::register_facets(); + REQUIRE(ok); + + FacetRegistry::instance().dump(&std::cerr); + + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + log && log("printable1 test:", xtag("i_tc", i_tc)); + + try { + const testcase_pp & tc = s_testcase_v[i_tc]; + + X1CollectorConfig cfg{ .name_ = "pp_test", + .arena_config_ = ArenaConfig{ + .size_ = tc.gc_gen_size_, + .store_header_flag_ = true}, + .object_types_z_ = 16384, + .gc_trigger_v_{{tc.gc_trigger_threshold_, + tc.gc_trigger_threshold_}}, + .debug_flag_ = c_debug_flag + }; + + DX1Collector gc(cfg); + auto gc_o = with_facet::mkobj(&gc); + auto c_o = with_facet::mkobj(&gc); + + bool ok = SetupObject2::register_types(c_o); + REQUIRE(ok); + + auto l0_o = ListOps::nil(); + + c_o.add_gc_root(&l0_o); + + for(int ip1 = tc.list_.size(); ip1 > 0; --ip1) { + obj elt; + + //elt = DInteger::box(gc_o, tc.list_[ip1 - 1]); + + if (ip1 % 2 == 0) { + elt = DInteger::box(gc_o, tc.list_[ip1 - 1]); + } else { + elt = obj(DString::printf(gc_o, 80, "%05d", tc.list_[ip1 - 1])); + } + + l0_o = ListOps::cons(gc_o, elt, l0_o); + } + + // TODO: log_streambuf using DArena + std::stringstream ss; + ppconfig ppc; + ppstate_standalone pps(&ss, 0, &ppc); + + obj l0_po(static_cast(l0_o.data())); + REQUIRE(l0_po._typeseq() == typeseq::id()); + + pps.pretty(l0_po); + + CHECK(ss.str() == string(tc.expected_)); + } catch (std::exception & ex) { + std::cerr << "caught exception: " << ex.what() << std::endl; + REQUIRE(false); + } + } + } /* TEST_CASE(printable1) */ +} /*namespace ut*/ + +/* end Object2.test.cpp */ diff --git a/utest/X1Collector.test.cpp b/utest/X1Collector.test.cpp new file mode 100644 index 0000000..67d477e --- /dev/null +++ b/utest/X1Collector.test.cpp @@ -0,0 +1,297 @@ +/** @file X1Collector.test.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "init_gc.hpp" +#include "ListOps.hpp" +#include "DFloat.hpp" +#include "DInteger.hpp" +#include "DList.hpp" +#include "DArray.hpp" + +#include +//#include "number/IGCObject_DFloat.hpp" +#include "number/IGCObject_DInteger.hpp" +#include "list/IGCObject_DList.hpp" + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +namespace ut { + using xo::S_gc_tag; + using xo::scm::ListOps; + using xo::scm::DList; + using xo::scm::DArray; + using xo::scm::DFloat; + using xo::scm::DInteger; + using xo::mm::CollectorTypeRegistry; + using xo::mm::AAllocator; + using xo::mm::ACollector; + using xo::mm::AllocHeader; + using xo::mm::AllocInfo; + using xo::mm::AGCObject; + using xo::mm::X1CollectorConfig; + using xo::mm::DX1Collector; + using xo::mm::DArena; + using xo::mm::ArenaConfig; + using xo::mm::Generation; + using xo::mm::role; + using xo::mm::padding; + using xo::facet::obj; + using xo::facet::with_facet; + using xo::facet::typeseq; + using xo::Subsystem; + using xo::InitEvidence; + using xo::InitSubsys; + using xo::scope; + using xo::xtag; + + namespace { + struct testcase_x1 { + testcase_x1(std::size_t nz, + std::size_t tz, + std::size_t n_gct, + std::size_t t_gct) + : nursery_z_{nz}, + tenured_z_{tz}, + incr_gc_threshold_{n_gct}, + full_gc_threshold_{t_gct} {} + + std::size_t nursery_z_; + std::size_t tenured_z_; + std::size_t incr_gc_threshold_; + std::size_t full_gc_threshold_; + }; + + std::vector + s_testcase_v = { + // n_gct: nursery gc threshold + // t_gct: tenured gc threshold + // + // nz tz n_gct t_gct + testcase_x1(4096, 8192, 1024, 1024) + }; + } + + static InitEvidence s_init = (InitSubsys::require()); + + TEST_CASE("x1", "[gc][x1]") + { + // real purpose: ensure s_init survives static linking + REQUIRE(s_init.evidence()); + + Subsystem::initialize_all(); + + /** + * This is a basic Collector test for xo-object2 data types + **/ + + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + scope log(XO_DEBUG(true), xtag("i_tc", i_tc)); + + try { + const testcase_x1 & tc = s_testcase_v[i_tc]; + + X1CollectorConfig cfg{ .name_ = "x1_test", + .arena_config_ = ArenaConfig{ + .size_ = tc.tenured_z_, + .store_header_flag_ = true}, + .object_types_z_ = 16384, + .gc_trigger_v_{{ + tc.incr_gc_threshold_, + tc.full_gc_threshold_}}, + .debug_flag_ = c_debug_flag, + }; + + DX1Collector gc(cfg); + + CollectorTypeRegistry::instance().install_types(obj(&gc)); + + DArena * to_0 = nullptr; + + /* verify configuration */ + { + REQUIRE(cfg.n_generation_ == 2); + } + + /* verify initial collector state */ + { + REQUIRE(gc.name() == "x1_test"); + + const DArena * otypes = gc.get_object_types(); + + REQUIRE(otypes != nullptr); + REQUIRE(otypes->reserved() >= cfg.object_types_z_); + REQUIRE(otypes->reserved() < cfg.object_types_z_ + otypes->page_z_); + + const DX1Collector::RootSet * roots = gc.get_root_set(); + + REQUIRE(roots != nullptr); + REQUIRE(roots->store()->reserved() >= cfg.object_roots_z_); + REQUIRE(roots->store()->reserved() < cfg.object_roots_z_ + roots->store()->page_z_); + + const DArena * from_0 = gc.get_space(role::from_space(), Generation{0}); + + REQUIRE(from_0 != nullptr); + REQUIRE(from_0->reserved() >= tc.tenured_z_); + REQUIRE(from_0->reserved() < tc.tenured_z_ + from_0->page_z_); + REQUIRE(from_0->reserved() % from_0->page_z_ == 0); + REQUIRE(from_0->allocated() == 0); + + const DArena * from_1 = gc.get_space(role::from_space(), Generation{1}); + + REQUIRE(from_1 != nullptr); + REQUIRE(from_1->reserved() == from_0->reserved()); + REQUIRE(from_1->allocated() == 0); + + to_0 = gc.get_space(role::to_space(), Generation{0}); + + REQUIRE(to_0 != nullptr); + REQUIRE(to_0->reserved() == from_0->reserved()); + REQUIRE(to_0->allocated() == 0); + + const DArena * to_1 = gc.get_space(role::to_space(), Generation{1}); + + REQUIRE(to_1 != nullptr); + REQUIRE(to_1->reserved() == to_0->reserved()); + REQUIRE(to_1->allocated() == 0); + + const DArena * from_2 = gc.get_space(role::from_space(), Generation{2}); + + REQUIRE(from_2 == nullptr); + + const DArena * to_2 = gc.get_space(role::to_space(), Generation{2}); + + REQUIRE(to_2 == nullptr); + + REQUIRE(gc.reserved() + == otypes->reserved() + roots->store()->reserved() + 4 * from_0->reserved()); + + log && log(xtag("from_0", from_0->lo_), xtag("to_0", to_0->lo_)); + } + + /* attempt allocation */ + auto gc_o = with_facet::mkobj(&gc); + auto c_o = with_facet::mkobj(&gc); + + /* register object types */ + bool ok = CollectorTypeRegistry::instance().install_types(c_o); + + REQUIRE(ok); + + ok = c_o.is_type_installed(typeseq::id()); + REQUIRE(ok); + ok = c_o.is_type_installed(typeseq::id()); + REQUIRE(ok); + ok = c_o.is_type_installed(typeseq::id()); + REQUIRE(ok); + ok = c_o.is_type_installed(typeseq::id()); + REQUIRE(ok); + + auto x0_o = DFloat::box(gc_o, 3.1415927); + c_o.add_gc_root(&x0_o); + REQUIRE(to_0->allocated() == sizeof(AllocHeader) + sizeof(DFloat)); + + auto n1_o = DInteger::box(gc_o, 42); + c_o.add_gc_root(&n1_o); + REQUIRE(to_0->allocated() == (sizeof(AllocHeader) + sizeof(DFloat) + + sizeof(AllocHeader) + sizeof(DInteger))); + + //DList * l0 = DList::list(gc_o, x0_o); + //auto l0_o = with_facet::mkobj(l0); + auto l0_o = ListOps::list(gc_o, x0_o); + c_o.add_gc_root(&l0_o); + REQUIRE(to_0->allocated() == (sizeof(AllocHeader) + sizeof(DFloat) + + sizeof(AllocHeader) + sizeof(DInteger) + + sizeof(AllocHeader) + sizeof(DList))); + + { + { + REQUIRE(x0_o.iface() != nullptr); + REQUIRE(x0_o.data() != nullptr); + REQUIRE(gc.contains(role::to_space(), x0_o.data())); + + /* check alloc info for newly-allocated object */ + AllocInfo info = gc.alloc_info((std::byte *)x0_o.data()); + + REQUIRE(info.age() == 0); + REQUIRE(info.tseq() == typeseq::id().seqno()); + REQUIRE(info.size() >= sizeof(DFloat)); + REQUIRE(info.size() < sizeof(DFloat) + padding::c_alloc_alignment); + } + + { + REQUIRE(n1_o.iface() != nullptr); + REQUIRE(n1_o.data() != nullptr); + REQUIRE(gc.contains(role::to_space(), n1_o.data())); + + /* check alloc info for newly-allocated object */ + AllocInfo info = gc.alloc_info((std::byte *)n1_o.data()); + + REQUIRE(info.age() == 0); + REQUIRE(info.tseq() == typeseq::id().seqno()); + REQUIRE(info.size() >= sizeof(DInteger)); + REQUIRE(info.size() < sizeof(DInteger) + padding::c_alloc_alignment); + } + + { + REQUIRE(l0_o.iface() != nullptr); + REQUIRE(l0_o.data() != nullptr); + REQUIRE(gc.contains(role::to_space(), l0_o.data())); + + /* check alloc info for newly-allocated object */ + AllocInfo info = gc.alloc_info((std::byte *)l0_o.data()); + + REQUIRE(info.age() == 0); + REQUIRE(info.tseq() == typeseq::id().seqno()); + REQUIRE(info.size() >= sizeof(DList)); + REQUIRE(info.size() < sizeof(DList) + padding::c_alloc_alignment); + } + } + + /* no GC roots, so GC is trivial */ + c_o.request_gc(Generation{1}); + + log && log(xtag("l0_o.data()", l0_o.data())); + log && log(xtag("l0_o.data()->head_.data()", l0_o.data()->head_.data())); + log && log(xtag("x0_o.data()", x0_o.data())); + + REQUIRE(!gc.contains(role::from_space(), x0_o.data())); + REQUIRE(gc.contains(role::to_space(), x0_o.data())); + REQUIRE(x0_o.data()->value() == 3.1415927); + + REQUIRE(!gc.contains(role::from_space(), n1_o.data())); + REQUIRE(gc.contains(role::to_space(), n1_o.data())); + REQUIRE(n1_o.data()->value() == 42); + + REQUIRE(!gc.contains(role::from_space(), l0_o.data())); + REQUIRE(gc.contains(role::to_space(), l0_o.data())); + REQUIRE(l0_o.data()->is_empty() == false); + + REQUIRE((void*)l0_o.data()->head_.data() == (void*)x0_o.data()); + REQUIRE((void*)l0_o.data()->rest_ == (void*)DList::_nil()); + + } catch (std::exception & ex) { + std::cerr << "caught exception: " << ex.what() << std::endl; + REQUIRE(false); + } + } + } +} + +/* end X1Collector.test.cpp */