xo-gc: use DArenaVector for DX1Collector.object_types_

Original implementation predated DArenaVector,
using it is more natural
This commit is contained in:
Roland Conybeare 2026-03-29 15:17:31 -04:00
commit 890fd0fe1a
3 changed files with 92 additions and 32 deletions

View file

@ -80,6 +80,49 @@ 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]);
}
/** 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)];
};
/** @brief info collected during a @ref DX1Collector::verify_ok call
*
**/
@ -119,6 +162,7 @@ namespace xo {
struct DX1Collector {
public:
using RootSet = DArenaVector<GCRoot>;
using ObjectTypeTable = DArenaVector<ObjectTypeSlot>;
/* TODO: AllocIterator pointing to free pointer instead of std::byte* */
using GCMoveCheckpoint = std::array<std::byte *, c_max_generation>;
using MutationLog = DArenaVector<MutationLogEntry>;
@ -142,7 +186,7 @@ namespace xo {
std::string_view name() const noexcept { return config_.name_; }
GCRunState runstate() const noexcept { return runstate_; }
const DArena * get_object_types() const noexcept { return &object_types_; }
const ObjectTypeTable * get_object_types() const noexcept { return &object_types_; }
const RootSet * get_root_set() const noexcept { return &root_set_; }
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]; }
@ -409,7 +453,7 @@ namespace xo {
/** (ab)using arena to get an extensible array of object types.
* For each type need to store one (8-byte) IGCObject_Any instance,
**/
DArena object_types_;
ObjectTypeTable object_types_;
/** gc disabled whenever gc_blocked_ > 0 **/
uint32_t gc_blocked_ = 0;

View file

@ -82,10 +82,10 @@ namespace xo {
* likely << .size/8
*/
this->object_types_
= DArena::map(ArenaConfig{.name_ = "x1-object-types",
.size_ = cfg.object_types_z_,
.hugepage_z_ = page_z,
.store_header_flag_ = false});
= ObjectTypeTable::map(ArenaConfig{.name_ = "x1-object-types",
.size_ = cfg.object_types_z_,
.hugepage_z_ = page_z,
.store_header_flag_ = false});
}
void
@ -242,7 +242,7 @@ namespace xo {
accumulate_total_aux(const DX1Collector & d,
size_t (DArena::* get_stat_fn)() const) noexcept
{
size_t z1 = (d.object_types_.*get_stat_fn)();
size_t z1 = (d.object_types_.store()->*get_stat_fn)();
size_t z2 = (d.root_set_.store()->*get_stat_fn)();
size_t z3 = 0;
@ -424,14 +424,14 @@ namespace xo {
bool
DX1Collector::is_type_installed(typeseq tseq) const noexcept
{
if (object_types_.committed() < sizeof(AGCObject) * (tseq.seqno() + 1))
if (tseq.is_sentinel()
|| static_cast<ObjectTypeTable::size_type>(tseq.seqno()) > object_types_.size()) {
return false;
}
AGCObject * v = reinterpret_cast<AGCObject *>(object_types_.lo_);
const ObjectTypeSlot & slot = object_types_[tseq.seqno()];
void * vtable = *(void **)&(v[tseq.seqno()]);
return (vtable != nullptr);
return slot.is_occupied();
}
AllocInfo
@ -600,24 +600,34 @@ namespace xo {
{
scope log(XO_DEBUG(false));
AGCObject * v = reinterpret_cast<AGCObject *>(object_types_.lo_);
if (tseq.is_sentinel()
|| static_cast<ObjectTypeTable::size_type>(tseq.seqno()) > object_types_.size()) {
const AGCObject * target = &(v[tseq.seqno()]);
log.retroactively_enable("out-of-bounds",
xtag("tseq", tseq), xtag("tname", TypeRegistry::id2name(tseq)));
if (reinterpret_cast<const std::byte *>(target) >= object_types_.limit_) {
log.retroactively_enable(xtag("tseq", tseq), xtag("tname", TypeRegistry::id2name(tseq)));
log(xtag("types.allocated", object_types_.allocated()),
xtag("types.committed", object_types_.committed()),
xtag("types.lo", object_types_.lo_),
xtag("types.limit", object_types_.limit_),
xtag("types.hi", object_types_.hi_));
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;
}
return target;
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();
}
/* editor bait: register_type */
@ -626,14 +636,20 @@ namespace xo {
{
typeseq tseq = meta._typeseq();
bool ok = object_types_.expand(sizeof(AGCObject) * (tseq.seqno() + 1));
if (!ok)
return false;
assert(tseq.seqno() > 0);
AGCObject * v = reinterpret_cast<AGCObject *>(object_types_.lo_);
auto ix = static_cast<ObjectTypeTable::size_type>(tseq.seqno());
/* explicitly copying vtable pointer here */
std::memcpy((void*)&(v[tseq.seqno()]), (void*)&meta, sizeof(AGCObject));
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;
}

View file

@ -133,11 +133,11 @@ namespace ut {
{
REQUIRE(gc.name() == "x1_test");
const DArena * otypes = gc.get_object_types();
const DX1Collector::ObjectTypeTable * otypes = gc.get_object_types();
REQUIRE(otypes != nullptr);
REQUIRE(otypes->reserved() >= cfg.object_types_z_);
REQUIRE(otypes->reserved() < cfg.object_types_z_ + otypes->page_z_);
REQUIRE(otypes->store()->reserved() >= cfg.object_types_z_);
REQUIRE(otypes->store()->reserved() < cfg.object_types_z_ + otypes->store()->page_z_);
const DX1Collector::RootSet * roots = gc.get_root_set();
@ -180,7 +180,7 @@ namespace ut {
REQUIRE(to_2 == nullptr);
REQUIRE(gc.reserved()
== otypes->reserved() + roots->store()->reserved() + 4 * from_0->reserved());
== otypes->store()->reserved() + roots->store()->reserved() + 4 * from_0->reserved());
log && log(xtag("from_0", from_0->lo_), xtag("to_0", to_0->lo_));
}