xo-gc: improve public/private between MutationLogStore and X1

This commit is contained in:
Roland Conybeare 2026-04-03 17:47:06 -04:00
commit 5d3c088ba7
6 changed files with 133 additions and 142 deletions

View file

@ -17,55 +17,26 @@ namespace xo {
class MutationLogConfig {
public:
MutationLogConfig(std::uint32_t ngen,
#ifdef OBSOLETE // in GCObjectStore
std::uint32_t survive,
#endif
std::size_t mlog_z,
bool enabled_flag,
bool debug_flag);
#ifdef OBSOLETE
/** generation that would contain an object that has survived
* @p age collections. Equals the number of times object
* has been promoted.
*
* Must be consistent
**/
Generation age2gen(object_age age) const noexcept {
return Generation(age % n_survive_threshold_);
}
#endif
#ifdef OBSOLETE
/** age threshold for promotion to generation @p g **/
uint32_t promotion_threshold(Generation g) const noexcept {
// TODO: may consider replacing with table-lookup
// Require: if two distinct ages promote to some gen g at the same time,
// then they also promote to gen g+k at the same time for all k>0.
return g * n_survive_threshold_;
}
#endif
public:
/** number of generations in use.
* Same as @ref X1CollectorConfig::n_generation_
**/
std::uint32_t n_generation_ = 0;
#ifdef OBSOLETE
/** Number of promotion steps.
* An object that survives this number of collections
* advances to the next generation.
**/
uint32_t n_survive_threshold_ = 2;
#endif
/** storage for xgen pointer bookkeeping (aka remembered sets).
* Use 3x this value per generation
**/
std::size_t mutation_log_z_ = 1024;
/** true if mlog feature enabled (i.e. incremental gc enabled).
* false to disable (in which case only full gc supported)
**/
bool enabled_flag_ = false;
/** true to enable debug logging **/
bool debug_flag_ = false;
};

View file

@ -40,13 +40,40 @@ namespace xo {
void visit_pools(const MemorySizeVisitor & visitor) const;
/** verify consistent mlog state,
* on behalf of collector @p gc.
* on behalf of gc-aware object store @p gc.
* (using gc to identify location of objects).
* Update counters in @p *p_verify_stats.
**/
void verify_ok(GCObjectStore * gc,
X1VerifyStats * p_verify_stats) noexcept;
/** on behalf of gc-aware object store @p gc,
* change the value of a child pointer at @p p_lhs
* with parent object @p parent. p_lhs and parent must belong
* to the same allocation.
**/
void assign_member(GCObjectStore * gc,
void * parent,
obj<AGCObject> * p_lhs,
obj<AGCObject> rhs);
/** swap {to, from} roles
**/
void swap_roles(Generation upto) noexcept;
/** On behalf of collector @p gc:
*
* forward mutation logs, for generations 0 <= g < @p upto,
* from from-space to to-space.
**/
void forward_mutation_log(DX1Collector * gc,
Generation upto);
private:
/** aux init function: create mutation log **/
MutationLog _make_mlog(uint32_t igen, char tag_char,
size_t mlog_z, std::size_t page_z);
/** Append a single mutation to log for generation @p dest_g
* Mutation modifies @p parent at address @p addr,
* to refer to @p rhs.
@ -64,27 +91,10 @@ namespace xo {
* pointer. This means can alway recover that pointer
* by consulting the AllocHeader for the pointer target
*/
void append_mutation(Generation dest_g,
void * parent,
void ** addr,
obj<AGCObject> rhs);
/** swap {to, from} roles
**/
void swap_roles(Generation upto) noexcept;
/** On behalf of collector @p gc:
*
* forward mutation logs, for generations 0 <= g < @p upto,
* from from-space to to-space.
**/
void forward_mutation_log(DX1Collector * gc,
Generation upto);
private:
/** aux init function: create mutation log **/
MutationLog _make_mlog(uint32_t igen, char tag_char,
size_t mlog_z, std::size_t page_z);
void _append_mutation(Generation dest_g,
void * parent,
void ** addr,
obj<AGCObject> rhs);
/** On behalf of collctor @p gc:
*

View file

@ -51,8 +51,11 @@ namespace xo {
/** fetch configuration for mutation log store **/
MutationLogConfig mlog_config() const noexcept {
bool mlog_enabled_flag = allow_incremental_gc_;
return MutationLogConfig(n_generation_,
mutation_log_z_,
mlog_enabled_flag,
debug_flag_);
}

View file

@ -701,78 +701,19 @@ namespace xo {
// ++ stats.n_mutation_;
*p_lhs = rhs;
if (runstate_.is_running()) {
*p_lhs = rhs;
// for removal of all doubt:
// don't log mutations during GC cycle
// don't log mutations during GC cycle.
// That said: should not be happening!
assert(false);
return;
}
if (!config_.allow_incremental_gc_) {
// only need to log mutations when incremental gc is enabled
return;
}
// logging policy depends on:
// 1. generation of lhs
// 2. generation of rhs
Generation src_g = this->generation_of(role::to_space(), p_lhs);
if (src_g.is_sentinel()) {
// only need mlog entries for gc-owned pointers.
// In this case pointer does not originate in gc-owned space
return;
}
Generation dest_g = this->generation_of(role::to_space(), rhs.data());
if (dest_g.is_sentinel()) {
// similarly, don't need mlog entry to non-gc-owned destination
return;
}
if (src_g < dest_g) {
// young-to-old pointers don't need to be remembered,
// since a GC cycle that collects an (old) generation is guarnatted
// to also collect all younger generations.
return;
}
if (src_g == dest_g) {
// for pointers within the same generation, need to log
// if source is older than destination.
const DArena * arena = this->get_space(role::to_space(), src_g);
const DArena::header_type * src_hdr = arena->obj2hdr(parent);
const DArena::header_type * dest_hdr = arena->obj2hdr(rhs.data());
assert(src_hdr && dest_hdr);
if (this->header2age(*src_hdr) <= this->header2age(*dest_hdr)) {
// source and destination have the same age;
// therefore are always collected on the same set of GC cycles
// -> no need to remember separately.
return;
} else {
// even though {src,dest} belong to the same generation:
// source will be eligible for promotion before destination.
// At that point pointer would become a cross-generational pointer,
// so need to track it now.
log && log("xage ptr -> must log");
}
} else {
log && log("xgen ptr -> must log");
mlog_store_.assign_member(&gco_store_, parent, p_lhs, rhs);
}
// control here: we have an older->younger pointer, need to log it
void ** lhs_addr = reinterpret_cast<void **>(&(p_lhs->data_));
mlog_store_.append_mutation(dest_g, parent, lhs_addr, rhs);
} /*assign_member*/
DX1CollectorIterator
@ -814,15 +755,6 @@ namespace xo {
arena_end);
}
#ifdef MOVED
void
DX1Collector::reverse_roles(Generation g) noexcept {
assert(g < config_.n_generation_);
std::swap(space_[role::from_space()][g], space_[role::to_space()][g]);
}
#endif
void
DX1Collector::clear() noexcept {
for (role ri : role::all()) {

View file

@ -9,16 +9,12 @@ namespace xo {
namespace mm {
MutationLogConfig::MutationLogConfig(std::uint32_t ngen,
#ifdef OBSOLETE
std::uint32_t survive,
#endif
std::size_t mlog_z,
bool enabled_flag,
bool debug_flag)
: n_generation_{ngen},
#ifdef OBSOLETE
n_survive_threshold_{survive},
#endif
mutation_log_z_{mlog_z},
enabled_flag_{enabled_flag},
debug_flag_{debug_flag}
{}

View file

@ -136,10 +136,89 @@ namespace xo {
} /*verify_ok*/
void
MutationLogStore::append_mutation(Generation dest_g,
void * parent,
void ** addr,
obj<AGCObject> rhs)
MutationLogStore::assign_member(GCObjectStore * gco_store,
void * parent,
obj<AGCObject> * p_lhs,
obj<AGCObject> rhs)
{
scope log(XO_DEBUG(config_.debug_flag_),
xtag("parent", parent), xtag("lhs", p_lhs), xtag("rhs", rhs.data()));
// ++ stats.n_mutation_;
*p_lhs = rhs;
if (!config_.enabled_flag_) {
// only need to log mutations when incremental gc is enabled
return;
}
// logging policy depends on:
// 1. generation of lhs
// 2. generation of rhs
Generation src_g = gco_store->generation_of(role::to_space(), p_lhs);
if (src_g.is_sentinel()) {
// only need mlog entries for gc-owned pointers.
// In this case pointer does not originate in gc-owned space
return;
}
Generation dest_g = gco_store->generation_of(role::to_space(), rhs.data());
if (dest_g.is_sentinel()) {
// similarly, don't need mlog entry to non-gc-owned destination
return;
}
if (src_g < dest_g) {
// young-to-old pointers don't need to be remembered,
// since a GC cycle that collects an (old) generation is guarnatted
// to also collect all younger generations.
return;
}
if (src_g == dest_g) {
// for pointers within the same generation, need to log
// if source is older than destination.
const DArena * arena = gco_store->get_space(role::to_space(), src_g);
const DArena::header_type * src_hdr = arena->obj2hdr(parent);
const DArena::header_type * dest_hdr = arena->obj2hdr(rhs.data());
assert(src_hdr && dest_hdr);
if (gco_store->header2age(*src_hdr) <= gco_store->header2age(*dest_hdr)) {
// source and destination have the same age;
// therefore are always collected on the same set of GC cycles
// -> no need to remember separately.
return;
} else {
// even though {src,dest} belong to the same generation:
// source will be eligible for promotion before destination.
// At that point pointer would become a cross-generational pointer,
// so need to track it now.
log && log("xage ptr -> must log");
}
} else {
log && log("xgen ptr -> must log");
}
// control here: we have an older->younger pointer, need to log it
void ** lhs_addr = reinterpret_cast<void **>(&(p_lhs->data_));
this->_append_mutation(dest_g, parent, lhs_addr, rhs);
}
void
MutationLogStore::_append_mutation(Generation dest_g,
void * parent,
void ** addr,
obj<AGCObject> rhs)
{
// mlog keyed by generation in which pointer _destination_ resides:
// collection that moves destination generation around needs to also