xo-alloc: check_write_barrier to verify mutation log

This commit is contained in:
Roland Conybeare 2025-12-05 10:00:36 -05:00
commit b6ccde3ddc
5 changed files with 133 additions and 4 deletions

View file

@ -177,7 +177,6 @@ namespace xo {
virtual std::byte * alloc(std::size_t z) final override;
virtual bool check_owned(IObject * src) const final override;
ArenaAlloc & operator=(const ArenaAlloc &) = delete;
ArenaAlloc & operator=(ArenaAlloc &&) = delete;

View file

@ -337,6 +337,12 @@ namespace xo {
* - incr GC -> if not tenured
**/
virtual bool check_move(IObject * src) const final override;
/** if src is cross-generational (or cross-checkpoint), verify that it
* is recorded in mutation log,
* 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 std::byte * alloc(std::size_t z) final override;
virtual std::byte * alloc_gc_copy(std::size_t z, const void * src) final override;
@ -447,7 +453,15 @@ namespace xo {
std::vector<IObject**> gc_root_v_;
/** log cross-generational and cross-checkpoint mutations.
* These need to be adjusted on next incremental collection
* These need to be adjusted on next incremental collection.
*
* mutation_log_[tospace] accumulates {xgen,xckp} pointers until
* the next GC.
*
* See GC aux functions
* @ref incremental_gc_forward_mlog
* @ref full_gc_forward_mlog
*
**/
std::array<up<MutationLog>, role2int(role::N)> mutation_log_;
/** temporary mutation log (for deferred entries) **/

View file

@ -41,6 +41,13 @@ namespace xo {
return generation::tenured;
}
const char * genresult2str(generation_result x);
inline std::ostream & operator<<(std::ostream & os, generation_result x) {
os << genresult2str(x);
return os;
}
} /*namespace gc*/
} /*namespace xo*/

View file

@ -587,6 +587,104 @@ namespace xo {
|| (this->tospace_generation_of(src) != gc::generation_result::tenured));
}
bool
GC::check_write_barrier(IObject * parent,
IObject ** lhs,
bool may_throw_flag) const
{
if (!this->contains(parent)) {
if (may_throw_flag) {
throw std::runtime_error(tostr("GC::check_write_barrier",
": expected parent object P in GC to-space",
xtag("P", parent)));
}
return false;
}
std::size_t parent_z = parent->_shallow_size();
std::byte * parent_addr = reinterpret_cast<std::byte *>(parent);
std::byte * lhs_addr = reinterpret_cast<std::byte *>(lhs);
if ((lhs_addr < parent_addr) || (parent_addr + parent_z < lhs_addr)) {
if (may_throw_flag) {
throw std::runtime_error
(tostr("GC::check_write_barrier",
": expected lhs address L within address extent z of parent P",
xtag("P", parent), xtag("z", parent_z),
xtag("P+z", parent_addr + parent_z),
xtag("L", lhs)));
}
return false;
}
IObject * rhs = *lhs;
auto parent_gen = tospace_generation_of(parent);
auto rhs_gen = tospace_generation_of(rhs);
switch(parent_gen) {
case generation_result::nursery:
if (is_before_checkpoint(parent)) {
switch(rhs_gen) {
case generation_result::nursery:
if (is_before_checkpoint(rhs)) {
/* no mlog entry needed */
return true;
} else {
/* need to check mlog */
;
}
break;
case generation_result::tenured:
/* no mlog entry needed */
return true;
case generation_result::not_found:
/* possible non-gc rhs */
return true;
}
} else {
/* no mlog entry needed */
return true;
}
break;
case generation_result::tenured:
switch(rhs_gen) {
case generation_result::nursery:
/* need to check mlog */
break;
case generation_result::tenured:
/* no mlog entry needed */
return true;
case generation_result::not_found:
/* possible non-gc rhs */
return true;
}
case generation_result::not_found:
/* already excluded -> impossible */
assert(false && "already verified parent owned by GC");
}
/* control here -> expect mutation log entry.
* search mutation log + verify such entry exists
*/
for (MutationLogEntry & mlog : *(mutation_log_[role2int(role::to_space)])) {
if ((mlog.parent() == parent) && (mlog.lhs() == lhs)) {
return true;
}
mlog.lhs();
}
if (may_throw_flag) {
throw std::runtime_error
(tostr("GC::check_write_barrier",
": expected mlog entry for xgen pointer L->C within parent P",
xtag("P", parent), xtag("L", lhs), xtag("C", rhs),
xtag("gen(P)", parent_gen), xtag("gen(C)", rhs_gen)));
}
return false;
}
void
GC::swap_nursery()
{
@ -1126,9 +1224,11 @@ namespace xo {
scope log(XO_DEBUG(config_.debug_flag_));
if (upto == generation::tenured) {
this->full_gc_forward_mlog(&object_statistics_sae_[gen2int(generation::tenured)]);
this->full_gc_forward_mlog
(&object_statistics_sae_[gen2int(generation::tenured)]);
} else {
this->incremental_gc_forward_mlog(&object_statistics_sae_[gen2int(generation::nursery)]);
this->incremental_gc_forward_mlog
(&object_statistics_sae_[gen2int(generation::nursery)]);
}
}

View file

@ -16,6 +16,15 @@ namespace xo {
return "?generation";
}
const char * genresult2str(generation_result x) {
switch (x) {
case generation_result::nursery: return "nursery";
case generation_result::tenured: return "tenured";
case generation_result::not_found: return "not-found";
}
return "?generation_result";
}
} /*namespace gc*/
} /*namespace xo*/