From 13f4fb09359ca1d048a1af393b7f9ad60a7e7580 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 6 Aug 2025 22:34:20 -0500 Subject: [PATCH] xo-allod: + per-type stats + pretty printing --- xo-alloc/include/xo/alloc/ArenaAlloc.hpp | 4 + xo-alloc/include/xo/alloc/Forwarding1.hpp | 1 + xo-alloc/include/xo/alloc/GC.hpp | 8 ++ xo-alloc/include/xo/alloc/GcStatistics.hpp | 12 +- xo-alloc/include/xo/alloc/ListAlloc.hpp | 9 ++ xo-alloc/include/xo/alloc/Object.hpp | 6 + .../include/xo/alloc/ObjectStatistics.hpp | 86 ++++++++++++ xo-alloc/src/alloc/ArenaAlloc.cpp | 39 ++++++ xo-alloc/src/alloc/CMakeLists.txt | 1 + xo-alloc/src/alloc/Forwarding1.cpp | 6 + xo-alloc/src/alloc/GC.cpp | 128 +++++------------- xo-alloc/src/alloc/GcStatistics.cpp | 87 ++++++++++++ xo-alloc/src/alloc/ListAlloc.cpp | 11 ++ xo-alloc/src/alloc/Object.cpp | 12 ++ xo-alloc/src/alloc/ObjectStatistics.cpp | 73 ++++++++++ xo-alloc/utest/LinearAlloc.test.cpp | 87 ------------ .../xo/indentlog/print/pretty_vector.hpp | 39 ++++-- xo-object/include/xo/object/Boolean.hpp | 1 + xo-object/include/xo/object/Integer.hpp | 1 + xo-object/include/xo/object/List.hpp | 1 + xo-object/include/xo/object/String.hpp | 1 + xo-object/src/object/Boolean.cpp | 6 + xo-object/src/object/Integer.cpp | 5 + xo-object/src/object/List.cpp | 12 ++ xo-object/src/object/String.cpp | 7 + xo-object/utest/GC.test.cpp | 37 ++++- 26 files changed, 475 insertions(+), 205 deletions(-) create mode 100644 xo-alloc/include/xo/alloc/ObjectStatistics.hpp create mode 100644 xo-alloc/src/alloc/ObjectStatistics.cpp delete mode 100644 xo-alloc/utest/LinearAlloc.test.cpp diff --git a/xo-alloc/include/xo/alloc/ArenaAlloc.hpp b/xo-alloc/include/xo/alloc/ArenaAlloc.hpp index 12033646..9f41a8f1 100644 --- a/xo-alloc/include/xo/alloc/ArenaAlloc.hpp +++ b/xo-alloc/include/xo/alloc/ArenaAlloc.hpp @@ -6,6 +6,7 @@ #pragma once #include "IAlloc.hpp" +#include "ObjectStatistics.hpp" namespace xo { namespace gc { @@ -48,6 +49,9 @@ namespace xo { std::byte * free_ptr() const { return free_ptr_; } void set_free_ptr(std::byte * x); + void capture_object_statistics(capture_phase phase, + ObjectStatistics * p_dest) const; + // inherited from IAlloc... virtual const std::string & name() const final override { return name_; } diff --git a/xo-alloc/include/xo/alloc/Forwarding1.hpp b/xo-alloc/include/xo/alloc/Forwarding1.hpp index 17a38df2..cf54597d 100644 --- a/xo-alloc/include/xo/alloc/Forwarding1.hpp +++ b/xo-alloc/include/xo/alloc/Forwarding1.hpp @@ -13,6 +13,7 @@ namespace xo { // inherited from Object.. virtual TaggedPtr self_tp() const final override; + virtual void display(std::ostream & os) const final override; virtual bool _is_forwarded() const final override { return true; } virtual Object * _offset_destination(Object * src) const final override; virtual Object * _destination() final override; diff --git a/xo-alloc/include/xo/alloc/GC.hpp b/xo-alloc/include/xo/alloc/GC.hpp index 4fb9f518..daef1881 100644 --- a/xo-alloc/include/xo/alloc/GC.hpp +++ b/xo-alloc/include/xo/alloc/GC.hpp @@ -42,6 +42,8 @@ namespace xo { std::size_t initial_tenured_z_ = 0; /** true to permit incremental garbage collection **/ bool allow_incremental_gc_ = true; + /** true to report statistics **/ + bool stats_flag_ = false; /** true to enable debug logging **/ bool debug_flag_ = false; }; @@ -207,6 +209,8 @@ namespace xo { void swap_mutation_log(); /** swap roles of FromSpace/ToSpace **/ void swap_spaces(generation g); + /** scan to-space for object statistics before GC */ + void capture_object_statistics(generation upto, capture_phase phase); /** copy object **/ void copy_object(Object ** addr, generation upto, ObjectStatistics * object_stats); /** copy everything reachable from global gc roots **/ @@ -273,6 +277,10 @@ namespace xo { /** allocation/collection counters **/ GcStatistics gc_statistics_; + /** optional per-object-type counters. snapshot at beginning of collection cycle **/ + std::array object_statistics_sab_; + /** optional per-object-type counters. snapshot at end of collection cycle **/ + std::array object_statistics_sae_; /** trigger full GC whenever this much data arrives in tenured generation **/ std::size_t full_gc_threshold_ = 0; diff --git a/xo-alloc/include/xo/alloc/GcStatistics.hpp b/xo-alloc/include/xo/alloc/GcStatistics.hpp index 01cfc80b..24cac461 100644 --- a/xo-alloc/include/xo/alloc/GcStatistics.hpp +++ b/xo-alloc/include/xo/alloc/GcStatistics.hpp @@ -6,20 +6,13 @@ #pragma once #include "generation.hpp" +#include "xo/reflect/TypeDescr.hpp" #include "xo/indentlog/print/pretty.hpp" #include #include namespace xo { namespace gc { - /** @class ObjectStatistics - * @brief placeholder for type-driven allocation statistics - * - * Passed to @ref Object::deep_move for example - **/ - class ObjectStatistics { - }; - /** @class PerGenerationStatistics * @brief garbage collection statistics for particular GC generation **/ @@ -103,9 +96,6 @@ namespace xo { * (N0 -> N1 when reported; cumulative across GCs) **/ std::size_t n_xckp_mutation_ = 0; - - /** per-type statistics (placeholder) **/ - ObjectStatistics per_type_stats_; }; inline std::ostream & operator<< (std::ostream & os, const GcStatistics & x) { diff --git a/xo-alloc/include/xo/alloc/ListAlloc.hpp b/xo-alloc/include/xo/alloc/ListAlloc.hpp index db1a2df7..f9c20d00 100644 --- a/xo-alloc/include/xo/alloc/ListAlloc.hpp +++ b/xo-alloc/include/xo/alloc/ListAlloc.hpp @@ -6,6 +6,7 @@ #pragma once #include "IAlloc.hpp" +#include "ObjectStatistics.hpp" #include #include #include @@ -43,6 +44,14 @@ namespace xo { /** current free pointer **/ std::byte * free_ptr() const; + /** scan space (must not contain forwarding pointers, because loses size info) + * + gather stats by object type + * + * See @ref Object::self_tp + **/ + void capture_object_statistics(capture_phase phase, + ObjectStatistics * p_dest) const; + // inherited from IAlloc.. virtual const std::string & name() const final override; diff --git a/xo-alloc/include/xo/alloc/Object.hpp b/xo-alloc/include/xo/alloc/Object.hpp index 47b26261..0f50bfed 100644 --- a/xo-alloc/include/xo/alloc/Object.hpp +++ b/xo-alloc/include/xo/alloc/Object.hpp @@ -157,6 +157,9 @@ namespace xo { **/ virtual TaggedPtr self_tp() const = 0; + /** print on stream @p os **/ + virtual void display(std::ostream & os) const = 0; + // GC support /** true iff this object represents a forwarding pointer. @@ -244,6 +247,9 @@ namespace xo { rhs.ptr()); } + std::ostream & + operator<< (std::ostream & os, gp x); + /** @class Cpof * @brief argument to operator new used for garbage collector evacuation phase * diff --git a/xo-alloc/include/xo/alloc/ObjectStatistics.hpp b/xo-alloc/include/xo/alloc/ObjectStatistics.hpp new file mode 100644 index 00000000..ced3b463 --- /dev/null +++ b/xo-alloc/include/xo/alloc/ObjectStatistics.hpp @@ -0,0 +1,86 @@ +/* file ObjectStatistics.hpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#pragma once + +#include "xo/indentlog/print/pretty.hpp" +#include +#include + +namespace xo { + namespace reflect { class TypeDescrBase; } + + namespace gc { + enum class capture_phase { + /** snapshot-at-beginning **/ + sab, + /** snapshot-at-end **/ + sae, + }; + + /** @class PerObjectTypeStatistics + * @brief statistics for a particular object type + * + * Gathered for each leaf type descended from xo::obj::Object. + * See @ref xo::obj::Object::self_tp + * + * See @ref GC::capture_object_statistics + * (gathers @ref scanned_n_, @ref scanned_z_) + **/ + struct PerObjectTypeStatistics { + using TypeDescr = xo::reflect::TypeDescrBase const *; + + void display(std::ostream & os) const; + + /** stats here are for objects of this type **/ + TypeDescr td_ = nullptr; + /** number of objects scanned **/ + std::size_t scanned_n_ = 0; + /** number of bytes scanned **/ + std::size_t scanned_z_ = 0; + /** number of objects surviving **/ + std::size_t survive_n_ = 0; + /** number of bytes from surviving objects **/ + std::size_t survive_z_ = 0; + }; + + inline std::ostream & operator<< (std::ostream & os, const PerObjectTypeStatistics & x) { + x.display(os); + return os; + } + + /** @class ObjectStatistics + * @brief placeholder for type-driven allocation statistics + * + * Passed to @ref Object::deep_move for example + **/ + struct ObjectStatistics { + void display(std::ostream & os) const; + + /** per-object-type statistics, indexed by TypeId **/ + std::vector per_type_stats_v_; + }; + + inline std::ostream & operator<< (std::ostream & os, const ObjectStatistics & x) { + x.display(os); + return os; + } + + } /*namespace gc*/ + + namespace print { + template <> + struct ppdetail { + static bool print_pretty(const ppindentinfo &, const xo::gc::PerObjectTypeStatistics &); + }; + + template <> + struct ppdetail { + static bool print_pretty(const ppindentinfo &, const xo::gc::ObjectStatistics &); + }; + } /*namespace print*/ +} /*namespace xo*/ + +/* end ObjectStatistics.hpp */ diff --git a/xo-alloc/src/alloc/ArenaAlloc.cpp b/xo-alloc/src/alloc/ArenaAlloc.cpp index c3f70d8c..ae67365a 100644 --- a/xo-alloc/src/alloc/ArenaAlloc.cpp +++ b/xo-alloc/src/alloc/ArenaAlloc.cpp @@ -4,6 +4,8 @@ */ #include "ArenaAlloc.hpp" +#include "Object.hpp" +#include "ObjectStatistics.hpp" #include "xo/indentlog/scope.hpp" #include "xo/indentlog/print/tag.hpp" #include @@ -64,6 +66,43 @@ namespace xo { } } + void + ArenaAlloc::capture_object_statistics(capture_phase phase, + ObjectStatistics * p_dest) const + { + using xo::reflect::TaggedPtr; + + std::byte * p = lo_; + + while (p < free_ptr_) { + Object * obj = reinterpret_cast(p); + TaggedPtr tp = obj->self_tp(); + std::size_t z = obj->_shallow_size(); + std::uint32_t id = tp.td()->id().id(); + + if (p_dest->per_type_stats_v_.size() < id + 1) + p_dest->per_type_stats_v_.resize(id + 1); + + PerObjectTypeStatistics & dest = p_dest->per_type_stats_v_.at(id); + + dest.td_ = tp.td(); + switch (phase) { + case capture_phase::sab: + ++dest.scanned_n_; + dest.scanned_z_ += z; + break; + case capture_phase::sae: + ++dest.survive_n_; + dest.survive_z_ += z; + break; + } + + p += z; + } + + assert(p == free_ptr_); + } + std::size_t ArenaAlloc::size() const { return limit_ - lo_; diff --git a/xo-alloc/src/alloc/CMakeLists.txt b/xo-alloc/src/alloc/CMakeLists.txt index 180b936f..55b8641c 100644 --- a/xo-alloc/src/alloc/CMakeLists.txt +++ b/xo-alloc/src/alloc/CMakeLists.txt @@ -7,6 +7,7 @@ set(SELF_SRCS ListAlloc.cpp GC.cpp GcStatistics.cpp + ObjectStatistics.cpp Object.cpp Forwarding1.cpp ) diff --git a/xo-alloc/src/alloc/Forwarding1.cpp b/xo-alloc/src/alloc/Forwarding1.cpp index 2122d248..5a941c82 100644 --- a/xo-alloc/src/alloc/Forwarding1.cpp +++ b/xo-alloc/src/alloc/Forwarding1.cpp @@ -23,6 +23,12 @@ namespace xo { return Reflect::make_tp(const_cast(this)); } + void + Forwarding1::display(std::ostream & os) const + { + os << ""; + } + Object * Forwarding1::_offset_destination(Object * src) const { diff --git a/xo-alloc/src/alloc/GC.cpp b/xo-alloc/src/alloc/GC.cpp index d3b3c978..afac26df 100644 --- a/xo-alloc/src/alloc/GC.cpp +++ b/xo-alloc/src/alloc/GC.cpp @@ -12,91 +12,6 @@ namespace xo { namespace gc { - void - PerGenerationStatistics::include_gc(std::size_t alloc_z, - std::size_t before_z, - std::size_t after_z, - std::size_t promote_z) - { - this->update_snapshot(after_z); - - new_alloc_z_ += alloc_z; - scanned_z_ += before_z; - survive_z_ += after_z; - promote_z_ += promote_z; - } - - void - PerGenerationStatistics::update_snapshot(std::size_t after_z) - { - used_z_ = after_z; - } - - void - PerGenerationStatistics::display(std::ostream & os) const - { - os << ""; - } - - void - GcStatistics::include_gc(generation upto, - std::size_t alloc_z, - std::size_t before_z, - std::size_t after_z, - std::size_t promote_z) - { - gen_v_[static_cast(upto)].include_gc(alloc_z, before_z, after_z, promote_z); - } - - void - GcStatistics::update_snapshot(generation upto, - std::size_t after_z) - { - gen_v_[static_cast(upto)].update_snapshot(after_z); - } - - void - GcStatistics::display(std::ostream & os) const - { - os << ""; - } - - void - GcStatisticsExt::display(std::ostream & os) const - { - os << ""; - } - bool MutationLogEntry::is_child_forwarded() const { @@ -542,6 +457,22 @@ namespace xo { } /*swap_spaces*/ + void + GC::capture_object_statistics(generation upto, capture_phase phase) + { + /* scan nursery */ + this->nursery_[role2int(role::to_space)]->capture_object_statistics + (phase, + &object_statistics_sab_[gen2int(generation::nursery)]); + + if (upto == generation::tenured) { + /* scan tenured */ + this->tenured_[role2int(role::to_space)]->capture_object_statistics + (phase, + &object_statistics_sab_[gen2int(generation::tenured)]); + } + } + void GC::copy_object(Object ** pp_object, generation upto, ObjectStatistics * object_stats) { @@ -566,7 +497,7 @@ namespace xo { GC::copy_globals(generation upto) { for (Object ** pp_root : gc_root_v_) { - this->copy_object(pp_root, upto, &gc_statistics_.per_type_stats_); + this->copy_object(pp_root, upto, &object_statistics_sae_[gen2int(upto)]); } } @@ -802,7 +733,7 @@ namespace xo { if (upto == generation::tenured) { log && log("TODO: forward mutation log for full GC"); } else { - this->incremental_gc_forward_mlog(&gc_statistics_.per_type_stats_); + this->incremental_gc_forward_mlog(&object_statistics_sae_[gen2int(generation::nursery)]); } } @@ -858,7 +789,7 @@ namespace xo { void GC::execute_gc(generation upto) { - scope log(XO_DEBUG(config_.debug_flag_)); + scope log(XO_DEBUG(config_.stats_flag_)); bool full_move = (upto == generation::tenured); @@ -876,7 +807,11 @@ namespace xo { log && log(xtag("new_alloc", new_alloc)); - log && log("step 1: swap to/from roles"); + log && log("step 0: (optional) scan for object statistics"); + + this->capture_object_statistics(upto, capture_phase::sab); + + log && log("step 1 : swap to/from roles"); this->swap_spaces(upto); @@ -886,18 +821,25 @@ namespace xo { log && log("step 2b: TODO: copy pinned"); - log && log("step 3: forward mutation log"); + log && log("step 3 : forward mutation log"); this->forward_mutation_log(upto); - log && log("step 4: TODO: notify destructor log"); + log && log("step 4 : TODO: notify destructor log"); - log && log("step 5: TODO: keep reachable weak pointers"); + log && log("step 5 : TODO: keep reachable weak pointers"); - log && log("step 6: cleanup"); + log && log("step 6 : cleanup"); this->cleanup_phase(upto); + this->capture_object_statistics(upto, capture_phase::sae); + + log && log("object statistics [nursery]:"); + log && log(refrtag("stats", object_statistics_sab_[gen2int(generation::nursery)])); + log && log("object statistics [tenured]:"); + log && log(refrtag("stats", object_statistics_sab_[gen2int(generation::tenured)])); + this->runstate_ = GCRunstate(); log && log("statistics:"); diff --git a/xo-alloc/src/alloc/GcStatistics.cpp b/xo-alloc/src/alloc/GcStatistics.cpp index b78b693b..d54c3cf2 100644 --- a/xo-alloc/src/alloc/GcStatistics.cpp +++ b/xo-alloc/src/alloc/GcStatistics.cpp @@ -7,6 +7,93 @@ #include "xo/indentlog/print/pretty_vector.hpp" namespace xo { + namespace gc { + void + PerGenerationStatistics::include_gc(std::size_t alloc_z, + std::size_t before_z, + std::size_t after_z, + std::size_t promote_z) + { + this->update_snapshot(after_z); + + new_alloc_z_ += alloc_z; + scanned_z_ += before_z; + survive_z_ += after_z; + promote_z_ += promote_z; + } + + void + PerGenerationStatistics::update_snapshot(std::size_t after_z) + { + used_z_ = after_z; + } + + void + PerGenerationStatistics::display(std::ostream & os) const + { + os << ""; + } + + void + GcStatistics::include_gc(generation upto, + std::size_t alloc_z, + std::size_t before_z, + std::size_t after_z, + std::size_t promote_z) + { + gen_v_[static_cast(upto)].include_gc(alloc_z, before_z, after_z, promote_z); + } + + void + GcStatistics::update_snapshot(generation upto, + std::size_t after_z) + { + gen_v_[static_cast(upto)].update_snapshot(after_z); + } + + void + GcStatistics::display(std::ostream & os) const + { + os << ""; + } + + void + GcStatisticsExt::display(std::ostream & os) const + { + os << ""; + } + } /*namespace gc*/ + namespace print { bool ppdetail::print_pretty(const ppindentinfo & ppii, diff --git a/xo-alloc/src/alloc/ListAlloc.cpp b/xo-alloc/src/alloc/ListAlloc.cpp index a5ae38e7..9d727591 100644 --- a/xo-alloc/src/alloc/ListAlloc.cpp +++ b/xo-alloc/src/alloc/ListAlloc.cpp @@ -50,6 +50,17 @@ namespace xo { return retval; } + void + ListAlloc::capture_object_statistics(capture_phase phase, + ObjectStatistics * p_dest) const + { + hd_->capture_object_statistics(phase, p_dest); + + for (const auto & arena : full_l_) + arena->capture_object_statistics(phase, p_dest); + + } + const std::string & ListAlloc::name() const { if (hd_) { diff --git a/xo-alloc/src/alloc/Object.cpp b/xo-alloc/src/alloc/Object.cpp index d3772003..b3db11ca 100644 --- a/xo-alloc/src/alloc/Object.cpp +++ b/xo-alloc/src/alloc/Object.cpp @@ -191,6 +191,18 @@ namespace xo { (void)fwd; } + std::ostream & + operator<< (std::ostream & os, gp x) + { + if (x.ptr()) { + x->display(os); + } else { + os << ""; + } + + return os; + } + } /*namespace xo*/ /* end Object.cpp*/ diff --git a/xo-alloc/src/alloc/ObjectStatistics.cpp b/xo-alloc/src/alloc/ObjectStatistics.cpp new file mode 100644 index 00000000..a69928dc --- /dev/null +++ b/xo-alloc/src/alloc/ObjectStatistics.cpp @@ -0,0 +1,73 @@ +/* file ObjectStatistics.cpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#include "ObjectStatistics.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "xo/indentlog/print/pretty_vector.hpp" + +namespace xo { + namespace gc { + void + PerObjectTypeStatistics::display(std::ostream & os) const + { + os << "short_name()); + else + os << rtag("td", "nullptr"); + os << rtag("scanned_n", scanned_n_) + << rtag("scanned_z", scanned_z_) + << rtag("survive_n", survive_n_) + << rtag("survive_z", survive_z_) + << ">"; + } + + void + ObjectStatistics::display(std::ostream & os) const + { + os << ""; + } + } /*namespace gc*/ + + namespace print { + bool + ppdetail::print_pretty(const ppindentinfo & ppii, + const xo::gc::PerObjectTypeStatistics & x) + { + static constexpr std::string_view c_nullptr_str = "nullptr"; + + if (x.td_) { + return ppii.pps()->pretty_struct(ppii, + "PerObjectTypeStatistics", + refrtag("td", x.td_ ? x.td_->short_name() : c_nullptr_str), + refrtag("scanned_n", x.scanned_n_), + refrtag("scanned_z", x.scanned_z_), + refrtag("survive_n", x.survive_n_), + refrtag("survive_z", x.survive_z_)); + } else { + /* print nothing -- empty struct */ + return true; + } + } + + bool + ppdetail::print_pretty(const ppindentinfo & ppii, + const xo::gc::ObjectStatistics & x) + { + return ppii.pps()->pretty_struct(ppii, + "ObjectTypeStatistics", + refrtag("per_type_stats_v", x.per_type_stats_v_)); + } + } /*namespace gc*/ +} /*namespace xo*/ + +/* end ObjectStatistics.cpp */ diff --git a/xo-alloc/utest/LinearAlloc.test.cpp b/xo-alloc/utest/LinearAlloc.test.cpp deleted file mode 100644 index b1909991..00000000 --- a/xo-alloc/utest/LinearAlloc.test.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* @file LinearAlloc.test.cpp - * - * author: Roland Conybeare, Jul 2025 - */ - -#include "xo/alloc/ArenaAlloc.hpp" -#include - -namespace xo { - using xo::gc::LinearAlloc; - - namespace ut { - - namespace { - struct testcase_alloc { - testcase_alloc(std::size_t rz, std::size_t z) - : redline_z_{rz}, arena_z_{z} {} - - std::size_t redline_z_; - std::size_t arena_z_; - - }; - - std::vector - s_testcase_v = { - testcase_alloc(0, 4096) - }; - } - - - TEST_CASE("linearalloc", "[alloc]") - { - for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { - const testcase_alloc & tc = s_testcase_v[i_tc]; - - constexpr bool c_debug_flag = false; - - auto alloc = LinearAlloc::make("linearalloc", tc.redline_z_, tc.arena_z_, c_debug_flag); - - REQUIRE(alloc.get()); - REQUIRE(alloc->name() == "linearalloc"); - REQUIRE(alloc->size() == tc.arena_z_); - REQUIRE(alloc->available() == tc.arena_z_); - REQUIRE(alloc->allocated() == 0); - REQUIRE(alloc->is_before_checkpoint(alloc->free_ptr()) == false); - REQUIRE(alloc->before_checkpoint() == 0); - REQUIRE(alloc->after_checkpoint() == 0); - - auto free0 = alloc->free_ptr(); - - auto mem = alloc->alloc(tc.arena_z_); - - REQUIRE(mem != nullptr); - - REQUIRE(mem == free0); - - REQUIRE(alloc->size() == tc.arena_z_); - REQUIRE(alloc->available() == 0); - REQUIRE(alloc->allocated() == tc.arena_z_); - REQUIRE(alloc->is_before_checkpoint(mem) == false); - REQUIRE(alloc->before_checkpoint() == 0); - REQUIRE(alloc->after_checkpoint() == tc.arena_z_); - - alloc->clear(); - - REQUIRE(alloc->free_ptr() == free0); - REQUIRE(alloc->available() == tc.arena_z_); - REQUIRE(alloc->allocated() == 0); - REQUIRE(alloc->is_before_checkpoint(free0) == false); - REQUIRE(alloc->before_checkpoint() == 0); - REQUIRE(alloc->after_checkpoint() == 0); - - mem = alloc->alloc(1); - - auto used = sizeof(void*); - REQUIRE(alloc->size() == tc.arena_z_); - REQUIRE(alloc->available() == tc.arena_z_ - used); - REQUIRE(alloc->allocated() == used); - REQUIRE(alloc->is_before_checkpoint(free0) == false); - REQUIRE(alloc->before_checkpoint() == 0); - REQUIRE(alloc->after_checkpoint() == used); - - } - } - - } /*namespace ut */ -} /*namespace xo*/ diff --git a/xo-indentlog/include/xo/indentlog/print/pretty_vector.hpp b/xo-indentlog/include/xo/indentlog/print/pretty_vector.hpp index b22fc149..8a5bc0d9 100644 --- a/xo-indentlog/include/xo/indentlog/print/pretty_vector.hpp +++ b/xo-indentlog/include/xo/indentlog/print/pretty_vector.hpp @@ -34,15 +34,38 @@ namespace xo { } else { ppii.pps()->write('['); - for (size_t i = 0, z = x.size(); i < z; ++i) { - if (i == 0) - ppii.pps()->indent(std::max(ppii.pps()->indent_width(), 1u) - 1); - else - ppii.pps()->newline_indent(ppii.ci1()); - ppii.pps()->pretty(x[i]); + bool skip_next_indent = false; - if (i+1 < z) - ppii.pps()->write(','); + for (size_t i = 0, z = x.size(); i < z; ++i) { + if (!skip_next_indent) { + if (i == 0) + ppii.pps()->indent(std::max(ppii.pps()->indent_width(), 1u) - 1); + else + ppii.pps()->newline_indent(ppii.ci1()); + } + + std::uint32_t pos0 = ppii.pps()->pos(); + ppii.pps()->pretty(x[i]); + std::uint32_t pos1 = ppii.pps()->pos(); + + bool skip_prev_indent = skip_next_indent; + + /* special case, did not print anything */ + skip_next_indent = (pos0 == pos1); + + if (skip_next_indent) { + if (skip_prev_indent) { + /* compress consecutive empty items */ + ; + } else { + /* catch edge from non-empty -> empty */ + ppii.pps()->write(",.. ,"); + ppii.pps()->newline_indent(ppii.ci1()); + } + } else { + if (i+1 < z) + ppii.pps()->write(','); + } } ppii.pps()->write(" ]"); diff --git a/xo-object/include/xo/object/Boolean.hpp b/xo-object/include/xo/object/Boolean.hpp index 883ace32..66f01ed7 100644 --- a/xo-object/include/xo/object/Boolean.hpp +++ b/xo-object/include/xo/object/Boolean.hpp @@ -22,6 +22,7 @@ namespace xo { // inherited from Object.. virtual TaggedPtr self_tp() const final override; + virtual void display(std::ostream & os) const final override; virtual std::size_t _shallow_size() const final override; virtual Object * _shallow_copy() const final override; virtual std::size_t _forward_children() final override; diff --git a/xo-object/include/xo/object/Integer.hpp b/xo-object/include/xo/object/Integer.hpp index 9f6b2aca..f9eb5cc2 100644 --- a/xo-object/include/xo/object/Integer.hpp +++ b/xo-object/include/xo/object/Integer.hpp @@ -24,6 +24,7 @@ namespace xo { // inherited from Object.. virtual TaggedPtr self_tp() const final override; + virtual void display(std::ostream & os) const final override; virtual std::size_t _shallow_size() const final override; virtual Object * _shallow_copy() const final override; virtual std::size_t _forward_children() final override; diff --git a/xo-object/include/xo/object/List.hpp b/xo-object/include/xo/object/List.hpp index 2cbfa857..35b2cec9 100644 --- a/xo-object/include/xo/object/List.hpp +++ b/xo-object/include/xo/object/List.hpp @@ -54,6 +54,7 @@ namespace xo { // inherited from Object.. virtual TaggedPtr self_tp() const final override; + virtual void display(std::ostream & os) const final override; virtual std::size_t _shallow_size() const final override; virtual Object * _shallow_copy() const final override; virtual std::size_t _forward_children() final override; diff --git a/xo-object/include/xo/object/String.hpp b/xo-object/include/xo/object/String.hpp index 0532c06f..9d74ff22 100644 --- a/xo-object/include/xo/object/String.hpp +++ b/xo-object/include/xo/object/String.hpp @@ -32,6 +32,7 @@ namespace xo { // inherited from Object.. virtual TaggedPtr self_tp() const final override; + virtual void display(std::ostream & os) const final override; virtual std::size_t _shallow_size() const final override; virtual Object * _shallow_copy() const final override; virtual std::size_t _forward_children() final override; diff --git a/xo-object/src/object/Boolean.cpp b/xo-object/src/object/Boolean.cpp index afb30232..43d822a3 100644 --- a/xo-object/src/object/Boolean.cpp +++ b/xo-object/src/object/Boolean.cpp @@ -42,6 +42,12 @@ namespace xo { return Reflect::make_tp(const_cast(this)); } + void + Boolean::display(std::ostream & os) const + { + os << (value_ ? "#t" : "#f"); + } + std::size_t Boolean::_shallow_size() const { diff --git a/xo-object/src/object/Integer.cpp b/xo-object/src/object/Integer.cpp index 94f51bbb..512cb245 100644 --- a/xo-object/src/object/Integer.cpp +++ b/xo-object/src/object/Integer.cpp @@ -29,6 +29,11 @@ namespace xo { return Reflect::make_tp(const_cast(this)); } + void + Integer::display(std::ostream & os) const { + os << value_; + } + std::size_t Integer::_shallow_size() const { return sizeof(Integer); diff --git a/xo-object/src/object/List.cpp b/xo-object/src/object/List.cpp index 2cf9f5a3..324605d5 100644 --- a/xo-object/src/object/List.cpp +++ b/xo-object/src/object/List.cpp @@ -75,6 +75,18 @@ namespace xo { return Reflect::make_tp(const_cast(this)); } + void + List::display(std::ostream & os) const { + gp l = const_cast(this); + + os << "("; + while (!l->is_nil()) { + os << l->head(); + l = l->rest(); + } + os << ")"; + } + std::size_t List::_shallow_size() const { return sizeof(List); diff --git a/xo-object/src/object/String.cpp b/xo-object/src/object/String.cpp index 1d420e7d..41399a2e 100644 --- a/xo-object/src/object/String.cpp +++ b/xo-object/src/object/String.cpp @@ -96,6 +96,13 @@ namespace xo { return Reflect::make_tp(const_cast(this)); } + void + String::display(std::ostream & os) const { + // TODO: print with escapes + + os << "\"" << c_str() << "\""; + } + // ----- GC support ----- std::size_t diff --git a/xo-object/utest/GC.test.cpp b/xo-object/utest/GC.test.cpp index 9fd19613..481dbecf 100644 --- a/xo-object/utest/GC.test.cpp +++ b/xo-object/utest/GC.test.cpp @@ -251,7 +251,7 @@ namespace xo { ObjectGraphModel::verify_equal_models(ObjectGraphModel & from_model, ObjectGraphModel & to_model) { - REQUIRE(from_model.roots_.size() == to_model.nodes_.size()); + REQUIRE(from_model.roots_.size() == to_model.roots_.size()); REQUIRE(from_model.nodes_.size() == to_model.nodes_.size()); std::unordered_set visited_set; @@ -288,7 +288,7 @@ namespace xo { std::size_t new_index = this->nodes_.size(); { if (x_int.is_null() && x_list.is_null()) - throw std::runtime_error("expecting object graph containing int|cons only"); + throw std::runtime_error(tostr("expecting object graph containing int|cons|nil only", xtag("x", x))); if (!x_int.is_null()) { new_model.index_ = new_index; @@ -402,6 +402,9 @@ namespace xo { void RandomMutationModel::generate_seed_values() { + w1_.clear(); + w2_.clear(); + { for (size_t i = 0; i < m_; ++i) { w1_.push_back(List::cons(List::nil, List::nil)); @@ -443,10 +446,12 @@ namespace xo { for (std::size_t i = 0; i < k_; ++i) { /* pick a root list cell at random */ gp l1 = w1_.at((*p_rgen)() % w1_.size()); + REQUIRE(l1.ptr()); if ((*p_rgen)() % 2 == 0) { /* pick another root list cell at random, and link it to l1 */ gp l2 = w1_.at((*p_rgen)() % w1_.size()); + REQUIRE(l2.ptr()); l1->assign_rest(l2); } else { @@ -454,6 +459,7 @@ namespace xo { * assign to head */ gp x2 = w2_.at((*p_rgen)() % w2_.size()); + REQUIRE(x2.ptr()); l1->assign_head(x2); } @@ -463,6 +469,8 @@ namespace xo { void RandomMutationModel::rejuvenate_seed_values() { for (std::size_t i = 0; i < w1_.size(); ++i) { + INFO(xtag("i", i)); + if (w1_.at(i)->_is_forwarded()) { /* w[i] survived GC */ w1_[i] = dynamic_cast(w1_[i]->_destination()); @@ -470,15 +478,20 @@ namespace xo { /* w[i] is garbage, replace */ w1_[i] = List::cons(List::nil, List::nil); } + REQUIRE(w1_[i].ptr()); } for (std::size_t j = 0; j < w2_.size(); ++j) { + INFO(xtag("j", j)); + if (w2_.at(j)->_is_forwarded()) { /* w2[i] survived GC */ - w2_[j] = dynamic_cast(w2_[j]->_destination()); + w2_[j] = w2_[j]->_destination(); + REQUIRE(w2_[j].ptr()); } else { /* w2[j] is garbage, replace */ w2_[j] = Integer::make((this->start_)++); + REQUIRE(w2_[j].ptr()); } } } @@ -534,8 +547,17 @@ namespace xo { std::vector s_testcase_v = { - /* nz tz m n r rr k stats, debug */ - testcase_stresstest(1024, 1024, 3, 7, 5, 2, 10, true, false) + /* nz: nursery size + * tz: tenured size + * m: #of random list cells to create + * n: #of random integers to create + * r: #of gc roots to create + * rr: #of gc roots to replace between iterations + * k: #of random mutations to apply + * + * nz tz m n r rr k stats, debug */ + testcase_stresstest(1024, 1024, 5, 1, 5, 2, 10, true, false), + testcase_stresstest(1024, 1024, 10, 10, 5, 2, 10, true, false) }; } /*namespace*/ @@ -550,6 +572,7 @@ namespace xo { { .initial_nursery_z_ = tc.nursery_z_, .initial_tenured_z_ = tc.tenured_z_, + .stats_flag_ = tc.gc_stats_flag_, .debug_flag_ = tc.debug_flag_ }); @@ -582,7 +605,9 @@ namespace xo { RandomMutationModel data_model(tc.m_, tc.n_, tc.r_, tc.rr_, tc.k_); - for (std::size_t cycle = 0; cycle < 2; ++cycle) { + for (std::size_t cycle = 0; cycle < 5; ++cycle) { + INFO(xtag("cycle", cycle)); + if (cycle == 0) { data_model.generate_seed_values(); data_model.generate_random_roots(gc.get(), &rgen);