diff --git a/include/xo/gc/DX1Collector.hpp b/include/xo/gc/DX1Collector.hpp index 9a0f281..6e7720e 100644 --- a/include/xo/gc/DX1Collector.hpp +++ b/include/xo/gc/DX1Collector.hpp @@ -6,7 +6,8 @@ #pragma once #include "X1CollectorConfig.hpp" -#include "GCObject.hpp" +//#include "ObjectTypeSlot.hpp" +//#include "GCObject.hpp" #include "GCObjectStore.hpp" #include "MutationLogState.hpp" #include "X1VerifyStats.hpp" @@ -82,55 +83,6 @@ namespace xo { obj * root_ = nullptr; }; - /** @brief Object Interface - * - * GC-object interface for a particular type. - * X1 maintains a table of these (X1Collector::object_types_) - * indexed by typeseq. - * - * Using a wrapper here for searchability - **/ - struct ObjectTypeSlot { - ObjectTypeSlot() {} - explicit ObjectTypeSlot(AGCObject * iface) { - this->store_iface(iface); - } - - /** true iff this slot is empty **/ - bool is_null() const noexcept { - return this->_iface()->_has_null_vptr(); - } - - bool is_occupied() const noexcept { - return !this->is_null(); - } - - AGCObject * _iface() const noexcept { - return std::launder((AGCObject *)&iface_[0]); - } - - AGCObject * iface() const noexcept { - AGCObject * x = this->_iface(); - - return (x->_has_null_vptr() ? nullptr : x); - } - - /** Store interface pointer @p iface. - * We just want the vtable here - **/ - void store_iface(const AGCObject * iface) { - ::memcpy((void*)&(this->iface_[0]), (void*)iface, sizeof(AGCObject)); - } - - private: - /** runtime interface for this object. - * We might prefer to declare this as AGCObject, but that's prohibited - * since AGCObject has abstract methods. - * Main downside of this form is it makes the data unintelligible to debugger. - **/ - alignas(AGCObject) std::byte iface_[sizeof(AGCObject)]; - }; - // ----- DX1Collector ----- /** @brief garbage collector 'X1' @@ -165,7 +117,7 @@ namespace xo { std::string_view name() const noexcept { return config_.name_; } GCRunState runstate() const noexcept { return runstate_; } - const ObjectTypeTable * get_object_types() const noexcept { return &object_types_; } + const ObjectTypeTable * get_object_types() const noexcept { return gco_store_.get_object_types(); } const RootSet * get_root_set() const noexcept { return &root_set_; } const DArena * get_space(role r, Generation g) const noexcept { return gco_store_.get_space(r, g); } DArena * get_space(role r, Generation g) noexcept { return gco_store_.get_space(r, g); } @@ -204,7 +156,8 @@ namespace xo { /** Report gc statistics as a dictionary. * Providing for the same of making GC statistics visible to schematika programs * - * @p mm allocate stats dictionary from this allocator. May be the same as this collector. + * @p mm allocate stats dictionary from this allocator. + * May be the same as this collector. * @p error_mm Allocator for last-report error reporting when out-of-memory. * @p p_output on exit @p *p_output contains stats dictionary **/ @@ -215,7 +168,8 @@ namespace xo { /** Report per-object-type information as a dictionary. * Scans to-space to count per-object-type information * - * @p mm allocate stats dictionary from this allocator. May be the same as this collector. + * @p mm allocate stats dictionary from this allocator. + * May be the same as this collector. * @p error_mm Allocator for last-report error reporting when out-of-memory. * @p p_output on exit @p *p_output contains stats dictionary **/ @@ -409,8 +363,10 @@ namespace xo { void clear() noexcept; private: +#ifdef OBSOLETE /** aux init function: initialize @ref object_types_ arena **/ void _init_object_types(const X1CollectorConfig & cfg, std::size_t page_z); +#endif /** aux init function: initialize @ref roots_ arena **/ void _init_gc_roots(const X1CollectorConfig & cfg, std::size_t page_z); /** aux init function: initialize @ref mlog_storage_[][] arenas **/ @@ -443,7 +399,8 @@ namespace xo { /** traverse objects allocated after @p ckp, to make sure their children * are forwarded. Repeat until traverse doesn't find any unforwarded children **/ - void _forward_children_until_fixpoint(Generation upto, GCMoveCheckpoint ckp); + void _forward_children_until_fixpoint(Generation upto, + GCMoveCheckpoint ckp); /** Evacuate object at @p *lhs_data to to-space. * Replace original with forwarding pointer to new location **/ @@ -460,10 +417,10 @@ namespace xo { /** current gc state **/ GCRunState runstate_; - /** (ab)using arena to get an extensible array of object types. - * For each type need to store one (8-byte) IGCObject_Any instance, - **/ +#ifdef MARKED + /** gc-aware object types **/ ObjectTypeTable object_types_; +#endif /** gc disabled whenever gc_blocked_ > 0 **/ uint32_t gc_blocked_ = 0; diff --git a/include/xo/gc/GCObjectStore.hpp b/include/xo/gc/GCObjectStore.hpp index dff223c..b1d9679 100644 --- a/include/xo/gc/GCObjectStore.hpp +++ b/include/xo/gc/GCObjectStore.hpp @@ -6,34 +6,51 @@ #pragma once #include "GCObjectStoreConfig.hpp" +#include "ObjectTypeSlot.hpp" #include "generation.hpp" #include "object_age.hpp" #include -//#include #include namespace xo { namespace mm { + class DX1Collector; /** @brief container to hold gc-aware objects for X1 collector **/ class GCObjectStore { public: + using ObjectTypeTable = DArenaVector; + /* TODO: AllocIterator pointing to free pointer instead of std::byte* */ + using GCMoveCheckpoint = std::array; using header_type = DArena::header_type; using value_type = DArena::value_type; using size_type = DArena::size_type; + using typeseq = xo::reflect::typeseq; public: explicit GCObjectStore(const GCObjectStoreConfig & cfg); const GCObjectStoreConfig & config() const noexcept { return config_; } + const ObjectTypeTable * get_object_types() const noexcept { return &object_types_; } const DArena * get_space(role r, Generation g) const noexcept { return space_[r][g]; } DArena * get_space(role r, Generation g) noexcept { return space_[r][g]; } DArena * from_space(Generation g) noexcept { return get_space(role::from_space(), g); } DArena * to_space(Generation g) noexcept { return get_space(role::to_space(), g); } DArena * new_space() noexcept { return to_space(Generation{0}); } + /** true iff type with id @p tseq has known metadata + * (i.e. has appeared in preceding call to install_type + * for this collector) + **/ + bool is_type_installed(typeseq tseq) const noexcept; + + /** lookup interface from type sequence + * (can use tseq = typeseq::id() for type T) + **/ + const AGCObject * lookup_type(typeseq tseq) const noexcept; + /** generation to which pointer @p addr belongs, given role @p r; * sentinel if not found in this collector **/ @@ -67,14 +84,48 @@ namespace xo { **/ bool contains_allocated(role r, const void * addr) const noexcept; + /** Report per-age-bucket information as an array of dictionaries. + * Scans to-space to count per-age statistics. + * Each dictionary has keys "n-live" and "bytes". + * Array index corresponds to object age. + * + * @p mm allocate stats from this allocator. + * @p error_mm allocator for error reporting when out-of-memory. + * @p p_output on exit @p *p_output contains stats array + **/ + bool report_object_types(obj mm, + obj error_mm, + obj * p_output) const noexcept; + + /** snap checkpoint containing allocator state + * use to detect forwarding activity after visiting objects + **/ + GCMoveCheckpoint snap_move_checkpoint(Generation upto); + + /** Register object type with this collector. + * Provides shallow copy and pointer forwarding for instances of this + * type. + **/ + bool install_type(const AGCObject & meta) noexcept; + + /** traverse objects allocated after @p ckp, to make sure their children + * are forwarded. Repeat until traverse doesn't find any unforwarded children. + * + * 1. Breadth-first implementation, bad for memory locality + * 2. Need @p gc for per-object-type forward_children api + **/ + void _forward_children_until_fixpoint(DX1Collector * gc, + Generation upto, + const GCMoveCheckpoint & gray_lo_v); + /** true iff {@p alloc_hdr, @p object_data} should move for * a collection of all generations strictly younger than @p upto. * * Require: runstate_.is_running() **/ - bool check_move_policy(Generation upto, - header_type alloc_hdr, - void * gco_data) const noexcept; + bool _check_move_policy(header_type alloc_hdr, + void * gco_data, + Generation upto) const noexcept; /** For each generation g in [0 ,.., upto) * swap arenas assigned to {to-space, from-space}. @@ -92,6 +143,10 @@ namespace xo { bool sanitize_flag); private: + + /** configure @ref object_types_, using @p page_z **/ + void _init_object_types(std::size_t page_z); + /** auxiliary init function **/ void _init_space(); @@ -99,6 +154,9 @@ namespace xo { /** configuration for gc-aware object store **/ GCObjectStoreConfig config_; + /** gc-aware object types **/ + ObjectTypeTable object_types_; + /** arena objects for collector managed memory * 1:1 with roles, but polarity reverses for each collection **/ diff --git a/include/xo/gc/GCObjectStoreConfig.hpp b/include/xo/gc/GCObjectStoreConfig.hpp index a5512d1..08cfd37 100644 --- a/include/xo/gc/GCObjectStoreConfig.hpp +++ b/include/xo/gc/GCObjectStoreConfig.hpp @@ -18,6 +18,7 @@ namespace xo { GCObjectStoreConfig(const ArenaConfig & arena_cfg, std::uint32_t ngen, std::uint32_t nsurvive, + std::size_t object_types_z, bool debug_flag); /** generation that would contain an object that has survived @@ -61,6 +62,9 @@ namespace xo { **/ std::uint32_t n_survive_threshold_ = 2; + /** storage for N object types requires 8*N bytes **/ + std::size_t object_types_z_ = 2*1024*1024; + /** true to enable debug logging **/ bool debug_flag_ = false; }; diff --git a/include/xo/gc/ObjectTypeSlot.hpp b/include/xo/gc/ObjectTypeSlot.hpp new file mode 100644 index 0000000..214d31c --- /dev/null +++ b/include/xo/gc/ObjectTypeSlot.hpp @@ -0,0 +1,65 @@ +/** @file ObjectTypeSlot.hpp + * + * @author Roland Conybeare, Apr 2026 + **/ + +#pragma once + +#include "GCObject.hpp" + +namespace xo { + namespace mm { + + /** @brief Object Interface + * + * GC-object interface for a particular type. + * X1 maintains a table of these (X1Collector::object_types_) + * indexed by typeseq. + * + * Using a wrapper here for searchability + **/ + struct ObjectTypeSlot { + ObjectTypeSlot() {} + explicit ObjectTypeSlot(AGCObject * iface) { + this->store_iface(iface); + } + + /** true iff this slot is empty **/ + bool is_null() const noexcept { + return this->_iface()->_has_null_vptr(); + } + + bool is_occupied() const noexcept { + return !this->is_null(); + } + + AGCObject * _iface() const noexcept { + return std::launder((AGCObject *)&iface_[0]); + } + + AGCObject * iface() const noexcept { + AGCObject * x = this->_iface(); + + return (x->_has_null_vptr() ? nullptr : x); + } + + /** Store interface pointer @p iface. + * We just want the vtable here + **/ + void store_iface(const AGCObject * iface) { + ::memcpy((void*)&(this->iface_[0]), (void*)iface, sizeof(AGCObject)); + } + + private: + /** runtime interface for this object. + * We might prefer to declare this as AGCObject, but that's prohibited + * since AGCObject has abstract methods. + * Main downside of this form is it makes the data unintelligible to debugger. + **/ + alignas(AGCObject) std::byte iface_[sizeof(AGCObject)]; + }; + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end ObjectTypeSlot.hpp */ diff --git a/include/xo/gc/X1CollectorConfig.hpp b/include/xo/gc/X1CollectorConfig.hpp index 3e46b67..6ea9562 100644 --- a/include/xo/gc/X1CollectorConfig.hpp +++ b/include/xo/gc/X1CollectorConfig.hpp @@ -15,6 +15,8 @@ namespace xo { namespace mm { + /** @brief configuration for X1 collector + **/ struct X1CollectorConfig { using size_type = std::size_t; @@ -43,15 +45,13 @@ namespace xo { return GCObjectStoreConfig(arena_config_, n_generation_, n_survive_threshold_, + object_types_z_, debug_flag_); } /** fetch configuration for mutation log store **/ MutationLogConfig mlog_config() const noexcept { return MutationLogConfig(n_generation_, -#ifdef OBSOLETE - n_survive_threshold_, -#endif mutation_log_z_, debug_flag_); } diff --git a/src/gc/DX1Collector.cpp b/src/gc/DX1Collector.cpp index 2df998f..e09fb10 100644 --- a/src/gc/DX1Collector.cpp +++ b/src/gc/DX1Collector.cpp @@ -5,11 +5,13 @@ #include "X1Collector.hpp" #include + #include #include #include #include #include + #include #include #include @@ -76,14 +78,18 @@ namespace xo { size_t page_z = getpagesize(); - this->_init_object_types(cfg, page_z); + //this->_init_object_types(cfg, page_z); this->_init_gc_roots(cfg, page_z); this->_init_mlogs(page_z); } +#ifdef OBSOLETE // called from GCObjectStore ctor void DX1Collector::_init_object_types(const X1CollectorConfig & cfg, std::size_t page_z) { + gco_state_._init_object_types(); + +#ifdef MOVED /* 1MB reserved address space enough for up to 128k distinct types. * In this case don't want to use hugepages since actual #of types * likely << .size/8 @@ -93,7 +99,9 @@ namespace xo { .size_ = cfg.object_types_z_, .hugepage_z_ = page_z, .store_header_flag_ = false}); +#endif } +#endif void DX1Collector::_init_gc_roots(const X1CollectorConfig & cfg, std::size_t page_z) @@ -114,7 +122,7 @@ namespace xo { void DX1Collector::visit_pools(const MemorySizeVisitor & visitor) const { - object_types_.visit_pools(visitor); + //object_types_.visit_pools(visitor); root_set_.visit_pools(visitor); gco_store_.visit_pools(visitor); @@ -154,7 +162,8 @@ namespace xo { accumulate_total_aux(const DX1Collector & d, size_t (DArena::* get_stat_fn)() const) noexcept { - size_t z1 = (d.object_types_.store()->*get_stat_fn)(); + //size_t z1 = (d.object_types_.store()->*get_stat_fn)(); + size_t z1 = (d.gco_store_.get_object_types()->store()->*get_stat_fn)(); size_t z2 = (d.root_set_.store()->*get_stat_fn)(); size_t z3 = 0; @@ -325,6 +334,9 @@ namespace xo { obj error_mm, obj * p_output) const noexcept { + return gco_store_.report_object_types(mm, error_mm, p_output); + +#ifdef MOVED scope log(XO_DEBUG(true)); (void)error_mm; @@ -429,6 +441,7 @@ namespace xo { *p_output = obj(final_stats_v); return ok; +#endif } bool @@ -436,6 +449,8 @@ namespace xo { obj error_mm, obj * p_output) const noexcept { + //return gco_store_.report_object_ages(mm, error_mm, p_output); + scope log(XO_DEBUG(true)); (void)error_mm; @@ -544,14 +559,7 @@ namespace xo { bool DX1Collector::is_type_installed(typeseq tseq) const noexcept { - if (tseq.is_sentinel() - || static_cast(tseq.seqno()) > object_types_.size()) { - return false; - } - - const ObjectTypeSlot & slot = object_types_[tseq.seqno()]; - - return slot.is_occupied(); + return gco_store_.is_type_installed(tseq); } bool @@ -661,42 +669,16 @@ namespace xo { const AGCObject * DX1Collector::lookup_type(typeseq tseq) const noexcept { - scope log(XO_DEBUG(false)); - - if (tseq.is_sentinel() - || static_cast(tseq.seqno()) > object_types_.size()) { - - log.retroactively_enable("out-of-bounds", - xtag("tseq", tseq), xtag("tname", TypeRegistry::id2name(tseq))); - - log(xtag("types.size", object_types_.size()), - xtag("types.allocated", object_types_.store()->allocated()), - xtag("types.committed", object_types_.store()->committed()), - xtag("types.lo", object_types_.store()->lo_), - xtag("types.limit", object_types_.store()->limit_), - xtag("types.hi", object_types_.store()->hi_)); - - assert(false); - return nullptr; - } - - const ObjectTypeSlot & slot = object_types_[tseq.seqno()]; - - if (slot.is_null()) { - log.retroactively_enable("null-vtable", - xtag("tseq", tseq), xtag("tname", TypeRegistry::id2name(tseq))); - - assert(false); - return nullptr; - } - - return slot.iface(); + return gco_store_.lookup_type(tseq); } /* editor bait: register_type */ bool DX1Collector::install_type(const AGCObject & meta) noexcept { + return gco_store_.install_type(meta); + +#ifdef MARKED typeseq tseq = meta._typeseq(); assert(tseq.seqno() > 0); @@ -715,6 +697,7 @@ namespace xo { slot.store_iface(&meta); return true; +#endif } void @@ -916,15 +899,15 @@ namespace xo { DX1Collector::_deep_move_gc_owned(void * from_src, Generation upto) { - scope log(XO_DEBUG(config_.debug_flag_)); + scope log(XO_DEBUG(gco_store_.config().debug_flag_)); - AllocInfo info = this->alloc_info((std::byte *)from_src); + AllocInfo info = gco_store_.alloc_info((std::byte *)from_src); AllocHeader hdr = info.header(); typeseq tseq(info.tseq()); - assert(this->contains_allocated(role::from_space(), from_src)); + assert(gco_store_.contains_allocated(role::from_space(), from_src)); - if (is_forwarding_header(hdr)) { + if (gco_store_.is_forwarding_header(hdr)) { /* already forwarded - pickup destination * * Coordinates with forward_inplace() @@ -936,7 +919,7 @@ namespace xo { /* here: object at from_src not already forwarded */ - if (!this->check_move_policy(hdr, from_src)) { + if (!gco_store_._check_move_policy(hdr, from_src, upto)) { /* object at from_src is in generation that is not being collected */ log && log("disposition: not moving from_src"); @@ -946,7 +929,7 @@ namespace xo { log && log("disposition: move subtree"); /* TODO: AllocIterator pointing to free pointer */ - GCMoveCheckpoint gray_lo_v = this->_snap_move_checkpoint(upto); + GCMoveCheckpoint gray_lo_v = gco_store_.snap_move_checkpoint(upto); obj alloc(this); const AGCObject * iface = lookup_type(tseq); @@ -965,19 +948,18 @@ namespace xo { auto DX1Collector::_snap_move_checkpoint(Generation upto) -> GCMoveCheckpoint { - GCMoveCheckpoint gray_lo_v; - - for (uint32_t g = 0; g < upto; ++g) { - gray_lo_v[g] = this->to_space(Generation{g})->free_; - } - - return gray_lo_v; + return gco_store_.snap_move_checkpoint(upto); } void DX1Collector::_forward_children_until_fixpoint(Generation upto, GCMoveCheckpoint gray_lo_v) { + // problem -- need object type lookup +#ifdef NOT_YET + gco_store_._forward_children_until_fixpoint(upto, gray_lo_v); +#endif + scope log(XO_DEBUG(config_.debug_flag_)); /** @@ -1384,9 +1366,9 @@ namespace xo { { assert(runstate_.is_running()); - return gco_store_.check_move_policy(runstate_.gc_upto(), - alloc_hdr, - object_data); + return gco_store_._check_move_policy(alloc_hdr, + object_data, + runstate_.gc_upto()); } auto diff --git a/src/gc/GCObjectStore.cpp b/src/gc/GCObjectStore.cpp index 3ba98e8..2b11882 100644 --- a/src/gc/GCObjectStore.cpp +++ b/src/gc/GCObjectStore.cpp @@ -4,10 +4,30 @@ **/ #include "GCObjectStore.hpp" + +#include +#include +#include +#include +#include + +#include + +#include #include #include +#include // for ::getpagesize() namespace xo { + using xo::scm::DDictionary; + using xo::scm::DArray; + using xo::scm::DString; + using xo::scm::DInteger; + + using xo::mm::DArena; + using xo::facet::TypeRegistry; + using xo::reflect::typeseq; + namespace mm { GCObjectStore::GCObjectStore(const GCObjectStoreConfig & cfg) @@ -17,9 +37,26 @@ namespace xo { config_.arena_config_.header_.age_bits_ + config_.arena_config_.header_.tseq_bits_ <= 64); + size_t page_z = getpagesize(); + + this->_init_object_types(page_z); this->_init_space(); } + void + GCObjectStore::_init_object_types(std::size_t page_z) + { + /* 1MB reserved address space enough for up to 128k distinct types. + * In this case don't want to use hugepages since actual #of types + * likely << .size/8 + */ + this->object_types_ + = ObjectTypeTable::map(ArenaConfig{.name_ = "x1-object-types", + .size_ = config_.object_types_z_, + .hugepage_z_ = page_z, + .store_header_flag_ = false}); + } + void GCObjectStore::_init_space() { @@ -59,6 +96,57 @@ namespace xo { } } + bool + GCObjectStore::is_type_installed(typeseq tseq) const noexcept + { + if (tseq.is_sentinel() + || static_cast(tseq.seqno()) > object_types_.size()) { + return false; + } + + const ObjectTypeSlot & slot = object_types_[tseq.seqno()]; + + return slot.is_occupied(); + } + + const AGCObject * + GCObjectStore::lookup_type(typeseq tseq) const noexcept + { + scope log(XO_DEBUG(false)); + + if (tseq.is_sentinel() + || (static_cast(tseq.seqno()) + > object_types_.size())) { + + log.retroactively_enable("out-of-bounds", + xtag("tseq", tseq), + xtag("tname", TypeRegistry::id2name(tseq))); + + log(xtag("types.size", object_types_.size()), + xtag("types.allocated", object_types_.store()->allocated()), + xtag("types.committed", object_types_.store()->committed()), + xtag("types.lo", object_types_.store()->lo_), + xtag("types.limit", object_types_.store()->limit_), + xtag("types.hi", object_types_.store()->hi_)); + + assert(false); + return nullptr; + } + + const ObjectTypeSlot & slot = object_types_[tseq.seqno()]; + + if (slot.is_null()) { + log.retroactively_enable("null-vtable", + xtag("tseq", tseq), + xtag("tname", TypeRegistry::id2name(tseq))); + + assert(false); + return nullptr; + } + + return slot.iface(); + } + Generation GCObjectStore::generation_of(role r, const void * addr) const noexcept { @@ -131,6 +219,8 @@ namespace xo { space_storage_[i][j].visit_pools(visitor); } } + + object_types_.visit_pools(visitor); } bool @@ -151,9 +241,120 @@ namespace xo { } bool - GCObjectStore::check_move_policy(Generation upto, - header_type alloc_hdr, - void * object_data) const noexcept + GCObjectStore::report_object_types(obj mm, + obj error_mm, + obj * p_output) const noexcept + { + scope log(XO_DEBUG(true)); + + (void)error_mm; + + bool ok = true; + + // stats, indexed by tseq + // could use c++ vector in scratch space instead of running on + // boxed types. + // + DArray * stats_v = DArray::empty(mm, object_types_.size()); + + if (!stats_v) + return false; + + stats_v->resize(stats_v->capacity()); + + log && log(xtag("object_types_.size", object_types_.size()), + xtag("stats_v.capacity", stats_v->capacity()), + xtag("stats_v.size", stats_v->size())); + + // count #of occupied type slots + std::uint32_t n_tseq_present = 0; + // largest tseq present with non-null AGCObject* iface + std::int32_t max_tseq = 0; + + for (const ObjectTypeSlot & slot : object_types_) { + AGCObject * iface = slot.iface(); + + if (iface) { + typeseq tseq = iface->_typeseq(); + + ++n_tseq_present; + if (max_tseq < tseq.seqno()) + max_tseq = tseq.seqno(); + + assert(tseq.seqno() >= 0); + + auto tname_sv = TypeRegistry::id2name(tseq); + DString * tname = DString::from_view(mm, tname_sv); + + DDictionary * recd = DDictionary::make(mm); + + if (!recd) + return false; + + recd->upsert_cstr(mm, "name", obj(tname)); + recd->upsert_cstr(mm, "tseq", DInteger::box(mm, tseq.seqno())); + recd->upsert_cstr(mm, "n-live", DInteger::box(mm, 0)); + recd->upsert_cstr(mm, "bytes", DInteger::box(mm, 0)); + + stats_v->assign_at(tseq.seqno(), obj(recd)); + } + } + + // scan to-space, count objects by type + + for (Generation g{0}; g < config_.n_generation_; ++g) { + const DArena * arena = this->get_space(role::to_space(), g); + + for (AllocInfo info : *arena) { + if (info.is_forwarding_tseq()) { + assert(false); + return false; + } + + uint32_t ix = info.tseq(); + size_t z = info.size(); + + auto recd = obj::from(stats_v->at(ix)); + + assert(recd); + + auto n_live_opt = recd->lookup_cstr("n-live"); + assert(n_live_opt); + auto bytes_opt = recd->lookup_cstr("bytes"); + assert(bytes_opt); + + if (n_live_opt && bytes_opt) { + auto n_live_gco = obj::from(n_live_opt.value()); + auto bytes_gco = obj::from(bytes_opt.value()); + + n_live_gco->assign_value(n_live_gco->value() + 1); + bytes_gco->assign_value(bytes_gco->value() + z); + } + } + } + + stats_v->resize(max_tseq + 1); + + DArray * final_stats_v = DArray::empty(mm, n_tseq_present); + + for (std::size_t i = 0, n = stats_v->size(); i < n; ++i) { + auto recd = stats_v->at(i); + + if (recd) { + bool ok = final_stats_v->push_back(recd); + assert(ok); + } + } + + *p_output = obj(final_stats_v); + + return ok; + } + + bool + GCObjectStore::_check_move_policy(header_type alloc_hdr, + void * object_data, + Generation upto) const noexcept { (void)object_data; @@ -201,6 +402,161 @@ namespace xo { } } + auto + GCObjectStore::snap_move_checkpoint(Generation upto) -> GCMoveCheckpoint + { + GCMoveCheckpoint gray_lo_v; + + for (uint32_t g = 0; g < upto; ++g) { + gray_lo_v[g] = this->to_space(Generation{g})->free_; + } + + return gray_lo_v; + } + + /* editor bait: register_type */ + bool + GCObjectStore::install_type(const AGCObject & meta) noexcept + { + typeseq tseq = meta._typeseq(); + + assert(tseq.seqno() > 0); + + auto ix = static_cast(tseq.seqno()); + + if (ix >= object_types_.size()) { + if (!object_types_.resize(std::max(2 * object_types_.size(), ix + 1))) + return false; + } + + assert(ix < object_types_.size()); + + ObjectTypeSlot & slot = object_types_[ix]; + + slot.store_iface(&meta); + + return true; + } + +#ifdef MARKED + void + GCObjectStore::_forward_children_until_fixpoint(DX1Collector * gc, + Generation upto, + const GCMoveCheckpoint & gray_lo_v) + { + scope log(XO_DEBUG(config_.debug_flag_)); + + /** + * To-space: + * + * to_lo = start of to-space + * w,W = white objects. An object x is white if x + * + all immediate children of x are in to-space + * (also implies this GC cycle put it there) + * g,G = grey objects. An object x is gray if it's in to-space, + * but possibly has >0 black children + * _ = free to-space memory + * N = nursery space (generation{0}) + * T = tenured space (generation{1}) + * + * wwwwwwwwwwwwwwwwwwwggggggggggggggggggggg_________________... + * ^ ^ ^ + * to_lo grey_lo(N) free_ptr(N) + * + * After moving children of one object, + * advancing {nursery_grey_lo, nursery_free_ptr} + * + * wwwwwwwwwwwwwwwwwwwWWWWgggggggggggggggggGGGGGGGGGGG______... + * ^ ^ ^ + * to_lo grey_lo(N) free_ptr(N) + * + * Invariant: + * + * objects in [to_lo, gray_lo) are white. + * all gray objects are in [gray_lo, free_ptr) + * memory starting at free_ptr is free. + * + * deep_move terminates when gray_lo catches up to free_ptr + * + * Above is simplified. Complication is that GC (including incremental) may + * promote objects from nursery (N) to tenured (T) + * + * So more accurate before/after picture + * + * N wwwwwwwwwwwwwwwwwwwggggggggggggggggggggg_________________... + * ^ ^ ^ + * to_lo(N) grey_lo(N) free_ptr(N) + * + * T wwwwwwwwwwwwwwgggggggggggg_______________________________... + * ^ ^ ^ + * to_lo(T) grey_lo(T) free_ptr(N) + * + * After moving children of one object, + * advancing {nursery_grey_lo, nursery_free_ptr} + * + * N wwwwwwwwwwwwwwwwwwwWWWWgggggggggggggggggGGGGGGGGGGG_____... + * ^ ^ ^ + * to_lo(N) grey_lo(N) free_ptr(N) + * + * T wwwwwwwwwwwwwwggggggggggggGGGGG_________________________... + * ^ ^ ^ + * to_lo(T) grey_lo(T) free_ptr(T) + * + * deep_move terminates when both: + * - gray_lo(N) catches up with free_ptr(N) + * - gray_lo(T) catches up with free_ptr(T) + * + **/ + + std::size_t fixup_work = 0; + + /* TODO: + * - loop here is bad for memory locality + * - replace with depth-first traversal + */ + do { + fixup_work = 0; + + for (Generation g = Generation{0}; g < upto; ++g) { + /** object index for this pass **/ + size_t i_obj = 0; + + /* TODO: use AllocIterator here */ + while(gray_lo_v[g] < to_space(g)->free_) { + AllocHeader * hdr = (AllocHeader *)gray_lo_v[g]; + void * src = (hdr + 1); + + const auto & hdr_cfg = config_.arena_config_.header_; + typeseq tseq = typeseq(hdr_cfg.tseq(*hdr)); + size_t z = hdr_cfg.size_with_padding(*hdr); + + log && log("deep_move_gc_owned: fwd to-space children", + xtag("g", g), + xtag("i_obj", i_obj), + xtag("src", src), + xtag("tseq", tseq), + xtag("tname", TypeRegistry::id2name(tseq)), + xtag("z", z)); + + const AGCObject * iface = this->lookup_type(tseq); + + assert(iface->_has_null_vptr() == false); + + auto gc = this->ref(); + + iface->forward_children(src, gc); + + gray_lo_v[g] = ((std::byte *)src) + z; + + ++i_obj; + ++fixup_work; + } + } + } while (fixup_work > 0); + } /*_forward_children_until_fixpoint*/ +#endif + + } /*namespace mm*/ } /*namespace xo*/ diff --git a/src/gc/GCObjectStoreConfig.cpp b/src/gc/GCObjectStoreConfig.cpp index 560d7c3..132b3fb 100644 --- a/src/gc/GCObjectStoreConfig.cpp +++ b/src/gc/GCObjectStoreConfig.cpp @@ -11,10 +11,12 @@ namespace xo { GCObjectStoreConfig::GCObjectStoreConfig(const ArenaConfig & arena_cfg, std::uint32_t ngen, std::uint32_t nsurvive, + std::size_t object_types_z, bool debug_flag) : arena_config_{arena_cfg}, n_generation_{ngen}, n_survive_threshold_{nsurvive}, + object_types_z_{object_types_z}, debug_flag_{debug_flag} {}