xo-gc: refactor: move object type table -> GCObjectStore
This commit is contained in:
parent
08b313f25c
commit
63857a3025
8 changed files with 548 additions and 124 deletions
|
|
@ -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<AGCObject> * 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;
|
||||
|
|
|
|||
|
|
@ -6,34 +6,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "GCObjectStoreConfig.hpp"
|
||||
#include "ObjectTypeSlot.hpp"
|
||||
#include "generation.hpp"
|
||||
#include "object_age.hpp"
|
||||
#include <xo/alloc2/role.hpp>
|
||||
//#include <xo/arena/ArenaConfig.hpp>
|
||||
#include <array>
|
||||
|
||||
namespace xo {
|
||||
namespace mm {
|
||||
class DX1Collector;
|
||||
|
||||
/** @brief container to hold gc-aware objects for X1 collector
|
||||
**/
|
||||
class GCObjectStore {
|
||||
public:
|
||||
using ObjectTypeTable = DArenaVector<ObjectTypeSlot>;
|
||||
/* TODO: AllocIterator pointing to free pointer instead of std::byte* */
|
||||
using GCMoveCheckpoint = std::array<std::byte *, c_max_generation>;
|
||||
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<T>() 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<AAllocator> mm,
|
||||
obj<AAllocator> error_mm,
|
||||
obj<AGCObject> * 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
|
||||
**/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
65
include/xo/gc/ObjectTypeSlot.hpp
Normal file
65
include/xo/gc/ObjectTypeSlot.hpp
Normal file
|
|
@ -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 */
|
||||
|
|
@ -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_);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@
|
|||
|
||||
#include "X1Collector.hpp"
|
||||
#include <xo/gc/DX1CollectorIterator.hpp>
|
||||
|
||||
#include <xo/object2/Dictionary.hpp>
|
||||
#include <xo/object2/Array.hpp>
|
||||
#include <xo/object2/Integer.hpp>
|
||||
#include <xo/object2/Boolean.hpp>
|
||||
#include <xo/stringtable2/String.hpp>
|
||||
|
||||
#include <xo/alloc2/GCObject.hpp>
|
||||
#include <xo/alloc2/Allocator.hpp>
|
||||
#include <xo/alloc2/Arena.hpp>
|
||||
|
|
@ -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<AAllocator> error_mm,
|
||||
obj<AGCObject> * 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<AGCObject,DArray>(final_stats_v);
|
||||
|
||||
return ok;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -436,6 +449,8 @@ namespace xo {
|
|||
obj<AAllocator> error_mm,
|
||||
obj<AGCObject> * 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<ObjectTypeTable::size_type>(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<ObjectTypeTable::size_type>(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<AAllocator, DX1Collector> 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
|
||||
|
|
|
|||
|
|
@ -4,10 +4,30 @@
|
|||
**/
|
||||
|
||||
#include "GCObjectStore.hpp"
|
||||
|
||||
#include <xo/object2/Dictionary.hpp>
|
||||
#include <xo/object2/Array.hpp>
|
||||
#include <xo/object2/Integer.hpp>
|
||||
#include <xo/object2/Boolean.hpp>
|
||||
#include <xo/stringtable2/String.hpp>
|
||||
|
||||
#include <xo/arena/DArenaIterator.hpp>
|
||||
|
||||
#include <xo/facet/TypeRegistry.hpp>
|
||||
#include <xo/indentlog/scope.hpp>
|
||||
#include <cassert>
|
||||
#include <unistd.h> // 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<ObjectTypeTable::size_type>(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<ObjectTypeTable::size_type>(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<AAllocator> mm,
|
||||
obj<AAllocator> error_mm,
|
||||
obj<AGCObject> * 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<AGCObject,DString>(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<AGCObject,DDictionary>(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<AGCObject,DDictionary>::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<AGCObject,DInteger>::from(n_live_opt.value());
|
||||
auto bytes_gco = obj<AGCObject,DInteger>::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<AGCObject,DArray>(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<ObjectTypeTable::size_type>(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<ACollector>();
|
||||
|
||||
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*/
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
{}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue