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/
This commit is contained in:
Roland Conybeare 2026-03-29 13:44:19 -04:00
commit f7c269a505
13 changed files with 74 additions and 11 deletions

View file

@ -61,6 +61,15 @@
noexcept: true,
attributes: [],
},
{
name: "error_allocator",
doc: [ "last-resort allocator for erros. e.g. regular allocator exhausted" ],
return_type: "obj<AAllocator>",
args: [],
const: true,
noexcept: true,
attributes: [],
},
{
name: "stringtable",
doc: [ "stringtable for unique symbols" ],

View file

@ -23,16 +23,19 @@ namespace xo {
using MemorySizeVisitor = xo::mm::MemorySizeVisitor;
public:
DSimpleRcx(obj<AAllocator> mm, StringTable * st)
: allocator_{mm}, stringtable_{st} {}
DSimpleRcx(obj<AAllocator> mm, obj<AAllocator> error_mm, StringTable * st)
: allocator_{mm}, error_allocator_{error_mm},
stringtable_{st} {}
obj<AAllocator> allocator() const noexcept { return allocator_; }
obj<ACollector> collector() const noexcept;
obj<AAllocator> error_allocator() const noexcept { return error_allocator_; }
StringTable * stringtable() const noexcept { return stringtable_; }
void visit_pools(const MemorySizeVisitor & visitor) const;
private:
obj<AAllocator> allocator_;
obj<AAllocator> error_allocator_;
StringTable * stringtable_ = nullptr;
};

View file

@ -5,6 +5,7 @@
#pragma once
#include "Primitive_gco_0.hpp"
#include "Primitive_gco_1_gco.hpp"
namespace xo {
@ -18,6 +19,10 @@ namespace xo {
using AAllocator = xo::mm::AAllocator;
public:
/** create primitive: report gc statistics **/
static DPrimitive_gco_0 * make_report_gc_statistics_pm(obj<AAllocator> mm,
StringTable * stbl);
/** create primitive: request collection **/
static DPrimitive_gco_1_gco * make_request_gc_pm(obj<AAllocator> mm,
StringTable * stbl);

View file

@ -47,6 +47,11 @@ public:
/** @defgroup scm-procedure-methods **/
///@{
// const methods
/** An uninitialized AProcedure instance will have zero vtable pointer (per {linux,osx} abi).
* Use case for this is narrow. We go to some lengths to avoid null vtable pointers. For example
* obj<AFacet> will have non-null vtable (via IFacet_Any) with all methods terminating.
**/
bool _has_null_vptr() const noexcept { return *reinterpret_cast<const void * const *>(this) == nullptr; }
/** RTTI: unique id# for actual runtime data representation **/
virtual typeseq _typeseq() const noexcept = 0;
/** destroy instance @p d; calls c++ dtor only for actual runtime type; does not recover memory **/

View file

@ -51,6 +51,11 @@ public:
/** @defgroup scm-runtimecontext-methods **/
///@{
// const methods
/** An uninitialized ARuntimeContext instance will have zero vtable pointer (per {linux,osx} abi).
* Use case for this is narrow. We go to some lengths to avoid null vtable pointers. For example
* obj<AFacet> will have non-null vtable (via IFacet_Any) with all methods terminating.
**/
bool _has_null_vptr() const noexcept { return *reinterpret_cast<const void * const *>(this) == nullptr; }
/** RTTI: unique id# for actual runtime data representation **/
virtual typeseq _typeseq() const noexcept = 0;
/** destroy instance @p d; calls c++ dtor only for actual runtime type; does not recover memory **/
@ -59,6 +64,8 @@ public:
virtual obj<AAllocator> allocator(Copaque data) const noexcept = 0;
/** collector facet for allocator. If non-null, same data pointer as allocator **/
virtual obj<ACollector> collector(Copaque data) const noexcept = 0;
/** last-resort allocator for erros. e.g. regular allocator exhausted **/
virtual obj<AAllocator> error_allocator(Copaque data) const noexcept = 0;
/** stringtable for unique symbols **/
virtual StringTable * stringtable(Copaque data) const noexcept = 0;
/** invoke visitor for each distinct memory pool **/

View file

@ -63,6 +63,7 @@ namespace scm {
// const methods
[[noreturn]] obj<AAllocator> allocator(Copaque) const noexcept override { _fatal(); }
[[noreturn]] obj<ACollector> collector(Copaque) const noexcept override { _fatal(); }
[[noreturn]] obj<AAllocator> error_allocator(Copaque) const noexcept override { _fatal(); }
[[noreturn]] StringTable * stringtable(Copaque) const noexcept override { _fatal(); }
[[noreturn]] void visit_pools(Copaque, MemorySizeVisitor) const override { _fatal(); }

View file

@ -52,6 +52,8 @@ namespace xo {
static obj<AAllocator> allocator(const DSimpleRcx & self) noexcept;
/** collector facet for allocator. If non-null, same data pointer as allocator **/
static obj<ACollector> collector(const DSimpleRcx & self) noexcept;
/** last-resort allocator for erros. e.g. regular allocator exhausted **/
static obj<AAllocator> error_allocator(const DSimpleRcx & self) noexcept;
/** stringtable for unique symbols **/
static StringTable * stringtable(const DSimpleRcx & self) noexcept;
/** invoke visitor for each distinct memory pool **/

View file

@ -54,6 +54,9 @@ namespace scm {
obj<ACollector> collector(Copaque data) const noexcept override {
return I::collector(_dcast(data));
}
obj<AAllocator> error_allocator(Copaque data) const noexcept override {
return I::error_allocator(_dcast(data));
}
StringTable * stringtable(Copaque data) const noexcept override {
return I::stringtable(_dcast(data));
}

View file

@ -61,6 +61,9 @@ public:
obj<ACollector> collector() const noexcept {
return O::iface()->collector(O::data());
}
obj<AAllocator> error_allocator() const noexcept {
return O::iface()->error_allocator(O::data());
}
StringTable * stringtable() const noexcept {
return O::iface()->stringtable(O::data());
}

View file

@ -21,23 +21,35 @@ namespace xo {
// ----- report-gc-status -----
#ifdef NOT_YET
obj<AGCObject>
xfer_report_gc_status(obj<ARuntimeContext> rcx)
xfer_report_gc_statistics(obj<ARuntimeContext> rcx)
{
bool have_gc = false;
if (rcx.collector()) {
// status currently only implemented for X1 collector
auto gc = obj<ACollector,DX1Collector>::from(rcx.collector());
obj<AGCObject> stats;
bool ok = rcx.collector().report_statistics(rcx.allocator(),
rcx.error_allocator(),
&stats);
if (ok && stats)
return stats;
}
return DBoolean::box(rcx.allocator(), false);
}
#endif
DPrimitive_gco_0 *
GcPrimitives::make_report_gc_statistics_pm(obj<AAllocator> mm,
StringTable * stbl)
{
(void)stbl;
auto any_ty = DAtomicType::make(mm, Metatype::t_any());
auto pm_ty = obj<AType,DFunctionType>(DFunctionType::_make(mm, any_ty));
return DPrimitive_gco_0::_make(mm, "report-gc-statistics", pm_ty, &xfer_report_gc_statistics);
}
// ----- request-gc -----

View file

@ -130,6 +130,13 @@ namespace xo {
ObjectPrimitives::make_fn_n_args_pm(mm, stbl),
flags & InstallFlags::f_generalpurpose));
// ----- gc primitives -----
ok = ok & (PrimitiveRegistry::install_aux
(sink,
GcPrimitives::make_report_gc_statistics_pm(mm, stbl),
flags & InstallFlags::f_generalpurpose));
ok = ok & (PrimitiveRegistry::install_aux
(sink,
GcPrimitives::make_request_gc_pm(mm, stbl),

View file

@ -27,6 +27,12 @@ namespace xo {
return self.collector();
}
auto
IRuntimeContext_DSimpleRcx::error_allocator(const DSimpleRcx & self) noexcept -> obj<AAllocator>
{
return self.error_allocator();
}
auto
IRuntimeContext_DSimpleRcx::stringtable(const DSimpleRcx & self) noexcept -> StringTable *
{

View file

@ -39,7 +39,7 @@ namespace xo {
auto stbl = StringTable(1024 /*hint_max_capacity*/,
false /*!debug_flag*/);
DSimpleRcx rcx(alloc, &stbl);
DSimpleRcx rcx(alloc, alloc, &stbl);
REQUIRE((void*)rcx.allocator().data() == (void*)alloc.data());
REQUIRE(rcx.stringtable() == &stbl);
@ -54,7 +54,7 @@ namespace xo {
auto stbl = StringTable(1024 /*hint_max_capacity*/,
false /*!debug_flag*/);
DSimpleRcx rcx(alloc, &stbl);
DSimpleRcx rcx(alloc, alloc, &stbl);
obj<ARuntimeContext> rcx_obj = with_facet<ARuntimeContext>::mkobj(&rcx);
// verify we can recover allocator from obj<ARuntimeContext>