diff --git a/xo-alloc/docs/glossary.rst b/xo-alloc/docs/glossary.rst new file mode 100644 index 00000000..4e50a499 --- /dev/null +++ b/xo-alloc/docs/glossary.rst @@ -0,0 +1,28 @@ +.. _glossary: + +Glossary +-------- + +.. glossary:: + GC + | garbage collector + + mlog + | mutation log. + | Remembers cross-generation and cross-checkpoint pointers + + nursery + | in garbage collector, memory region dedicated to young objects. + | These are objects that have survived less than 2 incremental collection cycles. + + tenured + | in garbage collector, memory region dedicated to older objects. + | These are defined as objects that have survived 2 or more incremental collection cycles. + + xgen + | cross-generation tenured->nursery pointer; requires special GC bookkeeping + + xckp + | cross-checkpoint pointer; requires special GC bookkeeping + +.. toctree:: diff --git a/xo-object/utest/GC.test.cpp b/xo-object/utest/GC.test.cpp new file mode 100644 index 00000000..544dd149 --- /dev/null +++ b/xo-object/utest/GC.test.cpp @@ -0,0 +1,144 @@ +/* @file GC.test.cpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#include "xo/alloc/GC.hpp" +#include "xo/object/List.hpp" +#include "xo/object/Integer.hpp" +#include + +namespace xo { + using xo::obj::List; + using xo::obj::Integer; + using xo::gc::GC; + using xo::gc::generation_result; + using xo::gc::generation; + + namespace ut { + + // Also see GC unit tests in xo-alloc/utest + +#ifdef NOT_YET + namespace { + struct testcase_mlog { + testcase_mlog(std::size_t nz, std::size_t tz) : nursery_z_{nz}, tenured_z_{tz} {} + + std::size_t nursery_z_; + std::size_t tenured_z_; + }; + } +#endif + + TEST_CASE("gc-mlog-1", "[alloc][gc][gc_mutation]") + { + up gc = GC::make( + { + .initial_nursery_z_ = 1024, + .initial_tenured_z_ = 2048, + .debug_flag_ = true + }); + + REQUIRE(gc->gc_statistics().n_mutation_ == 0); + REQUIRE(gc->gc_statistics().n_logged_mutation_ == 0); + + REQUIRE(gc.get()); + + /* use gc for all Object allocs */ + Object::mm = gc.get(); + + gp l = List::list(Integer::make(1)); + gc->add_gc_root(reinterpret_cast(l.ptr_address())); + { + REQUIRE(l->size() == 1); + + REQUIRE(gc->tospace_generation_of(l.ptr()) == generation_result::nursery); + REQUIRE(gc->tospace_generation_of(l->head().ptr()) == generation_result::nursery); + + REQUIRE(gc->is_before_checkpoint(l.ptr()) == false); + REQUIRE(gc->is_before_checkpoint(l->head().ptr()) == false); + + REQUIRE(gc->mlog_size() == 0); + } + + // mutation, but not {xgen, xckp} since parent,child both in N0 + + l->assign_head(Integer::make(2)); + { + REQUIRE(gc->gc_statistics().n_mutation_ == 1); + REQUIRE(gc->gc_statistics().n_logged_mutation_ == 0); + REQUIRE(gc->gc_statistics().n_xgen_mutation_ == 0); + REQUIRE(gc->gc_statistics().n_xckp_mutation_ == 0); + REQUIRE(gc->mlog_size() == 0); + + REQUIRE(gc->is_gc_enabled() == true); + + } + + gc->request_gc(generation::nursery); + { + REQUIRE(gc->is_before_checkpoint(l.ptr()) == true); + REQUIRE(gc->is_before_checkpoint(l->head().ptr()) == true); + + REQUIRE(gc->tospace_generation_of(l.ptr()) == generation_result::nursery); + + REQUIRE(l->size() == 1); + REQUIRE(Integer::from(l->head()).ptr()); + REQUIRE(Integer::from(l->head())->value() == 2); + } + + // mutation, xckp since parent in N1, child in N0 + + l->assign_head(Integer::make(3)); + { + REQUIRE(Integer::from(l->head())->value() == 3); + + REQUIRE(gc->tospace_generation_of(l->head().ptr()) == generation_result::nursery); + REQUIRE(gc->is_before_checkpoint(l->head().ptr()) == false); + + REQUIRE(gc->gc_statistics().n_mutation_ == 2); + REQUIRE(gc->gc_statistics().n_logged_mutation_ == 1); + REQUIRE(gc->gc_statistics().n_xgen_mutation_ == 0); + REQUIRE(gc->gc_statistics().n_xckp_mutation_ == 1); + REQUIRE(gc->mlog_size() == 1); + } + + // gc promotes parent, still need mutation log for xgen ptr + + gc->request_gc(generation::nursery); + { + REQUIRE(l->size() == 1); + REQUIRE(Integer::from(l->head()).ptr()); + REQUIRE(Integer::from(l->head())->value() == 3); + + REQUIRE(gc->tospace_generation_of(l.ptr()) == generation_result::tenured); + REQUIRE(gc->tospace_generation_of(l->head().ptr()) == generation_result::nursery); + REQUIRE(gc->is_before_checkpoint(l->head().ptr())); + + REQUIRE(gc->gc_statistics().n_mutation_ == 2); + REQUIRE(gc->gc_statistics().n_logged_mutation_ == 1); + // counters recorded when mutation created. + // not modified by gc + REQUIRE(gc->gc_statistics().n_xgen_mutation_ == 0); + REQUIRE(gc->gc_statistics().n_xckp_mutation_ == 1); + REQUIRE(gc->mlog_size() == 1); + } + + // gc promotes child, no longer need mutation log entry + + gc->request_gc(generation::nursery); + { + REQUIRE(l->size() == 1); + REQUIRE(Integer::from(l->head()).ptr()); + REQUIRE(Integer::from(l->head())->value() == 3); + + REQUIRE(gc->tospace_generation_of(l.ptr()) == generation_result::tenured); + REQUIRE(gc->tospace_generation_of(l->head().ptr()) == generation_result::tenured); + + REQUIRE(gc->mlog_size() == 0); + } + } + } /*namespace ut*/ +} /*namespace xo*/ + +/* end GC.test.cpp */