From 84e1f4e4d30263f53069183861fffb4128b31bf7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 29 Mar 2026 17:19:23 -0400 Subject: [PATCH] xo-gc stack: + gc-report-object-types() primitive --- include/xo/gc/DX1Collector.hpp | 21 ++- .../xo/gc/detail/ICollector_DX1Collector.hpp | 5 + src/gc/DX1Collector.cpp | 121 +++++++++++++++++- src/gc/facet/ICollector_DX1Collector.cpp | 6 + 4 files changed, 150 insertions(+), 3 deletions(-) diff --git a/include/xo/gc/DX1Collector.hpp b/include/xo/gc/DX1Collector.hpp index 9b6366c..c1e1669 100644 --- a/include/xo/gc/DX1Collector.hpp +++ b/include/xo/gc/DX1Collector.hpp @@ -96,17 +96,23 @@ namespace xo { /** true iff this slot is empty **/ bool is_null() const noexcept { - return this->iface()->_has_null_vptr(); + return this->_iface()->_has_null_vptr(); } bool is_occupied() const noexcept { return !this->is_null(); } - AGCObject * iface() const noexcept { + AGCObject * _iface() const noexcept { return std::launder((AGCObject *)&iface_[0]); } + AGCObject * iface() const noexcept { + AGCObject * x = this->_iface(); + + return (x->_has_null_vptr() ? nullptr : x); + } + /** Store interface pointer @p iface. * We just want the vtable here **/ @@ -230,6 +236,17 @@ namespace xo { obj error_mm, obj * p_output) const noexcept; + /** Report per-object-type information as a dictionary. + * Scans to-space to count per-object-type information + * + * @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_object_types(obj mm, + obj error_mm, + obj * p_output) const noexcept; + // ----- queries ----- /** introspection for memory use. diff --git a/include/xo/gc/detail/ICollector_DX1Collector.hpp b/include/xo/gc/detail/ICollector_DX1Collector.hpp index a9c8e01..f7db4b9 100644 --- a/include/xo/gc/detail/ICollector_DX1Collector.hpp +++ b/include/xo/gc/detail/ICollector_DX1Collector.hpp @@ -62,6 +62,11 @@ 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; + /** Report gc object types, 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_object_types(const DX1Collector & self, obj report_mm, obj error_mm, obj * output) noexcept; // non-const methods /** install interface @p iface for representation with typeseq @p tseq diff --git a/src/gc/DX1Collector.cpp b/src/gc/DX1Collector.cpp index 88ffef9..d9f09b8 100644 --- a/src/gc/DX1Collector.cpp +++ b/src/gc/DX1Collector.cpp @@ -6,7 +6,10 @@ #include "X1Collector.hpp" #include #include +#include #include +#include +#include #include #include #include @@ -20,10 +23,12 @@ #include // for ::getpagesize() namespace xo { - // for report_statistics() + // for report_statistics(), report_object_types() using xo::scm::DDictionary; + using xo::scm::DArray; using xo::scm::DString; using xo::scm::DInteger; + using xo::scm::DBoolean; using xo::mm::AAllocator; using xo::facet::TypeRegistry; @@ -347,6 +352,9 @@ namespace xo { DDictionary * rpt = DDictionary::make(mm); + if (!rpt) + return false; + bool ok = true; // note: totals taken across both roles and generations, @@ -388,6 +396,117 @@ namespace xo { return ok; } + bool + DX1Collector::report_object_types(obj mm, + obj error_mm, + obj * p_output) const noexcept + { + scope log(XO_DEBUG(true)); + + (void)error_mm; + + bool ok = true; + + // stats, indexed by tseq + // could use c++ vector in scratch space instead of running on + // boxed types. + // + DArray * stats_v = DArray::empty(mm, object_types_.size()); + + if (!stats_v) + return false; + + stats_v->resize(stats_v->capacity()); + + log && log(xtag("object_types_.size", object_types_.size()), + xtag("stats_v.capacity", stats_v->capacity()), + xtag("stats_v.size", stats_v->size())); + + // count #of occupied type slots + std::uint32_t n_tseq_present = 0; + // largest tseq present with non-null AGCObject* iface + std::int32_t max_tseq = 0; + + for (const ObjectTypeSlot & slot : object_types_) { + AGCObject * iface = slot.iface(); + + if (iface) { + typeseq tseq = iface->_typeseq(); + + ++n_tseq_present; + if (max_tseq < tseq.seqno()) + max_tseq = tseq.seqno(); + + assert(tseq.seqno() >= 0); + + auto tname_sv = TypeRegistry::id2name(tseq); + DString * tname = DString::from_view(mm, tname_sv); + + DDictionary * recd = DDictionary::make(mm); + + if (!recd) + return false; + + recd->upsert_cstr(mm, "name", obj(tname)); + recd->upsert_cstr(mm, "tseq", DInteger::box(mm, tseq.seqno())); + recd->upsert_cstr(mm, "n-live", DInteger::box(mm, 0)); + recd->upsert_cstr(mm, "bytes", DInteger::box(mm, 0)); + + stats_v->assign_at(tseq.seqno(), obj(recd)); + } + } + + // scan to-space, count objects by type + + for (Generation g{0}; g < config_.n_generation_; ++g) { + const DArena * arena = this->get_space(role::to_space(), g); + + for (AllocInfo info : *arena) { + if (info.is_forwarding_tseq()) { + assert(false); + return false; + } + + uint32_t ix = info.tseq(); + size_t z = info.size(); + + auto recd = obj::from(stats_v->at(ix)); + + assert(recd); + + auto n_live_opt = recd->lookup_cstr("n-live"); + assert(n_live_opt); + auto bytes_opt = recd->lookup_cstr("bytes"); + assert(bytes_opt); + + if (n_live_opt && bytes_opt) { + auto n_live_gco = obj::from(n_live_opt.value()); + auto bytes_gco = obj::from(bytes_opt.value()); + + n_live_gco->assign_value(n_live_gco->value() + 1); + bytes_gco->assign_value(bytes_gco->value() + z); + } + } + } + + stats_v->resize(max_tseq + 1); + + DArray * final_stats_v = DArray::empty(mm, n_tseq_present); + + for (std::size_t i = 0, n = stats_v->size(); i < n; ++i) { + auto recd = stats_v->at(i); + + if (recd) { + bool ok = final_stats_v->push_back(recd); + assert(ok); + } + } + + *p_output = obj(final_stats_v); + + return ok; + } + size_type DX1Collector::header2size(header_type hdr) const noexcept { diff --git a/src/gc/facet/ICollector_DX1Collector.cpp b/src/gc/facet/ICollector_DX1Collector.cpp index 10b4add..2e3f55f 100644 --- a/src/gc/facet/ICollector_DX1Collector.cpp +++ b/src/gc/facet/ICollector_DX1Collector.cpp @@ -51,6 +51,12 @@ namespace xo { return self.report_statistics(report_mm, error_mm, output); } + auto + ICollector_DX1Collector::report_object_types(const DX1Collector & self, obj report_mm, obj error_mm, obj * output) noexcept -> bool + { + return self.report_object_types(report_mm, error_mm, output); + } + auto ICollector_DX1Collector::install_type(DX1Collector & self, const AGCObject & iface) -> bool {