xo-gc: utest: mutation log tests

This commit is contained in:
Roland Conybeare 2026-04-19 15:13:13 -04:00
commit 39b1ad1b21
21 changed files with 1155 additions and 136 deletions

View file

@ -46,4 +46,4 @@ namespace xo {
} /*namespace mm*/
} /*namespace xo*/
/* end DGCObjectVisitor.hpp */
/* end DGCObjectStoreVisitor.hpp */

View file

@ -60,6 +60,18 @@ namespace xo {
**/
AGCObject * lookup_type(typeseq tseq) const noexcept;
/** report allocated memory for role r, generation g
**/
size_type allocated(Generation g, Role r) const noexcept;
/** report committed memory for role r, generation g
**/
size_type committed(Generation g, Role r) const noexcept;
/** report reserved memory for role r, generation g
**/
size_type reserved(Generation g, Role r) const noexcept;
/** generation to which pointer @p addr belongs, given Role @p r;
* sentinel if not found in this collector
**/

View file

@ -35,7 +35,9 @@ namespace xo {
void ** p_data() const { return p_data_; }
obj<AGCObject> snap() const { return snap_; }
/** true if child pointer has been altered since this mlog entry created **/
/** true iff child pointer matches value when this mlog entry created **/
bool is_active() const noexcept { return *p_data_ == snap_.data(); }
/** true iff child pointer has been altered since this mlog entry created **/
bool is_superseded() const noexcept { return *p_data_ != snap_.data(); }
private:

View file

@ -18,11 +18,12 @@ namespace xo {
class DX1Collector;
class X1VerifyStats;
using MutationLog = DArenaVector<MutationLogEntry>;
/** @brief container for X1 collector mutation logs
**/
class MutationLogStore {
public:
using MutationLog = DArenaVector<MutationLogEntry>;
using size_type = DArena::size_type;
public:
@ -34,6 +35,12 @@ namespace xo {
**/
void init_mlogs(std::size_t page_z);
MutationLog * get_mlog(Role r, Generation g) noexcept { return mlog_[r][g]; }
const MutationLog * get_mlog(Role r, Generation g) const noexcept { return mlog_[r][g]; }
/** reminder: abusing Role because we need one additional mlog **/
MutationLog * triage_mlog(Generation g) noexcept { return mlog_[Role{c_n_role}][g]; }
const MutationLog * triage_mlog(Generation g) const noexcept { return mlog_[Role{c_n_role}][g]; }
/** total number of active mlog entries (across all generations)
**/
size_type mutation_log_entries() const noexcept;
@ -41,12 +48,10 @@ namespace xo {
void visit_pools(const MemorySizeVisitor & visitor) const;
/** verify consistent mlog state,
* on behalf of gc-aware object store @p gc.
* (using gc to identify location of objects).
* Update counters in @p *p_verify_stats.
* Update counters associated with gco_store_
**/
void verify_ok(GCObjectStore * gc,
X1VerifyStats * p_verify_stats) noexcept;
void verify_ok() noexcept;
/** on behalf of gc-aware object store @p gc,
* change the value of a child pointer at @p p_lhs

View file

@ -420,8 +420,7 @@ namespace xo {
gco_store_.verify_ok();
// 4. scan mutation logs
mlog_store_.verify_ok(&gco_store_,
&(this->verify_stats_));
mlog_store_.verify_ok();
}
// restore run state at end of verify cycle

View file

@ -154,6 +154,43 @@ namespace xo {
return slot.iface();
}
namespace {
using size_type = GCObjectStore::size_type;
size_type
stat_helper(const GCObjectStore & 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;
}
}
auto
GCObjectStore::allocated(Generation g, Role r) const noexcept -> size_type
{
return stat_helper(*this, &DArena::allocated, g, r);
}
auto
GCObjectStore::committed(Generation g, Role r) const noexcept -> size_type
{
return stat_helper(*this, &DArena::committed, g, r);
}
auto
GCObjectStore::reserved(Generation g, Role r) const noexcept -> size_type
{
return stat_helper(*this, &DArena::reserved, g, r);
}
Generation
GCObjectStore::generation_of(Role r, const void * addr) const noexcept
{
@ -258,7 +295,7 @@ namespace xo {
obj<AAllocator> error_mm,
obj<AGCObject> * p_output) const noexcept
{
scope log(XO_DEBUG(true));
scope log(XO_DEBUG(this->config_.debug_flag_));
(void)error_mm;
@ -369,7 +406,7 @@ namespace xo {
obj<AAllocator> error_mm,
obj<AGCObject> * p_output) const noexcept
{
scope log(XO_DEBUG(true));
scope log(XO_DEBUG(this->config_.debug_flag_));
(void)error_mm;
@ -883,7 +920,7 @@ namespace xo {
std::byte *
GCObjectStore::alloc_copy(void * src) noexcept
{
scope log(XO_DEBUG(true));
scope log(XO_DEBUG(config_.debug_flag_));
AllocInfo src_info = this->alloc_info((std::byte *)src);
uint32_t age1p = std::min(src_info.age() + 1,

View file

@ -90,13 +90,14 @@ namespace xo {
}
void
MutationLogStore::verify_ok(GCObjectStore * gco_store,
X1VerifyStats * p_verify_stats) noexcept
MutationLogStore::verify_ok() noexcept
{
X1VerifyStats * p_verify_stats = gco_store_->verify_stats();
// 4. scan mutation logs
for (Generation g(0); g + 1 < config_.n_generation_; ++g) {
const DArena * space = gco_store->get_space(Role::to_space(), g);
const DArena * from = gco_store->get_space(Role::from_space(), g);
const DArena * space = gco_store_->get_space(Role::to_space(), g);
const DArena * from = gco_store_->get_space(Role::from_space(), g);
// mutation log for generation g records *incoming* pointers
// from more senior generations; includes objects from *this*

View file

@ -11,12 +11,23 @@ set(UTEST_SRCS
GCObjectStore.test.cpp
Object2.test.cpp
DMockCollector.cpp
ICollector_DMockCollector.cpp
MlsTestutil.cpp
GcosTestutil.cpp
init_gc_utest.cpp
random_allocs.cpp
)
# note: manual target; generated code committed to git
xo_add_genfacetimpl(
TARGET xo-gc-facetimpl-collector-mockcollector
FACET_PKG xo_alloc2
INPUT idl/ICollector_DMockCollector.json5
)
if (ENABLE_TESTING)
xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS})
xo_headeronly_dependency(${UTEST_EXE} randomgen)

106
utest/DMockCollector.cpp Normal file
View file

@ -0,0 +1,106 @@
/** @file DMockCollector.cpp
*
* @author Roland Conybeare, Apr 2026
**/
#include "DMockCollector.hpp"
namespace xo {
namespace mm {
auto DMockCollector::allocated(Generation g, Role r) const noexcept -> size_type {
return gcos_->allocated(g, r);
}
auto DMockCollector::committed(Generation g, Role r) const noexcept -> size_type {
return gcos_->committed(g, r);
}
auto DMockCollector::reserved(Generation g, Role r) const noexcept -> size_type {
return gcos_->reserved(g, r);
}
int32_t
DMockCollector::locate_address(const void * addr) const noexcept {
Generation g = gcos_->generation_of(Role::to_space(), addr);
if (!g.is_sentinel())
return g;
return -1;
}
bool
DMockCollector::contains(Role r, const void * addr) const noexcept {
return gcos_->contains(r, addr);
}
bool
DMockCollector::is_type_installed(typeseq tseq) const noexcept {
return gcos_->is_type_installed(tseq);
}
bool
DMockCollector::report_statistics(obj<AAllocator> mm,
obj<AAllocator> error_mm,
obj<AGCObject> * p_output) const noexcept
{
return false;
}
bool
DMockCollector::report_object_types(obj<AAllocator> mm,
obj<AAllocator> error_mm,
obj<AGCObject> * p_output) const noexcept
{
return gcos_->report_object_types(mm, error_mm, p_output);
}
bool
DMockCollector::report_object_ages(obj<AAllocator> mm,
obj<AAllocator> error_mm,
obj<AGCObject> * p_output) const noexcept
{
return gcos_->report_object_ages(mm, error_mm, p_output);
}
bool
DMockCollector::install_type(const AGCObject & meta) noexcept
{
return gcos_->install_type(meta);
}
void
DMockCollector::add_gc_root_poly(obj<AGCObject> * p_root)
{
assert(false);
}
void
DMockCollector::remove_gc_root_poly(obj<AGCObject> * p_root)
{
assert(false);
}
void
DMockCollector::request_gc(Generation upto)
{
assert(false);
}
void
DMockCollector::assign_member(void * parent, obj<AGCObject> * p_lhs, obj<AGCObject> & rhs)
{
mls_->assign_member(gcos_, parent, p_lhs, rhs);
}
void *
DMockCollector::alloc_copy(std::byte * src)
{
return gcos_->alloc_copy(src);
}
} /*namespace mm*/
} /*namespace xo*/
/* end DMockCollector.cpp */

65
utest/DMockCollector.hpp Normal file
View file

@ -0,0 +1,65 @@
/** @file DMockCollector.hpp
*
* @author Roland Conybeare, Apr 2026
**/
#pragma once
#include <xo/gc/MutationLogStore.hpp>
#include <xo/gc/GCObjectStore.hpp>
namespace xo {
namespace mm {
class DMockCollector {
public:
using size_type = GCObjectStore::size_type;
using typeseq = xo::facet::typeseq;
DMockCollector(MutationLogStore * mls, GCObjectStore * gcos)
: mls_{mls}, gcos_{gcos} {}
size_type allocated(Generation g, Role r) const noexcept;
size_type committed(Generation g, Role r) const noexcept;
size_type reserved(Generation g, Role r) const noexcept;
// like generation_fo(), but for ACollector api
int32_t locate_address(const void * addr) const noexcept;
// true iff gcos contains address @p addr in @p role
bool contains(Role r, const void * addr) const noexcept;
// true iff @p tseq has been installed in @p gcos_
bool is_type_installed(typeseq tseq) const noexcept;
bool report_statistics(obj<AAllocator> mm,
obj<AAllocator> error_mm,
obj<AGCObject> * p_output) const noexcept;
bool report_object_types(obj<AAllocator> mm,
obj<AAllocator> error_mm,
obj<AGCObject> * p_output) const noexcept;
bool report_object_ages(obj<AAllocator> mm,
obj<AAllocator> error_mm,
obj<AGCObject> * p_output) const noexcept;
bool install_type(const AGCObject & meta) noexcept;
void add_gc_root_poly(obj<AGCObject> * p_root);
void remove_gc_root_poly(obj<AGCObject> * p_root);
void request_gc(Generation upto);
// write barrier for assignment
void assign_member(void * parent, obj<AGCObject> * p_lhs, obj<AGCObject> & rhs);
void * alloc_copy(std::byte * src);
MutationLogStore * mls_ = nullptr;
GCObjectStore * gcos_ = nullptr;
};
} /*namespace mm*/
} /*namespace xo*/
/* end DMockCollector.hpp */

View file

@ -122,6 +122,7 @@ namespace ut {
constexpr TestGraphType c_selfcycle = TestGraphType::selfcycle;
constexpr TestGraphType c_random = TestGraphType::random;
/** arena size for object age/type reports **/
constexpr uint32_t c_report_z1 = 64 * 1024;
constexpr uint32_t c_error_z1 = 16 * 1024;
@ -166,7 +167,9 @@ namespace ut {
namespace {
// aux functions specific to GCObjectStore-1 unit test below
// fixture for GCObjectStore-1 test
/** Fixture for GCObjectStore-1 test.
* Compare similar but not identical fixture in MutationLogStore.test.cpp
**/
class GcosFixture {
public:
explicit GcosFixture(const Testcase & tc);
@ -174,6 +177,7 @@ namespace ut {
auto report_mm() { return obj<AAllocator,DArena>(&report_arena_); }
auto error_mm() { return obj<AAllocator,DArena>(&error_arena_); }
/** configuration for @ref gcos_ **/
GCObjectStoreConfig gcos_config_;
/** Parallel arena for reference
@ -186,6 +190,7 @@ namespace ut {
* It doesn't have or require any builtin ability to traverse an object model
**/
DArena arena2_;
/** Arena for holding report output:
* See GCObjectStore methods .report_object_types(), .report_object_ages()
**/
@ -225,7 +230,7 @@ namespace ut {
TEST_CASE("GCObjectStore-1", "[GCObjectStore]")
{
constexpr bool c_debug_flag = true;
constexpr bool c_debug_flag = false;
scope log0(XO_DEBUG(c_debug_flag), "GCObjectStore test");
std::uint64_t seed = 12168164826603821466ul;
@ -270,11 +275,14 @@ namespace ut {
// construct, extend, and/or modify object graphs in {x1_v, x2_v}
GcosTestutil::gcos_construct_ab_object_graphs(tc.obj_graph_type_,
GcosTestutil::gcos_construct_ab_object_graphs(nullptr /*cmd_seq*/,
tc.obj_graph_type_,
tc.n_i0_test_obj_,
tc.n_i0_test_assign_,
tc.n_i1_test_obj_,
tc.n_i1_test_assign_,
tc.debug_flag_,
nullptr /*p_mls*/,
&gcos,
&fixture.arena2_,
loop_index,

View file

@ -4,9 +4,11 @@
**/
#include "GcosTestutil.hpp"
#include "MockCollector.hpp"
#include <xo/gc/X1VerifyStats.hpp>
#include <xo/object2/ListOps.hpp>
#include <xo/object2/Boolean.hpp>
#include <xo/alloc2/Collector.hpp>
#include <xo/alloc2/Arena.hpp>
#include <xo/facet/TypeRegistry.hpp>
#include <xo/randomgen/xoshiro256.hpp>
@ -19,6 +21,8 @@ namespace ut {
using xo::scm::ListOps;
using xo::scm::DList;
using xo::scm::DBoolean;
using xo::mm::ACollector;
using xo::mm::DMockCollector;
using xo::mm::X1VerifyStats;
using xo::mm::GCObjectStore;
using xo::mm::AGCObject;
@ -84,17 +88,20 @@ namespace ut {
void
GcosTestutil::random_object_graph(uint32_t n_new_obj,
uint32_t n_assign,
bool debug_flag,
xoshiro256ss * p_rgen,
std::vector<Recd> * p_v,
GCObjectStore * p_gcos,
std::vector<Recd> * p_v2,
DArena * p_arena2)
{
scope log(XO_DEBUG(true));
scope log(XO_DEBUG(debug_flag));
if (n_new_obj == 0 && n_assign == 0)
return;
// TODO: combine // alloc setup w/ gco_construct_ab_object_graphs() bolierplate
for (uint32_t i_obj = 0; i_obj < n_new_obj; ++i_obj) {
auto alloc = obj<AAllocator,DArena>(p_gcos->new_space());
uint32_t sample = (*p_rgen)() % 100;
@ -309,11 +316,14 @@ namespace ut {
* @p loop_index counts iteration with one gc-like phase.
**/
void
GcosTestutil::gcos_construct_ab_object_graphs(TestGraphType obj_graph_type,
GcosTestutil::gcos_construct_ab_object_graphs(Step * cmd_seq,
TestGraphType obj_graph_type,
uint32_t n_i0_test_obj,
uint32_t n_i0_test_assign,
uint32_t n_i1_test_obj,
uint32_t n_i1_test_assign,
bool debug_flag,
MutationLogStore * p_mls,
GCObjectStore * p_gcos,
DArena * p_arena2,
uint32_t loop_index,
@ -321,6 +331,103 @@ namespace ut {
std::vector<Recd> * p_x2_v,
xoshiro256ss * p_rgen)
{
if (cmd_seq && (loop_index == 0)) {
// do scripted sequence only
auto alloc = obj<AAllocator,DArena>(p_gcos->new_space());
auto alloc2 = obj<AAllocator,DArena>(p_arena2);
DMockCollector mock(p_mls, p_gcos);
auto mockgc = obj<ACollector,DMockCollector>(&mock);
while (cmd_seq->is_command()) {
bool is_alloc = false;
obj<AGCObject> xi;
obj<AGCObject> xi2;
uint64_t alloc_z = 0;
typeseq tseq;
switch (cmd_seq->cmd_) {
case Step::Cmd::sentinel:
assert(false); // unreachable
break;
case Step::Cmd::make_nil:
// TODO combine with code in random_object_graph()
{
is_alloc = true;
xi = ListOps::nil();
alloc_z = 0; // not in gcos space
tseq = typeseq::id<DList>();
xi2 = ListOps::nil();
REQUIRE(xi._typeseq() == tseq);
REQUIRE(xi2._typeseq() == tseq);
}
break;
case Step::Cmd::make_cons:
// TODO combine with code in random_object_graph()
{
auto h1 = p_x1_v->at(cmd_seq->arg0_ix_).gco_;
auto r1 = obj<AGCObject,DList>::from(p_x1_v->at(cmd_seq->arg1_ix_).gco_);
auto h2 = p_x2_v->at(cmd_seq->arg0_ix_).gco_;
auto r2 = obj<AGCObject,DList>::from(p_x2_v->at(cmd_seq->arg1_ix_).gco_);
is_alloc = true;
xi = ListOps::cons(alloc, h1, r1);
alloc_z = sizeof(DList);
tseq = typeseq::id<DList>();
xi2 = ListOps::cons(alloc2, h2, r2);
}
break;
case Step::Cmd::make_bool:
// TODO combine with code in random_object_graph()
{
bool value = (cmd_seq->arg0_ix_ > 0);
is_alloc = true;
xi = DBoolean::box(alloc, value);
alloc_z = sizeof(DBoolean);
tseq = typeseq::id<DBoolean>();
xi2 = DBoolean::box(alloc2, value);
}
break;
case Step::Cmd::assign_head:
{
is_alloc = false;
auto lhs1 = obj<AGCObject,DList>::from(p_x1_v->at(cmd_seq->arg0_ix_).gco_);
auto rhs1 = p_x2_v->at(cmd_seq->arg1_ix_).gco_;
auto lhs2 = obj<AGCObject,DList>::from(p_x2_v->at(cmd_seq->arg0_ix_).gco_);
auto rhs2 = p_x2_v->at(cmd_seq->arg1_ix_).gco_;
assert(lhs1);
assert(!lhs1->is_empty());
assert(lhs2);
assert(!lhs2->is_empty());
assert(p_mls);
assert(mockgc);
lhs1->assign_head(mockgc, rhs1);
// alloc2 is ord arena -> no mlog
}
break;
}
if (is_alloc) {
p_x1_v->push_back(Recd(xi, alloc_z, tseq));
p_x2_v->push_back(Recd(xi2, alloc_z, tseq));
}
++cmd_seq;
}
} else {
switch (obj_graph_type) {
case TestGraphType::selfcycle:
if (loop_index == 0) {
@ -342,6 +449,7 @@ namespace ut {
GcosTestutil::random_object_graph(n_test_obj,
n_test_assign,
debug_flag,
p_rgen,
p_x1_v,
p_gcos,
@ -350,6 +458,7 @@ namespace ut {
}
break;
}
}
//x1_v.push_back(Recd(DBoolean::box(alloc, true),
// sizeof(DBoolean),
@ -402,6 +511,8 @@ namespace ut {
for (size_t i = 0, n = x1_v.size(); i < n; ++i) {
const auto & x1 = x1_v.at(i);
// x1 could be a global, such as ListOps::nil()
if (x1.alloc_z_ > 0) {
REQUIRE(gcos.contains_allocated(Role::to_space(), x1.gco_.data()));
AllocInfo obj_info = gcos.alloc_info((std::byte *)x1.gco_.data());
REQUIRE(obj_info.size() >= x1.alloc_z_);
@ -414,6 +525,10 @@ namespace ut {
REQUIRE(gcos.header2age(obj_info.header()) <= object_age{loop_index});
REQUIRE(gcos.header2tseq(obj_info.header()) == obj_info.tseq());
REQUIRE(gcos.is_forwarding_header(obj_info.header()) == false);
} else {
REQUIRE(!gcos.contains(Role::to_space(), x1.gco_.data()));
REQUIRE(!gcos.contains(Role::from_space(), x1.gco_.data()));
}
}
}
@ -431,11 +546,13 @@ namespace ut {
INFO(tostr(xtag("gi", gi)));
if (loop_index == 0) {
if ((gi == 0) && (x1_v.size() > 0))
REQUIRE(gcos.to_space(gi)->allocated() > 0);
else
if ((gi == 0) && (x1_v.size() > 0)) {
// conceivable that x1_v[] only contains non-gco objects
//REQUIRE(gcos.to_space(gi)->allocated() > 0);
} else {
REQUIRE(gcos.to_space(gi)->allocated() == 0);
}
}
REQUIRE(gcos.from_space(gi)->allocated() == 0);
}
@ -473,6 +590,7 @@ namespace ut {
// x1 should be in gen g from-space (with g < upto)
// or in gen g to-space (with g >= upto)
if (x1.alloc_z_ > 0) {
Generation g_from = gcos.generation_of(Role::from_space(), x1.gco_.data());
Generation g_to = gcos.generation_of(Role::to_space(), x1.gco_.data());
@ -501,6 +619,10 @@ namespace ut {
REQUIRE(obj_info.payload().first == (std::byte *)x1.gco_.data());
REQUIRE(obj_info.tseq() == x1.tseq_.seqno());
} else {
REQUIRE(!gcos.contains(Role::to_space(), x1.gco_.data()));
REQUIRE(!gcos.contains(Role::from_space(), x1.gco_.data()));
}
}
}
@ -603,6 +725,7 @@ namespace ut {
const Recd & x1,
obj<AGCObject> x1_gco)
{
if (x1.alloc_z_ > 0) {
REQUIRE((gcos.contains_allocated(Role::from_space(), x1_gco.data())
|| gcos.contains_allocated(Role::to_space(), x1_gco.data())));
AllocInfo obj_info = gcos.alloc_info((std::byte *)x1_gco.data());
@ -626,15 +749,16 @@ namespace ut {
// if (!obj_info.is_forwarding_tseq())
// print_backtrace_dwarf(true /*demangle*/);
// REQUIRE(obj_info.is_forwarding_tseq());
}
}
void
GcosTestutil::gcos_verify_forwarding_destination(const GCObjectStore & gcos,
const Recd & x1,
obj<AGCObject> x1p_gco)
{
if (x1.alloc_z_ > 0) {
REQUIRE(gcos.contains_allocated(Role::to_space(), x1p_gco.data()));
AllocInfo obj1p_info = gcos.alloc_info((std::byte *)x1p_gco.data());
REQUIRE(obj1p_info.size() >= x1.alloc_z_);
@ -646,6 +770,7 @@ namespace ut {
REQUIRE(gcos.contains(Role::to_space(), x1p_gco.data()));
REQUIRE(gcos.contains_allocated(Role::to_space(), x1p_gco.data()));
}
}
void
GcosTestutil::gcos_verify_forwarded_ab_equivalence(obj<AGCObject> x1p_gco,

View file

@ -5,6 +5,7 @@
#pragma once
#include <xo/gc/MutationLogStore.hpp>
#include <xo/gc/GCObjectStore.hpp>
#include <xo/alloc2/GCObject.hpp>
#include <xo/alloc2/Generation.hpp>
@ -15,7 +16,39 @@ namespace ut {
using xo::mm::Generation;
using xo::facet::obj;
/** specify a step in scripted sequence
**/
struct Step {
enum class Cmd {
/** sentinel for end of sequence **/
sentinel,
/** refer to nil DList **/
make_nil,
/** allocate DList w/ head x1_v[arg0_ix_], rest x1_v[arg1_ix_] **/
make_cons,
/** allocate a boolean **/
make_bool,
/** modify the head of a list x1_v[arg0_ix_]; replace with x1_v[arg1_ix_] **/
assign_head,
};
Step(Cmd cmd, uint32_t arg0, uint32_t arg1)
: cmd_{cmd}, arg0_ix_{arg0}, arg1_ix_{arg1} {}
bool is_sentinel() const { return cmd_ == Cmd::sentinel; }
bool is_command() const { return cmd_ != Cmd::sentinel; }
Cmd cmd_;
/** arg0 object index (index into x1_v[]) **/
uint32_t arg0_ix_;
/** arg1 object index (index into x1_v[]) **/
uint32_t arg1_ix_;
};
enum class TestGraphType {
/* spelled out sequence of Steps */
fixed,
/* list cell pointing to itself */
selfcycle,
/* random object graph */
@ -40,6 +73,7 @@ namespace ut {
};
struct GcosTestutil {
using MutationLogStore = xo::mm::MutationLogStore;
using GCObjectStore = xo::mm::GCObjectStore;
using AGCObject = xo::mm::AGCObject;
using DArena = xo::mm::DArena;
@ -54,6 +88,7 @@ namespace ut {
static void
random_object_graph(uint32_t n_new_obj,
uint32_t n_assign,
bool debug_flag,
xoshiro256ss * p_rgen,
std::vector<Recd> * p_v,
GCObjectStore * p_gcos,
@ -74,12 +109,17 @@ namespace ut {
size_t gc_size,
const GCObjectStore & gcos);
/** sequence of steps. if non-null, ends with step s: s.cmd_ == Step::Cmd::Sentinel
**/
static void
gcos_construct_ab_object_graphs(TestGraphType obj_graph_type,
gcos_construct_ab_object_graphs(Step * cmd_seq,
TestGraphType obj_graph_type,
uint32_t n_i0_test_obj,
uint32_t n_i0_test_assign,
uint32_t n_i1_test_obj,
uint32_t n_i1_test_assign,
bool debug_flag,
MutationLogStore * p_mls,
GCObjectStore * p_gcos,
DArena * p_arena2,
uint32_t loop_index,

View file

@ -0,0 +1,106 @@
/** @file ICollector_DMockCollector.cpp
*
* Generated automagically from ingredients:
* 1. code generator:
* [xo-facet/codegen/genfacet]
* arguments:
* --input [idl/ICollector_DMockCollector.json5]
* 2. jinja2 template for abstract facet .hpp file:
* [iface_facet_any.hpp.j2]
* 3. idl for facet methods
* [idl/ICollector_DMockCollector.json5]
**/
#include "detail/ICollector_DMockCollector.hpp"
namespace xo {
namespace mm {
auto
ICollector_DMockCollector::allocated(const DMockCollector & self, Generation g, Role r) noexcept -> size_type
{
return self.allocated(g, r);
}
auto
ICollector_DMockCollector::committed(const DMockCollector & self, Generation g, Role r) noexcept -> size_type
{
return self.committed(g, r);
}
auto
ICollector_DMockCollector::reserved(const DMockCollector & self, Generation g, Role r) noexcept -> size_type
{
return self.reserved(g, r);
}
auto
ICollector_DMockCollector::locate_address(const DMockCollector & self, const void * addr) noexcept -> std::int32_t
{
return self.locate_address(addr);
}
auto
ICollector_DMockCollector::contains(const DMockCollector & self, Role r, const void * addr) noexcept -> bool
{
return self.contains(r, addr);
}
auto
ICollector_DMockCollector::is_type_installed(const DMockCollector & self, typeseq tseq) noexcept -> bool
{
return self.is_type_installed(tseq);
}
auto
ICollector_DMockCollector::report_statistics(const DMockCollector & self, obj<AAllocator> report_mm, obj<AAllocator> error_mm, obj<AGCObject> * output) noexcept -> bool
{
return self.report_statistics(report_mm, error_mm, output);
}
auto
ICollector_DMockCollector::report_object_types(const DMockCollector & 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_DMockCollector::report_object_ages(const DMockCollector & self, obj<AAllocator> report_mm, obj<AAllocator> error_mm, obj<AGCObject> * output) noexcept -> bool
{
return self.report_object_ages(report_mm, error_mm, output);
}
auto
ICollector_DMockCollector::install_type(DMockCollector & self, const AGCObject & iface) -> bool
{
return self.install_type(iface);
}
auto
ICollector_DMockCollector::add_gc_root_poly(DMockCollector & self, obj<AGCObject> * p_root) -> void
{
self.add_gc_root_poly(p_root);
}
auto
ICollector_DMockCollector::remove_gc_root_poly(DMockCollector & self, obj<AGCObject> * p_root) -> void
{
self.remove_gc_root_poly(p_root);
}
auto
ICollector_DMockCollector::request_gc(DMockCollector & self, Generation upto) -> void
{
self.request_gc(upto);
}
auto
ICollector_DMockCollector::assign_member(DMockCollector & self, void * parent, obj<AGCObject> * p_lhs, obj<AGCObject> & rhs) -> void
{
self.assign_member(parent, p_lhs, rhs);
}
auto
ICollector_DMockCollector::alloc_copy(DMockCollector & self, std::byte * src) -> void *
{
return self.alloc_copy(src);
}
} /*namespace mm*/
} /*namespace xo*/
/* end ICollector_DMockCollector.cpp */

110
utest/MlsTestutil.cpp Normal file
View file

@ -0,0 +1,110 @@
/** @file MlsTestutil.cpp
*
* @author Roland Conybeare, Apr 2026
**/
#include "MlsTestutil.hpp"
#include <catch2/catch.hpp>
namespace ut {
using xo::mm::GCObjectStore;
using xo::mm::MutationLog;
using xo::mm::MutationLogEntry;
using xo::mm::AllocInfo;
using xo::mm::Role;
using xo::mm::Generation;
using xo::mm::c_max_generation;
void
MlsTestutil::verify_fromspace_only_logged(const MutationLogStore & mls,
Generation upto)
{
for (Generation gi{0}; gi < std::min(upto, Generation(c_max_generation - 1)); ++gi) {
// from-space mlog may be empty or not
// after swapping roles at beginning of GC,
// to-space mlog must be empty
{
const MutationLog * mlog = mls.get_mlog(Role::to_space(), gi);
REQUIRE(mlog->empty());
}
// triage mlog must be empty at beginning of GC phase
{
const MutationLog * mlog = mls.triage_mlog(gi);
REQUIRE(mlog->empty());
}
}
}
void
MlsTestutil::verify_tospace_only_logged(const MutationLogStore & mls,
Generation upto)
{
for (Generation gi{0}; gi < std::min(upto, Generation(c_max_generation - 1)); ++gi) {
// to-space mlog may be empty or not
// from-space mlog must be empty in all generations.
// (only non-empty in GC phase, before GC completes)
{
const MutationLog * mlog = mls.get_mlog(Role::from_space(), gi);
REQUIRE(mlog->empty());
}
// traige mlog must be empty in all generations
// (only non-empty in GC phase, before GC completes)
{
const MutationLog * mlog = mls.triage_mlog(gi);
REQUIRE(mlog->empty());
}
}
}
void
MlsTestutil::verify_mlog_load_bearing(const MutationLogStore & mls,
Generation upto)
{
// reminders:
// - pointers from non-gc-owned objects permitted only from root objects.
// Such source objects are visited on every collection and don't need (or get)
// mlog entries. Exclude from consideration here.
// - Similarly pointers to non-gco-owned objects also don't need mlog entries.
const GCObjectStore & gcos = *mls.gco_store_;
for (Generation gi{0}; gi < std::min(upto, Generation(c_max_generation - 1)); ++gi) {
const MutationLog * mlog = mls.get_mlog(Role::to_space(), gi);
for (const MutationLogEntry & entry : *mlog) {
REQUIRE(entry.parent());
REQUIRE(entry.p_data());
REQUIRE(entry.snap());
if (entry.is_active()) {
AllocInfo src_info = gcos.alloc_info((std::byte *)entry.parent());
void * dest = *entry.p_data();
AllocInfo dest_info = gcos.alloc_info((std::byte *)*entry.p_data());
// source and destination must both be in to-space
REQUIRE(gcos.contains_allocated(Role::to_space(), entry.parent()));
REQUIRE(gcos.contains_allocated(Role::to_space(), *entry.p_data()));
// either:
// 1. source in older generation than destination,
// (so destination may move under incremental collection,
// while parent generation stays put)
// 2. source may eventually promote to older generation,
// before destination.
//
// otherwise pointer does not require and should not have
// a mutation log entry
//
REQUIRE(src_info.age() > dest_info.age());
}
}
}
}
} /*namespace ut*/
/* end MlsTestutil.cpp */

27
utest/MlsTestutil.hpp Normal file
View file

@ -0,0 +1,27 @@
/** @file MlsTestutil.hpp
*
* @author Roland Conybeare, Apr 2026
**/
#pragma once
#include <xo/gc/MutationLogStore.hpp>
namespace ut {
class MlsTestutil {
public:
using MutationLogStore = xo::mm::MutationLogStore;
using Generation = xo::mm::Generation;
static void verify_fromspace_only_logged(const MutationLogStore & mls,
Generation upto);
static void verify_tospace_only_logged(const MutationLogStore & mls,
Generation upto);
/** verify that each mutation log entry is either:
* 1. invalid. cached destination no longer current
* 2. necessary: source age > dest age
**/
static void verify_mlog_load_bearing(const MutationLogStore & mls,
Generation upto);
};
}

11
utest/MockCollector.hpp Normal file
View file

@ -0,0 +1,11 @@
/** @file MockCollector.hpp
*
* @author Roland Conybeare, Apr 2026
**/
#pragma once
#include "DMockCollector.hpp"
#include "detail/ICollector_DMockCollector.hpp"
/* end MockCollector.hpp */

View file

@ -3,7 +3,12 @@
* @author Roland Conybeare, Apr 2026
**/
#include "GcosTestutil.hpp"
#include "MlsTestutil.hpp"
#include <xo/object2/List.hpp>
#include <xo/object2/Boolean.hpp>
#include <xo/gc/GCObjectStore.hpp>
#include <xo/gc/GCObjectStoreVisitor.hpp>
#include <xo/gc/MutationLogStore.hpp>
#include <xo/gc/X1VerifyStats.hpp>
#include <xo/indentlog/scope.hpp>
@ -12,28 +17,55 @@
#include <catch2/catch.hpp>
namespace ut {
using xo::scm::DList;
using xo::scm::DBoolean;
using xo::mm::MutationLogStore;
using xo::mm::MutationLogConfig;
using xo::mm::GCObjectStore;
using xo::mm::GCObjectStoreConfig;
using xo::mm::DGCObjectStoreVisitor;
using xo::mm::DArena;
using xo::mm::ArenaConfig;
using xo::mm::X1VerifyStats;
using xo::rng::xoshiro256ss;
using xo::rng::random_seed;
using xo::reflect::typeseq;
using xo::xtag;
using xo::scope;
namespace {
enum class TestGraphType {
/* list cell pointing to itself */
selfcycle,
/* random object graph */
random,
};
struct Testcase {
explicit Testcase(bool debug_flag) : debug_flag_{debug_flag} {}
explicit Testcase(uint32_t n_gen,
uint32_t n_survive,
size_t gc_z,
uint32_t type_z,
bool do_type_registration,
Step * cmd_seq,
uint32_t mlog_z,
bool mlog_enabled_flag,
TestGraphType obj_graph_type,
uint32_t n_gc_loop,
uint32_t n_i0_test_obj,
uint32_t n_i0_test_assign,
uint32_t n_i1_test_obj,
uint32_t n_i1_test_assign,
bool debug_flag)
: n_gen_{n_gen},
n_survive_{n_survive},
gc_size_{gc_z},
object_type_z_{type_z},
do_type_registration_{do_type_registration},
mutation_log_z_{mlog_z},
mlog_enabled_flag_{mlog_enabled_flag},
cmd_seq_{cmd_seq},
obj_graph_type_{obj_graph_type},
n_gc_loop_{n_gc_loop},
n_i0_test_obj_{n_i0_test_obj},
n_i0_test_assign_{n_i0_test_assign},
n_i1_test_obj_{n_i1_test_obj},
n_i1_test_assign_{n_i1_test_assign},
debug_flag_{debug_flag}
{}
/** number of generations in gco store **/
uint32_t n_gen_ = 0;
@ -51,17 +83,19 @@ namespace ut {
* (load-bearing for incremental gc)
**/
bool mlog_enabled_flag_ = false;
/** first loop: explicit cell alloc/assign **/
Step * cmd_seq_ = nullptr;
/** object graph type **/
TestGraphType obj_graph_type_ = TestGraphType::random;
/** #of gc-like "move all the roots" phases to perform **/
uint32_t n_gc_loop_ = 0;
/** first loop: #of cells in random object graph **/
/** 2nd loop: #of cells in random object graph **/
uint32_t n_i0_test_obj_ = 0;
/** first loop: #of random assignments to attempt **/
/** 2nd loop: #of random assignments to attempt **/
uint32_t n_i0_test_assign_ = 0;
/** 2nd+later loop: #of cells in random object graph **/
/** 3rd+later loop: #of cells in random object graph **/
uint32_t n_i1_test_obj_ = 0;
/** 2nd+later loop: #of random assignments to attempt **/
/** 3rd+later loop: #of random assignments to attempt **/
uint32_t n_i1_test_assign_ = 0;
/** true to enable debug when attempting this test case **/
bool debug_flag_;
@ -69,28 +103,91 @@ namespace ut {
constexpr TestGraphType c_selfcycle = TestGraphType::selfcycle;
constexpr TestGraphType c_random = TestGraphType::random;
constexpr uint32_t c_report_z1 = 64 * 1024;
constexpr uint32_t c_error_z1 = 16 * 1024;
constexpr TestGraphType c_fixed = TestGraphType::fixed;
using Cmd = Step::Cmd;
static Step seq0[] = {
{Cmd::make_bool, 0, 0}, // #f
{Cmd::make_nil, 0, 0}, // #nil
{Cmd::make_cons, 0, 1}, // cons(#f,#nil)
{Cmd::sentinel, 0, 0},
};
static Step seq1[] = {
{Cmd::make_bool, 0, 0}, // #f
{Cmd::make_bool, 1, 0}, // #t
{Cmd::make_nil, 0, 0}, // #nil
{Cmd::make_cons, 0, 2}, // cons(#f,#nil)
{Cmd::assign_head, 3, 1}, // set-car(cons(#f,#nil),#t)
{Cmd::sentinel, 0, 0},
};
# define nil nullptr
# define T true
# define F false
static std::vector<Testcase> s_testcase_v = {
/**
* debug_flag
* n_i1_test_assign |
* n_i1_test_obj | |
* n_i0_test_assign | | |
* n_i0_test_obj | | | |
* n_gc_loop | | | | |
* obj_graph_type | | | | | |
* mlog_enabled_flag | | | | | | |
* mutation_log_z | | | | | | | |
* cmd_seq | | | | | | | | |
* do_type_registration | | | | | | | | | |
* n_survive object_type_z | | | | | | | | | | |
* n_gen | gc_size | | | | | | | | | | | |
* v v v v v v v v v v v v v v v
**/
Testcase(2, 4, 16 * 1024, 8 * 128, F, nil, 0, F, c_random, 1, 0, 0, 0, 0, F),
Testcase(2, 4, 16 * 1024, 8 * 128, T, nil, 0, F, c_selfcycle, 1, 1, 0, 0, 0, F),
Testcase(2, 4, 16 * 1024, 8 * 128, T, seq0, 0, F, c_fixed, 1, 0, 0, 0, 0, F),
Testcase(2, 4, 16 * 1024, 8 * 128, T, seq1, 0, F, c_fixed, 1, 0, 0, 0, 0, T),
};
# undef T
# undef F
/** Fixture for MutationLogStore-1 test.
* Compare similar but not identical fixture in GCObjectStore.test.cpp
**/
class MlsFixture {
public:
explicit MlsFixture(const Testcase &);
/** configuration for @ref gcos_ **/
GCObjectStoreConfig gcos_config_;
/** configuration for @ref mls_ **/
MutationLogConfig mls_config_;
/** Parallel arena for reference
*
* We will allocate parallel object model in this arena
* for reference; then compare with GCObjectStore behavior.
*
* 1. arena2 doesn't have any generation layer cake stuff.
* all objects are in one place
* 2. arena2 doesn't have concept of installed types.
* It doesn't have or require any builtin ability to traverse an object model,
* storage recovery strategy is O(1) "clear the whole arena".
**/
DArena arena2_;
/** statistics called by GCObjectStore.verify_ok() **/
X1VerifyStats verify_stats_;
/** holds objects in multiple generations.
**/
GCObjectStore gcos_;
/**
* mutation log store tracks pointers
* from older objects to younger objects,
* which can only be created by mutation
**/
MutationLogStore mls_;
};
@ -107,6 +204,9 @@ namespace ut {
tc.mutation_log_z_,
tc.mlog_enabled_flag_,
tc.debug_flag_},
arena2_{DArena::map(ArenaConfig().with_name("arena2-ref")
.with_size(tc.gc_size_ * tc.n_gen_)
.with_store_header_flag(true))},
gcos_{gcos_config_, &verify_stats_},
mls_{mls_config_, &gcos_}
{}
@ -134,17 +234,127 @@ namespace ut {
MlsFixture fixture(tc);
// TODO:
// 1. move GCObjectStore.test.cpp
// shared code to separate .*pp files
// - gcos_testutil.*pp
// unlike GCObjectStore, separate init.
//
// 2. add mutation log tests. Entry points
// - init_mlogs()
// - verify_ok()
// - assign_member()
// - swap_roles()
// - forward_mutation_log()
// TODO: adopt GCObjectStore pattern
//
fixture.mls_.init_mlogs(getpagesize());
{
// updates counters in fixture.verify_stats_
fixture.gcos_.verify_ok();
fixture.mls_.verify_ok();
INFO(tostr(xtag("n_gc_root", fixture.verify_stats_.n_gc_root_),
xtag("n_ext", fixture.verify_stats_.n_ext_),
xtag("n_from", fixture.verify_stats_.n_from_),
xtag("n_to", fixture.verify_stats_.n_to_)));
INFO(tostr(xtag("n_fwd", fixture.verify_stats_.n_fwd_),
xtag("n_age_ok", fixture.verify_stats_.n_age_ok_),
xtag("n_age_bad", fixture.verify_stats_.n_age_bad_),
xtag("n_no_iface", fixture.verify_stats_.n_no_iface_)));
REQUIRE(fixture.verify_stats_.is_ok());
}
GCObjectStore & gcos = fixture.gcos_;
MutationLogStore & mls = fixture.mls_;
{
// gcos setup. parallels GCObjectStore.test.cpp
{
REQUIRE(gcos.is_type_installed(typeseq::id<DList>()) == false);
REQUIRE(gcos.is_type_installed(typeseq::id<DBoolean>()) == false);
GcosTestutil::gcos_install_test_types(tc.do_type_registration_, &gcos);
GcosTestutil::gcos_verify_arena_partitioning(tc.n_gen_, tc.gc_size_, gcos);
GcosTestutil::gcos_verify_vacant(tc.n_gen_, tc.gc_size_, gcos);
}
}
/** mutator/collector loop **/
/** parallel {test,reference} object state.
*
**/
std::vector<Recd> x1_v;
std::vector<Recd> x2_v;
for (uint32_t loop_index = 0; loop_index < tc.n_gc_loop_; ++loop_index) {
scope log2(XO_DEBUG(tc.debug_flag_), "gc loop", xtag("loop_index", loop_index));
GcosTestutil::gcos_construct_ab_object_graphs(tc.cmd_seq_,
tc.obj_graph_type_,
tc.n_i0_test_obj_,
tc.n_i0_test_assign_,
tc.n_i1_test_obj_,
tc.n_i1_test_assign_,
tc.debug_flag_,
&mls,
&gcos,
&fixture.arena2_,
loop_index,
&x1_v, &x2_v,
&rgen);
Generation gk = Generation::g1();
// no allocation errors
REQUIRE(gcos.last_error().error_ == xo::mm::error::ok);
GcosTestutil::gcos_verify_consistency(&gcos);
// someday: print the graph. Need a cycle-detecting printer
GcosTestutil::gcos_verify_ab_equivalence(x1_v, x2_v);
GcosTestutil::gcos_verify_allocinfo(gcos, loop_index, x1_v);
GcosTestutil::gcos_verify_gen0_only_allocated(tc.n_gen_, gcos, loop_index, x1_v);
// swap roles for generations g < gk
gcos.swap_roles(gk);
mls.swap_roles(gk);
GcosTestutil::gcos_verify_gen0_fromspace_only_allocated(tc.n_gen_, gcos, loop_index,
gk, x1_v);
// gc core: move stuff
GcosTestutil::gcos_move_roots_and_verify(tc.do_type_registration_,
&gcos,
gk, x1_v, x2_v, tc.debug_flag_);
DGCObjectStoreVisitor visitor(&gcos, gk);
// after swapping roles only from-space mlog can be non-empty
MlsTestutil::verify_fromspace_only_logged(mls, gk);
// forward mutation log + mutation-rescued objects
mls.forward_mutation_log(visitor.ref(), gk);
// now only to-space mlog can be non-empty
MlsTestutil::verify_tospace_only_logged(mls, gk);
MlsTestutil::verify_mlog_load_bearing(mls, gk);
// Might expect scanning generation g >= gk to confirm each object refs only to-space.
//
// reset (+ perhaps clean) from-space
{
// TODO: consider moving sanitize_flag to Testcase
bool sanitize_flag = true;
gcos.cleanup_phase(gk, sanitize_flag);
}
// scan {gcos, mls} to collect counters in *gcos.verify_stats()
{
gcos.verify_stats()->clear();
gcos.verify_ok();
mls.verify_ok();
REQUIRE(gcos.verify_stats()->is_ok());
}
}
}
}

View file

@ -0,0 +1,120 @@
/** @file ICollector_DMockCollector.hpp
*
* Generated automagically from ingredients:
* 1. code generator:
* [xo-facet/codegen/genfacet]
* arguments:
* --input [idl/ICollector_DMockCollector.json5]
* 2. jinja2 template for abstract facet .hpp file:
* [iface_facet_repr.hpp.j2]
* 3. idl for facet methods
* [idl/ICollector_DMockCollector.json5]
**/
#pragma once
#include "Collector.hpp"
#include "../DMockCollector.hpp"
namespace xo { namespace mm { class ICollector_DMockCollector; } }
namespace xo {
namespace facet {
template <>
struct FacetImplementation<xo::mm::ACollector,
xo::mm::DMockCollector>
{
using ImplType = xo::mm::ICollector_Xfer
<xo::mm::DMockCollector,
xo::mm::ICollector_DMockCollector>;
};
}
}
namespace xo {
namespace mm {
/** @class ICollector_DMockCollector
**/
class ICollector_DMockCollector {
public:
/** @defgroup mm-collector-dmockcollector-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-dmockcollector-methods **/
///@{
// const methods
/** memory in use for this collector **/
static size_type allocated(const DMockCollector & self, Generation g, Role r) noexcept;
/** memory committed for this collector **/
static size_type committed(const DMockCollector & self, Generation g, Role r) noexcept;
/** address space reserved for this collector **/
static size_type reserved(const DMockCollector & self, Generation g, Role r) noexcept;
/** Location of object in collector. -1 if not in collector memory.
Other negative values represent collector error states (good luck!).
Exact meaning of non-negative values up to collector implementation **/
static std::int32_t locate_address(const DMockCollector & self, const void * addr) noexcept;
/** true if gc responsible for data at @p addr, and data belongs to Role @p r **/
static bool contains(const DMockCollector & 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 DMockCollector & 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<AGCObject> return type to avoid #include cycle **/
static bool report_statistics(const DMockCollector & 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 DMockCollector & self, obj<AAllocator> report_mm, obj<AAllocator> error_mm, obj<AGCObject> * output) noexcept;
/** Report gc object ages, at discretion of collector implementation.
Creates array of dictionaries using memory from @p report_mm.
Each dictionary has keys n-live and bytes, indexed by object age.
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_ages(const DMockCollector & 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
in collector @p d.
The type AGCObject_Any here is misleading.
Will have been replaced by an instance of
@c AGCObject_Xfer<DFoo,AGCObject_DFoo> for some @c DFoo
in which case calls through @c std::launder(&iface)
will properly act on @c DFoo.
Return false if installation fails (e.g. memory exhausted) **/
static bool install_type(DMockCollector & 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(DMockCollector & self, obj<AGCObject> * 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(DMockCollector & self, obj<AGCObject> * 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(DMockCollector & self, Generation upto);
/** Assign pointer @p p_lhs to destination @p rhs, within parent allocation @p parent
Require: gc not in progress **/
static void assign_member(DMockCollector & self, void * parent, obj<AGCObject> * p_lhs, obj<AGCObject> & rhs);
/** allocate copy of source object at address @p src.
Source must be owned by this collector.
Increments object age **/
static void * alloc_copy(DMockCollector & self, std::byte * src);
///@}
};
} /*namespace mm*/
} /*namespace xo*/
/* end */

View file

@ -0,0 +1,24 @@
{
mode: "implementation",
output_cpp_dir: ".",
output_hpp_dir: ".",
output_impl_subdir: "detail",
includes: [
// "<xo/alloc2/GCObject.hpp>",
// "<xo/alloc2/Allocator.hpp>"
],
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 DMockCollector",
using_doxygen: true,
repr: "DMockCollector",
doc: [ "implement ACollector for DMockCollector" ],
}

View file

@ -4,7 +4,7 @@
**/
#include "init_gc_utest.hpp"
//#include "MockCollector.hpp"
#include "MockCollector.hpp"
#include <xo/gc/init_gc.hpp>
#include <xo/facet/FacetRegistry.hpp>
#include <xo/indentlog/scope.hpp>
@ -19,7 +19,7 @@ namespace xo {
{
scope log(XO_DEBUG(false));
//FacetRegistry::register_impl<AGCObjectVisitor, DMockCollector>();
FacetRegistry::register_impl<ACollector, DMockCollector>();
//log && log(xtag("DMockCollector.tseq", typeseq::id<DMockCollector>()));