diff --git a/xo-alloc2/idl/Collector.json5 b/xo-alloc2/idl/Collector.json5 index 865d992e..e7e53a0f 100644 --- a/xo-alloc2/idl/Collector.json5 +++ b/xo-alloc2/idl/Collector.json5 @@ -101,7 +101,7 @@ noexcept: true, attributes: [], }, - // obj summary() const noexcept; + // bool report_statistics(obj report_mm, obj error_mm, obj * output); { name: "report_statistics", doc: [ diff --git a/xo-arena/include/xo/arena/DArenaVector.hpp b/xo-arena/include/xo/arena/DArenaVector.hpp index 0ac5d689..21e059d1 100644 --- a/xo-arena/include/xo/arena/DArenaVector.hpp +++ b/xo-arena/include/xo/arena/DArenaVector.hpp @@ -89,7 +89,8 @@ namespace xo { * Always limited by ArenaConfig.size_ **/ void reserve(size_type z); - void resize(size_type z); + /** resize to size @p z. Return true on success. May fail iff oom. **/ + bool resize(size_type z); void shrink_to_fit(); /** reset vector to empty state **/ void clear(); @@ -184,7 +185,7 @@ namespace xo { } template - void + bool DArenaVector::resize(size_type z) { // new arena size in bytes size_t req_z = z * sizeof(T); @@ -192,7 +193,8 @@ namespace xo { if (z > size_) { // expand arena to accomodate - store_.expand(req_z); + if (!store_.expand(req_z)) + return false; // run ctors if constexpr (std::is_trivially_constructible_v) { @@ -226,6 +228,8 @@ namespace xo { store_.alloc(xo::reflect::typeseq::id(), req_z); this->size_ = z; + + return true; } template diff --git a/xo-gc/include/xo/gc/DX1Collector.hpp b/xo-gc/include/xo/gc/DX1Collector.hpp index d5934611..9b6366cb 100644 --- a/xo-gc/include/xo/gc/DX1Collector.hpp +++ b/xo-gc/include/xo/gc/DX1Collector.hpp @@ -80,6 +80,49 @@ 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]); + } + + /** 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; + using ObjectTypeTable = DArenaVector; /* TODO: AllocIterator pointing to free pointer instead of std::byte* */ using GCMoveCheckpoint = std::array; using MutationLog = DArenaVector; @@ -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; diff --git a/xo-gc/src/gc/DX1Collector.cpp b/xo-gc/src/gc/DX1Collector.cpp index a668b8d4..88ffef90 100644 --- a/xo-gc/src/gc/DX1Collector.cpp +++ b/xo-gc/src/gc/DX1Collector.cpp @@ -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(tseq.seqno()) > object_types_.size()) { return false; + } - AGCObject * v = reinterpret_cast(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(object_types_.lo_); + if (tseq.is_sentinel() + || static_cast(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(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(object_types_.lo_); + auto ix = static_cast(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; } diff --git a/xo-gc/utest/X1Collector.test.cpp b/xo-gc/utest/X1Collector.test.cpp index 67d477ec..535b64cd 100644 --- a/xo-gc/utest/X1Collector.test.cpp +++ b/xo-gc/utest/X1Collector.test.cpp @@ -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_)); } diff --git a/xo-interpreter2/src/interpreter2/SetupInterpreter2.cpp b/xo-interpreter2/src/interpreter2/SetupInterpreter2.cpp index 02ba2f13..6af697bd 100644 --- a/xo-interpreter2/src/interpreter2/SetupInterpreter2.cpp +++ b/xo-interpreter2/src/interpreter2/SetupInterpreter2.cpp @@ -93,7 +93,7 @@ namespace xo { log && log(xtag("DVsmEvalArgsFrame.tseq", typeseq::id())); log && log(xtag("DVsmApplyClosureFrame.tseq", typeseq::id())); log && log(xtag("DVsmDefContFrame.tseq", typeseq::id())); - log && log(xtag("DVsmDefContFrame.tseq", typeseq::id())); + //log && log(xtag("DVsmDefContFrame.tseq", typeseq::id())); log && log(xtag("DVsmIfElseContFrame.tseq", typeseq::id())); log && log(xtag("DVsmSeqContFrame.tseq", typeseq::id())); log && log(xtag("DClosure.tseq", typeseq::id())); @@ -113,6 +113,8 @@ namespace xo { ok &= gc.install_type(impl_for()); ok &= gc.install_type(impl_for()); + ok &= gc.install_type(impl_for()); + ok &= gc.install_type(impl_for()); return ok; diff --git a/xo-object2/src/object2/SetupObject2.cpp b/xo-object2/src/object2/SetupObject2.cpp index e2d8c2ee..ff22a800 100644 --- a/xo-object2/src/object2/SetupObject2.cpp +++ b/xo-object2/src/object2/SetupObject2.cpp @@ -97,6 +97,7 @@ namespace xo { ok &= gc.install_type(impl_for()); ok &= gc.install_type(impl_for()); ok &= gc.install_type(impl_for()); + ok &= gc.install_type(impl_for()); return ok; }