xo-gc: utest: mutation log tests
This commit is contained in:
parent
f042850cec
commit
39b1ad1b21
21 changed files with 1155 additions and 136 deletions
|
|
@ -46,4 +46,4 @@ namespace xo {
|
|||
} /*namespace mm*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end DGCObjectVisitor.hpp */
|
||||
/* end DGCObjectStoreVisitor.hpp */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
**/
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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*
|
||||
|
|
|
|||
|
|
@ -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
106
utest/DMockCollector.cpp
Normal 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
65
utest/DMockCollector.hpp
Normal 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 */
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,34 +331,133 @@ namespace ut {
|
|||
std::vector<Recd> * p_x2_v,
|
||||
xoshiro256ss * p_rgen)
|
||||
{
|
||||
switch (obj_graph_type) {
|
||||
case TestGraphType::selfcycle:
|
||||
if (loop_index == 0) {
|
||||
GcosTestutil::selfcycle_object_graph(p_x1_v,
|
||||
p_gcos,
|
||||
p_x2_v,
|
||||
p_arena2);
|
||||
}
|
||||
break;
|
||||
if (cmd_seq && (loop_index == 0)) {
|
||||
// do scripted sequence only
|
||||
|
||||
case TestGraphType::random:
|
||||
{
|
||||
uint32_t n_test_obj = ((loop_index == 0)
|
||||
? n_i0_test_obj
|
||||
: n_i1_test_obj);
|
||||
uint32_t n_test_assign = ((loop_index == 0)
|
||||
? n_i0_test_assign
|
||||
: n_i1_test_assign);
|
||||
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);
|
||||
|
||||
GcosTestutil::random_object_graph(n_test_obj,
|
||||
n_test_assign,
|
||||
p_rgen,
|
||||
p_x1_v,
|
||||
p_gcos,
|
||||
p_x2_v,
|
||||
p_arena2);
|
||||
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) {
|
||||
GcosTestutil::selfcycle_object_graph(p_x1_v,
|
||||
p_gcos,
|
||||
p_x2_v,
|
||||
p_arena2);
|
||||
}
|
||||
break;
|
||||
|
||||
case TestGraphType::random:
|
||||
{
|
||||
uint32_t n_test_obj = ((loop_index == 0)
|
||||
? n_i0_test_obj
|
||||
: n_i1_test_obj);
|
||||
uint32_t n_test_assign = ((loop_index == 0)
|
||||
? n_i0_test_assign
|
||||
: n_i1_test_assign);
|
||||
|
||||
GcosTestutil::random_object_graph(n_test_obj,
|
||||
n_test_assign,
|
||||
debug_flag,
|
||||
p_rgen,
|
||||
p_x1_v,
|
||||
p_gcos,
|
||||
p_x2_v,
|
||||
p_arena2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//x1_v.push_back(Recd(DBoolean::box(alloc, true),
|
||||
|
|
@ -402,18 +511,24 @@ namespace ut {
|
|||
for (size_t i = 0, n = x1_v.size(); i < n; ++i) {
|
||||
const auto & x1 = x1_v.at(i);
|
||||
|
||||
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_);
|
||||
// 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_);
|
||||
|
||||
REQUIRE(obj_info.payload().first == (std::byte *)x1.gco_.data());
|
||||
REQUIRE(obj_info.tseq() == x1.tseq_.seqno());
|
||||
REQUIRE(obj_info.payload().first == (std::byte *)x1.gco_.data());
|
||||
REQUIRE(obj_info.tseq() == x1.tseq_.seqno());
|
||||
|
||||
// also can use header2size / header2tseq convenience functions
|
||||
REQUIRE(gcos.header2size(obj_info.header()) == obj_info.size());
|
||||
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);
|
||||
// also can use header2size / header2tseq convenience functions
|
||||
REQUIRE(gcos.header2size(obj_info.header()) == obj_info.size());
|
||||
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,10 +546,12 @@ 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,34 +590,39 @@ namespace ut {
|
|||
// x1 should be in gen g from-space (with g < upto)
|
||||
// or in gen g to-space (with g >= upto)
|
||||
|
||||
Generation g_from = gcos.generation_of(Role::from_space(), x1.gco_.data());
|
||||
Generation g_to = gcos.generation_of(Role::to_space(), x1.gco_.data());
|
||||
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());
|
||||
|
||||
if (g_to.is_sentinel()) {
|
||||
// if not in to-space, must be in from-space
|
||||
REQUIRE(!g_from.is_sentinel());
|
||||
if (g_to.is_sentinel()) {
|
||||
// if not in to-space, must be in from-space
|
||||
REQUIRE(!g_from.is_sentinel());
|
||||
|
||||
// + for some gen we're collecting
|
||||
REQUIRE(g_from < upto);
|
||||
// + for some gen we're collecting
|
||||
REQUIRE(g_from < upto);
|
||||
|
||||
REQUIRE(gcos.contains(Role::from_space(), x1.gco_.data()));
|
||||
REQUIRE(gcos.contains_allocated(Role::from_space(), x1.gco_.data()));
|
||||
REQUIRE(gcos.contains(Role::from_space(), x1.gco_.data()));
|
||||
REQUIRE(gcos.contains_allocated(Role::from_space(), x1.gco_.data()));
|
||||
} else {
|
||||
// if in to-space, must not be in from-space
|
||||
REQUIRE(g_from.is_sentinel());
|
||||
|
||||
// + for some gen we're not collecting
|
||||
REQUIRE(g_to >= upto);
|
||||
|
||||
REQUIRE(gcos.contains(Role::to_space(), x1.gco_.data()));
|
||||
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_);
|
||||
|
||||
REQUIRE(obj_info.payload().first == (std::byte *)x1.gco_.data());
|
||||
REQUIRE(obj_info.tseq() == x1.tseq_.seqno());
|
||||
} else {
|
||||
// if in to-space, must not be in from-space
|
||||
REQUIRE(g_from.is_sentinel());
|
||||
|
||||
// + for some gen we're not collecting
|
||||
REQUIRE(g_to >= upto);
|
||||
|
||||
REQUIRE(gcos.contains(Role::to_space(), x1.gco_.data()));
|
||||
REQUIRE(gcos.contains_allocated(Role::to_space(), x1.gco_.data()));
|
||||
REQUIRE(!gcos.contains(Role::to_space(), x1.gco_.data()));
|
||||
REQUIRE(!gcos.contains(Role::from_space(), x1.gco_.data()));
|
||||
}
|
||||
|
||||
AllocInfo obj_info = gcos.alloc_info((std::byte *)x1.gco_.data());
|
||||
REQUIRE(obj_info.size() >= x1.alloc_z_);
|
||||
|
||||
REQUIRE(obj_info.payload().first == (std::byte *)x1.gco_.data());
|
||||
REQUIRE(obj_info.tseq() == x1.tseq_.seqno());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -603,31 +725,32 @@ namespace ut {
|
|||
const Recd & x1,
|
||||
obj<AGCObject> x1_gco)
|
||||
{
|
||||
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());
|
||||
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());
|
||||
|
||||
INFO(tostr(xtag("obj_info.tseq", obj_info.tseq()),
|
||||
xtag("obj_info.tname", TypeRegistry::id2name(typeseq(obj_info.tseq())))));
|
||||
INFO(tostr(xtag("obj_info.tseq", obj_info.tseq()),
|
||||
xtag("obj_info.tname", TypeRegistry::id2name(typeseq(obj_info.tseq())))));
|
||||
|
||||
REQUIRE(obj_info.size() >= x1.alloc_z_);
|
||||
REQUIRE(obj_info.payload().first == (std::byte *)x1_gco.data());
|
||||
REQUIRE(obj_info.size() >= x1.alloc_z_);
|
||||
REQUIRE(obj_info.payload().first == (std::byte *)x1_gco.data());
|
||||
|
||||
if (obj_info.is_forwarding_tseq()) {
|
||||
/* object was forwarded, so got collected */
|
||||
REQUIRE(obj_info.is_forwarding_tseq());
|
||||
} else {
|
||||
/* not forwarded is ok iff in generation g >= upto */
|
||||
if (obj_info.is_forwarding_tseq()) {
|
||||
/* object was forwarded, so got collected */
|
||||
REQUIRE(obj_info.is_forwarding_tseq());
|
||||
} else {
|
||||
/* not forwarded is ok iff in generation g >= upto */
|
||||
|
||||
Generation g = gcos.generation_of(Role::to_space(), x1_gco.data());
|
||||
Generation g = gcos.generation_of(Role::to_space(), x1_gco.data());
|
||||
|
||||
REQUIRE(g >= upto);
|
||||
REQUIRE(g >= upto);
|
||||
}
|
||||
|
||||
// if (!obj_info.is_forwarding_tseq())
|
||||
// print_backtrace_dwarf(true /*demangle*/);
|
||||
// REQUIRE(obj_info.is_forwarding_tseq());
|
||||
}
|
||||
|
||||
// if (!obj_info.is_forwarding_tseq())
|
||||
// print_backtrace_dwarf(true /*demangle*/);
|
||||
|
||||
// REQUIRE(obj_info.is_forwarding_tseq());
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -635,16 +758,18 @@ namespace ut {
|
|||
const Recd & x1,
|
||||
obj<AGCObject> x1p_gco)
|
||||
{
|
||||
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_);
|
||||
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_);
|
||||
|
||||
REQUIRE(obj1p_info.payload().first == (std::byte *)x1p_gco.data());
|
||||
REQUIRE(obj1p_info.tseq() == x1.tseq_.seqno());
|
||||
REQUIRE(obj1p_info.payload().first == (std::byte *)x1p_gco.data());
|
||||
REQUIRE(obj1p_info.tseq() == x1.tseq_.seqno());
|
||||
|
||||
REQUIRE(x1p_gco.data() != nullptr);
|
||||
REQUIRE(gcos.contains(Role::to_space(), x1p_gco.data()));
|
||||
REQUIRE(gcos.contains_allocated(Role::to_space(), x1p_gco.data()));
|
||||
REQUIRE(x1p_gco.data() != nullptr);
|
||||
REQUIRE(gcos.contains(Role::to_space(), x1p_gco.data()));
|
||||
REQUIRE(gcos.contains_allocated(Role::to_space(), x1p_gco.data()));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
106
utest/ICollector_DMockCollector.cpp
Normal file
106
utest/ICollector_DMockCollector.cpp
Normal 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
110
utest/MlsTestutil.cpp
Normal 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
27
utest/MlsTestutil.hpp
Normal 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
11
utest/MockCollector.hpp
Normal 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 */
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
120
utest/detail/ICollector_DMockCollector.hpp
Normal file
120
utest/detail/ICollector_DMockCollector.hpp
Normal 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 */
|
||||
24
utest/idl/ICollector_DMockCollector.json5
Normal file
24
utest/idl/ICollector_DMockCollector.json5
Normal 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" ],
|
||||
}
|
||||
|
|
@ -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>()));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue