xo-gc stack: fix mutation setup + xo-reader2 utest

This commit is contained in:
Roland Conybeare 2026-05-07 23:44:32 -04:00
commit b64940fa0a
24 changed files with 478 additions and 48 deletions

View file

@ -12,6 +12,7 @@
namespace xo { namespace xo {
namespace mm { namespace mm {
/** @class CollectorTypeRegistry /** @class CollectorTypeRegistry
* *
* @brief Runtime registry for gc-aware types * @brief Runtime registry for gc-aware types
@ -68,7 +69,8 @@ namespace xo {
/** initialization steps for a new Collector instance **/ /** initialization steps for a new Collector instance **/
std::vector<init_function_type> init_seq_v_; std::vector<init_function_type> init_seq_v_;
}; };
}
} } /*namespace mm*/
} /*namespace xo*/
/* end CollectorTypeRegistry.hpp */ /* end CollectorTypeRegistry.hpp */

View file

@ -13,13 +13,27 @@
namespace xo { namespace xo {
namespace scm { namespace scm {
class GCObjectConversionUtil {
public:
using AGCObject = xo::mm::AGCObject;
using typeseq = xo::reflect::typeseq;
/** helper method fro GCObjectConversion<..>::from_gco()
* on conversion failure
**/
static void _from_gco_fail_aux(obj<AGCObject> gco,
typeseq tseq,
scope * p_log);
};
/** @brief compile-time conversion obj<AGCObject> <-> T /** @brief compile-time conversion obj<AGCObject> <-> T
* *
* Specialize for each T that participates in conversion. * Specialize for each T that participates in conversion.
* Methods here aren't implemented * Methods here aren't implemented
**/ **/
template <typename T> template <typename T>
struct GCObjectConversion { class GCObjectConversion {
public:
using AGCObject = xo::mm::AGCObject; using AGCObject = xo::mm::AGCObject;
using AAllocator = xo::mm::AAllocator; using AAllocator = xo::mm::AAllocator;
@ -73,6 +87,13 @@ namespace xo {
} }
} }
/** Several use cases here:
* 1. runtime polymorphism
* obj<AGCObject,DArray> v(DArray::make(..));
* // from_gco() doesn't know v repr
* auto gc = GCObjectConversion<ASequence,DArray>::from_gco(mm, v);
*
**/
static obj<AFacet,DRepr> from_gco(obj<AAllocator>, static obj<AFacet,DRepr> from_gco(obj<AAllocator>,
obj<AGCObject> gco) { obj<AGCObject> gco) {
scope log(XO_DEBUG(false)); scope log(XO_DEBUG(false));
@ -92,14 +113,10 @@ namespace xo {
auto retval = obj<AFacet,DRepr>::from(gco); auto retval = obj<AFacet,DRepr>::from(gco);
if (!retval) { if (!retval) {
log.retroactively_enable(); GCObjectConversionUtil::_from_gco_fail_aux
(gco, reflect::typeseq::id<DRepr>(), &log);
log && log(xtag("gco.tseq", gco._typeseq()));
log && log(xtag("DRepr.tseq", reflect::typeseq::id<DRepr>()));
} }
assert(retval);
return retval; return retval;
} }
} else { } else {

View file

@ -64,6 +64,16 @@ namespace xo {
/** @defgroup mm-allocator-methods Allocator methods **/ /** @defgroup mm-allocator-methods Allocator methods **/
///@{ ///@{
/** An uninitialized AAllocator 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 **/ /** RTTI: unique id# for actual runtime data representation **/
virtual typeseq _typeseq() const noexcept = 0; virtual typeseq _typeseq() const noexcept = 0;
/** destroy instance @p d. Calls c++ destructor for actual runtime type. /** destroy instance @p d. Calls c++ destructor for actual runtime type.

View file

@ -52,6 +52,7 @@ namespace xo {
return nullptr; return nullptr;
} }
bool _has_null_vptr() const noexcept { return O::iface()->_has_null_vptr(); }
typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); } typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); }
void _drop() const noexcept { O::iface()->_drop(O::data()); } void _drop() const noexcept { O::iface()->_drop(O::data()); }
std::string_view name() const noexcept { return O::iface()->name(O::data()); } std::string_view name() const noexcept { return O::iface()->name(O::data()); }

View file

@ -33,9 +33,14 @@ namespace xo {
obj<AGCObject> * p_lhs, obj<AGCObject> * p_lhs,
obj<AGCObject> rhs) noexcept obj<AGCObject> rhs) noexcept
{ {
if (this->data()) {
this->barrier_assign_aux(parent, this->barrier_assign_aux(parent,
p_lhs->iface(), p_lhs->opaque_data_addr(), p_lhs->iface(), p_lhs->opaque_data_addr(),
rhs.iface(), rhs.opaque_data()); rhs.iface(), rhs.opaque_data());
} else {
// special case: for null allocator want no write-barrier
*p_lhs = rhs;
}
} }
template <typename Object> template <typename Object>
@ -48,11 +53,16 @@ namespace xo {
// need to get AGCObject i/face that goes with DRepr. // need to get AGCObject i/face that goes with DRepr.
obj<AGCObject,DRepr> rhs_gco(rhs_data); obj<AGCObject,DRepr> rhs_gco(rhs_data);
if (this->data()) {
this->barrier_assign_aux(parent, this->barrier_assign_aux(parent,
nullptr /*not needed*/, nullptr /*not needed*/,
lhs_data, lhs_data,
rhs_gco.iface(), rhs_gco.iface(),
rhs_data); rhs_data);
} else {
// special case: for null allocator want no write-barrier
*lhs_data = rhs_data;
}
} }
} /*namespace mm*/ } /*namespace mm*/

View file

@ -7,6 +7,7 @@ set(SELF_SRCS
SetupAlloc2.cpp SetupAlloc2.cpp
CollectorTypeRegistry.cpp CollectorTypeRegistry.cpp
GCObjectConversion.cpp
facet/ICollector_Any.cpp facet/ICollector_Any.cpp

View file

@ -0,0 +1,28 @@
/** @file GCObjectConversion.cpp
*
* @author Roland Conybeare, May 2026
**/
#include "GCObjectConversion.hpp"
namespace xo {
using xo::reflect::typeseq;
namespace scm {
void
GCObjectConversionUtil::_from_gco_fail_aux(obj<AGCObject> gco,
typeseq tseq,
scope * p_log)
{
p_log->retroactively_enable();
if (p_log) {
(*p_log)(xtag("gco.tseq", gco._typeseq()));
(*p_log)(xtag("DRepr.tseq", tseq));
}
}
} /*namespace scm*/
} /*namespace xo*/
/* end GCObjectConversion.cpp */

View file

@ -9,6 +9,8 @@ set(UTEST_SRCS
DArenaIterator.test.cpp DArenaIterator.test.cpp
# Collector.test.cpp # Collector.test.cpp
# DX1CollectorIterator.test.cpp # DX1CollectorIterator.test.cpp
Generation.test.cpp
dp.test.cpp
random_allocs.cpp random_allocs.cpp
) )

View file

@ -0,0 +1,42 @@
/** @file Generation.test.cpp
*
* @author Roland Conybeare, May 2026
**/
#include "Generation.hpp"
#include <catch2/catch.hpp>
namespace xo {
using xo::mm::Generation;
using xo::mm::c_max_generation;
namespace ut {
TEST_CASE("Generation-1", "[Generation]")
{
REQUIRE(Generation::nursery() == 0);
REQUIRE(Generation::g0() == 0);
REQUIRE(Generation::g1() == 1);
REQUIRE(Generation::sentinel() == c_max_generation);
REQUIRE(Generation::g0().is_sentinel() == false);
REQUIRE(Generation::g1().is_sentinel() == false);
REQUIRE(Generation::sentinel().is_sentinel());
REQUIRE(Generation::g0() != Generation::sentinel());
REQUIRE(Generation::g1() != Generation::sentinel());
REQUIRE(!(Generation::g0() > Generation::g1()));
REQUIRE(Generation::g1() > Generation::g0());
auto g = Generation::g0();
++g;
REQUIRE(g == Generation::g1());
++g;
REQUIRE(g > Generation::g1());
}
} /*namespace ut*/
} /*namespace xo*/
/* end Generation.hpp */

View file

@ -3,11 +3,10 @@
* @author Roland Conybeare, Dec 2025 * @author Roland Conybeare, Dec 2025
**/ **/
#include "xo/alloc2/Allocator.hpp" #include <xo/alloc2/Allocator.hpp>
#include "xo/alloc2/Arena.hpp" #include <xo/alloc2/Arena.hpp>
//#include "xo/alloc2/arena/IAllocator_DArena.hpp" #include <xo/arena/print.hpp>
#include "xo/arena/print.hpp" #include <xo/arena/padding.hpp>
#include "xo/arena/padding.hpp"
#include <xo/facet/obj.hpp> #include <xo/facet/obj.hpp>
#include <xo/indentlog/scope.hpp> #include <xo/indentlog/scope.hpp>
#include <catch2/catch.hpp> #include <catch2/catch.hpp>
@ -150,7 +149,6 @@ namespace xo {
.size_ = 64*1024, .size_ = 64*1024,
.debug_flag_ = false }; .debug_flag_ = false };
DArena arena = DArena::map(cfg); DArena arena = DArena::map(cfg);
//obj<AAllocator, DArena> a1o{&arena};
auto a1o = with_facet<AAllocator>::mkobj(&arena); auto a1o = with_facet<AAllocator>::mkobj(&arena);
REQUIRE(a1o.reserved() >= cfg.size_); REQUIRE(a1o.reserved() >= cfg.size_);

100
xo-alloc2/utest/dp.test.cpp Normal file
View file

@ -0,0 +1,100 @@
/** @file dp.test.cpp
*
* @author Roland Conybeare, May 2026
**/
#include "dp.hpp"
#include <xo/alloc2/Allocator.hpp>
#include <xo/alloc2/Arena.hpp>
#include <catch2/catch.hpp>
namespace xo {
using xo::mm::AAllocator;
using xo::mm::DArena;
using xo::mm::ArenaConfig;
namespace {
class Foo {
public:
explicit Foo(uint32_t * p_counter) : p_counter_{p_counter} {}
static constexpr bool is_gc_eligible() { return false; }
~Foo() {
++(*p_counter_);
}
private:
uint32_t * p_counter_ = nullptr;
};
}
namespace ut {
TEST_CASE("dp-1", "[dp]")
{
//ArenaConfig cfg { .name_ = "testarena", .size_ = 1024 };
//DArena arena = DArena::map(cfg);
//auto mm = obj<AAllocator,DArena>(&arena);
uint32_t counter = 0;
Foo foo(&counter);
REQUIRE(counter == 0);
{
dp<Foo> foo_dp(&foo);
REQUIRE(foo_dp);
REQUIRE(foo_dp.data());
REQUIRE(foo_dp.is_gc_eligible() == false);
// foo_dp dtor runs here, increments counter
}
REQUIRE(counter == 1);
}
TEST_CASE("dp-2", "[dp]")
{
uint32_t counter = 0;
Foo foo(&counter);
REQUIRE(counter == 0);
{
dp<Foo> foo_dp(&foo);
REQUIRE(foo_dp);
REQUIRE(foo_dp.data() == &foo);
foo_dp.release();
REQUIRE(!foo_dp);
REQUIRE(!foo_dp.data());
// foo_dp dtor runs here, increments counter
}
REQUIRE(counter == 0);
}
TEST_CASE("dp-DArena", "[dp][DArena]")
{
ArenaConfig cfg { .name_ = "testarena", .size_ = 1024 };
DArena arena = DArena::map(cfg);
//auto mm = obj<AAllocator,DArena>(&arena);
REQUIRE(arena.reserved() > 0);
REQUIRE(arena.is_mapped());
{
dp<DArena> arena_dp(&arena);
REQUIRE(arena_dp);
REQUIRE(arena_dp.data() == &arena);
}
REQUIRE(arena.reserved() == 0);
REQUIRE(!arena.is_mapped());
}
} /*namespace ut*/
} /*namespace xo*/
/* end dp.test.cpp */

View file

@ -57,6 +57,24 @@ namespace xo {
Generation gc_upto_; Generation gc_upto_;
}; };
/** @class GcStatistics
**/
struct GCStatistics {
public:
GCStatistics() = default;
explicit GCStatistics(uint32_t n_gc) : n_gc_{n_gc} {};
uint32_t n_gc() const noexcept { return n_gc_; }
void include_gc() {
++n_gc_;
}
private:
/** count #gc **/
uint32_t n_gc_ = 0;
};
struct DX1CollectorIterator; struct DX1CollectorIterator;
/** @brief GC root struct /** @brief GC root struct
@ -116,6 +134,8 @@ namespace xo {
std::string_view name() const noexcept { return config_.name_; } std::string_view name() const noexcept { return config_.name_; }
GCRunState runstate() const noexcept { return runstate_; } GCRunState runstate() const noexcept { return runstate_; }
const GCStatistics & gc_stats() const noexcept { return gc_stats_; }
const ObjectTypeTable * get_object_types() const noexcept { return gco_store_.get_object_types(); } const ObjectTypeTable * get_object_types() const noexcept { return gco_store_.get_object_types(); }
const RootSet * get_root_set() const noexcept { return &root_set_; } const RootSet * get_root_set() const noexcept { return &root_set_; }
const DArena * get_space(Role r, Generation g) const noexcept { return gco_store_.get_space(r, g); } const DArena * get_space(Role r, Generation g) const noexcept { return gco_store_.get_space(r, g); }
@ -405,6 +425,9 @@ namespace xo {
**/ **/
MutationLogStore mlog_store_; MutationLogStore mlog_store_;
/** counters collected across GC phases **/
GCStatistics gc_stats_;
/** counters collected during @ref verify_ok call **/ /** counters collected during @ref verify_ok call **/
X1VerifyStats verify_stats_; X1VerifyStats verify_stats_;
}; };

View file

@ -528,6 +528,9 @@ namespace xo {
log && log("step 5 : cleanup"); log && log("step 5 : cleanup");
this->_cleanup_phase(upto); this->_cleanup_phase(upto);
log && log("step 6 : update gc statistics");
gc_stats_.include_gc();
if (config_.sanitize_flag_) { if (config_.sanitize_flag_) {
log && log("step 5b : verify"); log && log("step 5b : verify");
bool ok = this->verify_ok(); bool ok = this->verify_ok();
@ -550,7 +553,7 @@ namespace xo {
void void
DX1Collector::_swap_roles(Generation upto) noexcept DX1Collector::_swap_roles(Generation upto) noexcept
{ {
scope log(XO_DEBUG(true), xtag("upto", upto)); scope log(XO_DEBUG(config_.debug_flag_), xtag("upto", upto));
gco_store_.swap_roles(upto); gco_store_.swap_roles(upto);
mlog_store_.swap_roles(upto); mlog_store_.swap_roles(upto);
@ -559,7 +562,7 @@ namespace xo {
void void
DX1Collector::_cleanup_phase(Generation upto) DX1Collector::_cleanup_phase(Generation upto)
{ {
scope log(XO_DEBUG(true), xtag("upto", upto)); scope log(XO_DEBUG(config_.debug_flag_), xtag("upto", upto));
this->gco_store_.cleanup_phase(upto, config_.sanitize_flag_); this->gco_store_.cleanup_phase(upto, config_.sanitize_flag_);
this->runstate_ = GCRunState::idle(); this->runstate_ = GCRunState::idle();
@ -568,7 +571,7 @@ namespace xo {
void void
DX1Collector::_copy_roots(Generation upto) noexcept DX1Collector::_copy_roots(Generation upto) noexcept
{ {
scope log(XO_DEBUG(true)); scope log(XO_DEBUG(config_.debug_flag_));
for (RootSet::size_type i = 0, n = root_set_.size(); i < n; ++i) { for (RootSet::size_type i = 0, n = root_set_.size(); i < n; ++i) {
GCRoot & slot = root_set_[i]; GCRoot & slot = root_set_[i];

View file

@ -705,7 +705,7 @@ namespace xo {
void void
GCObjectStore::_verify_aux(AGCObject * iface, void * data) GCObjectStore::_verify_aux(AGCObject * iface, void * data)
{ {
scope log(XO_DEBUG(config_.debug_flag_)); scope log(XO_DEBUG(false));
(void)iface; (void)iface;
@ -719,6 +719,7 @@ namespace xo {
if (!g2.is_sentinel()) { if (!g2.is_sentinel()) {
// verify failure - live pointer still refers to from-space // verify failure - live pointer still refers to from-space
log.retroactively_enable();
print_backtrace_dwarf(true /*demangle*/); print_backtrace_dwarf(true /*demangle*/);
++(p_verify_stats_->n_from_); ++(p_verify_stats_->n_from_);
@ -1020,13 +1021,17 @@ namespace xo {
AGCObject * iface, AGCObject * iface,
void * from_src) void * from_src)
{ {
scope log(XO_DEBUG(config_.debug_flag_)); scope log(XO_DEBUG(config_.debug_flag_),
xtag("iface", iface),
xtag("from_src", from_src));
assert(!iface->_has_null_vptr());
AllocInfo info = this->alloc_info((std::byte *)from_src); AllocInfo info = this->alloc_info((std::byte *)from_src);
void * to_dest = iface->gco_shallow_move(from_src, gc); void * to_dest = iface->gco_shallow_move(from_src, gc);
log && log(xtag("from_src", from_src), xtag("to_dest", to_dest)); log && log(xtag("to_dest", to_dest));
log && log(xtag("tseq", info.tseq()), log && log(xtag("tseq", info.tseq()),
xtag("tname", TypeRegistry::id2name(typeseq(info.tseq()))), xtag("tname", TypeRegistry::id2name(typeseq(info.tseq()))),
xtag("age", info.age()), xtag("age", info.age()),

View file

@ -160,8 +160,14 @@ namespace xo {
assert(rhs_iface); assert(rhs_iface);
assert(rhs_data); assert(rhs_data);
if (lhs_iface) if (lhs_iface) {
*lhs_iface = *rhs_iface; // memcpy (not assignment): lhs_iface points to AGCObject storage
// whose vptr was set at construction (e.g. IGCObject_Any from
// a default-constructed obj<AGCObject>). Polymorphic copy-assignment
// copies AGCObject's data members but NOT the vptr, so it would
// leave the slot dispatching to the wrong (often fatal) iface.
::memcpy((void *)lhs_iface, (void *)rhs_iface, sizeof(AGCObject));
}
*lhs_addr = rhs_data; *lhs_addr = rhs_data;
@ -195,6 +201,13 @@ namespace xo {
return; return;
} }
if (dest_g + 1 == config_.n_generation_) {
log && log(xtag("msg", "noop because dest in last gen"));
// don't need mlog entry to final gen
return;
}
if (src_g < dest_g) { if (src_g < dest_g) {
log && log(xtag("msg", "noop because src gen younger than dest gen")); log && log(xtag("msg", "noop because src gen younger than dest gen"));
@ -259,7 +272,7 @@ namespace xo {
void void
MutationLogStore::swap_roles(Generation upto) noexcept MutationLogStore::swap_roles(Generation upto) noexcept
{ {
scope log(XO_DEBUG(true), xtag("upto", upto)); scope log(XO_DEBUG(config_.debug_flag_), xtag("upto", upto));
for (Generation g = Generation{0}; g < upto; ++g) { for (Generation g = Generation{0}; g < upto; ++g) {
log && log("swap roles", xtag("g", g)); log && log("swap roles", xtag("g", g));

View file

@ -9,6 +9,7 @@ set(UTEST_SRCS
DX1CollectorIterator.test.cpp DX1CollectorIterator.test.cpp
MutationLogStore.test.cpp MutationLogStore.test.cpp
GCObjectStore.test.cpp GCObjectStore.test.cpp
GCObjectConversion.test.cpp
Object2.test.cpp Object2.test.cpp
DMockCollector.cpp DMockCollector.cpp

View file

@ -13,6 +13,7 @@
#include <xo/object2/Array.hpp> #include <xo/object2/Array.hpp>
#include <xo/object2/List.hpp> #include <xo/object2/List.hpp>
#include <xo/object2/Integer.hpp> #include <xo/object2/Integer.hpp>
#include <xo/alloc2/CollectorTypeRegistry.hpp>
#include <xo/alloc2/Allocator.hpp> #include <xo/alloc2/Allocator.hpp>
#include <xo/randomgen/xoshiro256.hpp> #include <xo/randomgen/xoshiro256.hpp>
#include <xo/randomgen/random_seed.hpp> #include <xo/randomgen/random_seed.hpp>
@ -25,6 +26,7 @@ namespace xo {
using xo::scm::DList; using xo::scm::DList;
using xo::scm::DArray; using xo::scm::DArray;
using xo::scm::DInteger; using xo::scm::DInteger;
using xo::mm::CollectorTypeRegistry;
using xo::mm::AAllocator; using xo::mm::AAllocator;
using xo::mm::ACollector; using xo::mm::ACollector;
using xo::mm::AGCObject; using xo::mm::AGCObject;
@ -33,9 +35,11 @@ namespace xo {
using xo::mm::Role; using xo::mm::Role;
using xo::mm::ArenaConfig; using xo::mm::ArenaConfig;
using xo::mm::AllocHeaderConfig; using xo::mm::AllocHeaderConfig;
using xo::mm::AllocHeader;
using xo::mm::Generation; using xo::mm::Generation;
using xo::mm::c_max_generation; using xo::mm::c_max_generation;
using xo::facet::with_facet; using xo::facet::with_facet;
using xo::reflect::typeseq;
using xo::scope; using xo::scope;
namespace ut { namespace ut {
@ -321,7 +325,11 @@ namespace xo {
.with_n_survive(tc.n_survive_) .with_n_survive(tc.n_survive_)
.with_size(tc.gc_halfspace_z_) .with_size(tc.gc_halfspace_z_)
.with_debug_flag(tc.debug_flag_)) .with_debug_flag(tc.debug_flag_))
{} {
auto gc = obj<ACollector,DX1Collector>(&gc_);
CollectorTypeRegistry::instance().install_types(gc);
}
# define nil nullptr # define nil nullptr
# define T true # define T true
@ -336,7 +344,7 @@ namespace xo {
* n_gen | | | | * n_gen | | | |
* v v v v v * v v v v v
**/ **/
Testcase(1, 2, 16 * 1024, 128, F), Testcase(1, 2, 16 * 1024, 128, T),
}; };
# undef T # undef T
@ -345,10 +353,23 @@ namespace xo {
} /*namespace*/ } /*namespace*/
// full collector test. // full collector test.
//
// PLAN:
// eventually: make generative
//
// Setup (
// 1. gc_utest_main.cpp Subsystem::initialize_all()
// invokes per-module plugin init. Gets types registered
// with FacetRegistry, CollectorTypeRegistry etc.
// 2. per-utest collector setup (fixture)
// calls CollectorTypeRegistry::instance().install_types(gc)
// to establish the set of types that collector knows.
//
TEST_CASE("collector-x1-gc", "[alloc2][gc]") TEST_CASE("collector-x1-gc", "[alloc2][gc]")
{ {
scope log(XO_DEBUG(true), const auto & testname = Catch::getResultCapture().getCurrentTestName();
"DX1Collector gc test");
scope log(XO_DEBUG(true), xtag("test", testname));
//std::uint64_t seed = 7988747704879432247ul; //std::uint64_t seed = 7988747704879432247ul;
//random_seed(&seed); //random_seed(&seed);
@ -372,7 +393,17 @@ namespace xo {
auto mm = x1.ref<AAllocator>(); auto mm = x1.ref<AAllocator>();
auto gc = mm.to_facet<ACollector>(); auto gc = mm.to_facet<ACollector>();
Generation g1{1};
REQUIRE(mm._typeseq() == typeseq::id<DX1Collector>());
REQUIRE(gc._typeseq() == typeseq::id<DX1Collector>());
Generation g0 = Generation::g0();
REQUIRE(mm.allocated() == tc.object_type_z_);
REQUIRE(gc.allocated(g0, Role::to_space()) == 0);
REQUIRE(gc.allocated(g0, Role::from_space()) == 0);
Generation g1 = Generation::g1();
{ {
auto roots = DArray::_empty(mm, 1)->ref<AGCObject>(); auto roots = DArray::_empty(mm, 1)->ref<AGCObject>();
REQUIRE(mm->contains_allocated(Role::to_space(), roots.data())); REQUIRE(mm->contains_allocated(Role::to_space(), roots.data()));
@ -383,12 +414,32 @@ namespace xo {
auto x1_gco = obj<AGCObject>(x1); auto x1_gco = obj<AGCObject>(x1);
auto l1 = DList::cons(mm, x1, DList::_nil()); auto l1 = DList::cons(mm, x1, DList::_nil());
#ifdef NOT_YET REQUIRE(l1._typeseq() == typeseq::id<DList>());
REQUIRE(roots->push_back(l1)); REQUIRE(roots->push_back(mm, l1));
REQUIRE(mm->contains_allocated(Role::to_space(), x1.data())); REQUIRE(mm->contains_allocated(Role::to_space(), x1.data()));
REQUIRE(mm->contains_allocated(Role::to_space(), l1.data())); REQUIRE(mm->contains_allocated(Role::to_space(), l1.data()));
gc->add_gc_root_poly(&(*roots.operator->())[0]); REQUIRE(roots->at(0) == l1);
REQUIRE(roots->at(0)._typeseq() == typeseq::id<DList>());
// z: total allocated so far
// 3x 8-byte header
// sizeof(DInteger)
// sizeof(DList)
// sizeof(DArray(1))
//
auto z = (3 * sizeof(AllocHeader)
+ sizeof(DInteger)
+ sizeof(DList)
+ sizeof(DArray) + sizeof(obj<AGCObject>));
{
REQUIRE(z == 80);
REQUIRE(mm.allocated() == tc.object_type_z_ + z);
REQUIRE(gc.allocated(g0, Role::to_space()) == z);
REQUIRE(gc.allocated(g1, Role::to_space()) == 0);
REQUIRE(gc.allocated(g0, Role::from_space()) == 0);
REQUIRE(gc.allocated(g1, Role::from_space()) == 0);
}
gc->request_gc(g1); // 1st GC gc->request_gc(g1); // 1st GC
@ -398,9 +449,20 @@ namespace xo {
// l1 target got moved, og locn now relabeled from-space // l1 target got moved, og locn now relabeled from-space
REQUIRE(mm->contains(Role::from_space(), l1.data())); REQUIRE(mm->contains(Role::from_space(), l1.data()));
REQUIRE(!mm->contains_allocated(Role::from_space(), l1.data())); REQUIRE(!mm->contains_allocated(Role::from_space(), l1.data()));
#endif
REQUIRE(mm.allocated() == tc.object_type_z_ + z);
REQUIRE(gc.allocated(g0, Role::to_space()) == z);
REQUIRE(gc.allocated(g1, Role::to_space()) == 0);
REQUIRE(gc.allocated(g0, Role::from_space()) == 0);
REQUIRE(gc.allocated(g1, Role::from_space()) == 0);
} }
// NOTE: if this fails:
// look for preceding GCObjectStore::lookup_type out-of-bounds.
// May need to add to CollectorTypeRegistry
//
REQUIRE(mm->contains_allocated(Role::to_space(), roots.data())); REQUIRE(mm->contains_allocated(Role::to_space(), roots.data()));
} }
} }

View file

@ -0,0 +1,95 @@
/** @file GCObjectConversion.test.cpp
*
* @author Roland Conybeare, May 2026
**/
#include "GCObjectConversion.hpp"
#include <xo/object2/ListOps.hpp>
#include <xo/object2/List.hpp>
#include <xo/object2/Array.hpp>
#include <xo/object2/Integer.hpp>
#include <xo/alloc2/Arena.hpp>
#include <catch2/catch.hpp>
namespace xo {
//using xo::scm::ASequence;
using xo::scm::ListOps;
using xo::scm::DArray;
using xo::scm::DList;
using xo::scm::DInteger;
using xo::scm::GCObjectConversion;
using xo::mm::AGCObject;
using xo::mm::ArenaConfig;
using xo::mm::AAllocator;
using xo::mm::DArena;
using xo::facet::obj;
namespace ut {
TEST_CASE("GCObjectConversion-1", "[GCObjectConversion]")
{
scope log(XO_DEBUG(true), "GCObjectConversion-1");
ArenaConfig cfg {
.name_ = "testarena",
.size_ = 128
};
DArena arena = DArena::map(cfg);
auto mm = obj<AAllocator,DArena>(&arena);
auto v1 = DArray::empty(mm, 3);
REQUIRE(v1);
REQUIRE(v1->size() == 0);
{
obj v1_seq
= GCObjectConversion<obj<AGCObject,DArray>>::from_gco(mm /*not used*/, v1);
REQUIRE(v1_seq);
REQUIRE(v1_seq == v1);
REQUIRE(v1_seq->size() == 0);
}
{
obj l1_seq
= GCObjectConversion<obj<AGCObject,DList>>::from_gco(mm /*not used*/, v1);
REQUIRE(!l1_seq);
}
}
TEST_CASE("GCObjectConversion-2", "[GCObjectConversion]")
{
scope log(XO_DEBUG(true), "GCObjectConversion-2");
ArenaConfig cfg {
.name_ = "testarena",
.size_ = 128
};
DArena arena = DArena::map(cfg);
auto mm = obj<AAllocator,DArena>(&arena);
auto l1 = ListOps::cons(mm, DInteger::box(mm, 42), ListOps::nil());
REQUIRE(l1);
REQUIRE(l1->size() == 1);
{
// will fail; source is DArena
obj l1_seq
= GCObjectConversion<obj<AGCObject,DList>>::from_gco(mm /*not used*/, l1);
REQUIRE(l1_seq);
}
{
obj v1_seq
= GCObjectConversion<obj<AGCObject,DArray>>::from_gco(mm /*not used*/, l1);
REQUIRE(!v1_seq);
}
}
} /*namespace ut*/
} /*namespace xo*/
/* end GCObjectConversion.cpp */

View file

@ -341,7 +341,7 @@ namespace ut {
Testcase(2, 1, 16 * 1024, 8 * 128, T, seq_2, 128, T, c_fixed, 3, 0, 0, 0, 0, F), Testcase(2, 1, 16 * 1024, 8 * 128, T, seq_2, 128, T, c_fixed, 3, 0, 0, 0, 0, F),
Testcase(2, 2, 16 * 1024, 8 * 128, T, seq_3, 128, T, c_fixed, 4, 0, 0, 0, 0, F), Testcase(2, 2, 16 * 1024, 8 * 128, T, seq_3, 128, T, c_fixed, 4, 0, 0, 0, 0, F),
Testcase(2, 2, 16 * 1024, 8 * 128, T, seq_4, 128, T, c_fixed, 4, 0, 0, 0, 0, F), Testcase(2, 2, 16 * 1024, 8 * 128, T, seq_4, 128, T, c_fixed, 4, 0, 0, 0, 0, F),
Testcase(2, 2, 16 * 1024, 8 * 128, T, seq_5, 128, T, c_fixed, 4, 0, 0, 0, 0, T), Testcase(2, 2, 16 * 1024, 8 * 128, T, seq_5, 128, T, c_fixed, 4, 0, 0, 0, 0, F),
}; };
# undef T # undef T

View file

@ -13,7 +13,6 @@
#include <xo/object2/Float.hpp> #include <xo/object2/Float.hpp>
#include <xo/object2/Integer.hpp> #include <xo/object2/Integer.hpp>
#include <xo/object2/List.hpp> #include <xo/object2/List.hpp>
//#include "list/IGCObject_DList.hpp"
#include <xo/gc/X1Collector.hpp> #include <xo/gc/X1Collector.hpp>
//#include <xo/alloc2/Collector.hpp> //#include <xo/alloc2/Collector.hpp>

View file

@ -76,6 +76,11 @@ namespace xo {
static DArray * _empty(obj<AAllocator> mm, static DArray * _empty(obj<AAllocator> mm,
size_type cap); size_type cap);
/** ofp version of _empty(mm,cap) **/
template <typename AFacet = AGCObject>
static obj<AFacet,DArray> empty(obj<AAllocator> mm,
size_type cap);
/** create copy of @p src using memory from @p mm /** create copy of @p src using memory from @p mm
* with capacity for @p new_cap elements * with capacity for @p new_cap elements
**/ **/
@ -198,6 +203,15 @@ namespace xo {
///@} ///@}
}; };
template <typename AFacet>
obj<AFacet,DArray>
DArray::empty(obj<AAllocator> mm, DArray::size_type cap)
{
DArray * retval = _empty(mm, cap);
return obj<AFacet,DArray>(retval);
}
template <typename... Args> template <typename... Args>
requires (std::convertible_to<Args, obj<DArray::AGCObject>> && ...) requires (std::convertible_to<Args, obj<DArray::AGCObject>> && ...)
DArray * DArray *

View file

@ -84,7 +84,8 @@ namespace xo {
} }
bool bool
DArray::push_back(obj<AAllocator> mm, obj<AGCObject> elt) noexcept DArray::push_back(obj<AAllocator> mm,
obj<AGCObject> elt) noexcept
{ {
if (size_ >= capacity_) { if (size_ >= capacity_) {
return false; return false;

View file

@ -63,6 +63,9 @@ namespace xo {
auto alloc = with_facet<AAllocator>::mkobj(&arena); auto alloc = with_facet<AAllocator>::mkobj(&arena);
obj<AAllocator> null_mm; obj<AAllocator> null_mm;
REQUIRE(!null_mm.data());
REQUIRE(!null_mm._has_null_vptr()); // any
DArray * arr = DArray::_empty(alloc, 16); DArray * arr = DArray::_empty(alloc, 16);
REQUIRE(arr != nullptr); REQUIRE(arr != nullptr);
REQUIRE(arr->capacity() == 16); REQUIRE(arr->capacity() == 16);

View file

@ -466,7 +466,7 @@ namespace xo {
DDefineSsm::on_parsed_typedescr(TypeDescr td, DDefineSsm::on_parsed_typedescr(TypeDescr td,
ParserStateMachine * p_psm) ParserStateMachine * p_psm)
{ {
scope log(XO_DEBUG(true), xtag("td", td)); scope log(XO_DEBUG(p_psm->debug_flag()), xtag("td", td));
if (defstate_ == defexprstatetype::def_3) { if (defstate_ == defexprstatetype::def_3) {
this->defstate_ = defexprstatetype::def_4; this->defstate_ = defexprstatetype::def_4;