diff --git a/CMakeLists.txt b/CMakeLists.txt index 38bc2ade..92cd55e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,9 +100,9 @@ add_subdirectory(xo-alloc) add_subdirectory(xo-alloc2) # experiment w/ facet object model add_subdirectory(xo-stringtable2) # experiment w/ facet object model #add_subdirectory(xo-reflect2) # experiment w/ facet object model -add_subdirectory(xo-gc) # experiment w/ facet object model add_subdirectory(xo-object) add_subdirectory(xo-object2) # experiment w/ facet object model +add_subdirectory(xo-gc) # experiment w/ facet object model add_subdirectory(xo-type) # experiment w/ fomo add_subdirectory(xo-procedure2) # schematika procedure abstraction + runtime context (fomo) add_subdirectory(xo-numeric) # experiment w/ facet object model diff --git a/xo-alloc2/idl/Collector.json5 b/xo-alloc2/idl/Collector.json5 index 1d34445c..865d992e 100644 --- a/xo-alloc2/idl/Collector.json5 +++ b/xo-alloc2/idl/Collector.json5 @@ -29,12 +29,6 @@ "A collector must also suppose the @ref AAllocator facet, see also" ], types: [ - // using typeseq = xo::facet::typeseq; // I think this is automatically provided -// { -// name: "typeseq", -// doc: ["type for an amount of memory"], -// definition: "std::size_t", -// }, // using size_type = std::size_t; { name: "size_type", @@ -87,7 +81,7 @@ name: "contains", doc: ["true if gc responsible for data at @p addr, and data belongs to role @p r"], return_type: "bool", - args:[ + args: [ {type: "role", name: "r"}, {type: "const void *", name: "addr"}, ], @@ -100,13 +94,32 @@ name: "is_type_installed", doc: ["true iff gc-aware object of type @p tseq is installed in this collector"], return_type: "bool", - args:[ + args: [ {type: "typeseq", name: "tseq"}, ], const: true, noexcept: true, attributes: [], }, + // obj summary() const noexcept; + { + name: "report_statistics", + doc: [ + "Report gc statistics, 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 0f5f91f7..f156a4e4 100644 --- a/xo-alloc2/include/xo/alloc2/gc/ACollector.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/ACollector.hpp @@ -69,6 +69,11 @@ public: virtual bool contains(Copaque data, role r, const void * addr) const noexcept = 0; /** true iff gc-aware object of type @p tseq is installed in this collector **/ virtual bool is_type_installed(Copaque data, typeseq tseq) const noexcept = 0; + /** Report gc statistics, 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_statistics(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 285467c7..34f90356 100644 --- a/xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp @@ -64,6 +64,7 @@ namespace mm { [[noreturn]] size_type reserved(Copaque, Generation, role) const noexcept override { _fatal(); } [[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(); } // 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 b1df1451..2df60064 100644 --- a/xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp @@ -61,6 +61,9 @@ namespace mm { bool is_type_installed(Copaque data, typeseq tseq) const noexcept override { return I::is_type_installed(_dcast(data), tseq); } + 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); + } // 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 345eb1f6..ad514854 100644 --- a/xo-alloc2/include/xo/alloc2/gc/RCollector.hpp +++ b/xo-alloc2/include/xo/alloc2/gc/RCollector.hpp @@ -94,6 +94,9 @@ public: bool is_type_installed(typeseq tseq) const noexcept { return O::iface()->is_type_installed(O::data(), tseq); } + 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); + } // non-const methods (still const in router!) bool install_type(const AGCObject & iface) { diff --git a/xo-alloc2/include/xo/alloc2/generation.hpp b/xo-alloc2/include/xo/alloc2/generation.hpp index 76ad1a8b..2c83485b 100644 --- a/xo-alloc2/include/xo/alloc2/generation.hpp +++ b/xo-alloc2/include/xo/alloc2/generation.hpp @@ -11,7 +11,7 @@ namespace xo { namespace mm { /** hard maximum number of generations **/ - static constexpr uint32_t c_max_generation = 2; + static constexpr uint32_t c_max_generation = 3; /** @class generation * @brief type-safe generation number diff --git a/xo-gc/CMakeLists.txt b/xo-gc/CMakeLists.txt index 2edb0ac3..bacf4f14 100644 --- a/xo-gc/CMakeLists.txt +++ b/xo-gc/CMakeLists.txt @@ -18,6 +18,19 @@ add_definitions(${PROJECT_CXX_FLAGS}) # ---------------------------------------------------------------- +# note: manual target; generated code committed to git +xo_add_genfacetimpl( + TARGET xo-gc-facetimpl-collector-x1collector + FACET_PKG xo_alloc2 + INPUT idl/ICollector_DX1Collector.json5 +) + +# ---------------------------------------------------------------- + +xo_add_genfacet_all(xo-gc-genfacet-all) + +# ---------------------------------------------------------------- + # must complete definition of expression lib before configuring examples add_subdirectory(src/gc) add_subdirectory(utest) diff --git a/xo-gc/cmake/xo_gcConfig.cmake.in b/xo-gc/cmake/xo_gcConfig.cmake.in index 548eb737..29f87480 100644 --- a/xo-gc/cmake/xo_gcConfig.cmake.in +++ b/xo-gc/cmake/xo_gcConfig.cmake.in @@ -1,6 +1,7 @@ @PACKAGE_INIT@ include(CMakeFindDependencyMacro) +find_dependency(xo_object2) find_dependency(xo_alloc2) find_dependency(xo_facet) find_dependency(subsys) diff --git a/xo-gc/idl/ICollector_DX1Collector.json5 b/xo-gc/idl/ICollector_DX1Collector.json5 new file mode 100644 index 00000000..9124288f --- /dev/null +++ b/xo-gc/idl/ICollector_DX1Collector.json5 @@ -0,0 +1,24 @@ +{ + mode: "implementation", + output_cpp_dir: "src/gc/facet", + output_hpp_dir: "include/xo/gc", + output_impl_subdir: "detail", + includes: [ +// "", +// "" + ], + local_types: [ + { + name: "typeseq", + doc: ["identifies a c++ type"], + definition: "xo::reflect::typeseq" + }, + ], + namespace1: "xo", + namespace2: "mm", + facet_idl: "idl/Collector.json5", + brief: "provide ACollector interface for DX1Collector", + using_doxygen: true, + repr: "DX1Collector", + doc: [ "implement ACollector for DX1Collector" ], +} diff --git a/xo-gc/include/xo/gc/DX1Collector.hpp b/xo-gc/include/xo/gc/DX1Collector.hpp index c72b802e..d5934611 100644 --- a/xo-gc/include/xo/gc/DX1Collector.hpp +++ b/xo-gc/include/xo/gc/DX1Collector.hpp @@ -153,15 +153,38 @@ namespace xo { // ----- basic statistics ----- /** total reserved memory in bytes, across all {role, generation} **/ - size_type reserved_total() const noexcept; + size_type reserved() const noexcept; /** total size in bytes (same as committed_total()) **/ size_type size_total() const noexcept; /** total committed memory in bytes, across all {role, generation} **/ - size_type committed_total() const noexcept; + size_type committed() const noexcept; /** total available memory in bytes, across all {role, generation} **/ - size_type available_total() const noexcept; + size_type available() const noexcept; /** total allocated memory in bytes, across all {role, generation} **/ - size_type allocated_total() const noexcept; + size_type allocated() const noexcept; + + /** total number of mutation log entries **/ + size_type mutation_log_entries() const noexcept; + + /** memory allocated for generation @p g in role @p r **/ + size_type allocated(Generation g, role r) const noexcept; + /** memory committed for generation @p g in role @p r **/ + size_type committed(Generation g, role r) const noexcept; + /** memory (virtual addresses) reserved for generation @p g in role @p r **/ + size_type reserved(Generation g, role r) const noexcept; + + // ----- full statistics ----- + + /** Report gc statistics as a dictionary. + * Providing for the same of making GC statistics visible to schematika programs + * + * @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_statistics(obj mm, + obj error_mm, + obj * p_output) const noexcept; // ----- queries ----- diff --git a/xo-gc/include/xo/gc/detail/ICollector_DX1Collector.hpp b/xo-gc/include/xo/gc/detail/ICollector_DX1Collector.hpp index fe7bb015..a9c8e012 100644 --- a/xo-gc/include/xo/gc/detail/ICollector_DX1Collector.hpp +++ b/xo-gc/include/xo/gc/detail/ICollector_DX1Collector.hpp @@ -1,63 +1,107 @@ /** @file ICollector_DX1Collector.hpp -* - * @author Roland Conybeare, Dec 2025 + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/ICollector_DX1Collector.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_repr.hpp.j2] + * 3. idl for facet methods + * [idl/ICollector_DX1Collector.json5] **/ #pragma once -#include -#include +#include "Collector.hpp" #include "DX1Collector.hpp" -namespace xo { - namespace mm { struct ICollector_DX1Collector; } +namespace xo { namespace mm { class ICollector_DX1Collector; } } +namespace xo { namespace facet { template <> struct FacetImplementation { - using ImplType = xo::mm::ICollector_Xfer; + using ImplType = xo::mm::ICollector_Xfer + ; }; } +} +namespace xo { namespace mm { - /* changes here coordinate with - * ACollector ACollector.hpp - * ICollector_Any ICollector_Any.hpp - * ICollector_Xfer ICollector_Xfer.hpp - * RCollector RCollector.hpp - */ - struct ICollector_DX1Collector { - using size_type = std::size_t; - using header_type = DArena::header_type; - using typeseq = xo::facet::typeseq; + /** @class ICollector_DX1Collector + **/ + class ICollector_DX1Collector { + public: + /** @defgroup mm-collector-dx1collector-type-traits **/ + ///@{ + using size_type = xo::mm::ACollector::size_type; + using Copaque = xo::mm::ACollector::Copaque; + using Opaque = xo::mm::ACollector::Opaque; + using typeseq = xo::reflect::typeseq; + ///@} + /** @defgroup mm-collector-dx1collector-methods **/ + ///@{ + // const methods + /** memory in use for this collector **/ + static size_type allocated(const DX1Collector & self, Generation g, role r) noexcept; + /** memory committed for this collector **/ + static size_type committed(const DX1Collector & self, Generation g, role r) noexcept; + /** address space reserved for this collector **/ + static size_type reserved(const DX1Collector & self, Generation g, role r) noexcept; + /** true if gc responsible for data at @p addr, and data belongs to role @p r **/ + static bool contains(const DX1Collector & self, role r, const void * addr) noexcept; + /** true iff gc-aware object of type @p tseq is installed in this collector **/ + static bool is_type_installed(const DX1Collector & self, typeseq tseq) noexcept; + /** Report gc statistics, 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_statistics(const DX1Collector & self, obj report_mm, obj error_mm, obj * output) noexcept; - static bool check_move_policy(const DX1Collector & d, - header_type alloc_hdr, - void * object_data); + // non-const methods + /** install interface @p iface for representation with typeseq @p tseq +in collector @p d. - // todo: available() +The type AGCObject_Any here is misleading. +Will have been replaced by an instance of + @c AGCObject_Xfer for some @c DFoo +in which case calls through @c std::launder(&iface) +will properly act on @c DFoo. - static size_type allocated(const DX1Collector & d, Generation g, role r); - static size_type reserved(const DX1Collector & d, Generation g, role r); - static size_type committed(const DX1Collector & d, Generation g, role r); - static bool is_type_installed(const DX1Collector & d, typeseq tseq); - static bool contains(const DX1Collector & d, role r, const void * addr); +Return false if installation fails (e.g. memory exhausted) **/ + static bool install_type(DX1Collector & self, const AGCObject & iface); + /** add gc root with address @p p_root. gc will preserve subgraph at this address **/ + static void add_gc_root_poly(DX1Collector & self, obj * p_root); + /** remove gc root with address @p p_root. Reverse effect of prior add_gc_root_poly call **/ + static void remove_gc_root_poly(DX1Collector & self, obj * p_root); + /** Request immediate collection. + 1. if collection is enabled, immediately collect all generations + up to (but not including) g + 2. may nevertheless escalate to older generations, + depending on collector state. + 3. if collection is currently disabled, + collection will trigger the next time gc is enabled. + **/ + static void request_gc(DX1Collector & self, Generation upto); + /** Assign pointer @p p_lhs to destination @p rhs, within parent allocation @p parent - static bool install_type(DX1Collector & d, const AGCObject & iface); - static void add_gc_root_poly(DX1Collector & d, obj * p_root); - static void remove_gc_root_poly(DX1Collector & d, obj * p_root); - static void request_gc(DX1Collector & d, Generation upto); - static void assign_member(DX1Collector & d, void * parent, - obj * p_lhs, obj & rhs); - static void forward_inplace(DX1Collector & d, AGCObject * lhs_iface, void ** lhs_data); +Require: gc not in progress **/ + static void assign_member(DX1Collector & self, void * parent, obj * p_lhs, obj & rhs); + /** evacuate @p *lhs, that refers to state with interface @p lhs_iface, +to collector @p d's to-space. Replace *lhs_data with forwarding pointer - static int32_t s_typeseq; - static bool _valid; +Require: gc in progress + **/ + static void forward_inplace(DX1Collector & self, AGCObject * lhs_iface, void ** lhs_data); + ///@} }; + } /*namespace mm*/ } /*namespace xo*/ -/* end ICollector_DX1_Collector.hpp */ +/* end */ \ No newline at end of file diff --git a/xo-gc/src/gc/CMakeLists.txt b/xo-gc/src/gc/CMakeLists.txt index eee6864b..2dec8735 100644 --- a/xo-gc/src/gc/CMakeLists.txt +++ b/xo-gc/src/gc/CMakeLists.txt @@ -7,10 +7,11 @@ set(SELF_SRCS SetupGc.cpp IAllocator_DX1Collector.cpp - ICollector_DX1Collector.cpp IAllocIterator_DX1CollectorIterator.cpp DX1Collector.cpp + facet/ICollector_DX1Collector.cpp + DX1CollectorIterator.cpp X1CollectorConfig.cpp @@ -20,6 +21,7 @@ set(SELF_SRCS xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # note: deps here must also appear in cmake/xo_alloc2Config.cmake.in +xo_dependency(${SELF_LIB} xo_object2) xo_dependency(${SELF_LIB} xo_alloc2) xo_dependency(${SELF_LIB} xo_facet) xo_dependency(${SELF_LIB} subsys) diff --git a/xo-gc/src/gc/DX1Collector.cpp b/xo-gc/src/gc/DX1Collector.cpp index bdb73ff1..a668b8d4 100644 --- a/xo-gc/src/gc/DX1Collector.cpp +++ b/xo-gc/src/gc/DX1Collector.cpp @@ -4,10 +4,12 @@ **/ #include "X1Collector.hpp" +#include +#include +#include #include #include #include -#include #include #include "object_age.hpp" #include @@ -18,6 +20,11 @@ #include // for ::getpagesize() namespace xo { + // for report_statistics() + using xo::scm::DDictionary; + using xo::scm::DString; + using xo::scm::DInteger; + using xo::mm::AAllocator; using xo::facet::TypeRegistry; using xo::facet::typeseq; @@ -165,6 +172,10 @@ namespace xo { this->space_[role::to_space()][igen] = nullptr; this->space_[role::from_space()][igen] = nullptr; } + + if (config_.n_generation_ == 2) { + assert(this->get_space(role::to_space(), Generation{2}) == nullptr); + } } void @@ -251,7 +262,7 @@ namespace xo { } size_type - DX1Collector::reserved_total() const noexcept + DX1Collector::reserved() const noexcept { return accumulate_total_aux(*this, &DArena::reserved); } @@ -259,27 +270,124 @@ namespace xo { size_type DX1Collector::size_total() const noexcept { - return committed_total(); + return this->committed(); } size_type - DX1Collector::committed_total() const noexcept + DX1Collector::committed() const noexcept { return accumulate_total_aux(*this, &DArena::committed); } size_type - DX1Collector::available_total() const noexcept + DX1Collector::available() const noexcept { return accumulate_total_aux(*this, &DArena::available); } size_type - DX1Collector::allocated_total() const noexcept + DX1Collector::allocated() const noexcept { return accumulate_total_aux(*this, &DArena::allocated); } + size_type + DX1Collector::mutation_log_entries() const noexcept + { + size_type z = 0; + + for (Generation gj{0}; gj + 1 < config_.n_generation_; ++gj) { + z += mlog_[role::to_space()][gj]->size(); + } + + return z; + } + + namespace { + size_type + stat_helper(const DX1Collector & d, + size_type (DArena::* getter)() const, + Generation g, + role r) + { + const DArena * arena = d.get_space(r, g); + + if (arena) [[likely]] + return (arena->*getter)(); + + return 0; + } + } + + size_type + DX1Collector::allocated(Generation g, role r) const noexcept + { + return stat_helper(*this, &DArena::allocated, g, r); + } + + size_type + DX1Collector::committed(Generation g, role r) const noexcept + { + return stat_helper(*this, &DArena::committed, g, r); + } + + size_type + DX1Collector::reserved(Generation g, role r) const noexcept + { + return stat_helper(*this, &DArena::reserved, g, r); + } + + // editor bait: report-gc-statistics + bool + DX1Collector::report_statistics(obj mm, + obj error_mm, + obj * p_output) const noexcept + { + (void)error_mm; + + DDictionary * rpt = DDictionary::make(mm); + + bool ok = true; + + // note: totals taken across both roles and generations, + // so counts both from-space and to-space + // + ok &= rpt->upsert_cstr(mm, "n-generation", DInteger::box(mm, config_.n_generation_)); + ok &= rpt->upsert_cstr(mm, "n-survive-threshold", DInteger::box(mm, config_.n_survive_threshold_)); + ok &= rpt->upsert_cstr(mm, "allocated", DInteger::box(mm, this->allocated())); + ok &= rpt->upsert_cstr(mm, "committed", DInteger::box(mm, this->committed())); + ok &= rpt->upsert_cstr(mm, "reserved", DInteger::box(mm, this->reserved())); + ok &= rpt->upsert_cstr(mm, "n-mlog-entry", DInteger::box(mm, this->mutation_log_entries())); + + // per-(generation,role) info + { + for (Generation gi{0}; gi < config_.n_generation_; ++gi) { + for (role rj : role::all()) { + const DArena * arena = this->get_space(rj, gi); + DDictionary * arena_d = DDictionary::make(mm); + + auto lo = reinterpret_cast(arena->lo_); + auto free = reinterpret_cast(arena->free_); + auto limit = reinterpret_cast(arena->limit_); + auto hi = reinterpret_cast(arena->hi_); + + ok &= arena_d->upsert_cstr(mm, "lo", DInteger::box(mm, lo)); + ok &= arena_d->upsert_cstr(mm, "d-free", DInteger::box(mm, free - lo)); + ok &= arena_d->upsert_cstr(mm, "d-limit", DInteger::box(mm, limit - lo)); + ok &= arena_d->upsert_cstr(mm, "d-hi", DInteger::box(mm, hi - lo)); + + const DString * key = DString::from_str(mm, arena->config_.name_); + + rpt->upsert(mm, std::make_pair(key, obj(arena_d))); + } + } + } + + *p_output = obj(rpt); + + return ok; + } + size_type DX1Collector::header2size(header_type hdr) const noexcept { diff --git a/xo-gc/src/gc/IAllocator_DX1Collector.cpp b/xo-gc/src/gc/IAllocator_DX1Collector.cpp index 039e84df..37ed497f 100644 --- a/xo-gc/src/gc/IAllocator_DX1Collector.cpp +++ b/xo-gc/src/gc/IAllocator_DX1Collector.cpp @@ -29,7 +29,7 @@ namespace xo { auto IAllocator_DX1Collector::reserved(const DX1Collector & d) noexcept -> size_type { - return d.reserved_total(); + return d.reserved(); } auto @@ -41,19 +41,19 @@ namespace xo { auto IAllocator_DX1Collector::committed(const DX1Collector & d) noexcept -> size_type { - return d.committed_total(); + return d.committed(); } auto IAllocator_DX1Collector::available(const DX1Collector & d) noexcept -> size_type { - return d.available_total(); + return d.available(); } auto IAllocator_DX1Collector::allocated(const DX1Collector & d) noexcept -> size_type { - return d.allocated_total(); + return d.allocated(); } void diff --git a/xo-gc/src/gc/ICollector_DX1Collector.cpp b/xo-gc/src/gc/ICollector_DX1Collector.cpp deleted file mode 100644 index 5503d429..00000000 --- a/xo-gc/src/gc/ICollector_DX1Collector.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/** @file ICollector_DX1Collector.cpp - * - * @author Roland Conybeare, Dec 2025 - * - * See also IAllocator_DX1Collector.cpp for allocator facet - **/ - -#include "xo/gc/detail/ICollector_DX1Collector.hpp" -#include "GCObject.hpp" - -namespace xo { - namespace mm { - using size_type = ICollector_DX1Collector::size_type; - using std::byte; - - namespace { - size_type - stat_helper(const DX1Collector & d, - size_type (DArena::* getter)() const, - Generation g, - role r) - { - const DArena * arena = d.get_space(r, g); - - if (arena) [[likely]] - return (arena->*getter)(); - - return 0; - } - } - - size_type - ICollector_DX1Collector::reserved(const DX1Collector & d, Generation g, role r) - { - return stat_helper(d, &DArena::reserved, g, r); - } - - size_type - ICollector_DX1Collector::allocated(const DX1Collector & d, Generation g, role r) - { - return stat_helper(d, &DArena::allocated, g, r); - } - - size_type - ICollector_DX1Collector::committed(const DX1Collector & d, Generation g, role r) - { - return stat_helper(d, &DArena::committed, g, r); - } - - bool - ICollector_DX1Collector::contains(const DX1Collector & d, role r, const void * addr) - { - return d.contains(r, addr); - } - - bool - ICollector_DX1Collector::is_type_installed(const DX1Collector & d, typeseq tseq) - { - return d.is_type_installed(tseq); - } - - bool - ICollector_DX1Collector::install_type(DX1Collector & d, - const AGCObject & iface) - { - return d.install_type(iface); - } - - void - ICollector_DX1Collector::add_gc_root_poly(DX1Collector & d, - obj * p_root) - { - d.add_gc_root_poly(p_root); - } - - void - ICollector_DX1Collector::remove_gc_root_poly(DX1Collector & d, - obj * p_root) - { - d.remove_gc_root_poly(p_root); - } - - void - ICollector_DX1Collector::request_gc(DX1Collector & d, - Generation upto) - { - d.request_gc(upto); - } - - void - ICollector_DX1Collector::assign_member(DX1Collector & d, - void * parent, - obj * p_lhs, - obj & rhs) - { - d.assign_member(parent, p_lhs, rhs); - } - - void - ICollector_DX1Collector::forward_inplace(DX1Collector & d, - AGCObject * lhs_iface, - void ** lhs_data) - { - d.forward_inplace(lhs_iface, lhs_data); - } - - bool - ICollector_DX1Collector::check_move_policy(const DX1Collector & d, - header_type alloc_hdr, - void * object_data) - { - return d.check_move_policy(alloc_hdr, object_data); - } - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end ICollector_DX1Collector.cpp */ diff --git a/xo-gc/src/gc/facet/ICollector_DX1Collector.cpp b/xo-gc/src/gc/facet/ICollector_DX1Collector.cpp new file mode 100644 index 00000000..10b4add5 --- /dev/null +++ b/xo-gc/src/gc/facet/ICollector_DX1Collector.cpp @@ -0,0 +1,88 @@ +/** @file ICollector_DX1Collector.cpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/ICollector_DX1Collector.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/ICollector_DX1Collector.json5] +**/ + +#include "detail/ICollector_DX1Collector.hpp" + +namespace xo { + namespace mm { + auto + ICollector_DX1Collector::allocated(const DX1Collector & self, Generation g, role r) noexcept -> size_type + { + return self.allocated(g, r); + } + + auto + ICollector_DX1Collector::committed(const DX1Collector & self, Generation g, role r) noexcept -> size_type + { + return self.committed(g, r); + } + + auto + ICollector_DX1Collector::reserved(const DX1Collector & self, Generation g, role r) noexcept -> size_type + { + return self.reserved(g, r); + } + + auto + ICollector_DX1Collector::contains(const DX1Collector & self, role r, const void * addr) noexcept -> bool + { + return self.contains(r, addr); + } + + auto + ICollector_DX1Collector::is_type_installed(const DX1Collector & self, typeseq tseq) noexcept -> bool + { + return self.is_type_installed(tseq); + } + + auto + ICollector_DX1Collector::report_statistics(const DX1Collector & self, obj report_mm, obj error_mm, obj * output) noexcept -> bool + { + return self.report_statistics(report_mm, error_mm, output); + } + + auto + ICollector_DX1Collector::install_type(DX1Collector & self, const AGCObject & iface) -> bool + { + return self.install_type(iface); + } + auto + ICollector_DX1Collector::add_gc_root_poly(DX1Collector & self, obj * p_root) -> void + { + self.add_gc_root_poly(p_root); + } + auto + ICollector_DX1Collector::remove_gc_root_poly(DX1Collector & self, obj * p_root) -> void + { + self.remove_gc_root_poly(p_root); + } + auto + ICollector_DX1Collector::request_gc(DX1Collector & self, Generation upto) -> void + { + self.request_gc(upto); + } + auto + ICollector_DX1Collector::assign_member(DX1Collector & self, void * parent, obj * p_lhs, obj & rhs) -> void + { + self.assign_member(parent, p_lhs, rhs); + } + auto + ICollector_DX1Collector::forward_inplace(DX1Collector & self, AGCObject * lhs_iface, void ** lhs_data) -> void + { + self.forward_inplace(lhs_iface, lhs_data); + } + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end ICollector_DX1Collector.cpp */ diff --git a/xo-gc/src/gc/init_gc.cpp b/xo-gc/src/gc/init_gc.cpp index 8747229f..40dd700a 100644 --- a/xo-gc/src/gc/init_gc.cpp +++ b/xo-gc/src/gc/init_gc.cpp @@ -5,6 +5,7 @@ #include "init_gc.hpp" #include "SetupGc.hpp" +#include #include namespace xo { @@ -21,6 +22,7 @@ namespace xo { InitEvidence retval; /* recursive subsystem deps for xo-gc/ */ + retval ^= InitSubsys::require(); retval ^= InitSubsys::require(); /* xo-gc/'s own initialization code */ diff --git a/xo-gc/utest/CMakeLists.txt b/xo-gc/utest/CMakeLists.txt index c48dbdb5..0f3c9676 100644 --- a/xo-gc/utest/CMakeLists.txt +++ b/xo-gc/utest/CMakeLists.txt @@ -5,7 +5,9 @@ set(UTEST_EXE utest.gc) set(UTEST_SRCS gc_utest_main.cpp Collector.test.cpp + X1Collector.test.cpp DX1CollectorIterator.test.cpp + Object2.test.cpp random_allocs.cpp ) diff --git a/xo-gc/utest/Object2.test.cpp b/xo-gc/utest/Object2.test.cpp new file mode 100644 index 00000000..fa0baecb --- /dev/null +++ b/xo-gc/utest/Object2.test.cpp @@ -0,0 +1,134 @@ +/** @file Object2.test.cpp + * + * Tests that target xo-object2/ features, but also rely on xo-gc/ + * + * @author Roland Conybeare, Mar 2026 + **/ + +#include +#include +#include +#include +#include +#include + +namespace ut { + using xo::scm::SetupObject2; + using xo::scm::ListOps; + using xo::scm::DList; + using xo::scm::DInteger; + using xo::scm::DString; + using xo::mm::AAllocator; + using xo::mm::ACollector; + using xo::mm::AGCObject; + using xo::mm::X1CollectorConfig; + using xo::mm::DX1Collector; + using xo::mm::ArenaConfig; + using xo::print::APrintable; + using xo::facet::FacetRegistry; + using xo::facet::with_facet; + using xo::facet::obj; + using xo::facet::typeseq; + using xo::print::ppstate_standalone; + using xo::print::ppconfig; + using xo::scope; + using xo::xtag; + using std::string; + + namespace { + struct testcase_pp { + explicit testcase_pp(size_t gc_z, size_t gc_threshold, int z, const std::string & expected) + : gc_gen_size_{gc_z}, gc_trigger_threshold_{gc_threshold}, expected_{expected} { + for (int i = 0; i < z; ++i) { + list_.push_back(1000 + 197 * i); + } + } + + size_t gc_gen_size_ = 0; + size_t gc_trigger_threshold_ = 0; + std::vector list_; + std::string expected_; + }; + + std::vector + s_testcase_v = { + testcase_pp(16384, 8192, 0, "()"), + testcase_pp(16384, 8192, 1, "(01000)"), + testcase_pp(16384, 8192, 2, "(01000 1197)"), + testcase_pp(16384, 8192, 5, "(01000 1197 01394 1591 01788)"), + testcase_pp(16384, 8192, 10, "(01000 1197 01394 1591 01788 1985 02182 2379 02576 2773)"), + testcase_pp(16384, 8192, 20, "(...)"), + }; + } + + TEST_CASE("printable1", "[pp][x1][list]") + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + bool ok = SetupObject2::register_facets(); + REQUIRE(ok); + + FacetRegistry::instance().dump(&std::cerr); + + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + log && log("printable1 test:", xtag("i_tc", i_tc)); + + try { + const testcase_pp & tc = s_testcase_v[i_tc]; + + X1CollectorConfig cfg{ .name_ = "pp_test", + .arena_config_ = ArenaConfig{ + .size_ = tc.gc_gen_size_, + .store_header_flag_ = true}, + .object_types_z_ = 16384, + .gc_trigger_v_{{tc.gc_trigger_threshold_, + tc.gc_trigger_threshold_}}, + .debug_flag_ = c_debug_flag + }; + + DX1Collector gc(cfg); + auto gc_o = with_facet::mkobj(&gc); + auto c_o = with_facet::mkobj(&gc); + + bool ok = SetupObject2::register_types(c_o); + REQUIRE(ok); + + auto l0_o = ListOps::nil(); + + c_o.add_gc_root(&l0_o); + + for(int ip1 = tc.list_.size(); ip1 > 0; --ip1) { + obj elt; + + //elt = DInteger::box(gc_o, tc.list_[ip1 - 1]); + + if (ip1 % 2 == 0) { + elt = DInteger::box(gc_o, tc.list_[ip1 - 1]); + } else { + elt = obj(DString::printf(gc_o, 80, "%05d", tc.list_[ip1 - 1])); + } + + l0_o = ListOps::cons(gc_o, elt, l0_o); + } + + // TODO: log_streambuf using DArena + std::stringstream ss; + ppconfig ppc; + ppstate_standalone pps(&ss, 0, &ppc); + + obj l0_po(static_cast(l0_o.data())); + REQUIRE(l0_po._typeseq() == typeseq::id()); + + pps.pretty(l0_po); + + CHECK(ss.str() == string(tc.expected_)); + } catch (std::exception & ex) { + std::cerr << "caught exception: " << ex.what() << std::endl; + REQUIRE(false); + } + } + } /* TEST_CASE(printable1) */ +} /*namespace ut*/ + +/* end Object2.test.cpp */ diff --git a/xo-gc/utest/X1Collector.test.cpp b/xo-gc/utest/X1Collector.test.cpp new file mode 100644 index 00000000..67d477ec --- /dev/null +++ b/xo-gc/utest/X1Collector.test.cpp @@ -0,0 +1,297 @@ +/** @file X1Collector.test.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "init_gc.hpp" +#include "ListOps.hpp" +#include "DFloat.hpp" +#include "DInteger.hpp" +#include "DList.hpp" +#include "DArray.hpp" + +#include +//#include "number/IGCObject_DFloat.hpp" +#include "number/IGCObject_DInteger.hpp" +#include "list/IGCObject_DList.hpp" + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +namespace ut { + using xo::S_gc_tag; + using xo::scm::ListOps; + using xo::scm::DList; + using xo::scm::DArray; + using xo::scm::DFloat; + using xo::scm::DInteger; + using xo::mm::CollectorTypeRegistry; + using xo::mm::AAllocator; + using xo::mm::ACollector; + using xo::mm::AllocHeader; + using xo::mm::AllocInfo; + using xo::mm::AGCObject; + using xo::mm::X1CollectorConfig; + using xo::mm::DX1Collector; + using xo::mm::DArena; + using xo::mm::ArenaConfig; + using xo::mm::Generation; + using xo::mm::role; + using xo::mm::padding; + using xo::facet::obj; + using xo::facet::with_facet; + using xo::facet::typeseq; + using xo::Subsystem; + using xo::InitEvidence; + using xo::InitSubsys; + using xo::scope; + using xo::xtag; + + namespace { + struct testcase_x1 { + testcase_x1(std::size_t nz, + std::size_t tz, + std::size_t n_gct, + std::size_t t_gct) + : nursery_z_{nz}, + tenured_z_{tz}, + incr_gc_threshold_{n_gct}, + full_gc_threshold_{t_gct} {} + + std::size_t nursery_z_; + std::size_t tenured_z_; + std::size_t incr_gc_threshold_; + std::size_t full_gc_threshold_; + }; + + std::vector + s_testcase_v = { + // n_gct: nursery gc threshold + // t_gct: tenured gc threshold + // + // nz tz n_gct t_gct + testcase_x1(4096, 8192, 1024, 1024) + }; + } + + static InitEvidence s_init = (InitSubsys::require()); + + TEST_CASE("x1", "[gc][x1]") + { + // real purpose: ensure s_init survives static linking + REQUIRE(s_init.evidence()); + + Subsystem::initialize_all(); + + /** + * This is a basic Collector test for xo-object2 data types + **/ + + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + scope log(XO_DEBUG(true), xtag("i_tc", i_tc)); + + try { + const testcase_x1 & tc = s_testcase_v[i_tc]; + + X1CollectorConfig cfg{ .name_ = "x1_test", + .arena_config_ = ArenaConfig{ + .size_ = tc.tenured_z_, + .store_header_flag_ = true}, + .object_types_z_ = 16384, + .gc_trigger_v_{{ + tc.incr_gc_threshold_, + tc.full_gc_threshold_}}, + .debug_flag_ = c_debug_flag, + }; + + DX1Collector gc(cfg); + + CollectorTypeRegistry::instance().install_types(obj(&gc)); + + DArena * to_0 = nullptr; + + /* verify configuration */ + { + REQUIRE(cfg.n_generation_ == 2); + } + + /* verify initial collector state */ + { + REQUIRE(gc.name() == "x1_test"); + + const DArena * 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_); + + const DX1Collector::RootSet * roots = gc.get_root_set(); + + REQUIRE(roots != nullptr); + REQUIRE(roots->store()->reserved() >= cfg.object_roots_z_); + REQUIRE(roots->store()->reserved() < cfg.object_roots_z_ + roots->store()->page_z_); + + const DArena * from_0 = gc.get_space(role::from_space(), Generation{0}); + + REQUIRE(from_0 != nullptr); + REQUIRE(from_0->reserved() >= tc.tenured_z_); + REQUIRE(from_0->reserved() < tc.tenured_z_ + from_0->page_z_); + REQUIRE(from_0->reserved() % from_0->page_z_ == 0); + REQUIRE(from_0->allocated() == 0); + + const DArena * from_1 = gc.get_space(role::from_space(), Generation{1}); + + REQUIRE(from_1 != nullptr); + REQUIRE(from_1->reserved() == from_0->reserved()); + REQUIRE(from_1->allocated() == 0); + + to_0 = gc.get_space(role::to_space(), Generation{0}); + + REQUIRE(to_0 != nullptr); + REQUIRE(to_0->reserved() == from_0->reserved()); + REQUIRE(to_0->allocated() == 0); + + const DArena * to_1 = gc.get_space(role::to_space(), Generation{1}); + + REQUIRE(to_1 != nullptr); + REQUIRE(to_1->reserved() == to_0->reserved()); + REQUIRE(to_1->allocated() == 0); + + const DArena * from_2 = gc.get_space(role::from_space(), Generation{2}); + + REQUIRE(from_2 == nullptr); + + const DArena * to_2 = gc.get_space(role::to_space(), Generation{2}); + + REQUIRE(to_2 == nullptr); + + REQUIRE(gc.reserved() + == otypes->reserved() + roots->store()->reserved() + 4 * from_0->reserved()); + + log && log(xtag("from_0", from_0->lo_), xtag("to_0", to_0->lo_)); + } + + /* attempt allocation */ + auto gc_o = with_facet::mkobj(&gc); + auto c_o = with_facet::mkobj(&gc); + + /* register object types */ + bool ok = CollectorTypeRegistry::instance().install_types(c_o); + + REQUIRE(ok); + + ok = c_o.is_type_installed(typeseq::id()); + REQUIRE(ok); + ok = c_o.is_type_installed(typeseq::id()); + REQUIRE(ok); + ok = c_o.is_type_installed(typeseq::id()); + REQUIRE(ok); + ok = c_o.is_type_installed(typeseq::id()); + REQUIRE(ok); + + auto x0_o = DFloat::box(gc_o, 3.1415927); + c_o.add_gc_root(&x0_o); + REQUIRE(to_0->allocated() == sizeof(AllocHeader) + sizeof(DFloat)); + + auto n1_o = DInteger::box(gc_o, 42); + c_o.add_gc_root(&n1_o); + REQUIRE(to_0->allocated() == (sizeof(AllocHeader) + sizeof(DFloat) + + sizeof(AllocHeader) + sizeof(DInteger))); + + //DList * l0 = DList::list(gc_o, x0_o); + //auto l0_o = with_facet::mkobj(l0); + auto l0_o = ListOps::list(gc_o, x0_o); + c_o.add_gc_root(&l0_o); + REQUIRE(to_0->allocated() == (sizeof(AllocHeader) + sizeof(DFloat) + + sizeof(AllocHeader) + sizeof(DInteger) + + sizeof(AllocHeader) + sizeof(DList))); + + { + { + REQUIRE(x0_o.iface() != nullptr); + REQUIRE(x0_o.data() != nullptr); + REQUIRE(gc.contains(role::to_space(), x0_o.data())); + + /* check alloc info for newly-allocated object */ + AllocInfo info = gc.alloc_info((std::byte *)x0_o.data()); + + REQUIRE(info.age() == 0); + REQUIRE(info.tseq() == typeseq::id().seqno()); + REQUIRE(info.size() >= sizeof(DFloat)); + REQUIRE(info.size() < sizeof(DFloat) + padding::c_alloc_alignment); + } + + { + REQUIRE(n1_o.iface() != nullptr); + REQUIRE(n1_o.data() != nullptr); + REQUIRE(gc.contains(role::to_space(), n1_o.data())); + + /* check alloc info for newly-allocated object */ + AllocInfo info = gc.alloc_info((std::byte *)n1_o.data()); + + REQUIRE(info.age() == 0); + REQUIRE(info.tseq() == typeseq::id().seqno()); + REQUIRE(info.size() >= sizeof(DInteger)); + REQUIRE(info.size() < sizeof(DInteger) + padding::c_alloc_alignment); + } + + { + REQUIRE(l0_o.iface() != nullptr); + REQUIRE(l0_o.data() != nullptr); + REQUIRE(gc.contains(role::to_space(), l0_o.data())); + + /* check alloc info for newly-allocated object */ + AllocInfo info = gc.alloc_info((std::byte *)l0_o.data()); + + REQUIRE(info.age() == 0); + REQUIRE(info.tseq() == typeseq::id().seqno()); + REQUIRE(info.size() >= sizeof(DList)); + REQUIRE(info.size() < sizeof(DList) + padding::c_alloc_alignment); + } + } + + /* no GC roots, so GC is trivial */ + c_o.request_gc(Generation{1}); + + log && log(xtag("l0_o.data()", l0_o.data())); + log && log(xtag("l0_o.data()->head_.data()", l0_o.data()->head_.data())); + log && log(xtag("x0_o.data()", x0_o.data())); + + REQUIRE(!gc.contains(role::from_space(), x0_o.data())); + REQUIRE(gc.contains(role::to_space(), x0_o.data())); + REQUIRE(x0_o.data()->value() == 3.1415927); + + REQUIRE(!gc.contains(role::from_space(), n1_o.data())); + REQUIRE(gc.contains(role::to_space(), n1_o.data())); + REQUIRE(n1_o.data()->value() == 42); + + REQUIRE(!gc.contains(role::from_space(), l0_o.data())); + REQUIRE(gc.contains(role::to_space(), l0_o.data())); + REQUIRE(l0_o.data()->is_empty() == false); + + REQUIRE((void*)l0_o.data()->head_.data() == (void*)x0_o.data()); + REQUIRE((void*)l0_o.data()->rest_ == (void*)DList::_nil()); + + } catch (std::exception & ex) { + std::cerr << "caught exception: " << ex.what() << std::endl; + REQUIRE(false); + } + } + } +} + +/* end X1Collector.test.cpp */ diff --git a/xo-interpreter2/include/xo/interpreter2/detail/IRuntimeContext_DVsmRcx.hpp b/xo-interpreter2/include/xo/interpreter2/detail/IRuntimeContext_DVsmRcx.hpp index 1eddf28c..d87596e6 100644 --- a/xo-interpreter2/include/xo/interpreter2/detail/IRuntimeContext_DVsmRcx.hpp +++ b/xo-interpreter2/include/xo/interpreter2/detail/IRuntimeContext_DVsmRcx.hpp @@ -52,6 +52,8 @@ namespace xo { static obj allocator(const DVsmRcx & self) noexcept; /** collector facet for allocator. If non-null, same data pointer as allocator **/ static obj collector(const DVsmRcx & self) noexcept; + /** last-resort allocator for erros. e.g. regular allocator exhausted **/ + static obj error_allocator(const DVsmRcx & self) noexcept; /** stringtable for unique symbols **/ static StringTable * stringtable(const DVsmRcx & self) noexcept; /** invoke visitor for each distinct memory pool **/ diff --git a/xo-interpreter2/src/interpreter2/IRuntimeContext_DVsmRcx.cpp b/xo-interpreter2/src/interpreter2/IRuntimeContext_DVsmRcx.cpp index 9b1a73d0..b2259c75 100644 --- a/xo-interpreter2/src/interpreter2/IRuntimeContext_DVsmRcx.cpp +++ b/xo-interpreter2/src/interpreter2/IRuntimeContext_DVsmRcx.cpp @@ -27,6 +27,12 @@ namespace xo { return self.collector(); } + auto + IRuntimeContext_DVsmRcx::error_allocator(const DVsmRcx & self) noexcept -> obj + { + return self.error_allocator(); + } + auto IRuntimeContext_DVsmRcx::stringtable(const DVsmRcx & self) noexcept -> StringTable * { diff --git a/xo-object2/CMakeLists.txt b/xo-object2/CMakeLists.txt index 23cbf6b1..b4ef253f 100644 --- a/xo-object2/CMakeLists.txt +++ b/xo-object2/CMakeLists.txt @@ -31,7 +31,6 @@ xo_add_genfacet( xo_add_genfacetimpl( TARGET xo-object2-facetimpl-sequence-list FACET_PKG xo_object2 -# REPR List INPUT idl/ISequence_DList.json5 ) diff --git a/xo-object2/include/xo/object2/DDictionary.hpp b/xo-object2/include/xo/object2/DDictionary.hpp index f05eb56a..af66f3e6 100644 --- a/xo-object2/include/xo/object2/DDictionary.hpp +++ b/xo-object2/include/xo/object2/DDictionary.hpp @@ -139,6 +139,16 @@ namespace xo { **/ bool try_update(const pair_type & kvpair); + /** convenience method: + * try_upsert pair (k, @p value), after boxing c-style string @p key with @p mm to get k + **/ + bool try_upsert_cstr(obj mm, const char * key, obj value); + + /** convenience method: + * upsert pair (k, @p value), after boxing c-style string @p key with @p mm to get k + **/ + bool upsert_cstr(obj mm, const char * key, obj value); + /** upsert key-value pair @p kvpair into dictionary. * If key kvpair.first not already present, add it. * In either case replace/establish associated value with kvpair.second. @@ -210,7 +220,7 @@ namespace xo { { DDictionary * result = empty(mm, sizeof...(args)); if (result) { - (result->upsert(args), ...); + (result->upsert(mm, args), ...); } return result; } diff --git a/xo-object2/include/xo/object2/DInteger.hpp b/xo-object2/include/xo/object2/DInteger.hpp index 3fd13672..f1fa16aa 100644 --- a/xo-object2/include/xo/object2/DInteger.hpp +++ b/xo-object2/include/xo/object2/DInteger.hpp @@ -16,13 +16,14 @@ namespace xo { struct DInteger { using AAllocator = xo::mm::AAllocator; using ACollector = xo::mm::ACollector; + using AGCObject = xo::mm::AGCObject; using ppindentinfo = xo::print::ppindentinfo; using value_type = long; explicit DInteger(long x) : value_{x} {} /** will likely want this to default to ANumeric, once we have it **/ - template + template static obj box(obj mm, long x); /** allocate boxed value @p x using memory from @p mm **/ diff --git a/xo-object2/src/object2/DDictionary.cpp b/xo-object2/src/object2/DDictionary.cpp index f92cbbb3..faa44589 100644 --- a/xo-object2/src/object2/DDictionary.cpp +++ b/xo-object2/src/object2/DDictionary.cpp @@ -104,6 +104,22 @@ namespace xo { return false; } + bool + DDictionary::try_upsert_cstr(obj mm, const char * key_cstr, obj value) + { + const DString * k1 = DString::from_cstr(mm, key_cstr); + + return this->try_upsert(std::make_pair(k1, value)); + } + + bool + DDictionary::upsert_cstr(obj mm, const char * key_cstr, obj value) + { + const DString * k1 = DString::from_cstr(mm, key_cstr); + + return this->upsert(mm, std::make_pair(k1, value)); + } + bool DDictionary::try_upsert(const pair_type & kv_pair) { @@ -190,7 +206,29 @@ 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 {. using same line for first (key,value) */ + ppii.pps()->indent(std::max(ppii.pps()->indent_width(), 1u) - 1); + } else { + /* indent after newline */ + ppii.pps()->newline_indent(ppii.ci1()); + } + + obj key + = FacetRegistry::instance().variant((*keys_)[i]); + obj value + = FacetRegistry::instance().variant((*values_)[i]); + + pps->pretty(key); + pps->write(": "); + pps->pretty(value); + pps->write(";"); + } + + pps->write(" }"); return false; } } diff --git a/xo-object2/utest/CMakeLists.txt b/xo-object2/utest/CMakeLists.txt index e4532c26..34046715 100644 --- a/xo-object2/utest/CMakeLists.txt +++ b/xo-object2/utest/CMakeLists.txt @@ -6,12 +6,12 @@ set(UTEST_SRCS DArray.test.cpp # DString.test.cpp # StringOps.test.cpp - X1Collector.test.cpp - Printable.test.cpp +# X1Collector.test.cpp # moved to xo-gc/ +# Printable.test.cpp # moved to xo-gc/ ) xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) xo_self_dependency(${UTEST_EXE} xo_object2) -xo_dependency(${UTEST_EXE} xo_gc) +#xo_dependency(${UTEST_EXE} xo_gc) #xo_dependency(${UTEST_EXE} randomgen) xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) diff --git a/xo-object2/utest/Printable.test.cpp b/xo-object2/utest/Printable.test.cpp index d31f8456..289029e8 100644 --- a/xo-object2/utest/Printable.test.cpp +++ b/xo-object2/utest/Printable.test.cpp @@ -18,9 +18,9 @@ #include -#include -#include -#include +//#include +//#include +//#include #include @@ -31,123 +31,5 @@ #include -namespace ut { - using xo::scm::SetupObject2; - using xo::scm::ListOps; - using xo::scm::DList; - using xo::scm::DInteger; - using xo::scm::DString; - using xo::mm::AAllocator; - using xo::mm::ACollector; - using xo::mm::AGCObject; - using xo::mm::DX1Collector; - using xo::mm::X1CollectorConfig; - using xo::mm::ArenaConfig; - using xo::print::APrintable; - using xo::facet::FacetRegistry; - using xo::facet::with_facet; - using xo::facet::obj; - using xo::facet::typeseq; - using xo::print::ppstate_standalone; - using xo::print::ppconfig; - using xo::scope; - using xo::xtag; - using std::string; - - namespace { - struct testcase_pp { - explicit testcase_pp(size_t gc_z, size_t gc_threshold, int z, const std::string & expected) - : gc_gen_size_{gc_z}, gc_trigger_threshold_{gc_threshold}, expected_{expected} { - for (int i = 0; i < z; ++i) { - list_.push_back(1000 + 197 * i); - } - } - - size_t gc_gen_size_ = 0; - size_t gc_trigger_threshold_ = 0; - std::vector list_; - std::string expected_; - }; - - std::vector - s_testcase_v = { - testcase_pp(16384, 8192, 0, "()"), - testcase_pp(16384, 8192, 1, "(01000)"), - testcase_pp(16384, 8192, 2, "(01000 1197)"), - testcase_pp(16384, 8192, 5, "(01000 1197 01394 1591 01788)"), - testcase_pp(16384, 8192, 10, "(01000 1197 01394 1591 01788 1985 02182 2379 02576 2773)"), - testcase_pp(16384, 8192, 20, "(...)"), - }; - } - - TEST_CASE("printable1", "[pp][x1][list]") - { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - bool ok = SetupObject2::register_facets(); - REQUIRE(ok); - - FacetRegistry::instance().dump(&std::cerr); - - for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { - log && log("printable1 test:", xtag("i_tc", i_tc)); - - try { - const testcase_pp & tc = s_testcase_v[i_tc]; - - X1CollectorConfig cfg{ .name_ = "pp_test", - .arena_config_ = ArenaConfig{ - .size_ = tc.gc_gen_size_, - .store_header_flag_ = true}, - .object_types_z_ = 16384, - .gc_trigger_v_{{tc.gc_trigger_threshold_, - tc.gc_trigger_threshold_}}, - .debug_flag_ = c_debug_flag - }; - - DX1Collector gc(cfg); - auto gc_o = with_facet::mkobj(&gc); - auto c_o = with_facet::mkobj(&gc); - - bool ok = SetupObject2::register_types(c_o); - REQUIRE(ok); - - auto l0_o = ListOps::nil(); - - c_o.add_gc_root(&l0_o); - - for(int ip1 = tc.list_.size(); ip1 > 0; --ip1) { - obj elt; - - //elt = DInteger::box(gc_o, tc.list_[ip1 - 1]); - - if (ip1 % 2 == 0) { - elt = DInteger::box(gc_o, tc.list_[ip1 - 1]); - } else { - elt = obj(DString::printf(gc_o, 80, "%05d", tc.list_[ip1 - 1])); - } - - l0_o = ListOps::cons(gc_o, elt, l0_o); - } - - // TODO: log_streambuf using DArena - std::stringstream ss; - ppconfig ppc; - ppstate_standalone pps(&ss, 0, &ppc); - - obj l0_po(static_cast(l0_o.data())); - REQUIRE(l0_po._typeseq() == typeseq::id()); - - pps.pretty(l0_po); - - CHECK(ss.str() == string(tc.expected_)); - } catch (std::exception & ex) { - std::cerr << "caught exception: " << ex.what() << std::endl; - REQUIRE(false); - } - } - } /* TEST_CASE(printable1) */ -} /* end Printable.test.cpp */ diff --git a/xo-object2/utest/X1Collector.test.cpp b/xo-object2/utest/X1Collector.test.cpp index 085764f6..59565c1f 100644 --- a/xo-object2/utest/X1Collector.test.cpp +++ b/xo-object2/utest/X1Collector.test.cpp @@ -16,10 +16,9 @@ #include #include -#include - -#include -#include +//#include +//#include +//#include #include #include @@ -44,9 +43,9 @@ namespace ut { using xo::mm::AllocHeader; using xo::mm::AllocInfo; using xo::mm::AGCObject; - using xo::mm::DX1Collector; +// using xo::mm::DX1Collector; using xo::mm::DArena; - using xo::mm::X1CollectorConfig; +// using xo::mm::X1CollectorConfig; using xo::mm::ArenaConfig; using xo::mm::Generation; using xo::mm::role; @@ -171,7 +170,7 @@ namespace ut { REQUIRE(to_2 == nullptr); - REQUIRE(gc.reserved_total() + REQUIRE(gc.reserved() == otypes->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-procedure2/idl/RuntimeContext.json5 b/xo-procedure2/idl/RuntimeContext.json5 index 481d0cbf..8736f38d 100644 --- a/xo-procedure2/idl/RuntimeContext.json5 +++ b/xo-procedure2/idl/RuntimeContext.json5 @@ -61,6 +61,15 @@ noexcept: true, attributes: [], }, + { + name: "error_allocator", + doc: [ "last-resort allocator for erros. e.g. regular allocator exhausted" ], + return_type: "obj", + args: [], + const: true, + noexcept: true, + attributes: [], + }, { name: "stringtable", doc: [ "stringtable for unique symbols" ], diff --git a/xo-procedure2/include/xo/procedure2/DSimpleRcx.hpp b/xo-procedure2/include/xo/procedure2/DSimpleRcx.hpp index f6bee0b5..1bdf1f73 100644 --- a/xo-procedure2/include/xo/procedure2/DSimpleRcx.hpp +++ b/xo-procedure2/include/xo/procedure2/DSimpleRcx.hpp @@ -23,16 +23,19 @@ namespace xo { using MemorySizeVisitor = xo::mm::MemorySizeVisitor; public: - DSimpleRcx(obj mm, StringTable * st) - : allocator_{mm}, stringtable_{st} {} + DSimpleRcx(obj mm, obj error_mm, StringTable * st) + : allocator_{mm}, error_allocator_{error_mm}, + stringtable_{st} {} obj allocator() const noexcept { return allocator_; } obj collector() const noexcept; + obj error_allocator() const noexcept { return error_allocator_; } StringTable * stringtable() const noexcept { return stringtable_; } void visit_pools(const MemorySizeVisitor & visitor) const; private: obj allocator_; + obj error_allocator_; StringTable * stringtable_ = nullptr; }; diff --git a/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp b/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp index 65ead09b..d0a1525a 100644 --- a/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp +++ b/xo-procedure2/include/xo/procedure2/GcPrimitives.hpp @@ -5,6 +5,7 @@ #pragma once +#include "Primitive_gco_0.hpp" #include "Primitive_gco_1_gco.hpp" namespace xo { @@ -18,6 +19,10 @@ namespace xo { using AAllocator = xo::mm::AAllocator; public: + /** create primitive: report gc statistics **/ + static DPrimitive_gco_0 * make_report_gc_statistics_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/include/xo/procedure2/detail/AProcedure.hpp b/xo-procedure2/include/xo/procedure2/detail/AProcedure.hpp index a5c41b41..2197c5df 100644 --- a/xo-procedure2/include/xo/procedure2/detail/AProcedure.hpp +++ b/xo-procedure2/include/xo/procedure2/detail/AProcedure.hpp @@ -47,6 +47,11 @@ public: /** @defgroup scm-procedure-methods **/ ///@{ // const methods + /** An uninitialized AProcedure instance will have zero vtable pointer (per {linux,osx} abi). + * Use case for this is narrow. We go to some lengths to avoid null vtable pointers. For example + * obj will have non-null vtable (via IFacet_Any) with all methods terminating. + **/ + bool _has_null_vptr() const noexcept { return *reinterpret_cast(this) == nullptr; } /** RTTI: unique id# for actual runtime data representation **/ virtual typeseq _typeseq() const noexcept = 0; /** destroy instance @p d; calls c++ dtor only for actual runtime type; does not recover memory **/ diff --git a/xo-procedure2/include/xo/procedure2/detail/ARuntimeContext.hpp b/xo-procedure2/include/xo/procedure2/detail/ARuntimeContext.hpp index 626e2ba6..c9da6d6e 100644 --- a/xo-procedure2/include/xo/procedure2/detail/ARuntimeContext.hpp +++ b/xo-procedure2/include/xo/procedure2/detail/ARuntimeContext.hpp @@ -51,6 +51,11 @@ public: /** @defgroup scm-runtimecontext-methods **/ ///@{ // const methods + /** An uninitialized ARuntimeContext instance will have zero vtable pointer (per {linux,osx} abi). + * Use case for this is narrow. We go to some lengths to avoid null vtable pointers. For example + * obj will have non-null vtable (via IFacet_Any) with all methods terminating. + **/ + bool _has_null_vptr() const noexcept { return *reinterpret_cast(this) == nullptr; } /** RTTI: unique id# for actual runtime data representation **/ virtual typeseq _typeseq() const noexcept = 0; /** destroy instance @p d; calls c++ dtor only for actual runtime type; does not recover memory **/ @@ -59,6 +64,8 @@ public: virtual obj allocator(Copaque data) const noexcept = 0; /** collector facet for allocator. If non-null, same data pointer as allocator **/ virtual obj collector(Copaque data) const noexcept = 0; + /** last-resort allocator for erros. e.g. regular allocator exhausted **/ + virtual obj error_allocator(Copaque data) const noexcept = 0; /** stringtable for unique symbols **/ virtual StringTable * stringtable(Copaque data) const noexcept = 0; /** invoke visitor for each distinct memory pool **/ diff --git a/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_Any.hpp b/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_Any.hpp index d0789e52..89a0d7a9 100644 --- a/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_Any.hpp +++ b/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_Any.hpp @@ -63,6 +63,7 @@ namespace scm { // const methods [[noreturn]] obj allocator(Copaque) const noexcept override { _fatal(); } [[noreturn]] obj collector(Copaque) const noexcept override { _fatal(); } + [[noreturn]] obj error_allocator(Copaque) const noexcept override { _fatal(); } [[noreturn]] StringTable * stringtable(Copaque) const noexcept override { _fatal(); } [[noreturn]] void visit_pools(Copaque, MemorySizeVisitor) const override { _fatal(); } diff --git a/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_DSimpleRcx.hpp b/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_DSimpleRcx.hpp index 496b593c..cf2a052f 100644 --- a/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_DSimpleRcx.hpp +++ b/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_DSimpleRcx.hpp @@ -52,6 +52,8 @@ namespace xo { static obj allocator(const DSimpleRcx & self) noexcept; /** collector facet for allocator. If non-null, same data pointer as allocator **/ static obj collector(const DSimpleRcx & self) noexcept; + /** last-resort allocator for erros. e.g. regular allocator exhausted **/ + static obj error_allocator(const DSimpleRcx & self) noexcept; /** stringtable for unique symbols **/ static StringTable * stringtable(const DSimpleRcx & self) noexcept; /** invoke visitor for each distinct memory pool **/ diff --git a/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_Xfer.hpp b/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_Xfer.hpp index 2c4e80e6..d1a8e8c7 100644 --- a/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_Xfer.hpp +++ b/xo-procedure2/include/xo/procedure2/detail/IRuntimeContext_Xfer.hpp @@ -54,6 +54,9 @@ namespace scm { obj collector(Copaque data) const noexcept override { return I::collector(_dcast(data)); } + obj error_allocator(Copaque data) const noexcept override { + return I::error_allocator(_dcast(data)); + } StringTable * stringtable(Copaque data) const noexcept override { return I::stringtable(_dcast(data)); } diff --git a/xo-procedure2/include/xo/procedure2/detail/RRuntimeContext.hpp b/xo-procedure2/include/xo/procedure2/detail/RRuntimeContext.hpp index b80cfce0..9c047fd2 100644 --- a/xo-procedure2/include/xo/procedure2/detail/RRuntimeContext.hpp +++ b/xo-procedure2/include/xo/procedure2/detail/RRuntimeContext.hpp @@ -61,6 +61,9 @@ public: obj collector() const noexcept { return O::iface()->collector(O::data()); } + obj error_allocator() const noexcept { + return O::iface()->error_allocator(O::data()); + } StringTable * stringtable() const noexcept { return O::iface()->stringtable(O::data()); } diff --git a/xo-procedure2/src/procedure2/GcPrimitives.cpp b/xo-procedure2/src/procedure2/GcPrimitives.cpp index 9948ff65..9573f665 100644 --- a/xo-procedure2/src/procedure2/GcPrimitives.cpp +++ b/xo-procedure2/src/procedure2/GcPrimitives.cpp @@ -21,23 +21,35 @@ namespace xo { // ----- report-gc-status ----- -#ifdef NOT_YET obj - xfer_report_gc_status(obj rcx) + xfer_report_gc_statistics(obj rcx) { - bool have_gc = false; - if (rcx.collector()) { // status currently only implemented for X1 collector - auto gc = obj::from(rcx.collector()); - + obj stats; + bool ok = rcx.collector().report_statistics(rcx.allocator(), + rcx.error_allocator(), + &stats); + if (ok && stats) + return stats; } return DBoolean::box(rcx.allocator(), false); } -#endif + + DPrimitive_gco_0 * + GcPrimitives::make_report_gc_statistics_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-statistics", pm_ty, &xfer_report_gc_statistics); + } // ----- request-gc ----- diff --git a/xo-procedure2/src/procedure2/SetupProcedure2.cpp b/xo-procedure2/src/procedure2/SetupProcedure2.cpp index 00f24608..6772fff3 100644 --- a/xo-procedure2/src/procedure2/SetupProcedure2.cpp +++ b/xo-procedure2/src/procedure2/SetupProcedure2.cpp @@ -130,6 +130,13 @@ namespace xo { ObjectPrimitives::make_fn_n_args_pm(mm, stbl), flags & InstallFlags::f_generalpurpose)); + // ----- gc primitives ----- + + ok = ok & (PrimitiveRegistry::install_aux + (sink, + GcPrimitives::make_report_gc_statistics_pm(mm, stbl), + flags & InstallFlags::f_generalpurpose)); + ok = ok & (PrimitiveRegistry::install_aux (sink, GcPrimitives::make_request_gc_pm(mm, stbl), diff --git a/xo-procedure2/src/procedure2/facet/IRuntimeContext_DSimpleRcx.cpp b/xo-procedure2/src/procedure2/facet/IRuntimeContext_DSimpleRcx.cpp index ba560969..d32f5740 100644 --- a/xo-procedure2/src/procedure2/facet/IRuntimeContext_DSimpleRcx.cpp +++ b/xo-procedure2/src/procedure2/facet/IRuntimeContext_DSimpleRcx.cpp @@ -27,6 +27,12 @@ namespace xo { return self.collector(); } + auto + IRuntimeContext_DSimpleRcx::error_allocator(const DSimpleRcx & self) noexcept -> obj + { + return self.error_allocator(); + } + auto IRuntimeContext_DSimpleRcx::stringtable(const DSimpleRcx & self) noexcept -> StringTable * { diff --git a/xo-procedure2/utest/DSimpleRcx.test.cpp b/xo-procedure2/utest/DSimpleRcx.test.cpp index 365bd3f6..0fe56b86 100644 --- a/xo-procedure2/utest/DSimpleRcx.test.cpp +++ b/xo-procedure2/utest/DSimpleRcx.test.cpp @@ -39,7 +39,7 @@ namespace xo { auto stbl = StringTable(1024 /*hint_max_capacity*/, false /*!debug_flag*/); - DSimpleRcx rcx(alloc, &stbl); + DSimpleRcx rcx(alloc, alloc, &stbl); REQUIRE((void*)rcx.allocator().data() == (void*)alloc.data()); REQUIRE(rcx.stringtable() == &stbl); @@ -54,7 +54,7 @@ namespace xo { auto stbl = StringTable(1024 /*hint_max_capacity*/, false /*!debug_flag*/); - DSimpleRcx rcx(alloc, &stbl); + DSimpleRcx rcx(alloc, alloc, &stbl); obj rcx_obj = with_facet::mkobj(&rcx); // verify we can recover allocator from obj diff --git a/xo-reader2/src/reader2/ParserStateMachine.cpp b/xo-reader2/src/reader2/ParserStateMachine.cpp index b7f01f0d..984841b5 100644 --- a/xo-reader2/src/reader2/ParserStateMachine.cpp +++ b/xo-reader2/src/reader2/ParserStateMachine.cpp @@ -49,7 +49,10 @@ namespace xo { DGlobalEnv * env = DGlobalEnv::_make(mm, global_symtab); - DSimpleRcx rcx(mm, &stringtable); + // FUDGING this for now + obj err_mm; + + DSimpleRcx rcx(mm, err_mm, &stringtable); InstallSink sink = ([env, rcx, &log] (std::string_view name,