xo-gc xo-object2: gc bugfixes and logging [WORKING]

This commit is contained in:
Roland Conybeare 2026-01-04 15:33:08 -05:00
commit c739fba5eb
4 changed files with 135 additions and 20 deletions

View file

@ -293,6 +293,9 @@ namespace xo {
log && log("step 1 : swap from/to roles");
this->swap_roles(upto);
log && log(xtag("from_0", get_space(role::from_space(), generation{0})->lo_),
xtag("to_0", get_space(role::to_space(), generation{0})->lo_));
log && log("step 2a : copy roots");
this->copy_roots(upto);
@ -305,7 +308,11 @@ namespace xo {
void
DX1Collector::swap_roles(generation upto) noexcept
{
scope log(XO_DEBUG(true), xtag("upto", upto));
for (generation g = generation{0}; g < upto; ++g) {
log && log("swap roles", xtag("g", g));
std::swap(space_[role::to_space()][g], space_[role::from_space()][g]);
}
}
@ -345,6 +352,7 @@ namespace xo {
*
* Coordinates with forward_inplace()
*/
log && log("disposition: already forwarded");
return *(void **)from_src;
}
@ -353,6 +361,8 @@ namespace xo {
if (!this->check_move_policy(hdr, from_src)) {
/* object at from_src in generation that is not being collected */
log && log("disposition: not moving from_src");
return from_src;
}
@ -418,6 +428,8 @@ namespace xo {
*
**/
log && log("disposition: move subtree");
/* TODO: AllocIterator pointing to free pointer */
std::array<std::byte *, c_max_generation> gray_lo_v;
{
@ -462,16 +474,24 @@ namespace xo {
}
} while (fixup_work > 0);
log && log(xtag("to_dest", to_dest));
return to_dest;
}
void
DX1Collector::copy_roots(generation upto) noexcept
{
scope log(XO_DEBUG(true));
for (obj<AGCObject> ** p_root = (obj<AGCObject> **)roots_.lo_;
p_root < (obj<AGCObject> **)roots_.free_; ++p_root)
{
log && log("copy root", xtag("**p_root.data.pre", (**p_root).data_));
(*p_root)->reset_opaque(this->deep_move((*p_root)->data_, upto));
log && log(xtag("**p_root.data.post", (**p_root).data_));
}
}
@ -479,6 +499,10 @@ namespace xo {
DX1Collector::forward_inplace(AGCObject * lhs_iface,
void ** lhs_data)
{
scope log(XO_DEBUG(config_.debug_flag_),
xtag("lhs_data", lhs_data),
xtag("*lhs_data", *lhs_data));
/* coordinates with DX1Collector::_deep_move() */
(void)lhs_iface;
@ -497,28 +521,31 @@ namespace xo {
* +----------+
*/
void * object_data = (std::byte *)lhs_data;
void * object_data = (std::byte *)*lhs_data;
if (!this->contains(role::from_space(), object_data)) {
/* *lhs isn't in GC-allocated space.
/* *lhs_data either:
* 1. already in to-space
* 2. not in GC-allocated space at all
* (small number of niche examples of this)
*
* This happens for a modest number of global
* constants, for example DBoolean {true, false}.
*
* It's important we recognize these up front.
* It's important we recognize case (2) up front.
* Since not allocated from GC, they don't have
* an alloc-header.
*/
log && log("disposition: not in from-space");
return;
}
log && log("disposition: in from-space");
/** NOTE: for form's sake:
* better to lookup actual arena that
* lookup actual arena that
* allocated object data.
* Only using this to get alloc header
*
**/
DArena * some_arena = this->to_space(generation(0));
DArena * some_arena = this->from_space(generation(0));
DArena::header_type * p_header
= some_arena->obj2hdr(object_data);
@ -528,37 +555,96 @@ namespace xo {
/* recover allocation size */
std::size_t alloc_z = some_arena->config_.header_.size_with_padding(alloc_hdr);
log && log(xtag("some_arena.lo", some_arena->lo_),
xtag("p_header", p_header),
xtag("alloc_z", alloc_z));
/* need to be able to fit forwarding pointer
* in place of forwarded object.
*
* This is guaranteed anyway, by alignment rules
*/
assert(alloc_z > sizeof(uintptr_t));
assert(alloc_z >= sizeof(uintptr_t));
if (this->is_forwarding_header(alloc_hdr)) {
/* *lhs already refers to a forwarding pointer */
/* *lhs_data already refers to a forwarding pointer */
/*
* lhs obj<AGCObject>
* lhs obj<AGCObject> (from-space)
* | +---------+ +---+-+----+
* \--->| .iface | |FWD|G|size| alloc_hdr
* +---------+ object_data +---+-+----+
* | .data x----------------->| x-------->
* +---------+ | | dest
* | .data x----------------->| x--------\
* +---------+ | | | dest
* | | |
* +----------+ |
* |
* (to-space) |
* +---+-+----+ |
* |FWD|G|size|<--/
* +---+-+----+
* | |
* | |
* | |
* +----------+
*/
void * dest = *(void**)object_data;
/* update *lhs in-place */
*lhs_data = dest;
/*
* lhs obj<AGCObject>
* | +---------+
* \--->| .iface |
* +---------+
* | .data x------------\
* +---------+ |
* | dest
* |
* |
* | (to-space)
* | +---+-+----+
* \---->|FWD|G|size|
* +---+-+----+
* | |
* | |
* | |
* +----------+
*/
} else if (this->check_move_policy(alloc_hdr, object_data)) {
/* copy object *lhs + replace with forwarding pointer */
/* which arena are we writing to? need allocator interface */
/*
* lhs obj<AGCObject> (from-space)
* | +---------+ +---+-+----+
* \--->| .iface | |FWD|G|size| alloc_hdr
* +---------+ object_data +---+-+----+
* | .data x----------------->| |
* +---------+ | |
* | |
* +----------+
*/
*lhs_data = this->shallow_move(lhs_iface, *lhs_data);
/*
* lhs obj<AGCObject> (from-space)
* | +---------+ +---+-+----+
* \--->| .iface | |FWD|G|SIZE|
* +---------+ +---+-+----+
* | .data x------------\ | x--------\
* +---------+ | | | |
* | | | |
* dest | +----------+ |
* | |
* | (to-space) |
* | +---+-+----+ |
* \---->|FWD|G|size|<--/
* +---+-+----+
* | |
* | |
* | |
* +----------+
*/
} else {
/* object doesn't need to move.
* e.g. incremental collection + object is tenured
@ -569,11 +655,15 @@ namespace xo {
void *
DX1Collector::shallow_move(const AGCObject * iface, void * from_src)
{
scope log(XO_DEBUG(config_.debug_flag_));
AllocInfo info = this->alloc_info((std::byte *)from_src);
obj<AAllocator, DX1Collector> alloc(this);
void * to_dest = iface->shallow_copy(from_src, alloc);
log && log(xtag("from_src", from_src), xtag("to_dest", to_dest));
if(to_dest == from_src) {
assert(false);
} else {
@ -699,7 +789,7 @@ namespace xo {
DX1Collector::reverse_roles(generation g) noexcept {
assert(g < config_.n_generation_);
std::swap(space_[0][g], space_[1][g]);
std::swap(space_[role::from_space()][g], space_[role::to_space()][g]);
}
void

View file

@ -41,7 +41,7 @@ namespace xo {
bool
DList::is_empty() const noexcept
{
return this != &s_null;
return this == &s_null;
}
auto

View file

@ -4,6 +4,7 @@
**/
#include "IGCObject_DList.hpp"
#include <xo/indentlog/scope.hpp>
namespace xo {
using xo::mm::AGCObject;
@ -36,6 +37,8 @@ namespace xo {
IGCObject_DList::forward_children(DList & src,
obj<ACollector> gc) noexcept
{
scope log(XO_DEBUG(true));
gc.forward_inplace(src.head_.iface(), (void **)&(src.head_.data_));
auto iface = xo::facet::impl_for<AGCObject, DList>();

View file

@ -19,6 +19,9 @@
#include <xo/alloc2/AllocInfo.hpp>
#include <xo/alloc2/padding.hpp>
#include <xo/indentlog/scope.hpp>
#include <xo/indentlog/print/tag.hpp>
#include <catch2/catch.hpp>
namespace ut {
@ -39,6 +42,8 @@ namespace ut {
using xo::mm::padding;
using xo::facet::with_facet;
using xo::facet::typeseq;
using xo::scope;
using xo::xtag;
namespace {
struct testcase_x1 {
@ -69,6 +74,9 @@ namespace ut {
TEST_CASE("x1", "[gc][x1]")
{
constexpr bool c_debug_flag = true;
scope log(XO_DEBUG(c_debug_flag));
for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) {
try {
const testcase_x1 & tc = s_testcase_v[i_tc];
@ -82,6 +90,7 @@ namespace ut {
.gc_trigger_v_{{
tc.incr_gc_threshold_,
tc.full_gc_threshold_}},
.debug_flag_ = c_debug_flag,
};
DX1Collector gc(cfg);
@ -140,6 +149,8 @@ namespace ut {
REQUIRE(gc.reserved_total()
== otypes->reserved() + roots->reserved() + 4 * from_0->reserved());
log && log(xtag("from_0", from_0->lo_), xtag("to_0", to_0->lo_));
}
/* attempt allocation */
@ -197,11 +208,22 @@ namespace ut {
}
}
/* no GC roots, so GC is trivial */
c_o.request_gc(generation{1});
log && log(xtag("l0_o.data()", l0_o.data()));
log && log(xtag("l0_o.data()->head_.data()", l0_o.data()->head_.data()));
log && log(xtag("x0_o.data()", x0_o.data()));
REQUIRE(!gc.contains(role::from_space(), x0_o.data()));
REQUIRE(gc.contains(role::to_space(), x0_o.data()));
REQUIRE(x0_o.data()->value() == 3.1415927);
REQUIRE(!gc.contains(role::from_space(), l0_o.data()));
REQUIRE(gc.contains(role::to_space(), l0_o.data()));
REQUIRE(l0_o.data()->is_empty() == false);
REQUIRE((void*)l0_o.data()->head_.data() == (void*)x0_o.data());
} catch (std::exception & ex) {
std::cerr << "caught exception: " << ex.what() << std::endl;
REQUIRE(false);