diff --git a/xo-alloc2/idl/Collector.json5 b/xo-alloc2/idl/Collector.json5 index e7e53a0f..6cc82433 100644 --- a/xo-alloc2/idl/Collector.json5 +++ b/xo-alloc2/idl/Collector.json5 @@ -120,6 +120,25 @@ noexcept: true, attributes: [], }, + // bool report_types(obj report_mm, obj error_mm, obj * output); + { + name: "report_object_types", + doc: [ + "Report gc object types, at discretion of collector implementation.", + "Creates dictionary using memory from @p report_mm.", + "If unable to comply (e.g. oom), return runtime error allocated from @p error_mm.", + "Avoiding obj return type to avoid #include cycle" + ], + return_type: "bool", + args: [ + {type: "obj", name: "report_mm"}, + {type: "obj", name: "error_mm"}, + {type: "obj *", name: "output"}, + ], + const: true, + noexcept: true, + attributes: [], + }, ], nonconst_methods: [ // bool install_type(const AGCObject & iface) diff --git a/xo-alloc2/include/xo/alloc2/gc/ACollector.hpp b/xo-alloc2/include/xo/alloc2/gc/ACollector.hpp index f156a4e4..432d3820 100644 --- a/xo-alloc2/include/xo/alloc2/gc/ACollector.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/ACollector.hpp @@ -74,6 +74,11 @@ Creates dictionary using memory from @p report_mm. If unable to comply (e.g. oom), return runtime error allocated from @p error_mm. Avoiding obj return type to avoid #include cycle **/ virtual bool report_statistics(Copaque data, obj report_mm, obj error_mm, obj * output) const noexcept = 0; + /** Report gc object types, at discretion of collector implementation. +Creates dictionary using memory from @p report_mm. +If unable to comply (e.g. oom), return runtime error allocated from @p error_mm. +Avoiding obj return type to avoid #include cycle **/ + virtual bool report_object_types(Copaque data, obj report_mm, obj error_mm, obj * output) const noexcept = 0; // nonconst methods /** install interface @p iface for representation with typeseq @p tseq diff --git a/xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp b/xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp index 34f90356..d5789310 100644 --- a/xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp @@ -65,6 +65,7 @@ namespace mm { [[noreturn]] bool contains(Copaque, role, const void *) const noexcept override { _fatal(); } [[noreturn]] bool is_type_installed(Copaque, typeseq) const noexcept override { _fatal(); } [[noreturn]] bool report_statistics(Copaque, obj, obj, obj *) const noexcept override { _fatal(); } + [[noreturn]] bool report_object_types(Copaque, obj, obj, obj *) const noexcept override { _fatal(); } // nonconst methods [[noreturn]] bool install_type(Opaque, const AGCObject &) override; diff --git a/xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp b/xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp index 2df60064..77cde5fc 100644 --- a/xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp @@ -64,6 +64,9 @@ namespace mm { bool report_statistics(Copaque data, obj report_mm, obj error_mm, obj * output) const noexcept override { return I::report_statistics(_dcast(data), report_mm, error_mm, output); } + bool report_object_types(Copaque data, obj report_mm, obj error_mm, obj * output) const noexcept override { + return I::report_object_types(_dcast(data), report_mm, error_mm, output); + } // non-const methods bool install_type(Opaque data, const AGCObject & iface) override { diff --git a/xo-alloc2/include/xo/alloc2/gc/RCollector.hpp b/xo-alloc2/include/xo/alloc2/gc/RCollector.hpp index ad514854..be44e90e 100644 --- a/xo-alloc2/include/xo/alloc2/gc/RCollector.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/RCollector.hpp @@ -97,6 +97,9 @@ public: bool report_statistics(obj report_mm, obj error_mm, obj * output) const noexcept { return O::iface()->report_statistics(O::data(), report_mm, error_mm, output); } + bool report_object_types(obj report_mm, obj error_mm, obj * output) const noexcept { + return O::iface()->report_object_types(O::data(), report_mm, error_mm, output); + } // non-const methods (still const in router!) bool install_type(const AGCObject & iface) { diff --git a/xo-gc/include/xo/gc/DX1Collector.hpp b/xo-gc/include/xo/gc/DX1Collector.hpp index 9b6366cb..c1e1669e 100644 --- a/xo-gc/include/xo/gc/DX1Collector.hpp +++ b/xo-gc/include/xo/gc/DX1Collector.hpp @@ -96,17 +96,23 @@ namespace xo { /** true iff this slot is empty **/ bool is_null() const noexcept { - return this->iface()->_has_null_vptr(); + return this->_iface()->_has_null_vptr(); } bool is_occupied() const noexcept { return !this->is_null(); } - AGCObject * iface() const noexcept { + 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 **/ @@ -230,6 +236,17 @@ namespace xo { obj error_mm, obj * p_output) const noexcept; + /** 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 error_mm Allocator for last-report error reporting when out-of-memory. + * @p p_output on exit @p *p_output contains stats dictionary + **/ + bool report_object_types(obj mm, + obj error_mm, + obj * p_output) const noexcept; + // ----- queries ----- /** introspection for memory use. diff --git a/xo-gc/include/xo/gc/detail/ICollector_DX1Collector.hpp b/xo-gc/include/xo/gc/detail/ICollector_DX1Collector.hpp index a9c8e012..f7db4b90 100644 --- a/xo-gc/include/xo/gc/detail/ICollector_DX1Collector.hpp +++ b/xo-gc/include/xo/gc/detail/ICollector_DX1Collector.hpp @@ -62,6 +62,11 @@ Creates dictionary using memory from @p report_mm. If unable to comply (e.g. oom), return runtime error allocated from @p error_mm. Avoiding obj return type to avoid #include cycle **/ static bool report_statistics(const DX1Collector & self, obj report_mm, obj error_mm, obj * output) noexcept; + /** Report gc object types, at discretion of collector implementation. +Creates dictionary using memory from @p report_mm. +If unable to comply (e.g. oom), return runtime error allocated from @p error_mm. +Avoiding obj return type to avoid #include cycle **/ + static bool report_object_types(const DX1Collector & self, obj report_mm, obj error_mm, obj * output) noexcept; // non-const methods /** install interface @p iface for representation with typeseq @p tseq diff --git a/xo-gc/src/gc/DX1Collector.cpp b/xo-gc/src/gc/DX1Collector.cpp index 88ffef90..d9f09b88 100644 --- a/xo-gc/src/gc/DX1Collector.cpp +++ b/xo-gc/src/gc/DX1Collector.cpp @@ -6,7 +6,10 @@ #include "X1Collector.hpp" #include #include +#include #include +#include +#include #include #include #include @@ -20,10 +23,12 @@ #include // for ::getpagesize() namespace xo { - // for report_statistics() + // for report_statistics(), report_object_types() using xo::scm::DDictionary; + using xo::scm::DArray; using xo::scm::DString; using xo::scm::DInteger; + using xo::scm::DBoolean; using xo::mm::AAllocator; using xo::facet::TypeRegistry; @@ -347,6 +352,9 @@ namespace xo { DDictionary * rpt = DDictionary::make(mm); + if (!rpt) + return false; + bool ok = true; // note: totals taken across both roles and generations, @@ -388,6 +396,117 @@ namespace xo { return ok; } + bool + DX1Collector::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; + } + size_type DX1Collector::header2size(header_type hdr) const noexcept { diff --git a/xo-gc/src/gc/facet/ICollector_DX1Collector.cpp b/xo-gc/src/gc/facet/ICollector_DX1Collector.cpp index 10b4add5..2e3f55f4 100644 --- a/xo-gc/src/gc/facet/ICollector_DX1Collector.cpp +++ b/xo-gc/src/gc/facet/ICollector_DX1Collector.cpp @@ -51,6 +51,12 @@ namespace xo { return self.report_statistics(report_mm, error_mm, output); } + auto + ICollector_DX1Collector::report_object_types(const DX1Collector & self, obj report_mm, obj error_mm, obj * output) noexcept -> bool + { + return self.report_object_types(report_mm, error_mm, output); + } + auto ICollector_DX1Collector::install_type(DX1Collector & self, const AGCObject & iface) -> bool { diff --git a/xo-object2/include/xo/object2/DDictionary.hpp b/xo-object2/include/xo/object2/DDictionary.hpp index af66f3e6..63a7eba1 100644 --- a/xo-object2/include/xo/object2/DDictionary.hpp +++ b/xo-object2/include/xo/object2/DDictionary.hpp @@ -112,6 +112,8 @@ namespace xo { /** return value associated with @p key, if key is present **/ std::optional> lookup(const DString * key) const noexcept; + /** return value associated with @p key, if key is present **/ + std::optional> lookup_cstr(const char * key) const noexcept; /** return element @p key-value pair at position @p index (0-based) **/ std::pair> at_index(size_type index) const; @@ -139,6 +141,11 @@ namespace xo { **/ bool try_update(const pair_type & kvpair); + /** update key-value pair for existing @p key to map to @p value. + * false if @p key not already present. + **/ + bool try_update_cstr(const char * key, obj value); + /** convenience method: * try_upsert pair (k, @p value), after boxing c-style string @p key with @p mm to get k **/ diff --git a/xo-object2/include/xo/object2/DInteger.hpp b/xo-object2/include/xo/object2/DInteger.hpp index f1fa16aa..a67e18d3 100644 --- a/xo-object2/include/xo/object2/DInteger.hpp +++ b/xo-object2/include/xo/object2/DInteger.hpp @@ -35,6 +35,8 @@ namespace xo { operator long() const noexcept { return value_; } + void assign_value(long x) noexcept { this->value_ = x; } + // GCObject facet std::size_t shallow_size() const noexcept; diff --git a/xo-object2/src/object2/DArray.cpp b/xo-object2/src/object2/DArray.cpp index 8ec73691..a55fbf97 100644 --- a/xo-object2/src/object2/DArray.cpp +++ b/xo-object2/src/object2/DArray.cpp @@ -101,7 +101,7 @@ namespace xo { bool DArray::resize(size_type new_z) noexcept { - if (new_z >= capacity_) { + if (new_z > capacity_) { return false; } else if (new_z > size_) { // ensure new size is zeroed (we/re not zeroing if/when we shrink) @@ -149,7 +149,25 @@ namespace xo { pps->write("]"); return true; } else { - pps->write("[...]"); + pps->write("["); + + for (size_type i = 0, n = this->size(); i < n; ++i) { + if (i == 0) { + /* indent, but credit initial [ */ + pps->indent(std::max(pps->indent_width(), 1u) - 1); + } else { + /* indent after newline */ + pps->newline_indent(ppii.ci1()); + } + + obj elt = this->at(i).to_facet(); + + assert(elt.data()); + + pps->pretty(elt); + } + + pps->write("]"); return false; } } diff --git a/xo-object2/src/object2/DDictionary.cpp b/xo-object2/src/object2/DDictionary.cpp index faa44589..d585ded4 100644 --- a/xo-object2/src/object2/DDictionary.cpp +++ b/xo-object2/src/object2/DDictionary.cpp @@ -50,6 +50,21 @@ namespace xo { return {}; } + std::optional> + DDictionary::lookup_cstr(const char * key) const noexcept + { + for (DArray::size_type i = 0, z = keys_->size(); i < z; ++i) { + auto i_key = obj::from(keys_->at(i)); + + assert(i_key); + + if (strcmp(key, i_key->data()) == 0) + return values_->at(i); + } + + return {}; + } + std::pair> DDictionary::at_index(size_type ix) const { @@ -104,6 +119,24 @@ namespace xo { return false; } + bool + DDictionary::try_update_cstr(const char * key, obj value) + { + for (size_type i = 0, n = keys_->size(); i < n; ++i) { + auto key_i = obj::from((*keys_)[i]); + + assert(key_i); + + if (strcmp(key, key_i->data()) == 0) { + values_->assign_at(i, value); + + return true; + } + } + + return false; + } + bool DDictionary::try_upsert_cstr(obj mm, const char * key_cstr, obj value) { @@ -183,8 +216,7 @@ namespace xo { pps->write("{"); for (size_type i = 0, n = this->size(); i < n; ++i) { - if (i > 0) - pps->write(" "); + pps->write(" "); obj key = FacetRegistry::instance().variant((*keys_)[i]); @@ -203,7 +235,7 @@ namespace xo { pps->write(";"); } - pps->write("}"); + pps->write(" }"); return true; } else { pps->write("{"); @@ -211,10 +243,10 @@ namespace xo { for (size_type i = 0, n = this->size(); i < n; ++i) { if (i == 0) { /* indent, but credit initial {. using same line for first (key,value) */ - ppii.pps()->indent(std::max(ppii.pps()->indent_width(), 1u) - 1); + pps->indent(std::max(pps->indent_width(), 1u) - 1); } else { /* indent after newline */ - ppii.pps()->newline_indent(ppii.ci1()); + pps->newline_indent(ppii.ci1()); } obj key diff --git a/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp b/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp index d0a1525a..4b134fd7 100644 --- a/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp +++ b/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp @@ -23,6 +23,10 @@ namespace xo { static DPrimitive_gco_0 * make_report_gc_statistics_pm(obj mm, StringTable * stbl); + /** create primitive: report gc object-type statistics **/ + static DPrimitive_gco_0 * make_report_gc_object_types_pm(obj mm, + StringTable * stbl); + /** create primitive: request collection **/ static DPrimitive_gco_1_gco * make_request_gc_pm(obj mm, StringTable * stbl); diff --git a/xo-procedure2/src/procedure2/GcPrimitives.cpp b/xo-procedure2/src/procedure2/GcPrimitives.cpp index 9573f665..76570fa7 100644 --- a/xo-procedure2/src/procedure2/GcPrimitives.cpp +++ b/xo-procedure2/src/procedure2/GcPrimitives.cpp @@ -25,8 +25,6 @@ namespace xo { xfer_report_gc_statistics(obj rcx) { if (rcx.collector()) { - // status currently only implemented for X1 collector - obj stats; bool ok = rcx.collector().report_statistics(rcx.allocator(), rcx.error_allocator(), @@ -51,6 +49,36 @@ namespace xo { return DPrimitive_gco_0::_make(mm, "report-gc-statistics", pm_ty, &xfer_report_gc_statistics); } + // ----- report-gc-object-types ----- + + obj + xfer_report_gc_object_types(obj rcx) + { + if (rcx.collector()) { + obj stats; + bool ok = rcx.collector().report_object_types(rcx.allocator(), rcx.error_allocator(), &stats); + + + if (ok && stats) + return stats; + } + + return DBoolean::box(rcx.allocator(), false); + } + + DPrimitive_gco_0 * + GcPrimitives::make_report_gc_object_types_pm(obj mm, + StringTable * stbl) + { + (void)stbl; + + auto any_ty = DAtomicType::make(mm, Metatype::t_any()); + auto pm_ty = obj(DFunctionType::_make(mm, any_ty)); + + return DPrimitive_gco_0::_make(mm, "report-gc-object-types", pm_ty, &xfer_report_gc_object_types); + + } + // ----- request-gc ----- obj diff --git a/xo-procedure2/src/procedure2/SetupProcedure2.cpp b/xo-procedure2/src/procedure2/SetupProcedure2.cpp index 6772fff3..a07a6ffa 100644 --- a/xo-procedure2/src/procedure2/SetupProcedure2.cpp +++ b/xo-procedure2/src/procedure2/SetupProcedure2.cpp @@ -137,6 +137,11 @@ namespace xo { GcPrimitives::make_report_gc_statistics_pm(mm, stbl), flags & InstallFlags::f_generalpurpose)); + ok = ok & (PrimitiveRegistry::install_aux + (sink, + GcPrimitives::make_report_gc_object_types_pm(mm, stbl), + flags & InstallFlags::f_generalpurpose)); + ok = ok & (PrimitiveRegistry::install_aux (sink, GcPrimitives::make_request_gc_pm(mm, stbl),