xo-gc stack: + gc-report-object-types() primitive

This commit is contained in:
Roland Conybeare 2026-03-29 17:19:23 -04:00
commit 84e1f4e4d3
4 changed files with 150 additions and 3 deletions

View file

@ -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<AAllocator> error_mm,
obj<AGCObject> * 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<AAllocator> mm,
obj<AAllocator> error_mm,
obj<AGCObject> * p_output) const noexcept;
// ----- queries -----
/** introspection for memory use.

View file

@ -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<AGCObject> return type to avoid #include cycle **/
static bool report_statistics(const DX1Collector & self, obj<AAllocator> report_mm, obj<AAllocator> error_mm, obj<AGCObject> * 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<AGCObject> return type to avoid #include cycle **/
static bool report_object_types(const DX1Collector & self, obj<AAllocator> report_mm, obj<AAllocator> error_mm, obj<AGCObject> * output) noexcept;
// non-const methods
/** install interface @p iface for representation with typeseq @p tseq

View file

@ -6,7 +6,10 @@
#include "X1Collector.hpp"
#include <xo/gc/DX1CollectorIterator.hpp>
#include <xo/object2/Dictionary.hpp>
#include <xo/object2/Array.hpp>
#include <xo/object2/Integer.hpp>
#include <xo/object2/Boolean.hpp>
#include <xo/stringtable2/String.hpp>
#include <xo/alloc2/GCObject.hpp>
#include <xo/alloc2/Allocator.hpp>
#include <xo/alloc2/Arena.hpp>
@ -20,10 +23,12 @@
#include <unistd.h> // 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<AAllocator> mm,
obj<AAllocator> error_mm,
obj<AGCObject> * 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<AGCObject,DString>(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<AGCObject,DDictionary>(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<AGCObject,DDictionary>::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<AGCObject,DInteger>::from(n_live_opt.value());
auto bytes_gco = obj<AGCObject,DInteger>::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<AGCObject,DArray>(final_stats_v);
return ok;
}
size_type
DX1Collector::header2size(header_type hdr) const noexcept
{

View file

@ -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<AAllocator> report_mm, obj<AAllocator> error_mm, obj<AGCObject> * output) noexcept -> bool
{
return self.report_object_types(report_mm, error_mm, output);
}
auto
ICollector_DX1Collector::install_type(DX1Collector & self, const AGCObject & iface) -> bool
{