xo-ordinaltree: expand unittest + debug logging

This commit is contained in:
Roland Conybeare 2025-12-05 18:38:29 -05:00
commit bd8ca68e7c
4 changed files with 106 additions and 14 deletions

View file

@ -107,7 +107,7 @@ namespace xo {
bool is_dead() const { return false; } bool is_dead() const { return false; }
MutationLogEntry update_parent_moved(IObject * parent_to) const; MutationLogEntry update_parent_moved(IObject * parent_to) const;
void fixup_parent_child_moved(IObject * child_to) { *lhs_ = child_to; } void fixup_parent_child_moved(IObject * child_to);
private: private:
IObject * parent_ = nullptr; IObject * parent_ = nullptr;
@ -341,7 +341,7 @@ namespace xo {
* is recorded in mutation log, * is recorded in mutation log,
* given an object @p parent that contains object pointer @p lhs * given an object @p parent that contains object pointer @p lhs
**/ **/
virtual bool check_write_barrier(IObject * parent, IObject ** lhs, bool may_throw) const final; virtual bool check_write_barrier(const void * parent, const void * const * lhs, bool may_throw) const final;
virtual std::byte * alloc(std::size_t z) final override; virtual std::byte * alloc(std::size_t z) final override;
virtual std::byte * alloc_gc_copy(std::size_t z, const void * src) final override; virtual std::byte * alloc_gc_copy(std::size_t z, const void * src) final override;

View file

@ -58,6 +58,12 @@ namespace xo {
reinterpret_cast<IObject **>(lhs_to)); reinterpret_cast<IObject **>(lhs_to));
} }
void
MutationLogEntry::fixup_parent_child_moved(IObject * child_to)
{
*(this->lhs_) = child_to;
}
GC::GC(const Config & config) GC::GC(const Config & config)
: config_{config} : config_{config}
{ {
@ -508,12 +514,17 @@ namespace xo {
assert(retval); assert(retval);
log && log(xtag("retval", retval));
return retval; return retval;
} }
void void
GC::assign_member(IObject * parent, IObject ** lhs, IObject * rhs) GC::assign_member(IObject * parent, IObject ** lhs, IObject * rhs)
{ {
scope log(XO_DEBUG(config_.debug_flag_),
xtag("parent", parent), xtag("lhs", lhs), xtag("rhs", rhs));
++gc_statistics_.n_mutation_; ++gc_statistics_.n_mutation_;
*lhs = rhs; *lhs = rhs;
@ -532,23 +543,28 @@ namespace xo {
{ {
case generation_result::tenured: case generation_result::tenured:
/* only need to log mutations that create tenured->nursery pointers */ /* only need to log mutations that create tenured->nursery pointers */
log && log(xtag("act", "any->T no log"));
return; return;
case generation_result::nursery: case generation_result::nursery:
switch (tospace_generation_of(parent)) { switch (tospace_generation_of(parent)) {
case generation_result::nursery: case generation_result::nursery:
if (is_before_checkpoint(parent)) { if (is_before_checkpoint(parent)) {
log && log(xtag("act", "N1->N0 must mlog"));
// N1->N0, so must log // N1->N0, so must log
this->mutation_log_[role2int(role::to_space)]->push_back(MutationLogEntry(parent, lhs)); this->mutation_log_[role2int(role::to_space)]->push_back(MutationLogEntry(parent, lhs));
++(this->gc_statistics_.n_logged_mutation_); ++(this->gc_statistics_.n_logged_mutation_);
++(this->gc_statistics_.n_xckp_mutation_); ++(this->gc_statistics_.n_xckp_mutation_);
} else { } else {
// parent in N0, not an xckp mutation // parent in N0, not an xckp mutation
return; log && log(xtag("act", "N0->any no long"));
} }
break; break;
case generation_result::tenured: case generation_result::tenured:
// T->N, so must log // T->N, so must log
log && log(xtag("act", "T->N must mlog"));
this->mutation_log_[role2int(role::to_space)]->push_back(MutationLogEntry(parent, lhs)); this->mutation_log_[role2int(role::to_space)]->push_back(MutationLogEntry(parent, lhs));
++(this->gc_statistics_.n_logged_mutation_); ++(this->gc_statistics_.n_logged_mutation_);
++(this->gc_statistics_.n_xgen_mutation_); ++(this->gc_statistics_.n_xgen_mutation_);
@ -556,11 +572,13 @@ namespace xo {
case generation_result::not_found: case generation_result::not_found:
// parent is global // parent is global
// This may be ok (provided lhs is a gc root) // This may be ok (provided lhs is a gc root)
log && log(xtag("act", "root->any no log"));
break; break;
} }
break; break;
case generation_result::not_found: case generation_result::not_found:
log && log(xtag("act", "any->root no log"));
// child is global; // child is global;
// logging not required // logging not required
@ -571,6 +589,8 @@ namespace xo {
void void
GC::forward_inplace(IObject ** lhs) GC::forward_inplace(IObject ** lhs)
{ {
scope log(XO_DEBUG(config_.debug_flag_), xtag("lhs", lhs));
Object::_forward_inplace(lhs, this); Object::_forward_inplace(lhs, this);
} }
@ -588,10 +608,13 @@ namespace xo {
} }
bool bool
GC::check_write_barrier(IObject * parent, GC::check_write_barrier(const void * parent,
IObject ** lhs, const void * const * lhs,
bool may_throw_flag) const bool may_throw_flag) const
{ {
scope log(XO_DEBUG(config_.debug_flag_),
xtag("P", parent), xtag("L", lhs));
if (!this->contains(parent)) { if (!this->contains(parent)) {
if (may_throw_flag) { if (may_throw_flag) {
throw std::runtime_error(tostr("GC::check_write_barrier", throw std::runtime_error(tostr("GC::check_write_barrier",
@ -601,10 +624,11 @@ namespace xo {
return false; return false;
} }
#ifdef NOPE // don't want to assume IObject*
std::size_t parent_z = parent->_shallow_size(); std::size_t parent_z = parent->_shallow_size();
std::byte * parent_addr = reinterpret_cast<std::byte *>(parent); const std::byte * parent_addr = reinterpret_cast<const std::byte *>(parent);
std::byte * lhs_addr = reinterpret_cast<std::byte *>(lhs); const std::byte * lhs_addr = reinterpret_cast<const std::byte *>(lhs);
if ((lhs_addr < parent_addr) || (parent_addr + parent_z < lhs_addr)) { if ((lhs_addr < parent_addr) || (parent_addr + parent_z < lhs_addr)) {
if (may_throw_flag) { if (may_throw_flag) {
@ -617,12 +641,21 @@ namespace xo {
} }
return false; return false;
} }
#endif
IObject * rhs = *lhs; const void * rhs = *lhs;
if (!rhs)
return true;
auto parent_gen = tospace_generation_of(parent); auto parent_gen = tospace_generation_of(parent);
auto rhs_gen = tospace_generation_of(rhs); auto rhs_gen = tospace_generation_of(rhs);
log && log(xtag("C", rhs),
xtag("gen(P)", parent_gen), xtag("gen(C)", rhs_gen),
xtag("P.before-ckp", is_before_checkpoint(parent)),
xtag("C.before-ckp", is_before_checkpoint(rhs)));
switch(parent_gen) { switch(parent_gen) {
case generation_result::nursery: case generation_result::nursery:
if (is_before_checkpoint(parent)) { if (is_before_checkpoint(parent)) {
@ -630,21 +663,25 @@ namespace xo {
case generation_result::nursery: case generation_result::nursery:
if (is_before_checkpoint(rhs)) { if (is_before_checkpoint(rhs)) {
/* no mlog entry needed */ /* no mlog entry needed */
log && log(xtag("msg", "N1->N1 - trivial"));
return true; return true;
} else { } else {
/* need to check mlog */ /* need to check mlog */
; log && log(xtag("msg", "N1->N0 - xgen"));
} }
break; break;
case generation_result::tenured: case generation_result::tenured:
/* no mlog entry needed */ /* no mlog entry needed */
log && log(xtag("msg", "N1->T - trivial"));
return true; return true;
case generation_result::not_found: case generation_result::not_found:
/* possible non-gc rhs */ /* possible non-gc rhs */
log && log(xtag("msg", "non-gc rhs - trivial"));
return true; return true;
} }
} else { } else {
/* no mlog entry needed */ /* no mlog entry needed */
log && log(xtag("msg", "N0->any - trivial"));
return true; return true;
} }
break; break;
@ -652,16 +689,21 @@ namespace xo {
switch(rhs_gen) { switch(rhs_gen) {
case generation_result::nursery: case generation_result::nursery:
/* need to check mlog */ /* need to check mlog */
log && log(xtag("msg", "T->N - xgen"));
break; break;
case generation_result::tenured: case generation_result::tenured:
/* no mlog entry needed */ /* no mlog entry needed */
log && log(xtag("msg", "T->T - trivial"));
return true; return true;
case generation_result::not_found: case generation_result::not_found:
/* possible non-gc rhs */ /* possible non-gc rhs */
log && log(xtag("msg", "non-gc rhs - trivial"));
return true; return true;
} }
break;
case generation_result::not_found: case generation_result::not_found:
/* already excluded -> impossible */ /* already excluded -> impossible */
log && log(xtag("msg", "assert"));
assert(false && "already verified parent owned by GC"); assert(false && "already verified parent owned by GC");
} }
@ -669,7 +711,7 @@ namespace xo {
* search mutation log + verify such entry exists * search mutation log + verify such entry exists
*/ */
for (MutationLogEntry & mlog : *(mutation_log_[role2int(role::to_space)])) { for (MutationLogEntry & mlog : *(mutation_log_[role2int(role::to_space)])) {
if ((mlog.parent() == parent) && (mlog.lhs() == lhs)) { if ((mlog.parent() == parent) && ((const void * const *)mlog.lhs() == lhs)) {
return true; return true;
} }
mlog.lhs(); mlog.lhs();
@ -872,7 +914,7 @@ namespace xo {
for (MutationLogEntry & from_entry : *from_mlog) for (MutationLogEntry & from_entry : *from_mlog)
{ {
if (log) { if (log) {
if (i_from % 10000 == 0) if (i_from % 10000 == 0 || true)
log(xtag("i_from", i_from)); log(xtag("i_from", i_from));
} }
@ -905,8 +947,12 @@ namespace xo {
++n_rescue; ++n_rescue;
log && log(xtag("parent", parent), xtag("act", "move child"), xtag("child.from", child_from));
Object::_deep_move(child_from, this, per_type_stats); Object::_deep_move(child_from, this, per_type_stats);
log && log(xtag("child.to", child_from->_destination()));
// C forwards to C', fall thru to parent fixup below // C forwards to C', fall thru to parent fixup below
// (a) T->N1' // (a) T->N1'
// (c) T->T // (c) T->T
@ -924,13 +970,24 @@ namespace xo {
IObject * child_to = child_from->_destination(); IObject * child_to = child_from->_destination();
log && log(xtag("act", "fixup parent"), xtag("parent", parent), xtag("lhs", from_entry.lhs()), xtag("child.from", child_from), xtag("child.to", child_to));
from_entry.fixup_parent_child_moved(child_to); from_entry.fixup_parent_child_moved(child_to);
{
// verify fixup was effective
IObject * child_from2 = from_entry.child();
assert(child_from2 == child_to);
}
// P->C', loc(C') in {N1', T'} // P->C', loc(C') in {N1', T'}
if (tospace_generation_of(child_to) == generation_result::nursery) { if (tospace_generation_of(child_to) == generation_result::nursery) {
// (b) loc(P)=T, loc(C')=N1'; also case (a) // (b) loc(P)=T, loc(C')=N1'; also case (a)
log && log(xtag("act", "still xgen -> keep mlog entry"));
// still have xgen pointer, so need mlog for it // still have xgen pointer, so need mlog for it
to_mlog->push_back(from_entry); to_mlog->push_back(from_entry);
} else { } else {
@ -972,6 +1029,8 @@ namespace xo {
} }
} else { } else {
log && log("defer");
// loc(P) = N1, loc(C) = N0, P may be garbage // loc(P) = N1, loc(C) = N0, P may be garbage
// Includes cases: // Includes cases:
// (e) P->C, C not moved // (e) P->C, C not moved
@ -1347,7 +1406,7 @@ namespace xo {
void void
GC::execute_gc(generation upto) GC::execute_gc(generation upto)
{ {
scope log(XO_DEBUG(config_.stats_flag_)); scope log(XO_DEBUG(config_.stats_flag_ || config_.debug_flag_));
auto t0 = std::chrono::steady_clock::now(); auto t0 = std::chrono::steady_clock::now();

View file

@ -42,18 +42,24 @@ namespace xo {
Object::_forward(IObject * src, Object::_forward(IObject * src,
gc::IAlloc * gc) gc::IAlloc * gc)
{ {
scope log(XO_DEBUG(gc->debug_flag()), xtag("src", src));
if (!src) if (!src)
return src; return src;
if (src->_is_forwarded()) if (src->_is_forwarded()) {
log && log("already forwarded", xtag("dest", src->_offset_destination(src)));
return src->_offset_destination(src); return src->_offset_destination(src);
}
if (gc->check_move(src)) { if (gc->check_move(src)) {
log && log("needs forwarding");
Object::_shallow_move(src, gc); Object::_shallow_move(src, gc);
/* *src is now a forwarding pointer to a copy in to-space */ /* *src is now a forwarding pointer to a copy in to-space */
return src->_offset_destination(src); return src->_offset_destination(src);
} else { } else {
log && log("already tenured + incr collection");
/* don't move tenured objects during incremental collection */ /* don't move tenured objects during incremental collection */
return src; return src;
} }
@ -62,6 +68,8 @@ namespace xo {
IObject * IObject *
Object::_deep_move(IObject * from_src, gc::GC * gc, gc::ObjectStatistics * /*stats*/) Object::_deep_move(IObject * from_src, gc::GC * gc, gc::ObjectStatistics * /*stats*/)
{ {
scope log(XO_DEBUG(gc->config().debug_flag_));
using gc::generation; using gc::generation;
if (!from_src) if (!from_src)
@ -146,13 +154,15 @@ namespace xo {
do { do {
fixup_work = 0; fixup_work = 0;
auto fixup_generation = [gc, &gray_lo_v](generation gen) { auto fixup_generation = [gc, &log, &gray_lo_v](generation gen) {
std::size_t work = 0; std::size_t work = 0;
while(gray_lo_v[gen2int(gen)] < gc->free_ptr(gen)) { while(gray_lo_v[gen2int(gen)] < gc->free_ptr(gen)) {
Object * x = reinterpret_cast<Object *>(gray_lo_v[gen2int(gen)]); Object * x = reinterpret_cast<Object *>(gray_lo_v[gen2int(gen)]);
// update per-class stats here // update per-class stats here
log && log("fwd children", xtag("x", x));
std::size_t xz = x->_forward_children(gc); std::size_t xz = x->_forward_children(gc);
// must pad xz to multiple of word size, // must pad xz to multiple of word size,

View file

@ -202,6 +202,29 @@ namespace xo {
vector_type member2_; vector_type member2_;
bool ctor_ran_ = false; bool ctor_ran_ = false;
}; };
#ifdef NOT_YET
struct MemberType2 {
public:
MemberType2() = default;
/** GC hooks rely on copy constructor. But can't write it without allocator state.
* Therefore: need copy-like constructor that takes allocator argument
**/
template <typename Allocator>
explicit MemberType2(Allocator & alloc, uint64 payload) {
using traits = gc_allocator_traits<Allocator>;
uint64_t * ptr = traits::allocate(alloc, 1);
this->payload_ = payload;
this->ctor_ran_ = true;
}
uint64_t * payload_ = nullptr;
bool ctor_ran_ = false;
}
#endif
} }
TEST_CASE("vector_custom_allocator", "[alloc][vector]") TEST_CASE("vector_custom_allocator", "[alloc][vector]")