diff --git a/xo-alloc2/idl/Collector.json5 b/xo-alloc2/idl/Collector.json5 index 19c44907..97ca2ce0 100644 --- a/xo-alloc2/idl/Collector.json5 +++ b/xo-alloc2/idl/Collector.json5 @@ -155,6 +155,26 @@ noexcept: true, attributes: [], }, + // bool report_object_ages(obj report_mm, obj error_mm, obj * output); + { + name: "report_object_ages", + doc: [ + "Report gc object ages, at discretion of collector implementation.", + "Creates array of dictionaries using memory from @p report_mm.", + "Each dictionary has keys n-live and bytes, indexed by object age.", + "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 e682222b..9fc4a583 100644 --- a/xo-alloc2/include/xo/alloc2/gc/ACollector.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/ACollector.hpp @@ -83,6 +83,12 @@ 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; + /** Report gc object ages, at discretion of collector implementation. +Creates array of dictionaries using memory from @p report_mm. +Each dictionary has keys n-live and bytes, indexed by object age. +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_ages(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 3847243b..7d36415d 100644 --- a/xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp @@ -67,6 +67,7 @@ namespace mm { [[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(); } + [[noreturn]] bool report_object_ages(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 4293817c..bce805bc 100644 --- a/xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp @@ -70,6 +70,9 @@ namespace mm { 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); } + bool report_object_ages(Copaque data, obj report_mm, obj error_mm, obj * output) const noexcept override { + return I::report_object_ages(_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 47dac461..4580f059 100644 --- a/xo-alloc2/include/xo/alloc2/gc/RCollector.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/RCollector.hpp @@ -103,6 +103,9 @@ public: 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); } + bool report_object_ages(obj report_mm, obj error_mm, obj * output) const noexcept { + return O::iface()->report_object_ages(O::data(), report_mm, error_mm, output); + } // non-const methods (still const in router!) bool install_type(const AGCObject & iface) { diff --git a/xo-arena/include/xo/arena/AllocHeaderConfig.hpp b/xo-arena/include/xo/arena/AllocHeaderConfig.hpp index d2b6db99..d574b292 100644 --- a/xo-arena/include/xo/arena/AllocHeaderConfig.hpp +++ b/xo-arena/include/xo/arena/AllocHeaderConfig.hpp @@ -60,6 +60,12 @@ namespace xo { std::uint64_t mkheader(std::uint64_t t, std::uint64_t a, std::uint64_t z) const noexcept { + + // don't let age wrap around. + // Expect std::min() to compile to cmov (no branch) + // + a = std::min(a, this->max_age()); + uint64_t tseq_bits = (t << (age_bits_ + size_bits_)) & tseq_mask(); uint64_t age_bits = (a << size_bits_) & age_mask(); uint64_t size_bits = z & size_mask();; @@ -75,12 +81,16 @@ namespace xo { return ((1ul << tseq_bits_) - 1) << (age_bits_ + size_bits_); } + std::uint64_t max_age() const noexcept { + return ((1ul << age_bits_) - 1); + } + std::uint64_t age_mask() const noexcept { // e.g. // 00 00 00 FF 00 00 00 00 // with age_bits=8, size_bits=32 // - return ((1ul << age_bits_) - 1) << size_bits_; + return this->max_age() << size_bits_; } std::uint64_t size_mask() const noexcept { diff --git a/xo-gc/include/xo/gc/DX1Collector.hpp b/xo-gc/include/xo/gc/DX1Collector.hpp index 8279c8e9..c81191d4 100644 --- a/xo-gc/include/xo/gc/DX1Collector.hpp +++ b/xo-gc/include/xo/gc/DX1Collector.hpp @@ -250,6 +250,19 @@ namespace xo { obj error_mm, obj * p_output) 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_ages(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 f73cb012..1ae5fac0 100644 --- a/xo-gc/include/xo/gc/detail/ICollector_DX1Collector.hpp +++ b/xo-gc/include/xo/gc/detail/ICollector_DX1Collector.hpp @@ -71,6 +71,12 @@ 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; + /** Report gc object ages, at discretion of collector implementation. +Creates array of dictionaries using memory from @p report_mm. +Each dictionary has keys n-live and bytes, indexed by object age. +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_ages(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 8a90a0b5..f8eb7513 100644 --- a/xo-gc/src/gc/DX1Collector.cpp +++ b/xo-gc/src/gc/DX1Collector.cpp @@ -529,6 +529,87 @@ namespace xo { return ok; } + bool + DX1Collector::report_object_ages(obj mm, + obj error_mm, + obj * p_output) const noexcept + { + scope log(XO_DEBUG(true)); + + (void)error_mm; + + std::uint64_t n_age = config_.arena_config_.header_.max_age() + 1; + + // stats, indexed by age + DArray * stats_v = DArray::empty(mm, n_age); + + if (!stats_v) + return false; + + // pre-populate with empty dictionaries for each age bucket + for (std::uint64_t a = 0; a < n_age; ++a) { + DDictionary * recd = DDictionary::make(mm); + + if (!recd) + return false; + + recd->upsert_cstr(mm, "age", DInteger::box(mm, a)); + recd->upsert_cstr(mm, "n-live", DInteger::box(mm, 0)); + recd->upsert_cstr(mm, "bytes", DInteger::box(mm, 0)); + + stats_v->push_back(obj(recd)); + } + + log && log(xtag("n_age", n_age), + xtag("stats_v.size", stats_v->size())); + + // scan to-space, count objects by age + + // track largest age with at least one object + std::int64_t max_age_present = 0; + + 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 age = info.age(); + size_t z = info.size(); + + if (static_cast(age) > max_age_present) + max_age_present = age; + + auto recd = obj::from(stats_v->at(age)); + + 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); + } + } + } + + // trim to only report ages up to max observed + stats_v->resize(max_age_present + 1); + + *p_output = obj(stats_v); + + return true; + } + 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 b07f1695..33a28aa9 100644 --- a/xo-gc/src/gc/facet/ICollector_DX1Collector.cpp +++ b/xo-gc/src/gc/facet/ICollector_DX1Collector.cpp @@ -63,6 +63,12 @@ namespace xo { return self.report_object_types(report_mm, error_mm, output); } + auto + ICollector_DX1Collector::report_object_ages(const DX1Collector & self, obj report_mm, obj error_mm, obj * output) noexcept -> bool + { + return self.report_object_ages(report_mm, error_mm, output); + } + auto ICollector_DX1Collector::install_type(DX1Collector & self, const AGCObject & iface) -> bool { diff --git a/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp b/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp index 9b64ecaa..b52b12c2 100644 --- a/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp +++ b/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp @@ -27,6 +27,10 @@ namespace xo { static DPrimitive_gco_0 * make_report_gc_object_types_pm(obj mm, StringTable * stbl); + /** create primitive: report gc object-age statistics **/ + static DPrimitive_gco_0 * make_report_gc_object_ages_pm(obj mm, + StringTable * stbl); + /** create primitive: report gc location of a value **/ static DPrimitive_gco_1_gco * make_gc_location_of_pm(obj mm, StringTable * stbl); diff --git a/xo-procedure2/src/procedure2/GcPrimitives.cpp b/xo-procedure2/src/procedure2/GcPrimitives.cpp index f693bd01..261a8c6a 100644 --- a/xo-procedure2/src/procedure2/GcPrimitives.cpp +++ b/xo-procedure2/src/procedure2/GcPrimitives.cpp @@ -78,6 +78,34 @@ namespace xo { } + // ----- report-gc-object-ages ----- + + obj + xfer_report_gc_object_ages(obj rcx) + { + if (rcx.collector()) { + obj stats; + bool ok = rcx.collector().report_object_ages(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_ages_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-ages", pm_ty, &xfer_report_gc_object_ages); + } + // ----- gc-location-of ----- obj diff --git a/xo-procedure2/src/procedure2/SetupProcedure2.cpp b/xo-procedure2/src/procedure2/SetupProcedure2.cpp index 44d9b3e9..3ab0b52d 100644 --- a/xo-procedure2/src/procedure2/SetupProcedure2.cpp +++ b/xo-procedure2/src/procedure2/SetupProcedure2.cpp @@ -142,6 +142,11 @@ namespace xo { GcPrimitives::make_report_gc_object_types_pm(mm, stbl), flags & InstallFlags::f_generalpurpose)); + ok = ok & (PrimitiveRegistry::install_aux + (sink, + GcPrimitives::make_report_gc_object_ages_pm(mm, stbl), + flags & InstallFlags::f_generalpurpose)); + ok = ok & (PrimitiveRegistry::install_aux (sink, GcPrimitives::make_gc_location_of_pm(mm, stbl),