From 6a82040d4897ac3a5ee7969779f5a2d01b9ab5c3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 6 Jun 2026 22:01:14 -0400 Subject: [PATCH] git subrepo clone git@github.com:Rconybea/xo-alloc2.git xo-alloc2 subrepo: subdir: "xo-alloc2" merged: "4039c29f" upstream: origin: "git@github.com:Rconybea/xo-alloc2.git" branch: "main" commit: "4039c29f" git-subrepo: version: "0.4.9" origin: "???" commit: "???" --- xo-alloc2/.gitrepo | 12 + xo-alloc2/CMakeLists.txt | 75 ++ xo-alloc2/README.md | 191 +++++ xo-alloc2/cmake/xo-bootstrap-macros.cmake | 35 + xo-alloc2/cmake/xo_alloc2Config.cmake.in | 10 + xo-alloc2/docs/AAllocIterator-reference.rst | 67 ++ xo-alloc2/docs/AAllocator-reference.rst | 58 ++ xo-alloc2/docs/AllocInfo-reference.rst | 61 ++ xo-alloc2/docs/ArenaConfig-reference.rst | 60 ++ xo-alloc2/docs/CMakeLists.txt | 23 + xo-alloc2/docs/DArena-reference.rst | 102 +++ xo-alloc2/docs/DArenaIterator-reference.rst | 55 ++ xo-alloc2/docs/IAllocator_Xfer-reference.rst | 76 ++ xo-alloc2/docs/README | 70 ++ xo-alloc2/docs/_static/README | 1 + xo-alloc2/docs/_static/img/favicon.ico | Bin 0 -> 309936 bytes xo-alloc2/docs/cmpresult-reference.rst | 55 ++ xo-alloc2/docs/conf.py | 39 + xo-alloc2/docs/examples.rst | 125 +++ xo-alloc2/docs/glossary.rst | 24 + xo-alloc2/docs/implementation.rst | 161 ++++ xo-alloc2/docs/index.rst | 36 + xo-alloc2/idl/Collector.json5 | 332 ++++++++ xo-alloc2/idl/GCObject.json5 | 104 +++ xo-alloc2/idl/GCObjectVisitor.json5 | 144 ++++ xo-alloc2/idl/ResourceVisitor.json5 | 52 ++ xo-alloc2/include/xo/alloc2/AllocIterator.hpp | 13 + xo-alloc2/include/xo/alloc2/AllocRange.hpp | 34 + xo-alloc2/include/xo/alloc2/Allocator.hpp | 11 + .../include/xo/alloc2/Allocator_basic.hpp | 13 + xo-alloc2/include/xo/alloc2/Arena.hpp | 17 + xo-alloc2/include/xo/alloc2/ArenaIterator.hpp | 15 + xo-alloc2/include/xo/alloc2/Collector.hpp | 22 + xo-alloc2/include/xo/alloc2/Collector2.hpp | 22 + .../xo/alloc2/CollectorTypeRegistry.hpp | 76 ++ xo-alloc2/include/xo/alloc2/GCObject.hpp | 21 + .../include/xo/alloc2/GCObjectConversion.hpp | 134 ++++ .../include/xo/alloc2/GCObjectVisitor.hpp | 23 + xo-alloc2/include/xo/alloc2/Generation.hpp | 55 ++ .../include/xo/alloc2/ResourceVisitor.hpp | 22 + xo-alloc2/include/xo/alloc2/SetupAlloc2.hpp | 18 + xo-alloc2/include/xo/alloc2/VisitReason.hpp | 54 ++ xo-alloc2/include/xo/alloc2/abox.hpp | 131 ++++ .../xo/alloc2/alloc/AAllocIterator.hpp | 45 ++ .../include/xo/alloc2/alloc/AAllocator.hpp | 221 ++++++ .../xo/alloc2/alloc/IAllocIterator_Any.hpp | 48 ++ .../xo/alloc2/alloc/IAllocIterator_Xfer.hpp | 62 ++ .../xo/alloc2/alloc/IAllocator_Any.hpp | 81 ++ .../xo/alloc2/alloc/IAllocator_Xfer.hpp | 107 +++ .../xo/alloc2/alloc/RAllocIterator.hpp | 67 ++ .../include/xo/alloc2/alloc/RAllocator.hpp | 112 +++ .../xo/alloc2/alloc/RAllocator_aux.hpp | 71 ++ .../arena/IAllocIterator_DArenaIterator.hpp | 36 + .../xo/alloc2/arena/IAllocator_DArena.hpp | 92 +++ xo-alloc2/include/xo/alloc2/dp.hpp | 128 ++++ xo-alloc2/include/xo/alloc2/gc/ACollector.hpp | 146 ++++ xo-alloc2/include/xo/alloc2/gc/AGCObject.hpp | 95 +++ .../include/xo/alloc2/gc/AGCObjectVisitor.hpp | 97 +++ .../include/xo/alloc2/gc/ICollector2_Any.hpp | 99 +++ .../include/xo/alloc2/gc/ICollector2_Xfer.hpp | 116 +++ .../include/xo/alloc2/gc/ICollector_Any.hpp | 103 +++ .../include/xo/alloc2/gc/ICollector_Xfer.hpp | 136 ++++ .../xo/alloc2/gc/IGCObjectVisitor_Any.hpp | 91 +++ .../xo/alloc2/gc/IGCObjectVisitor_Xfer.hpp | 103 +++ .../include/xo/alloc2/gc/IGCObject_Any.hpp | 93 +++ .../include/xo/alloc2/gc/IGCObject_Xfer.hpp | 101 +++ xo-alloc2/include/xo/alloc2/gc/RCollector.hpp | 170 ++++ .../include/xo/alloc2/gc/RCollector2.hpp | 115 +++ .../include/xo/alloc2/gc/RCollector_aux.hpp | 15 + xo-alloc2/include/xo/alloc2/gc/RGCObject.hpp | 91 +++ .../include/xo/alloc2/gc/RGCObjectVisitor.hpp | 129 ++++ .../xo/alloc2/gc/RGCObjectVisitor_aux.hpp | 61 ++ xo-alloc2/include/xo/alloc2/init_alloc2.hpp | 23 + xo-alloc2/include/xo/alloc2/role.hpp | 40 + .../xo/alloc2/visitor/AResourceVisitor.hpp | 81 ++ .../alloc2/visitor/IResourceVisitor_Any.hpp | 89 +++ .../alloc2/visitor/IResourceVisitor_Xfer.hpp | 92 +++ .../xo/alloc2/visitor/RResourceVisitor.hpp | 85 ++ xo-alloc2/src/alloc2/AAllocator.cpp | 10 + xo-alloc2/src/alloc2/CMakeLists.txt | 32 + .../src/alloc2/CollectorTypeRegistry.cpp | 50 ++ xo-alloc2/src/alloc2/GCObjectConversion.cpp | 28 + xo-alloc2/src/alloc2/IAllocIterator_Any.cpp | 41 + .../alloc2/IAllocIterator_DArenaIterator.cpp | 52 ++ xo-alloc2/src/alloc2/IAllocator_Any.cpp | 44 ++ xo-alloc2/src/alloc2/IAllocator_DArena.cpp | 186 +++++ xo-alloc2/src/alloc2/IGCObject_Any.cpp | 54 ++ xo-alloc2/src/alloc2/IResourceVisitor_Any.cpp | 42 + xo-alloc2/src/alloc2/SetupAlloc2.cpp | 39 + xo-alloc2/src/alloc2/facet/ICollector_Any.cpp | 78 ++ .../src/alloc2/facet/IGCObjectVisitor_Any.cpp | 54 ++ xo-alloc2/src/alloc2/init_alloc2.cpp | 36 + xo-alloc2/utest/CMakeLists.txt | 30 + xo-alloc2/utest/DArenaIterator.test.cpp | 305 ++++++++ xo-alloc2/utest/Generation.test.cpp | 45 ++ xo-alloc2/utest/IAllocator_Any.test.cpp | 32 + xo-alloc2/utest/ResourceVisitor.test.cpp | 28 + xo-alloc2/utest/Role.test.cpp | 49 ++ xo-alloc2/utest/VisitReason.test.cpp | 29 + xo-alloc2/utest/alloc2_utest_main.cpp | 18 + xo-alloc2/utest/arena.test.cpp | 422 ++++++++++ xo-alloc2/utest/dp.test.cpp | 107 +++ xo-alloc2/utest/objectmodel.test.cpp | 724 ++++++++++++++++++ xo-alloc2/utest/random_allocs.cpp | 222 ++++++ xo-alloc2/utest/random_allocs.hpp | 46 ++ 105 files changed, 8693 insertions(+) create mode 100644 xo-alloc2/.gitrepo create mode 100644 xo-alloc2/CMakeLists.txt create mode 100644 xo-alloc2/README.md create mode 100644 xo-alloc2/cmake/xo-bootstrap-macros.cmake create mode 100644 xo-alloc2/cmake/xo_alloc2Config.cmake.in create mode 100644 xo-alloc2/docs/AAllocIterator-reference.rst create mode 100644 xo-alloc2/docs/AAllocator-reference.rst create mode 100644 xo-alloc2/docs/AllocInfo-reference.rst create mode 100644 xo-alloc2/docs/ArenaConfig-reference.rst create mode 100644 xo-alloc2/docs/CMakeLists.txt create mode 100644 xo-alloc2/docs/DArena-reference.rst create mode 100644 xo-alloc2/docs/DArenaIterator-reference.rst create mode 100644 xo-alloc2/docs/IAllocator_Xfer-reference.rst create mode 100644 xo-alloc2/docs/README create mode 100644 xo-alloc2/docs/_static/README create mode 100644 xo-alloc2/docs/_static/img/favicon.ico create mode 100644 xo-alloc2/docs/cmpresult-reference.rst create mode 100644 xo-alloc2/docs/conf.py create mode 100644 xo-alloc2/docs/examples.rst create mode 100644 xo-alloc2/docs/glossary.rst create mode 100644 xo-alloc2/docs/implementation.rst create mode 100644 xo-alloc2/docs/index.rst create mode 100644 xo-alloc2/idl/Collector.json5 create mode 100644 xo-alloc2/idl/GCObject.json5 create mode 100644 xo-alloc2/idl/GCObjectVisitor.json5 create mode 100644 xo-alloc2/idl/ResourceVisitor.json5 create mode 100644 xo-alloc2/include/xo/alloc2/AllocIterator.hpp create mode 100644 xo-alloc2/include/xo/alloc2/AllocRange.hpp create mode 100644 xo-alloc2/include/xo/alloc2/Allocator.hpp create mode 100644 xo-alloc2/include/xo/alloc2/Allocator_basic.hpp create mode 100644 xo-alloc2/include/xo/alloc2/Arena.hpp create mode 100644 xo-alloc2/include/xo/alloc2/ArenaIterator.hpp create mode 100644 xo-alloc2/include/xo/alloc2/Collector.hpp create mode 100644 xo-alloc2/include/xo/alloc2/Collector2.hpp create mode 100644 xo-alloc2/include/xo/alloc2/CollectorTypeRegistry.hpp create mode 100644 xo-alloc2/include/xo/alloc2/GCObject.hpp create mode 100644 xo-alloc2/include/xo/alloc2/GCObjectConversion.hpp create mode 100644 xo-alloc2/include/xo/alloc2/GCObjectVisitor.hpp create mode 100644 xo-alloc2/include/xo/alloc2/Generation.hpp create mode 100644 xo-alloc2/include/xo/alloc2/ResourceVisitor.hpp create mode 100644 xo-alloc2/include/xo/alloc2/SetupAlloc2.hpp create mode 100644 xo-alloc2/include/xo/alloc2/VisitReason.hpp create mode 100644 xo-alloc2/include/xo/alloc2/abox.hpp create mode 100644 xo-alloc2/include/xo/alloc2/alloc/AAllocIterator.hpp create mode 100644 xo-alloc2/include/xo/alloc2/alloc/AAllocator.hpp create mode 100644 xo-alloc2/include/xo/alloc2/alloc/IAllocIterator_Any.hpp create mode 100644 xo-alloc2/include/xo/alloc2/alloc/IAllocIterator_Xfer.hpp create mode 100644 xo-alloc2/include/xo/alloc2/alloc/IAllocator_Any.hpp create mode 100644 xo-alloc2/include/xo/alloc2/alloc/IAllocator_Xfer.hpp create mode 100644 xo-alloc2/include/xo/alloc2/alloc/RAllocIterator.hpp create mode 100644 xo-alloc2/include/xo/alloc2/alloc/RAllocator.hpp create mode 100644 xo-alloc2/include/xo/alloc2/alloc/RAllocator_aux.hpp create mode 100644 xo-alloc2/include/xo/alloc2/arena/IAllocIterator_DArenaIterator.hpp create mode 100644 xo-alloc2/include/xo/alloc2/arena/IAllocator_DArena.hpp create mode 100644 xo-alloc2/include/xo/alloc2/dp.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/ACollector.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/AGCObject.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/AGCObjectVisitor.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/ICollector2_Any.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/ICollector2_Xfer.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/IGCObjectVisitor_Any.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/IGCObjectVisitor_Xfer.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/IGCObject_Any.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/IGCObject_Xfer.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/RCollector.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/RCollector2.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/RCollector_aux.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/RGCObject.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/RGCObjectVisitor.hpp create mode 100644 xo-alloc2/include/xo/alloc2/gc/RGCObjectVisitor_aux.hpp create mode 100644 xo-alloc2/include/xo/alloc2/init_alloc2.hpp create mode 100644 xo-alloc2/include/xo/alloc2/role.hpp create mode 100644 xo-alloc2/include/xo/alloc2/visitor/AResourceVisitor.hpp create mode 100644 xo-alloc2/include/xo/alloc2/visitor/IResourceVisitor_Any.hpp create mode 100644 xo-alloc2/include/xo/alloc2/visitor/IResourceVisitor_Xfer.hpp create mode 100644 xo-alloc2/include/xo/alloc2/visitor/RResourceVisitor.hpp create mode 100644 xo-alloc2/src/alloc2/AAllocator.cpp create mode 100644 xo-alloc2/src/alloc2/CMakeLists.txt create mode 100644 xo-alloc2/src/alloc2/CollectorTypeRegistry.cpp create mode 100644 xo-alloc2/src/alloc2/GCObjectConversion.cpp create mode 100644 xo-alloc2/src/alloc2/IAllocIterator_Any.cpp create mode 100644 xo-alloc2/src/alloc2/IAllocIterator_DArenaIterator.cpp create mode 100644 xo-alloc2/src/alloc2/IAllocator_Any.cpp create mode 100644 xo-alloc2/src/alloc2/IAllocator_DArena.cpp create mode 100644 xo-alloc2/src/alloc2/IGCObject_Any.cpp create mode 100644 xo-alloc2/src/alloc2/IResourceVisitor_Any.cpp create mode 100644 xo-alloc2/src/alloc2/SetupAlloc2.cpp create mode 100644 xo-alloc2/src/alloc2/facet/ICollector_Any.cpp create mode 100644 xo-alloc2/src/alloc2/facet/IGCObjectVisitor_Any.cpp create mode 100644 xo-alloc2/src/alloc2/init_alloc2.cpp create mode 100644 xo-alloc2/utest/CMakeLists.txt create mode 100644 xo-alloc2/utest/DArenaIterator.test.cpp create mode 100644 xo-alloc2/utest/Generation.test.cpp create mode 100644 xo-alloc2/utest/IAllocator_Any.test.cpp create mode 100644 xo-alloc2/utest/ResourceVisitor.test.cpp create mode 100644 xo-alloc2/utest/Role.test.cpp create mode 100644 xo-alloc2/utest/VisitReason.test.cpp create mode 100644 xo-alloc2/utest/alloc2_utest_main.cpp create mode 100644 xo-alloc2/utest/arena.test.cpp create mode 100644 xo-alloc2/utest/dp.test.cpp create mode 100644 xo-alloc2/utest/objectmodel.test.cpp create mode 100644 xo-alloc2/utest/random_allocs.cpp create mode 100644 xo-alloc2/utest/random_allocs.hpp diff --git a/xo-alloc2/.gitrepo b/xo-alloc2/.gitrepo new file mode 100644 index 00000000..4c6b9f7e --- /dev/null +++ b/xo-alloc2/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme +; +[subrepo] + remote = git@github.com:Rconybea/xo-alloc2.git + branch = main + commit = 4039c29f5d641f1356e640cc9bb3cde7c7e40f2c + parent = cf0bd4d975c55177ffce33f2a32bd4257ddb4d20 + method = merge + cmdver = 0.4.9 diff --git a/xo-alloc2/CMakeLists.txt b/xo-alloc2/CMakeLists.txt new file mode 100644 index 00000000..9c102706 --- /dev/null +++ b/xo-alloc2/CMakeLists.txt @@ -0,0 +1,75 @@ +# xo-alloc2/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_alloc2 VERSION 0.1) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") # gcc-only! +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +xo_add_genfacet( + TARGET xo-alloc2-facet-collector + FACET Collector + INPUT idl/Collector.json5 +) + +# ---------------------------------------------------------------- + +# note: manual target; generated code committed to git +xo_add_genfacet( + TARGET xo-alloc2-facet-gcobject + FACET GCObject + INPUT idl/GCObject.json5 +) + +# ---------------------------------------------------------------- + +# note: manual target; generated code committed to git +xo_add_genfacet( + TARGET xo-alloc2-facet-gcobjectvisitor + FACET GCObjectVisitor + INPUT idl/GCObjectVisitor.json5 +) + +# ---------------------------------------------------------------- + +# note: manual target; generated code committed to git +xo_add_genfacet( + TARGET xo-alloc2-facet-resourcevisitor + FACET ResourceVisitor + INPUT idl/ResourceVisitor.json5 + OUTPUT_HPP_DIR include/xo/alloc2 + OUTPUT_IMPL_SUBDIR visitor +) + +# ---------------------------------------------------------------- + +xo_add_genfacet_all(xo-alloc2-genfacet-all) + +# ---------------------------------------------------------------- + + +# must complete definition of expression lib before configuring examples +add_subdirectory(src/alloc2) +add_subdirectory(utest) + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# docs targets depend on other library/utest/exec targets above, +# --> must come after them. +# +add_subdirectory(docs) + +# end CMakeLists.txt diff --git a/xo-alloc2/README.md b/xo-alloc2/README.md new file mode 100644 index 00000000..465172d6 --- /dev/null +++ b/xo-alloc2/README.md @@ -0,0 +1,191 @@ +# xo-alloc2 -- arena allocator and incremental garbage collector + +# Relative to xo-alloc: + +1. keep interface and data separate. + 1a. *Representation* or *Data* classes. Entirely passive; strictly no methods. + motivation: data doesn't carry any linker-dependency baggage; + it's just layout. + + example: +``` + struct DPolar { double arg; double mag; }; + struct DRRect { double x; double y; }; +``` + + 1b. `Interface` classes. These have abstract methods only. + motivation: for runtime polymorphism, specify interface + without assuming anything about data layout. + Methods in an interface will take opaque data pointer + as first argument. + + example: +``` + struct AComplex { + using repr_type = void; + + virtual double xcoord(void * repr) const = 0; + virtual double ycoord(void * repr) const = 0; + virtual double magnitude(void * repr) const = 0; + virtual double argument(void * repr) const = 0; + }; +``` + + 1c. `Implementation` classes. Implement a specific interface (as in 1b) + for a specific data representation (as in 1a). + All methods will be `final override`. + Methods in implementation, since they inherit an interface, + wil have opaque data pointer as their first argument. + They will downcast this pointer to specific target representation. + + example: +``` + struct IComplex_Rect : public AComplex { + using repr_type = RRect; + + double _xcoord(RRect * repr) const { return repr->x; } + double _ycoord(RRect * repr) const { return repr->y; } + double _magnitude(RRect * repr) const { + double x = repr->x; + double y = repr->y; + return ::sqrt(x*x + y*y); + } + double _argument(RRect * repr) const { + double tan = repr->x / repr->y; + return ::arctan(tan); + } + + // implement IComplex for RRect + double xcoord(void * repr) const final override { + return _xcoord((RRect*)repr); + } + double ycoord(void * repr) const final override; + double magnitude(void * repr) const final override; + double argument(void * repr) const final override; + }; + + struct IComplex_Polar : public AComplex { + using repr_type = RPolar; + + // implement IComplex for RPolar + ..similar.. + }; +``` + + Here `IComplex_Rect` and `IComplex_Polar` are constructible. + They're concrete in the sense that they expect a specific representation + (`IComplex_Rect::repr_type`, `IComplex_Polar::repr_type` respectively). + + 1d. `Object` classes. Pair implementation and interface. + May use smart pointer here to express strategy for managing + memory used for representation. Don't expect to need this for + interfaces, since interface content entirely known at compile time. + + example: +``` + // borrowed + struct OComplex_Rect : public IComplex_Rect { + DRect * data() const { return data_; } + + bp data_; // naked pointer + }; + + struct OComplex_Polar : public IComplex_Polar { + DPolar * data() const { return data_; } + + bp data_; + }; + + // unique + struct OComplex_Rect : public IComplex_Rect { + DRect * data() const { return data_; } + + up data_; // unique_ptr + }; + + .. +``` + Can do this generically. + +``` + // in bx: 'b' short for 'borrowed' as in unowned. + // 'x' just to distinguish from 'pointer'. + // + template + struct bx : public Iface { + explicit bx(Repr * data) : data_{data} {} + Repr * data() const { return data_; } + + bp data_; + }; + + DRect z1_data{1.0, -1.0}; + bx z1{&z1_data}; + + DPolar z2_data{sqrt(2.0), pi * 8/7}; + bx z2{&z2_data}; +``` + Then to invoke a method (compile-time polymorphism) +``` + z1._xcoord(z1.data()); +``` + + 1e. Runtime polymorphism + Observe that bxp and bxp have the same + top-level representation. + - Both have iface member that inherits IComplex, + - both have data pointer compatible with their respective iface member + Can have common representation for runtime polymorphism + - `bxp` and `bxp` have the same size + and compatible representation. + - both inherit `IComplex` + - safe to reinterpret cast +``` + // type-erased (placeholder, never used) + struct IComplex_Any : public AComplex { + using repr_type = void; + + double xcoord(void * repr) const final override { assert(false); return 0.0; } + }; + + bx z1 = ...; + bx z1_any = reinterpret_cast(z1); +``` + Capturing the pattern: +``` + // in abstract interface + struct AComplex { + using ErasedIfaceType = IComplex_Any; + .. + } + + template + struct bx : public Iface { + .. + operator bx() { + // in particular, overwrites vtable pointer + return reinterpret_cast>(*this); + } + .. + }; + ``` + + 2. Remarks + - shared pattern with pimpl idiom, + except impl isn't private + - can use the same Data type with an unrelated interface. + Although lose the automatic assocation + - can put forwarding methods into object structs, + though will be boilerplatey. + + struct bxp_ext : public bxp { + double xcoord() { return iface->xcoord(data); } + double ycoord() { return iface->ycoord(date); } + double magnitude() { return iface->magnitude(data); } + double argument() { return iface->argument(data); } + }; + - since interface and data are segregated, + it's easier to devirtualize. Interface pointers are explicit, + and don't need to be changed to refer to different data. diff --git a/xo-alloc2/cmake/xo-bootstrap-macros.cmake b/xo-alloc2/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/xo-alloc2/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/xo-alloc2/cmake/xo_alloc2Config.cmake.in b/xo-alloc2/cmake/xo_alloc2Config.cmake.in new file mode 100644 index 00000000..417bfe11 --- /dev/null +++ b/xo-alloc2/cmake/xo_alloc2Config.cmake.in @@ -0,0 +1,10 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(xo_arena) +find_dependency(xo_facet) +find_dependency(subsys) +find_dependency(indentlog) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Share.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/xo-alloc2/docs/AAllocIterator-reference.rst b/xo-alloc2/docs/AAllocIterator-reference.rst new file mode 100644 index 00000000..a576ef2b --- /dev/null +++ b/xo-alloc2/docs/AAllocIterator-reference.rst @@ -0,0 +1,67 @@ +.. _AAllocIterator-reference: + +AAllocIterator Reference +======================== + +Abstract interface facet for an alloc iterator. + +Base class for runtime polymorphism over alloc-iterator implementations, +using faceted object model. + +* runtime size consists of vtable pointer only. + +* per FOMO principles, runtime state is stored separately. + Classes that inherit AAllocIterator will not add state. + +Context +------- + +.. ditaa:: + :--scale: 0.99 + + +----------------------+-------------------------+-----------------------------------+ + | RAllocator | RAllocIterator | IAllocator_DArena | + | | | IAllocIterator_DArenaIterator | + +----------------------+-------------------------+-----------------------------------+ + | IAllocator_Xfer | IAllocIterator_Xfer | DArena | + | IAllocator_Any | IAllocIterator_Any | DArenaIterator | + | IAllocator_Impltype | IAllocIterator_Impltype | | + | | | | + +----------------------+-------------------------+-----------------------------------+ + | AAllocator | AAllocIterator cBLU| ArenaConfig | + +----------------------+-------------------------+-----------------------------------+ + +-----------------+----------------------------------------------+-------------------+ + | | AllocInfo | | + | +----------------------------------------------+ | + | AllocError | AllocHeaderConfig | cmpresult | + | +----------------------------------------------+ | + | | AllocHeader | | + +-----------------+----------------------------------------------+-------------------+ + +Application code will likely use: + +.. code-block:: cpp + + #include + +to get definitions for cooperating AllocIterator classes +:cpp:class:`xo::mm::AAllocIterator`, +:cpp:class:`xo::mm::IAllocIterator_Any`, +:cpp:class:`xo::mm::IAllocIterator_Xfer`, +:cpp:class:`xo::mm::RAllocator` + +Instead, to get just :cpp:class:`xo::mm::AAllocIterator` definition: + +.. code-block:: cpp + + #include + +Class +----- + +.. doxygenclass:: xo::mm::AAllocIterator + +Methods +------- + +.. doxygengroup:: mm-allociterator-methods diff --git a/xo-alloc2/docs/AAllocator-reference.rst b/xo-alloc2/docs/AAllocator-reference.rst new file mode 100644 index 00000000..1191d6bf --- /dev/null +++ b/xo-alloc2/docs/AAllocator-reference.rst @@ -0,0 +1,58 @@ +.. _AAllocator-reference: + +AAllocator Reference +==================== + +Abstract interface facet for an allocator. + +Base class for runtime polymorphism over allocator implementations, +using faceted object model. + +* runtime size consists of vtable pointer only. + +* per FOMO prinicples, runtime state is stored separately. + Classes that inherit ``AAllocator`` will not add state + +Context +------- + +.. ditaa:: + :--scale: 0.99 + + +----------------------+-------------------------+-----------------------------------+ + | RAllocator | RAllocIterator | IAllocator_DArena | + | | | IAllocIterator_DArenaIterator | + +----------------------+-------------------------+-----------------------------------+ + | IAllocator_Xfer | IAllocIterator_Xfer | DArena | + | IAllocator_Any | IAllocIterator_Any | DArenaIterator | + | IAllocator_Impltype | IAllocIterator_Impltype | | + | | | | + +----------------------+-------------------------+-----------------------------------+ + |cBLU AAllocator | AAllocIterator | ArenaConfig | + +----------------------+-------------------------+-----------------------------------+ + +-----------------+----------------------------------------------+-------------------+ + | | AllocInfo | | + | +----------------------------------------------+ | + | AllocError | AllocHeaderConfig | cmpresult | + | +----------------------------------------------+ | + | | AllocHeader | | + +-----------------+----------------------------------------------+-------------------+ + +.. code-block:: cpp + + #include + +Class +----- + +.. doxygenclass:: xo::mm::AAllocator + +Types +----- + +.. doxygengroup:: mm-allocator-type-traits + +Methods +------- + +.. doxygengroup:: mm-allocator-methods diff --git a/xo-alloc2/docs/AllocInfo-reference.rst b/xo-alloc2/docs/AllocInfo-reference.rst new file mode 100644 index 00000000..417a9974 --- /dev/null +++ b/xo-alloc2/docs/AllocInfo-reference.rst @@ -0,0 +1,61 @@ +.. _AllocInfo-reference: + +AllocInfo Reference +=================== + +Information, including alloc metadata, pertaining to a particular allocation. + +Context +------- + +.. ditaa:: + :--scale: 0.99 + + +----------------------+-------------------------+-----------------------------------+ + | RAllocator | RAllocIterator | IAllocator_DArena | + | | | IAllocIterator_DArenaIterator | + +----------------------+-------------------------+-----------------------------------+ + | IAllocator_Xfer | IAllocIterator_Xfer | DArena | + | IAllocator_Any | IAllocIterator_Any | DArenaIterator | + | IAllocator_Impltype | IAllocIterator_Impltype | | + | | | | + +----------------------+-------------------------+-----------------------------------+ + | AAllocator | AAllocIterator | ArenaConfig cBLU | + +----------------------+-------------------------+-----------------------------------+ + +-----------------+----------------------------------------------+-------------------+ + | | AllocInfo | | + | +----------------------------------------------+ | + | AllocError | AllocHeaderConfig | cmpresult | + | +----------------------------------------------+ | + | | AllocHeader | | + +-----------------+----------------------------------------------+-------------------+ + +.. code-block:: cpp + + #include + + +Class +----- + +.. doxygenclass:: xo::mm::AllocInfo + +Member Variables +---------------- + +.. doxygengroup:: mm-allocinfo-instance-vars + +Type Traits +----------- + +.. doxygengroup:: mm-allocinfo-traits + +Constructors +------------ + +.. doxygengroup:: mm-allocinfo-ctors + +Methods +------- + +.. doxygengroup:: mm-allocinfo-methods diff --git a/xo-alloc2/docs/ArenaConfig-reference.rst b/xo-alloc2/docs/ArenaConfig-reference.rst new file mode 100644 index 00000000..20dbabb5 --- /dev/null +++ b/xo-alloc2/docs/ArenaConfig-reference.rst @@ -0,0 +1,60 @@ +.. _ArenaConfig-reference: + +ArenaConfig Reference +===================== + +Configuration for an arena allocator + +Context +------- + +.. ditaa:: + :--scale: 0.99 + + +----------------------+-------------------------+-----------------------------------+ + | RAllocator | RAllocIterator | IAllocator_DArena | + | | | IAllocIterator_DArenaIterator | + +----------------------+-------------------------+-----------------------------------+ + | IAllocator_Xfer | IAllocIterator_Xfer | DArena | + | IAllocator_Any | IAllocIterator_Any | DArenaIterator | + | IAllocator_Impltype | IAllocIterator_Impltype | | + | | | | + +----------------------+-------------------------+-----------------------------------+ + | AAllocator | AAllocIterator | ArenaConfig cBLU| + +----------------------+-------------------------+-----------------------------------+ + +-----------------+----------------------------------------------+-------------------+ + | | AllocInfo | | + | +----------------------------------------------+ | + | AllocError | AllocHeaderConfig | cmpresult | + | +----------------------------------------------+ | + | | AllocHeader | | + +-----------------+----------------------------------------------+-------------------+ + +.. uml:: + :caption: example arena config + :scale: 99% + :align: center + + object cfg<> + cfg : name = "tmp" + cfg : size = 128MB + cfg : hugepage_z = 2MB + cfg : guard_z = 8 + cfg : guard_byte = 0xfd + cfg : store_header_flag = true + cfg : header_size_mask = 0xffffffff + cfg : debug_flag = false + +.. code-block:: cpp + + #include + +Class +----- + +.. doxygenclass:: xo::mm::ArenaConfig + +Instance Variables +------------------ + +.. doxygengroup:: mm-arenaconfig-instance-vars diff --git a/xo-alloc2/docs/CMakeLists.txt b/xo-alloc2/docs/CMakeLists.txt new file mode 100644 index 00000000..54c28e59 --- /dev/null +++ b/xo-alloc2/docs/CMakeLists.txt @@ -0,0 +1,23 @@ +# xo-alloc2/docs/CMakeLists.txt + +xo_doxygen_collect_deps() +xo_docdir_doxygen_config() +xo_docdir_sphinx_config( + index.rst + glossary.rst + examples.rst + implementation.rst + AAllocator-reference.rst + IAllocator_Xfer-reference.rst + AAllocIterator-reference.rst + ArenaConfig-reference.rst + DArena-reference.rst + AllocInfo-reference.rst + cmpresult-reference.rst + #install.rst + #introduction.rst + #implementation.rst +) + +# see xo-reader/doc or xo-unit/doc for working examples +# example.rst install.rst implementation.rst diff --git a/xo-alloc2/docs/DArena-reference.rst b/xo-alloc2/docs/DArena-reference.rst new file mode 100644 index 00000000..11037fc5 --- /dev/null +++ b/xo-alloc2/docs/DArena-reference.rst @@ -0,0 +1,102 @@ +.. _DArena-reference: + +DArena +====== + +Native arena allocator + +Context +------- + +.. ditaa:: + :--scale: 0.99 + + +----------------------+-------------------------+-----------------------------------+ + | RAllocator | RAllocIterator | IAllocator_DArena | + | | | IAllocIterator_DArenaIterator | + +----------------------+-------------------------+-----------------------------------+ + | IAllocator_Xfer | IAllocIterator_Xfer | DArena cBLU | + | IAllocator_Any | IAllocIterator_Any +-----------------------------------+ + | IAllocator_Impltype | IAllocIterator_Impltype | DArenaIterator | + | | | | + +----------------------+-------------------------+-----------------------------------+ + | AAllocator | AAllocIterator | ArenaConfig | + +----------------------+-------------------------+-----------------------------------+ + +-----------------+----------------------------------------------+-------------------+ + | | AllocInfo | | + | +----------------------------------------------+ | + | AllocError | AllocHeaderConfig | cmpresult | + | +----------------------------------------------+ | + | | AllocHeader | | + +-----------------+----------------------------------------------+-------------------+ + +.. code-block:: cpp + + #include + +Arena memory layout +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + <------------------------reserved--------------------------> + <------------committed-----------><-------uncommitted------> + <--allocated--><----available----> + + XXXXXXXXXXXXXXX___________________.......................... + ^ ^ ^ ^ + lo free limit hi + + [X] allocated: in use + [_] committed: physical memory obtained + [.] uncommitted: mapped in virtual memory, not backed by memory + + +Representation for a single allocation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + free_(pre) + v + + <-------------z1---------------> + < guard >< hz >< req_z >< dz >< guard > + + used <== +++++++++0000zzzz@@@@@@@@@@@@@@@@@ppppppp+++++++++ ==> avail + + ^ ^ ^ + header mem | + ^ | + last_header_ free_(post) + + [+] guard surrounding each allocation, for simple sanitize checks + [0] unused header bits (available for application metadata) + [z] record allocation size + [@] new allocated memory + [p] padding (to uintptr_t alignment) + +Class +----- + +.. doxygenclass:: xo::mm::DArena + +Member Variables +---------------- + +.. doxygengroup:: mm-arena-instance-vars + +Type Traits +----------- + +.. doxygengroup:: mm-arena-traits + +Constructors +------------ + +.. doxygengroup:: mm-arena-ctors + +Methods +------- + +.. doxygengroup:: mm-arena-methods diff --git a/xo-alloc2/docs/DArenaIterator-reference.rst b/xo-alloc2/docs/DArenaIterator-reference.rst new file mode 100644 index 00000000..c70bd642 --- /dev/null +++ b/xo-alloc2/docs/DArenaIterator-reference.rst @@ -0,0 +1,55 @@ +.. _DArenaIterator-reference: + +DArenaIterator +============== + +Iterator for allocs obtained from a :cpp:class:`xo::mm::DArena`. + +Context +------- + +.. ditaa:: + :--scale: 0.99 + + +----------------------+-------------------------+-----------------------------------+ + | RAllocator | RAllocIterator | IAllocator_DArena | + | | | IAllocIterator_DArenaIterator | + +----------------------+-------------------------+-----------------------------------+ + | IAllocator_Xfer | IAllocIterator_Xfer | DArena | + | IAllocator_Any | IAllocIterator_Any +-----------------------------------+ + | IAllocator_Impltype | IAllocIterator_Impltype | DArenaIterator | + | | | cBLU| + +----------------------+-------------------------+-----------------------------------+ + | AAllocator | AAllocIterator | ArenaConfig | + +----------------------+-------------------------+-----------------------------------+ + +-----------------+----------------------------------------------+-------------------+ + | | AllocInfo | | + | +----------------------------------------------+ | + | AllocError | AllocHeaderConfig | cmpresult | + | +----------------------------------------------+ | + | | AllocHeader | | + +-----------------+----------------------------------------------+-------------------+ + +.. code-block:: cpp + + #include + +Class +----- + +.. doxygenclass:: xo::mm::DArenaIterator + +Member Variables +---------------- + +.. doxygengroup:: mm-arenaiterator-instance-vars + +Constructors +------------ + +.. doxygengroup:: mm-arenaiterator-ctors + +Methods +------- + +.. doxygengroup:: mm-arenaiterator-methods diff --git a/xo-alloc2/docs/IAllocator_Xfer-reference.rst b/xo-alloc2/docs/IAllocator_Xfer-reference.rst new file mode 100644 index 00000000..e57544b2 --- /dev/null +++ b/xo-alloc2/docs/IAllocator_Xfer-reference.rst @@ -0,0 +1,76 @@ +.. _IAllocator_Xfer-reference: + +IAllocator_Xfer +=============== + +IAllocator_Xfer provides a type-erased interface to a specific native allocator +implementation. + +It supports runtime polymorphism for the cpp:class:`xo::mm::AAllocator` facet. +Application code iis unlikely to directly interact with this class + +Context +------- + +.. ditaa:: + :--scale: 0.99 + + +----------------------+-------------------------+-----------------------------------+ + | RAllocator | RAllocIterator | IAllocator_DArena | + | | | IAllocIterator_DArenaIterator | + +----------------------+-------------------------+-----------------------------------+ + |cBLU IAllocator_Xfer | IAllocIterator_Xfer | DArena | + +----------------------+ IAllocIterator_Any | DArenaIterator | + | IAllocator_Any | IAllocIterator_Impltype | | + | IAllocator_Impltype | | | + +----------------------+-------------------------+-----------------------------------+ + | AAllocator | AAllocIterator | ArenaConfig | + +----------------------+-------------------------+-----------------------------------+ + +-----------------+----------------------------------------------+-------------------+ + | | AllocInfo | | + | +----------------------------------------------+ | + | AllocError | AllocHeaderConfig | cmpresult | + | +----------------------------------------------+ | + | | AllocHeader | | + +-----------------+----------------------------------------------+-------------------+ + +Each method operates by downcasting its first argument to the known target +native interface type, and delegating to native interface method with the same name. + +For example (paraphrasing): + +.. code-block:: cpp + + template + bool IAllocator_Xfer::contains(Copaque d, + const void * p) const ... + { + return IAllocator_DRepr::contains(*(DRepr*)(d), p); + }; + +Code for other methods follows the same pattern; +in fact expect to be able to generate class definition mechanically at some point. + +Application code will not normally interact with :cpp:class:`IAllocator_Xfer`. +It will be used once for each specific allocator implementation +(e.g. with :cpp:class:`xo::mm::IAllocator_DArena`). +In any case, can include the transfer template with: + +.. code-block:: cpp + + #include + +Class +----- + +.. doxygenclass:: xo::mm::IAllocator_Xfer + +Types +----- + +.. doxygengroup:: mm-allocator-xfer-types + +Methods +------- + +.. doxygengroup:: mm-allocator-xfer-methods diff --git a/xo-alloc2/docs/README b/xo-alloc2/docs/README new file mode 100644 index 00000000..2fab6399 --- /dev/null +++ b/xo-alloc2/docs/README @@ -0,0 +1,70 @@ +build + + +-----------------------------------------------+ + | cmake | + | CMakeLists.txt | + | $PREFIX/share/cmake/xo_macros/xo_cxx.cmake | + +-----------------------------------------------+ + | + | +----------------------+ + +------------------------------------------------->| .build/docs/Doxyfile | + | +----------------------+ + | | + | /------------/ + | | + | v + | +---------------------------------------+ +-----------------+ + +---->| doxygen |--->| .build/docs/dox | + | | $PREFIX/share/xo-macros/Doxyfile.in | | +- html/ | + | +---------------------------------------+ | +- xml/ | + | +-----------------+ + | | + | /------------/ + | | + | v + | +---------------------------------------+ +--------------------+ + \---->| sphinx |--->| .build/docs/sphinx | + | +- conf.py | | +- html/ | + | +- _static/ | +--------------------+ + | +- *.rst | + +---------------------------------------+ + +files + + README this file + CMakeLists.txt build entry point + conf.py sphinx config + _static static files for sphinx + +map + + index.rst + +- install.rst + +- examples.rst + +- unit-quantities.rst + +- classes.rst + +- glossary.rst + ... + +examples + +.. doxygenclass:: ${c++ class name} + :project: + :path: + :members: + :protected-members: + :private-members: + :undoc-members: + :member-groups: + :members-only: + :outline: + :no-link: + :allow-dot-graphs: + +.. doxygendefine:: ${c preprocessor define} + +.. doxygenconcept:: ${c++ concept definition} + +.. doxygenenum:: ${c++ enum definition} + +.. doxygenfunction:: ${c++ function name} diff --git a/xo-alloc2/docs/_static/README b/xo-alloc2/docs/_static/README new file mode 100644 index 00000000..7297d046 --- /dev/null +++ b/xo-alloc2/docs/_static/README @@ -0,0 +1 @@ +add any static {.html, .js, ..} files for sphinx to pickup here diff --git a/xo-alloc2/docs/_static/img/favicon.ico b/xo-alloc2/docs/_static/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4163dd69c734f8186cf1ce5e726213cebd231c31 GIT binary patch literal 309936 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 + +Class +----- + +.. doxygenclass:: xo::mm::cmpresult + +Constructors +------------ + +.. doxgyengroup:: mm-cmpresult-ctors + +Methods +------- + +.. doxygengroup:: mm-cmpresult-methods + +Member Variables +---------------- + +.. doxygengroup:: mm-cmpresult-instance-vars diff --git a/xo-alloc2/docs/conf.py b/xo-alloc2/docs/conf.py new file mode 100644 index 00000000..401b73ec --- /dev/null +++ b/xo-alloc2/docs/conf.py @@ -0,0 +1,39 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'xo alloc2 documentation' +copyright = '2025, Roland Conybeare' +author = 'Roland Conybeare' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +#extensions = [] +extensions = [ "breathe", + "sphinx.ext.mathjax", # inline math + "sphinx.ext.autodoc", # generate info from docstrings + "sphinxcontrib.ditaa", # diagrams-through-ascii-art + "sphinxcontrib.plantuml" # text -> uml diagrams + ] + +# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in +# match project name in Doxyfile.in +breathe_default_project = "xodoxxml" + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +pygments_style = 'sphinx' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +#html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] +html_favicon = '_static/img/favicon.ico' diff --git a/xo-alloc2/docs/examples.rst b/xo-alloc2/docs/examples.rst new file mode 100644 index 00000000..5e78bbba --- /dev/null +++ b/xo-alloc2/docs/examples.rst @@ -0,0 +1,125 @@ +.. _examples: + +.. toctree + :maxdepth: 2 + +Examples +======== + +Arena allocation +----------------- + +.. code-block:: cpp + + #include + + using namespace xo::mm; + using namespace std; + + +Create an arena: + +.. code-block:: cpp + + // create arena, size 64k + DArena arena = DArena::map(ArenaConfig { .size_ = 64*1024; }); + + cout << arena.lo() << ".." << arena.hi(); + +This determines a VM memory address range. +Actually address range is rounded up to a whole number of VM pages. +Size here is a hard maximum. It cannot be changed for this arena instance. + +.. code-block:: cpp + + arena.reserved(); // 64k + arena.committed(); // 0k + arena.allocated(); // ok + arena.available(); // 0k + +Although we know the address range for arena, it doesn't own any physical +memory yet. Two ways to commit memory: + +1. Attempt allocation: + + .. code-block:: cpp + + std::byte * mem = arena.alloc(5*1024); + if (!mem) + throw std::runtime_error("alloc failed"); + + arena.reserved(); // 64k + arena.committed(); // 8k - 2 pages + arena.allocateed(); // 5k + arena.available(); // 3k + +2. Expand committed memory explicitly: + + .. code-block:: cpp + + bool ok = arena.expand(5*1024); + assert(ok); + + arena.reserved(); // 64k + arena.committed(); // 8k - 2 pages + arena.allocated(); // 0k + arena.available(); // 8k + +Examining alloc metadata +------------------------ + +Given a successful allocation: + +.. code-block:: cpp + + std::size_t req_z = 5*1024; + std::byte * mem = arena.alloc(req_z); + if (!mem) + throw std::runtime_error("alloc failed"); + + AllocInfo info = arena.alloc_info(mem); + + info.payload(); // [mem, mem + req_z (+ up to 7 bytes padding)] + info.is_valid(); // true + info.guard_lo(); // guard bytes preceding alloc + info.guard_hi(); // guard bytes following alloc + +Can alternatively scan all live allocs in arena: + +.. code-block:: cpp + + for (AllocInfo info : arena) { + info.payload(); // allocated memory range + info.is_valid(); // true + info.guard_lo(); // guard bytes preceding alloc + info.guard_hi(); // guard bytes following alloc + } + +Recycling memory +---------------- + +.. code-block:: cpp + + // arena in non-empty state + arena.reserved(); // 64k + arena.committed(); // 8k - 2 pages + arena.allocateed(); // 5k + arena.available(); // 3k + + arena.clear(); + + arena.reserved() // 64k + arena.committed(); // 8k - 2 pages + arena.allocated(); // 0k + arena.available(); // 8k + +Memory recycled by :cpp:func:`DArena::clear()` +is available for reuse by application; it's still owned by arena. +We're just resetting the free pointer back to the beginning of arena +memory. + +To release memory to the operating system, destroy arena: + +.. code-block:: cpp + + arena.~DArena(); // or just let arena go out of scope diff --git a/xo-alloc2/docs/glossary.rst b/xo-alloc2/docs/glossary.rst new file mode 100644 index 00000000..cb992ef7 --- /dev/null +++ b/xo-alloc2/docs/glossary.rst @@ -0,0 +1,24 @@ +.. _glossary: + +Glossary +-------- + +.. glossary:: + FOMO + | facet object model + + page + | a (4k) page of virtual memory. + | O/S manages virtual memory in chunks of this size. + + hugepage + | large (2MB) VM page; use to reduce page fault expense and TLB pressure. + + THP + | transparent huge pages + + TLB + | translation lookaside buffer + + VM + | virtual memory diff --git a/xo-alloc2/docs/implementation.rst b/xo-alloc2/docs/implementation.rst new file mode 100644 index 00000000..0ef021be --- /dev/null +++ b/xo-alloc2/docs/implementation.rst @@ -0,0 +1,161 @@ +.. _implementation: + +Implementation +============== + +Library dependency tower for *xo-alloc2* + +.. ditaa:: + + +-----------------+ + | xo_alloc2 | + +-----------------+ + | xo_facet | + +-----------------+ + | xo_cmake | + +-----------------+ + +Abstraction tower for *xo-alloc2* components (simplified) + +.. ditaa:: + :--scale: 0.99 + + +----------------+-----------------+-------------------+ + | | | DArena | + | Allocator | AllocIterator | DArenaIterator | + | | +-------------------+ + | | | ArenaConfig | + +----------------+-----------------+-------------------+ + | auxiliary types | + +------------------------------------------------------+ + + +Abstraction tower for *xo-alloc2* components (detailed) + +.. ditaa:: + :--scale: 0.99 + + +----------------------+-------------------------+-----------------------------------+ + | RAllocator | RAllocIterator | IAllocator_DArena | + | | | IAllocIterator_DArenaIterator | + +----------------------+-------------------------+-----------------------------------+ + | IAllocator_Xfer | IAllocIterator_Xfer | DArena | + | IAllocator_Any | IAllocIterator_Any | DArenaIterator | + | IAllocator_Impltype | IAllocIterator_Impltype | | + | | | | + +----------------------+-------------------------+-----------------------------------+ + | AAllocator | AAllocIterator | ArenaConfig | + +----------------------+-------------------------+-----------------------------------+ + +-----------------+----------------------------------------------+-------------------+ + | | AllocInfo | | + | +----------------------------------------------+ | + | AllocError | AllocHeaderConfig | cmpresult | + | +----------------------------------------------+ | + | | AllocHeader | | + +-----------------+----------------------------------------------+-------------------+ + +.. list-table:: Polymorphic Allocator + :header-rows: 1 + :widths: 20 90 + + * - Class + - Description + * - ``AAllocator`` + - Abstract allocator interface for runtime polymorphism + * - ``IAllocator_Any`` + - Stub allocator interface for uninitialized variant + * - ``IAllocator_Xfer`` + - Allocator interface template for representation ``D`` + * - ``IAllocator_Impltype`` + - Lookup allocator interface for representation ``D`` + * - ``RAllocator`` + - Provide allocator methods for FOMO object ``O`` + +.. list-table:: Polymorphich Alloc Iterator + :header-rows: 1 + :widths: 20 90 + + * - Class + - Description + * - ``AAllocIterator`` + - Abstract interface for iteration over allocs + * - ``IAllocIterator_Any`` + - Stub alloc-iterator interface for uninitialized variant + * - ``IAllocIterator_Xfer`` + - Alloc-iterator interface template for representation ``D`` + * - ``IAllocIterator_Impltype`` + - Lookup alloc-iterator interface for representation ``D`` + * - ``RAllocIterator`` + - Provide alloc-iterator methods for FOMO object ``O``. + +.. list-table:: Native Arena Allocator + :header-rows: 1 + :widths: 20 90 + + * - Class + - Description + * - ``ArenaConfig`` + - Configuration for a ``DArena`` instance + * - ``DArena`` + - VM-aware arena allocator + * - ``DArenaIterator`` + - Iterator over ``DArena`` allocations + * - ``IAlllocator_DArena`` + - Adapt a ``DArena`` to facet ``AAllocator`` + * - ``IAllocIterator_DArenaAllocator`` + - Adapt a ``DArenaIterator`` to facet ``AAllocIterator`` + +.. list-table:: Auxiliary/Support Types + :header-rows: 1 + :widths: 20 90 + + * - Class + - Description + * - ``AllocError`` + - Return type for an alloc request, with error details. + * - ``AllocInfo`` + - An opaque allocation. Value of an alloc-iterator. + * - ``AllocHeaderConfig`` + - Per-allocator configuration of alloc headers + * - ``AllocHeader`` + - Per-allocation header (8 bytes) + * - ``cmpresult`` + - Result of alloc-iterator comparison + +Example Object Diagram + +.. uml:: + :caption: representation for an arena allocator + :scale: 99% + :align: center + + object rarena1<> + rarena1 : iface = vtable1 + rarena1 : data = darena1 + + object vtable1<> + vtable1 : alloc() + + object darena1<> + darena1 : config + darena1 : lo + darena1 : hi + darena1 : free + darena1 : limit + darena1 : last_error + + rarena1 o-- vtable1 + rarena1 o-- darena1 + + +Remarks: + +* When we know the allocator representation at compile time (``DArena`` here), + then we also know the interface (``IAllocator_DArena``). + Devirtualization is easy since interface methods are all final. + +* Size of a FOMO object is two pointers; it's natural to create such objects + on the fly and pass them by value. + When storing an allocator in another data structure, we only need to use + the RAllocator stack if we want runtime polymorphism for the stored allocator. + Otherwise can store a ``DArena`` instance. diff --git a/xo-alloc2/docs/index.rst b/xo-alloc2/docs/index.rst new file mode 100644 index 00000000..a93873d4 --- /dev/null +++ b/xo-alloc2/docs/index.rst @@ -0,0 +1,36 @@ +# xo-alloc2 documentation master file + +xo-alloc2 documentation +======================= + +xo-alloc2 provides: + +* Fast vm-aware arena allocation. +* Allocates uncommitted virtual memory, and commits on demand. +* When available, uses THP (Transparent Huge Pages) to mitigate pagetable pressure. +* Optional GC support, with per-alloc header. + +Diagnostic features: + +* with alloc headers: forward iterators over individual allocations +* configurable guard memory between allocations. + +Implemented using FOMO (faceted rust-like object model) from xo-facet + +.. toctree:: + :maxdepth: 2 + :caption: xo-alloc2 contents + + examples + implementation + AAllocator-reference + IAllocator_Xfer-reference + AAllocIterator-reference + ArenaConfig-reference + DArena-reference + DArenaIterator-reference + AllocInfo-reference + cmpresult-reference + glossary + genindex + search diff --git a/xo-alloc2/idl/Collector.json5 b/xo-alloc2/idl/Collector.json5 new file mode 100644 index 00000000..6bc4755d --- /dev/null +++ b/xo-alloc2/idl/Collector.json5 @@ -0,0 +1,332 @@ +{ + mode: "facet", + output_cpp_dir: "src/alloc2/facet", + output_hpp_dir: "include/xo/alloc2", + output_impl_subdir: "gc", + includes: [ + "", + "", + "", +// "", +// "", + ], + // extra includes in GCObject.hpp, if any + user_hpp_includes: [ + // "\"gc/RCollector_aux.hpp\"", // not usable here, + ], + namespace1: "xo", + namespace2: "mm", + pretext: [ + "namespace xo { namespace mm { class AGCObject; } }", + "namespace xo { namespace mm { class IGCObject_Any; } }", + "// more pretext here..", + ], + facet: "Collector", + detail_subdir: "gc", + brief: "garbage collector interface", + using_doxygen: true, + doc: [ + "A collector must also suppose the @ref AAllocator facet, see also" + ], + types: [ + // using size_type = std::size_t; + { + name: "size_type", + doc: ["allocation size type"], + definition: "std::size_t", + }, + ], + const_methods: [ + // size_type allocated(Generation g, Role r) const noexcept + { + name: "allocated", + doc: ["memory in use for this collector"], + return_type: "size_type", + args: [ + {type: "Generation", name: "g"}, + {type: "Role", name: "r"}, + ], + const: true, + noexcept: true, + attributes: [], + }, + // size_type committed(Generation g, Role r) const noexcept + { + name: "committed", + doc: ["memory committed for this collector"], + return_type: "size_type", + args: [ + {type: "Generation", name: "g"}, + {type: "Role", name: "r"}, + ], + const: true, + noexcept: true, + attributes: [], + }, + // size_type reserved(Generation g, Role r) const noexcept + { + name: "reserved", + doc: ["address space reserved for this collector"], + return_type: "size_type", + args: [ + {type: "Generation", name: "g"}, + {type: "Role", name: "r"}, + ], + const: true, + noexcept: true, + attributes: [], + }, + // int32_t locate() const noexcept + { + name: "locate_address", + doc: [ + "Location of object in collector. -1 if not in collector memory.", + "Other negative values represent collector error states (good luck!).", + "Exact meaning of non-negative values up to collector implementation" + ], + return_type: "std::int32_t", + args: [ + {type: "const void *", name: "addr"}, + ], + const: true, + noexcept: true, + attributes: [], + }, + // bool contains(Role r, const void * addr) const noexcept + { + name: "contains", + doc: ["true if gc responsible for data at @p addr, and data belongs to Role @p r"], + return_type: "bool", + args: [ + {type: "Role", name: "r"}, + {type: "const void *", name: "addr"}, + ], + const: true, + noexcept: true, + attributes: [], + }, + // bool is_type_installed(typeseq tseq) const noexcept; + { + name: "is_type_installed", + doc: ["true iff gc-aware object of type @p tseq is installed in this collector"], + return_type: "bool", + args: [ + {type: "typeseq", name: "tseq"}, + ], + const: true, + noexcept: true, + attributes: [], + }, + // bool report_statistics(obj report_mm, obj error_mm, obj * output); + { + 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: [], + }, + // bool report_types(obj report_mm, obj error_mm, obj * output); + { + name: "report_object_types", + doc: [ + "Report gc object types, at discretion of collector implementation.", + "Creates dictionary using memory from @p report_mm.", + "If unable to comply (e.g. oom), return runtime error allocated from @p error_mm.", + "Avoiding obj return type to avoid #include cycle" + ], + return_type: "bool", + args: [ + {type: "obj", name: "report_mm"}, + {type: "obj", name: "error_mm"}, + {type: "obj *", name: "output"}, + ], + const: true, + noexcept: true, + attributes: [], + }, + // 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) + { + name: "install_type", + doc: [ + "install interface @p iface for representation with typeseq @p tseq", + "in collector @p d.", + "", + "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.", + "", + "Return false if installation fails (e.g. memory exhausted)" + ], + return_type: "bool", + args: [ + {type: "const AGCObject &", name: "iface"}, + ], + const: false, + noexcept: false, + attributes: [], + }, + // void add_gc_root_poly(obj * p_root) + { + name: "add_gc_root_poly", + doc: [ + "add gc root with address @p p_root. gc will preserve subgraph at this address" + ], + return_type: "void", + args: [ + {type: "obj *", name: "p_root"}, + ], + const: false, + noexcept: false, + attributes: [], + }, + // void remove_gc_root_poly(obj * p_root) + { + name: "remove_gc_root_poly", + doc: [ + "remove gc root with address @p p_root. Reverse effect of prior add_gc_root_poly call" + ], + return_type: "void", + args: [ + {type: "obj *", name: "p_root"}, + ], + const: false, + noexcept: false, + attributes: [], + }, + // void request_gc(Generation upto) + { + name: "request_gc", + doc: [ + "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.", + "", + ], + return_type: "void", + args: [ + {type: "Generation", name: "upto"}, + ], + const: false, + noexcept: false, + attributes: [], + }, + // void assign_member(void * parent, obj * p_lhs, obj *", name: "p_lhs"}, + {type: "obj &", name: "rhs"}, + ], + const: false, + noexcept: false, + attributes: [], + }, + // void alloc_copy(void * src) + { + name: "alloc_copy", + doc: [ + "allocate copy of source object at address @p src.", + "Source must be owned by this collector.", + "Increments object age" + ], + return_type: "void *", + args: [ + {type: "std::byte *", name: "src"}, + ], + const: false, + noexcept: false, + attributes: [], + }, + ], + router_facet_explicit_content: [ + "/** convenience template for gc object copy **/", + "template ", + "void * alloc_copy_for(const T * src) noexcept {", + " return O::iface()->alloc_copy(O::data(), (std::byte *)const_cast(src));", + "}", + "", + "/** convenience template for move-constructible T (this is common) **/", + "template ", + "T * std_move_for(T * src) noexcept {", + " void * mem = this->alloc_copy_for(src);", + " if (mem) {", + " return new (mem) T(std::move(*src));", + " }", + " return nullptr;", + "}", + "", + "/** forward faceted object pointer in place. Defined in GCObject.hpp to avoid #include cycle **/", + "template ", + "void forward_inplace(obj * p_obj);", + "", + "/** another convenience template for forwarding.", + " * Defined in RGCObject.hpp to avoid #include cycle.", + "**/", + "template ", + "void forward_inplace(DRepr ** pp_repr);", + "", + "/** convenience template where pointer requires pivot **/", + "template ", + "requires (!std::is_same_v)", + "void forward_pivot_inplace(obj * p_obj);", + "", + "/** add root @p p_root **/", + "template", + "void add_gc_root(obj * p_root) {", + " O::iface()->add_gc_root_poly(O::data(), (obj *)p_root);", + "}", + "", + "/** remove root @p p_root **/", + "template ", + "void remove_gc_root(obj * p_root) {", + " O::iface()->remove_gc_root_poly(O::data(), (obj *)p_root);", + "}", + ], +} diff --git a/xo-alloc2/idl/GCObject.json5 b/xo-alloc2/idl/GCObject.json5 new file mode 100644 index 00000000..2cd7eed8 --- /dev/null +++ b/xo-alloc2/idl/GCObject.json5 @@ -0,0 +1,104 @@ +{ + mode: "facet", + output_cpp_dir: "src/alloc2", + output_hpp_dir: "include/xo/alloc2", + output_impl_subdir: "gc", + includes: [ + "", +// "", + "", + "", + "", + ], + // extra includes in GCObject.hpp, if any + user_hpp_includes: [ + "\"gc/RCollector_aux.hpp\"", + ], + namespace1: "xo", + namespace2: "mm", + pretext: [ + "namespace xo { namespace mm { class ACollector; }}", + ], + facet: "GCObject", + detail_subdir: "gc", + brief: "gc-aware object, providing collector hooks", + using_doxygen: true, + doc: [ + "GC hooks for collector-aware data" + ], + types: [ + // using size_type = std::size_t + { + name: "size_type", + doc: ["type for an amount of memory"], + definition: "std::size_t", + }, + { + name: "AAllocator", + doc: ["fomo allocator type"], + definition: "xo::mm::AAllocator", + }, +// { +// name: "ACollector", +// doc: ["fomo collector type"], +// definition: "xo::mm::ACollector", +// }, + { + name: "AGCObjectVisitor", + doc: ["fomo collector type"], + definition: "xo::mm::AGCObjectVisitor", + }, + { + name: "VisitReason", + doc: ["hint arg when navigating object graph"], + definition: "xo::mm::VisitReason", + }, + ], + const_methods: [ + // size_type shallow_size() const noexcept +// { +// name: "shallow_size", +// doc: ["memory consumption for this instance"], +// return_type: "size_type", +// args: [], +// const: true, +// noexcept: true, +// attributes: [], +// }, + ], + nonconst_methods: [ + // Opaque shallow_move(obj) noexcept + { + name: "gco_shallow_move", + doc: [ + "move instance using object visitor.", + "Arguably abusing the word 'visitor' here", + ], + return_type: "Opaque", + args:[ + {type: "obj", name: "gc"}, + ], + const: true, + noexcept: true, + attributes: [], + }, + // size_type visit_gco_children(VisitReason reason, obj) noexcept + { + name: "visit_gco_children", + doc: [ + "Invoke fn.visit_child(iface,data) for each child GCObject pointer.", + "Context: provides address of data pointer so it can be updated in place", + "when @p fn invokes garbage collector reentry point" + ], + return_type: "void", + args: [ + {type: "VisitReason", name: "reason"}, + {type: "obj", name: "fn"}, + ], + const: true, + noexcept: true, + attributes: [], + }, + ], + router_facet_explicit_content: [] +} diff --git a/xo-alloc2/idl/GCObjectVisitor.json5 b/xo-alloc2/idl/GCObjectVisitor.json5 new file mode 100644 index 00000000..07d99732 --- /dev/null +++ b/xo-alloc2/idl/GCObjectVisitor.json5 @@ -0,0 +1,144 @@ +{ + mode: "facet", + output_cpp_dir: "src/alloc2/facet", + output_hpp_dir: "include/xo/alloc2", + output_impl_subdir: "gc", + includes: [ + "", + "", + "", + "", + ], + // extra includes in GCObject.hpp, if any + user_hpp_includes: [ +// "\"gc/RCollector_aux.hpp\"", + ], + namespace1: "xo", + namespace2: "mm", + pretext: [ + "// see GCObject.hpp, also in xo-alloc2/", + "namespace xo { namespace mm { class AGCObject; }}", + "namespace xo { namespace mm { class AllocInfo; }}", + "namespace xo { namespace mm { class Generation; }}", + ], + facet: "GCObjectVisitor", + detail_subdir: "gc", + brief: "gc-aware object visitor", + using_doxygen: true, + doc: [ + "Visit a gc-aware object. Visitor can traverse and update child pointers in-place." + ], + types: [ + // using size_type = std::size_t +// { +// name: "size_type", +// doc: ["type for an amount of memory"], +// definition: "std::size_t", +// }, + ], + const_methods: [ + // AllocInfo alloc_info(void * gco) const noexcept; + { + name: "alloc_info", + doc: [ + "allocation metadata for gc-aware data at address @p gco.", + "@p gco must be the result of a call to collector's alloc() function", + "note: load-bearing for xo-gc/MutationLogStore", + ], + return_type: "AllocInfo", + args: [ + {type: "void *", name: "addr"}, + ], + const: true, + noexcept: false, + attributes: [], + }, + // Generation generation_of(Role r, const void * addr) const noexcept; + { + name: "generation_of", + doc: [ + "generation to which pointer @p addr belongs, given role @p r;", + "sentinel if @p addr is not owned by collector.", + "note: load-bearing for xo-gc/MutationLogStore", + ], + return_type: "Generation", + args: [ + {type: "Role", name: "r"}, + {type: "const void *", name: "addr"}, + ], + const: true, + noexcept: true, + attributes: [], + }, + ], + nonconst_methods: [ + // void alloc_copy(void * src) + { + name: "alloc_copy", + doc: [ + "allocate copy of source object at address @p src.", + "Source must be owned by this collector.", + "Increments object age" + ], + return_type: "void *", + args: [ + {type: "std::byte *", name: "src"}, + ], + const: true, // refers to interface. + noexcept: false, + attributes: [], + }, + // void visit_child(VisitReason reason, AGCObject * iface, void ** pp_data) noexcept; + { + name: "visit_child", + doc: ["visit child of a gc-aware object. May update child in-place!"], + return_type: "void", + args:[ + {type: "VisitReason", name: "reason"}, + {type: "AGCObject *", name: "iface"}, + {type: "void **", name: "pp_data"}, + ], + const: true, // technical const. I/face not modified + noexcept: true, + attributes: [], + }, + ], + router_facet_explicit_content: [ + "/** convenience: allocate copy for typed pointer **/", + "template ", + "void * alloc_copy_for(const T * src) noexcept {", + " return O::iface()->alloc_copy(O::data(), (std::byte *)const_cast(src));", + "}", + "", + "/** convenience: move typed pointer **/", + "template ", + "T * std_move_for(T * src) noexcept {", + " void * mem = this->alloc_copy_for(src);", + " if (mem) {", + " return new (mem) T(std::move(*src));", + " }", + " return nullptr;", + "}", + "", + "/** visit a gcobject child pointer in place.", + " Defined in RCollector_aux.hpp to avoid #include cycle", + " (for historical reasons - coul d be in RGCObject_aux.hpp?)", + " **/", + "template ", + "void visit_child(VisitReason reason, xo::facet::obj * p_obj);", + "", + "/** visit typed child data pointer in place.", + " Defined in RGCObject.hpp to avoid #include cycle", + " **/", + "template ", + "void visit_child(VisitReason reason, DRepr ** pp_data);", + "", + "/** visit faceted object pointer stored using some facet", + " other than AGCObject", + " **/", + "template ", + "requires (!std::is_same_v)", + "void visit_poly_child(VisitReason reason, obj * p_pivot);", + "", + ] +} diff --git a/xo-alloc2/idl/ResourceVisitor.json5 b/xo-alloc2/idl/ResourceVisitor.json5 new file mode 100644 index 00000000..54effc41 --- /dev/null +++ b/xo-alloc2/idl/ResourceVisitor.json5 @@ -0,0 +1,52 @@ +{ + mode: "facet", + output_cpp_dir: "src/alloc2", + output_hpp_dir: "include/xo/alloc2", + output_impl_subdir: "visitor", + includes: [ + "\"Allocator.hpp\"" + ], + // extra includes in ResourceVisitor.hpp, if any + user_hpp_includes: [], + namespace1: "xo", + namespace2: "mm", + // text after includes, before AResourceVisitor + pretext: [ "// {pretext} here" ], + facet: "ResourceVisitor", + detail_subdir: "visitor", + brief: "visitor to inspect resource consumption", + using_doxygen: true, + doc: [ + "Visitor to receive measured resource consumption" + ], + types: [ + // using size_type = std::size_t + { + name: "size_type", + doc: ["type for length of a sequence"], + definition: "std::size_t", + }, +// // using AGCObject = xo::mm::AGCObject +// { +// name: "AGCObject", +// doc: ["facet for types with GC support"], +// definition: "xo::mm::AGCObject", +// } + ], + const_methods: [ + // bool on_memory(name, allocated, committed, reserved) const noexcept + { + name: "on_allocator", + doc: ["report memory consumption"], + return_type: "void", + args: [ + {type: "obj", name: "mm"}, + ], + const: true, + noexcept: true, + attributes: [], + }, + ], + nonconst_methods: [ + ], +} diff --git a/xo-alloc2/include/xo/alloc2/AllocIterator.hpp b/xo-alloc2/include/xo/alloc2/AllocIterator.hpp new file mode 100644 index 00000000..a741dd78 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/AllocIterator.hpp @@ -0,0 +1,13 @@ +/** @file AllocIterator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "alloc/AAllocIterator.hpp" +#include "alloc/IAllocIterator_Any.hpp" +#include "alloc/IAllocIterator_Xfer.hpp" +#include "alloc/RAllocIterator.hpp" + +/* end AllocIterator.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/AllocRange.hpp b/xo-alloc2/include/xo/alloc2/AllocRange.hpp new file mode 100644 index 00000000..74656abc --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/AllocRange.hpp @@ -0,0 +1,34 @@ +/** @file AllocRange.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "AllocIterator.hpp" + +namespace xo { + namespace mm { + /** @class AllocRange + * @brief Provide range iteration over an @ref AAllcator + * + * Return value type for @ref AAllocator::alloc_range + **/ + struct AllocRange { + public: + using repr_type = std::pair, obj>; + + public: + AllocRange() = default; + explicit AllocRange(repr_type range) : range_{std::move(range)} {} + + obj begin() const { return range_.first; } + obj end() const { return range_.second; } + + /** state: {begin,end} pair of alloc iterators **/ + repr_type range_; + }; + } /*namsepace mm*/ +} /*namespace xo*/ + +/* end AllocRange.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/Allocator.hpp b/xo-alloc2/include/xo/alloc2/Allocator.hpp new file mode 100644 index 00000000..9e7230d6 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/Allocator.hpp @@ -0,0 +1,11 @@ +/** @file Allocator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "Allocator_basic.hpp" +#include "alloc/RAllocator_aux.hpp" + +/* end Allocator.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/Allocator_basic.hpp b/xo-alloc2/include/xo/alloc2/Allocator_basic.hpp new file mode 100644 index 00000000..60e4e980 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/Allocator_basic.hpp @@ -0,0 +1,13 @@ +/** @file Allocator_basic.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "alloc/AAllocator.hpp" +#include "alloc/IAllocator_Any.hpp" +#include "alloc/IAllocator_Xfer.hpp" +#include "alloc/RAllocator.hpp" + +/* end Allocator_basic.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/Arena.hpp b/xo-alloc2/include/xo/alloc2/Arena.hpp new file mode 100644 index 00000000..fac3f302 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/Arena.hpp @@ -0,0 +1,17 @@ +/** @file Arena.hpp + * + * @author Roland Conybeare, Feb 2026 + **/ + +#pragma once + +// reminder: we can't put this AAllocator +// (or APrintable for that matter) support in xo-arena, +// because xo-arena is a dependency of xo-facet, which is in turn +// a dependency of xo-alloc2 +// + +#include +#include "arena/IAllocator_DArena.hpp" + +/* end Arena.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/ArenaIterator.hpp b/xo-alloc2/include/xo/alloc2/ArenaIterator.hpp new file mode 100644 index 00000000..02b49e85 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/ArenaIterator.hpp @@ -0,0 +1,15 @@ +/** @file ArenaIterator.hpp + * + * @author Roland Conybeare, May 2026 + **/ + +#pragma once + +// reminder: we can't put this AAllocIterator support in xo-arena +// because xo-arena is a dependency of xo-facet, which we're relying +// on here + +#include +#include "arena/IAllocIterator_DArenaIterator.hpp" + +/* end ArenaIterator.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/Collector.hpp b/xo-alloc2/include/xo/alloc2/Collector.hpp new file mode 100644 index 00000000..afa41a6e --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/Collector.hpp @@ -0,0 +1,22 @@ +/** @file Collector.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/Collector.json5] + * 2. jinja2 template for facet .hpp file: + * [facet.hpp.j2] + * 3. idl for facet methods + * [idl/Collector.json5] + **/ + +#pragma once + +#include "gc/ACollector.hpp" +#include "gc/ICollector_Any.hpp" +#include "gc/ICollector_Xfer.hpp" +#include "gc/RCollector.hpp" + + +/* end Collector.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/Collector2.hpp b/xo-alloc2/include/xo/alloc2/Collector2.hpp new file mode 100644 index 00000000..2f2b2847 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/Collector2.hpp @@ -0,0 +1,22 @@ +/** @file Collector2.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/Collector2.json5] + * 2. jinja2 template for facet .hpp file: + * [facet.hpp.j2] + * 3. idl for facet methods + * [idl/Collector2.json5] + **/ + +#pragma once + +#include "gc/ACollector2.hpp" +#include "gc/ICollector2_Any.hpp" +#include "gc/ICollector2_Xfer.hpp" +#include "gc/RCollector2.hpp" + + +/* end Collector2.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/CollectorTypeRegistry.hpp b/xo-alloc2/include/xo/alloc2/CollectorTypeRegistry.hpp new file mode 100644 index 00000000..2515f486 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/CollectorTypeRegistry.hpp @@ -0,0 +1,76 @@ +/** @file CollectorTypeRegistry.hpp + * + * @brief Runtime type registration for gc-aware types + * + * @author Roland Conybeare, Jan 2026 + **/ + +#pragma once + +#include "Collector.hpp" +#include + +namespace xo { + namespace mm { + + /** @class CollectorTypeRegistry + * + * @brief Runtime registry for gc-aware types + * + * Singleton to remember known gc-aware types; + * use to simplify registering such types + * with a collector instance. + * + * Remark: splitting work here between + * 1. static initializer work: tracking gc-aware types, + * 2. runtime post-configuration work: report + * gc-aware types to GC instances + * + * Use: + * 1. subsystem foo provides function foo_register_types(obj gc) + * Function calls + * gc.install_type(impl_for()) + * for each gc-aware type provided by subsystem foo + * + * Example: in file xo-object2/src/object2/object2_register_types.cpp, see + * object2_register_types() + * + * 2. during subsystem init, call + * CollectorTypeRegistry::instance().register_types(&foo_register_types); + * + * Example: in file xo-object2/src/object2/init_object2.cpp, see + * InitSubsys::init() + * + * 3. during Collector setup, call + * obj gc = ...; + * CollectorTypeRegistry::instance().install_types(gc); + * + * Example: in file xo-object2/utest/X1Collector.test.cpp + * TEST_CASE("x1") + **/ + class CollectorTypeRegistry { + public: + using init_function_type = std::function)>; + + public: + /** singleton instance **/ + static CollectorTypeRegistry & instance(); + + /** remember a gc-aware type-registration function **/ + void register_types(init_function_type init_fn); + + /** register known GC-aware types with @p gc. + * Calls @c gc.install_type() for each + * such type. + **/ + bool install_types(obj gc); + + private: + /** initialization steps for a new Collector instance **/ + std::vector init_seq_v_; + }; + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end CollectorTypeRegistry.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/GCObject.hpp b/xo-alloc2/include/xo/alloc2/GCObject.hpp new file mode 100644 index 00000000..dc018e44 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/GCObject.hpp @@ -0,0 +1,21 @@ +/** @file GCObject.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/GCObject.json5] + * 2. jinja2 template for facet .hpp file: + * [facet.hpp.j2] + * 3. idl for facet methods + * [idl/GCObject.json5] + **/ + +#pragma once + +#include "gc/AGCObject.hpp" +#include "gc/IGCObject_Any.hpp" +#include "gc/IGCObject_Xfer.hpp" +#include "gc/RGCObject.hpp" + +/* end GCObject.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/GCObjectConversion.hpp b/xo-alloc2/include/xo/alloc2/GCObjectConversion.hpp new file mode 100644 index 00000000..e7c06797 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/GCObjectConversion.hpp @@ -0,0 +1,134 @@ +/** @file GCObjectConversion.hpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace xo { + namespace scm { + class GCObjectConversionUtil { + public: + using AGCObject = xo::mm::AGCObject; + using typeseq = xo::reflect::typeseq; + + /** helper method fro GCObjectConversion<..>::from_gco() + * on conversion failure + **/ + static void _from_gco_fail_aux(obj gco, + typeseq tseq, + scope * p_log); + }; + + /** @brief compile-time conversion obj <-> T + * + * Specialize for each T that participates in conversion. + * Methods here aren't implemented + **/ + template + class GCObjectConversion { + public: + using AGCObject = xo::mm::AGCObject; + using AAllocator = xo::mm::AAllocator; + + /** find gc-aware representation for @p x. + * If necessary allocate from @p mm, but may + * refer to @p x in-place + **/ + static obj to_gco(obj mm, const T & x); + /** convert to native representation @tparam T from gc-aware + * @p gco. If necessary allocate from @p mm, but + * may instead refer to @p x in-place + **/ + static T from_gco(obj mm, obj gco); + }; + + /** Motivating use-case for GCObjectConversion is to transform + * primitive function arguments and results to/from gc-aware + * representation. + * + * However: Schematika also supports runtime polymorphism + * which leads to primitives that expect obj arguments. + * + * Also, Schematika expression parser needs representation for + * expressions, before type unification. + * + * Consider a function like: + * def fact = lambda (n : i64) { if (n <= 0) then 1 else (n * fact(n - 1)); } + * During expression parsing the rhs argument to multiply has unknown type. + * To construct an expression for input to unification will use polymorphic + * binding for multiply primitive, relying on specialization here for + * its implementation. + **/ + template + struct GCObjectConversion> { + using AGCObject = xo::mm::AGCObject; + using AAllocator = xo::mm::AAllocator; + using FacetRegistry = xo::facet::FacetRegistry; + using DVariantPlaceholder = xo::facet::DVariantPlaceholder; + + static obj to_gco(obj, + obj gco) { + if constexpr (std::is_same_v) { + // trivial conversion! + return gco; + } else if constexpr (std::is_same_v) { + // runtime polymorphism + return FacetRegistry::instance().variant(gco); + } else /* DRepr != DVariantPlaceholder */ { + // known content w/ fat object pointer + return obj(gco.data()); + } + } + + /** Several use cases here: + * 1. runtime polymorphism + * obj v(DArray::make(..)); + * // from_gco() doesn't know v repr + * auto gc = GCObjectConversion::from_gco(mm, v); + * + **/ + static obj from_gco(obj, + obj gco) { + scope log(XO_DEBUG(false)); + + if constexpr (std::is_same_v) { + // Need accurate handling of DVariantPlaceholder. + // runtime type must be some concrete type. + // Only use obj::from when DRepr is a concrete type + + if constexpr (std::is_same_v) { + // At comptime gco has unknown repr. At runtime + // will have some known repr, which assignment here will transfer + return gco; + } else { + // Runtime conversion to concrete type DRepr + + auto retval = obj::from(gco); + + if (!retval) { + GCObjectConversionUtil::_from_gco_fail_aux + (gco, reflect::typeseq::id(), &log); + } + + return retval; + } + } else { + // both runtime and comptime polymorphism + // use same path here, since representation of @p gco + // is type-erased here + + return FacetRegistry::instance().variant(gco); + } + } + }; + } /*namespace scm */ +} /*namespace xo*/ + +/* end GCObjectConversion.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/GCObjectVisitor.hpp b/xo-alloc2/include/xo/alloc2/GCObjectVisitor.hpp new file mode 100644 index 00000000..3573b56a --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/GCObjectVisitor.hpp @@ -0,0 +1,23 @@ +/** @file GCObjectVisitor.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/GCObjectVisitor.json5] + * 2. jinja2 template for facet .hpp file: + * [facet.hpp.j2] + * 3. idl for facet methods + * [idl/GCObjectVisitor.json5] + **/ + +#pragma once + +#include "gc/AGCObjectVisitor.hpp" +#include "gc/IGCObjectVisitor_Any.hpp" +#include "gc/IGCObjectVisitor_Xfer.hpp" +#include "gc/RGCObjectVisitor.hpp" + +#include "gc/RGCObjectVisitor_aux.hpp" + +/* end GCObjectVisitor.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/Generation.hpp b/xo-alloc2/include/xo/alloc2/Generation.hpp new file mode 100644 index 00000000..0eb24074 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/Generation.hpp @@ -0,0 +1,55 @@ +/** @file generation.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include +#include + +namespace xo { + namespace mm { + /** hard maximum number of generations **/ + static constexpr uint32_t c_max_generation = 3; + + /** @class generation + * @brief type-safe generation number + **/ + class Generation { + public: + using value_type = std::uint32_t; + + constexpr Generation() = default; + explicit constexpr Generation(value_type x) : value_{x} {} + + static Generation nursery() { return Generation{0}; } + static Generation g0() { return Generation{0}; } + static Generation g1() { return Generation{1}; } + static Generation sentinel() { return Generation(c_max_generation); } + + bool is_sentinel() const noexcept { return value_ == c_max_generation; } + + constexpr operator value_type() const { return value_; } + + Generation & operator++() { ++value_; return *this; } + + std::uint32_t value_ = 0; + }; + + inline bool operator==(Generation lhs, Generation rhs) { + return lhs.value_ == rhs.value_; + } + + inline bool operator<(Generation lhs, Generation rhs) { + return lhs.value_ < rhs.value_; + } + + inline bool operator>(Generation lhs, Generation rhs) { + return lhs.value_ > rhs.value_; + } + + } +} + +/* end generation.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/ResourceVisitor.hpp b/xo-alloc2/include/xo/alloc2/ResourceVisitor.hpp new file mode 100644 index 00000000..b995e891 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/ResourceVisitor.hpp @@ -0,0 +1,22 @@ +/** @file ResourceVisitor.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/ResourceVisitor.json5] + * 2. jinja2 template for facet .hpp file: + * [facet.hpp.j2] + * 3. idl for facet methods + * [idl/ResourceVisitor.json5] + **/ + +#pragma once + +#include "visitor/AResourceVisitor.hpp" +#include "visitor/IResourceVisitor_Any.hpp" +#include "visitor/IResourceVisitor_Xfer.hpp" +#include "visitor/RResourceVisitor.hpp" + + +/* end ResourceVisitor.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/SetupAlloc2.hpp b/xo-alloc2/include/xo/alloc2/SetupAlloc2.hpp new file mode 100644 index 00000000..f55d6edf --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/SetupAlloc2.hpp @@ -0,0 +1,18 @@ +/** @file SetupAlloc2.hpp + * + * @author Roland Conybeare, Feb 2026 + **/ + +#pragma once + +namespace xo { + namespace mm { + class SetupAlloc2 { + public: + /** Register alloc2 (facet,impl) combinations with Facet Registry **/ + static bool register_facets(); + }; + } +} + +/* end SetupAlloc2.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/VisitReason.hpp b/xo-alloc2/include/xo/alloc2/VisitReason.hpp new file mode 100644 index 00000000..38c87c77 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/VisitReason.hpp @@ -0,0 +1,54 @@ +/** @file VisitReason.hpp + * + * @author Roland Conybeare, Apr 2026 + **/ + +#pragma once + +namespace xo { + namespace mm { + + /** @brief tag when navigating object graph + * + * Used with + * @ref DX1Collector::visit_child + * @ref GCObject::visit_gco_children + **/ + class VisitReason { + public: + enum class code { + invalid = -1, + + /** color not needed **/ + unspecified, + + /** Forward child pointers inplace for GC. + * See @ref GCObjectStore::forward_inplace_aux + **/ + forward, + /** verify GC store consistency + * See @ref DX1Collector::_verify_aux + **/ + verify, + + N, + }; + + explicit VisitReason(code x) : code_{x} {} + + static VisitReason unspecified() { return VisitReason(code::unspecified); } + static VisitReason forward() { return VisitReason(code::forward); } + static VisitReason verify() { return VisitReason(code::verify); } + + code code() const noexcept { return code_; } + + enum code code_; + }; + + inline bool operator==(VisitReason x, VisitReason y) { return x.code() == y.code(); } + inline bool operator!=(VisitReason x, VisitReason y) { return x.code() != y.code(); } + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end VisitReason.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/abox.hpp b/xo-alloc2/include/xo/alloc2/abox.hpp new file mode 100644 index 00000000..c104e3dc --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/abox.hpp @@ -0,0 +1,131 @@ +/** @file abox.hpp + * + * @author Roland Conybeare, Feb 2026 + **/ + +#pragma once + +#include "Allocator.hpp" +#include +#include +#include + +namespace xo { + namespace mm { + + /** object with owned state + * - with default DRepr argument: + * type-erased container (runtime polymorphism). + * - with sepcific DRepr argument: + * typed container (comptime polymorphism). + * + * Similar to box (see box.hpp in xo-facet/): + * 1. inherits fat object pointer with AFacet*, DRepr* pair + * 2. automatically calls polymorphic DRepr::~DRepr when + * abox goes out of scope. + * Unlike box: + * 3. gets memory from explicit arena-like allocator + * 4. calls dtor DRepr::~DRepr(), but not delete. + * Does not retain allocator. + **/ + template + struct abox : public xo::facet::RoutingType> { + using DVariantPlaceholder = xo::facet::DVariantPlaceholder; + using Super = xo::facet::RoutingType>; + + abox() : Super() {} + + /** abox takes ownership of data @p *d; + * will destroy when abox goes out of scope. + * + * Note this is not useful when DRepr=DVariablePlaceholder + **/ + explicit abox(Super::DataPtr d) : Super(d) {} + + /** Adopt instance that has interface @p iface and (type-erased here) + * representation @p data + **/ + abox(const AFacet * iface, void * data) + requires std::is_same_v + : Super(iface, data) + {} + + /** (copy ctor not supported -- ownership is unique) **/ + abox(const abox & other) = delete; + + /** Move constructor **/ + template + abox(abox && other) + requires (std::is_same_v + || std::is_same_v) + : xo::facet::RoutingType>() + { + /* replacing .iface_ along w/ .data_ */ + this->from_obj(other); + + other.reset_opaque(nullptr); + } + + /** allocates for sizeof(DRepr), so DRepr must not use flexible array **/ + template + static abox make(obj alloc, Args&&... args) { + void * mem = alloc.alloc_for(); + if (mem) { + DRepr * data = ::new (mem) DRepr(std::forward(args)...); + + assert(data); + + return abox(data); + } else { + auto avail = alloc.available(); + auto req = sizeof(DRepr); + + std::cout << "panic: unable to allocate for abox" + << " :req " << req << " :avail " << avail + << std::endl; + std::terminate(); + + return abox(); + } + } + + // -------------------------------- + + /** explicit conversion to obj **/ + obj to_op() const noexcept + requires std::is_same_v + { + return obj(this->iface(), this->data()); + } + + obj to_op() const noexcept + requires (!std::is_same_v) + { + return obj(this->data()); + } + + /** Take ownership from unowned object **/ + template + abox & adopt(const obj & other) + requires (std::is_same_v + || std::is_same_v) + { + /* replace .iface_ along w/ .data_ */ + this->from_obj(other); + + return *this; + } + + ~abox() { + auto p = this->data(); + if (p) { + this->_drop(); + } + } + }; + } /*namespace mm*/ + + using mm::abox; +} /*namespace xo*/ + +/* end abox.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/alloc/AAllocIterator.hpp b/xo-alloc2/include/xo/alloc2/alloc/AAllocIterator.hpp new file mode 100644 index 00000000..e7581dc4 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/alloc/AAllocIterator.hpp @@ -0,0 +1,45 @@ +/** @file AAllocIterator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "AllocInfo.hpp" +#include "cmpresult.hpp" +#include "typeseq.hpp" +#include + +namespace xo { + namespace mm { + using Copaque = const void *; + using Opaque = void *; + + /** @class AAllocIterator + * @brief Abstract facet for iterating over allocs + * + * Iterator refers to an AllocInfo instance + * Only supporting forward-allocator. + **/ + struct AAllocIterator { + using obj_AAllocIterator = xo::facet::obj; + using typeseq = xo::reflect::typeseq; + + /** @defgroup mm-allociterator-methods AllocIterator methods **/ + ///@{ + /** RTTI: unique id# for actual runtime *data* representation **/ + virtual typeseq _typeseq() const noexcept = 0; + /** retrieve AllocInfo for current iterator position + **/ + virtual AllocInfo deref(Copaque d) const noexcept = 0; + /** compare alloc iterators @p d and @p other **/ + virtual cmpresult compare(Copaque d, + const obj_AAllocIterator & other) const noexcept = 0; + /** advance iterator to next position **/ + virtual void next(Opaque d) const noexcept = 0; + ///@} + }; + } /*namespace mm*/ +} /*namespace xo*/ + +/* end AAllocIterator.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/alloc/AAllocator.hpp b/xo-alloc2/include/xo/alloc2/alloc/AAllocator.hpp new file mode 100644 index 00000000..7a940beb --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/alloc/AAllocator.hpp @@ -0,0 +1,221 @@ +/** @file AAllocator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include +#include +#include "AllocInfo.hpp" +#include "AllocRange.hpp" +#include "typeseq.hpp" +#include +#include +#include +#include + +namespace xo { + namespace mm { + using Copaque = const void *; + using Opaque = void *; + + class AGCObject; // see AGCObject.hpp + struct DArena; // see DArena.hpp + + /** @class AAllocator + * @brief Abstract facet for allocation + * + * Methods take a opaque data pointer. + * Implementations of AAllocator will downcast to a + * to some specific representation. + **/ + class AAllocator { + public: + /** @defgroup mm-allocator-type-traits allocator type traits **/ + ///@{ + using AGCObject = xo::mm::AGCObject; + /** memory size report **/ + using MemorySizeInfo = xo::mm::MemorySizeInfo; + /** type used for allocation amounts **/ + using size_type = std::size_t; + /** type used for allocation responses **/ + using value_type = std::byte *; + /** object header, if configured **/ + using header_type = std::uint64_t; + /** iterator range. These are forward iterators over allocs **/ + using range_type = AllocRange; + /** sequence number identifying a datatype **/ + using typeseq = xo::facet::typeseq; + ///@} + + /* + * <----------------------------size--------------------------> + * <------------committed-----------><-------uncommitted------> + * <--allocated--> + * + * XXXXXXXXXXXXXXX___________________.......................... + * + * allocated: in use + * committed: physical memory obtained + * uncommitted: mapped in virtual memory, not backed by memory + */ + + /** @defgroup mm-allocator-methods Allocator methods **/ + ///@{ + + /** An uninitialized AAllocator 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++ destructor for actual runtime type. + * does not recover memory. + **/ + virtual void _drop(Opaque d) const noexcept = 0; + /** optional name for allocator @p d . + * Allows labeling allocators, for diagnostics/instrumentation. + **/ + virtual std::string_view name(Copaque d) const noexcept = 0; + /** reserved size in bytes for allocator @p d. + * Includes committed + uncommitted memory. + **/ + virtual size_type reserved(Copaque d) const noexcept = 0; + /** Synonym for @ref committed. + * Can increase automatically on @ref alloc + **/ + virtual size_type size(Copaque d) const noexcept = 0; + /** committed size (physical addresses obtained) + * for allocator @p d. + * @ref alloc may auto-increase this + **/ + virtual size_type committed(Copaque d) const noexcept = 0; + /** unallocated (but committed) size in bytes for allocator @p d. + * An alloc request up to this size (including guard / header) + * is guaranteed to succeed. + * An alloc request of more than this size may still succeed, + * if allocator can automatically extend committed memory. + * This is the case for the @ref xo::mm::DArena allocator + **/ + virtual size_type available(Copaque d) const noexcept = 0; + /** allocated (i.e. currently in-use) amount in bytes for allocator @p d. + * Includes alloc headers and guard regions + **/ + virtual size_type allocated(Copaque d) const noexcept = 0; + /** call @p fn(i,n,info) for each memory pool owned by this allocator. + * Note: using std::function instead of obj<> to avoid leveling trouble + * with DArena + **/ + virtual void visit_pools(Copaque d, + const MemorySizeVisitor & fn) const = 0; + /** true iff allocator @p d is responsible for memory at address @p p. + **/ + virtual bool contains(Copaque d, const void * p) const noexcept = 0; + /** report details of last error for allocator @p d. **/ + virtual AllocError last_error(Copaque d) const noexcept = 0; + /** fetch alloc info: given memory @p mem previously obtained + * from {@ref alloc, @ref super_alloc}, get {tseq, age, size} details + * for that allocation. + * + * Non-const @p d because may stash error details + **/ + virtual AllocInfo alloc_info(Copaque d, value_type mem) const noexcept = 0; + /** + * Create an iterator range for allocator @p d. + * An iterator range has begin and end methods, so supports c++ range iteration. + * Memory for iterator state will be obtained from @p mm. + **/ + virtual range_type alloc_range(Copaque d, DArena & mm) const noexcept = 0; + + /** expand committed space in arena @p d + * to size at least @p z. + * In practice will round up to a multiple of page size (4K) or hugepage size (2MB) + * depending on configuration. + **/ + virtual bool expand(Opaque d, std::size_t z) const noexcept = 0; + /** attempt to allocate @p z bytes of memory from allocator @p d. + * for object with type @p t. + * (DX1collector cares about @p t, DArena does not) + * If allocation fails returns nullptr. In this case error details may be retrieved + * using last error + **/ + virtual value_type alloc(Opaque d, typeseq t, size_type z) const = 0; + /** like @ref alloc, but follow with one or more consecutive + * @ref sub_alloc() calls. This sequence of allocs will share + * the initial allocation header. + **/ + virtual value_type super_alloc(Opaque d, typeseq t, size_type z) const = 0; + /** follow a preceding @ref super_alloc call with additional + * subsidiary allocs that share the same object header. + * Must finish sequence with exactly one sub_alloc call + * with @p complete_flag set. This sub_alloc call may have + * zero @p z. + **/ + virtual value_type sub_alloc(Opaque d, size_type z, bool complete_flag) const = 0; +#ifdef OBSOLETE + /** Allocate copy of an existing object @p src. + * Existing object must be contained in @p d. + * NOTE: load bearing for copying garbage collector. + **/ + virtual value_type alloc_copy(Opaque d, value_type src) const = 0; +#endif + /** reset allocator @p d to empty state. **/ + virtual void clear(Opaque d) const = 0; + /** assign helper for allocator that may require a write barrier + * (for example Collector). Using obj here causes + * include cycle, use spelled out form instead; + * + * For: + * obj lhs_ = rhs + * with allocator d, that owns some allocaiton p, would use + * mm.barrier_assign_aux(d, p, + * lhs_.iface(), lhs_.opaque_data_addr(), + * rhs.iface(), rhs.opaque_data()); + * when lhs has known data type: + * DRepr * lhs_ = rhs.data(); + * use + * mm.barrier_assign_aux(d, p, + * nullptr, lhs_.opaque_data_addr(), + * rhs.iface(), rhs.opaque_data()); + * barrier needs to be able to construct complete fop for rhs + * to administrate write barrier. + **/ + virtual void barrier_assign_aux(Opaque d, void * parent, + AGCObject * lhs_iface, void ** lhs_data, + AGCObject * rhs_iface, void * rhs_data) const = 0; + + ///@} + }; /*AAllocator*/ + + // implementation IAllocator_DRepr of AAllocator for state DRepr + // should provide a specialization: + // + // template <> + // struct xo::facet::FacetImplementation { + // using ImplType = IAllocator_DRepr; + // }; + // + // then IAllocator_ImplType --> IAllocator_DRepr + // + template + using IAllocator_ImplType = xo::facet::FacetImplType; + +// can't we do this with FacetImplementation +// +// template +// struct IAllocator_Impl {}; + +// template +// using IAllocator_ImplType = IAllocator_Impl::ImplType; + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end AAllocator.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/alloc/IAllocIterator_Any.hpp b/xo-alloc2/include/xo/alloc2/alloc/IAllocIterator_Any.hpp new file mode 100644 index 00000000..1bed0ff1 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/alloc/IAllocIterator_Any.hpp @@ -0,0 +1,48 @@ +/** @file IAllocIterator_Any.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "AAllocIterator.hpp" + +namespace xo { + namespace mm { struct IAllocIterator_Any; } + + namespace facet { + template <> + struct FacetImplementation { + using ImplType = xo::mm::IAllocIterator_Any; + }; + } + + namespace mm { + /** @class IAllocIterator_Any + * @brief AllocIterator implementation for empty variant instance + **/ + struct IAllocIterator_Any : public AAllocIterator { + using typeseq = xo::reflect::typeseq; + + const AAllocIterator * iface() const { return std::launder(this); } + + // from AAllocIterator + typeseq _typeseq() const noexcept override { return s_typeseq; } + + // const methods + [[noreturn]] AllocInfo deref(Copaque) const noexcept override { _fatal(); } + [[noreturn]] cmpresult compare(Copaque, + const obj_AAllocIterator &) const noexcept override { _fatal(); } + + // non-const methods + [[noreturn]] void next(Opaque) const noexcept override { _fatal(); } + + private: + [[noreturn]] static void _fatal(); + + public: + static typeseq s_typeseq; + static bool _valid; + }; + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocIterator_Any.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/alloc/IAllocIterator_Xfer.hpp b/xo-alloc2/include/xo/alloc2/alloc/IAllocIterator_Xfer.hpp new file mode 100644 index 00000000..251f4afd --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/alloc/IAllocIterator_Xfer.hpp @@ -0,0 +1,62 @@ +/** @file IAllocIterator_Xfer.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "AAllocIterator.hpp" + +namespace xo { + namespace mm { + /** @class IAllocIterator_Xfer + * @brief Adapts typed alloc iterator implementation + * @tparam IAllocIterator_DRepr to type-erased + * @ref AAllocIterator instance + **/ + template + struct IAllocIterator_Xfer : public AAllocIterator { + using Impl = IAllocIterator_DRepr; + using typeseq = xo::reflect::typeseq; + + static const DRepr & _dcast(Copaque d) { return *(const DRepr *)d; } + static DRepr & _dcast(Opaque d) { return *(DRepr *)d; } + + // from AAllocIterator + + // const methods + + typeseq _typeseq() const noexcept override { return s_typeseq; } + AllocInfo deref(Copaque d) + const noexcept override { return I::deref(_dcast(d)); } + cmpresult compare(Copaque d, + const obj_AAllocIterator & other) + const noexcept override + { return I::compare(_dcast(d), other); } + + // non-const methods + + void next(Opaque d) const noexcept override { I::next(_dcast(d)); } + + private: + using I = Impl; + + public: + static typeseq s_typeseq; + static bool _valid; + }; + + template + xo::reflect::typeseq + IAllocIterator_Xfer::s_typeseq + = reflect::typeseq::id(); + + template + bool + IAllocIterator_Xfer::_valid + = facet::valid_facet_implementation(); + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocIterator_Xfer.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Any.hpp b/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Any.hpp new file mode 100644 index 00000000..bc91fcac --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Any.hpp @@ -0,0 +1,81 @@ +/** @file IAllocator_Any.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "AAllocator.hpp" +#include "AllocIterator.hpp" +#include "typeseq.hpp" +#include + +namespace xo { + namespace mm { struct IAllocator_Any; } + + namespace facet { + template <> + struct FacetImplementation { + using ImplType = xo::mm::IAllocator_Any; + }; + } + + namespace mm { + /** @class IAllocator_Any + * @brief Allocator implementation for empty variant instance. + **/ + struct IAllocator_Any : public AAllocator { + //using Impl = IAllocator_ImplType; + using typeseq = xo::facet::typeseq; + using size_type = std::size_t; + + const AAllocator * iface() const { return std::launder(this); } + + // from AAllocator + + // builtin methods + typeseq _typeseq() const noexcept override { return s_typeseq; } + + // LCOV_EXCL_START + void _drop(Opaque) const noexcept override { _fatal(); } + + // const methods + [[noreturn]] std::string_view name(Copaque) const noexcept override { _fatal(); } + [[noreturn]] size_type reserved(Copaque) const noexcept override { _fatal(); } + [[noreturn]] size_type size(Copaque) const noexcept override { _fatal(); } + [[noreturn]] size_type committed(Copaque) const noexcept override { _fatal(); } + [[noreturn]] size_type available(Copaque) const noexcept override { _fatal(); } + [[noreturn]] size_type allocated(Copaque) const noexcept override { _fatal(); } + [[noreturn]] bool contains(Copaque, const void *) const noexcept override { _fatal(); } + [[noreturn]] void visit_pools(Copaque, + const MemorySizeVisitor &) const override { _fatal(); } + [[noreturn]] AllocError last_error(Copaque) const noexcept override { _fatal(); } + [[noreturn]] AllocInfo alloc_info(Copaque, value_type) const noexcept override { _fatal(); } + // defn in .cpp - problematic to require compiler know vt defn here + //[[noreturn]] facet::vt begin(Copaque, DArena &) const noexcept override; // { _fatal(); } + [[noreturn]] range_type alloc_range(Copaque, DArena &) const noexcept override { _fatal(); } + + // non-const methods + [[noreturn]] bool expand(Opaque, std::size_t) const noexcept override { _fatal(); } + [[noreturn]] value_type alloc(Opaque, typeseq, std::size_t) const override { _fatal(); } + [[noreturn]] value_type super_alloc(Opaque, typeseq, std::size_t) const override { _fatal(); } + [[noreturn]] value_type sub_alloc(Opaque, std::size_t, bool) const override { _fatal(); } + [[noreturn]] void clear(Opaque) const override { _fatal(); } + [[noreturn]] void barrier_assign_aux(Opaque, + void *, + AGCObject *, void **, + AGCObject *, void *) const override { _fatal(); } + + private: + [[noreturn]] static void _fatal(); + // LCOV_EXCL_STOP + + public: + static typeseq s_typeseq; + static bool _valid; + }; + } + +} + +/* end IAllocator_Any.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Xfer.hpp b/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Xfer.hpp new file mode 100644 index 00000000..d2cd9504 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/alloc/IAllocator_Xfer.hpp @@ -0,0 +1,107 @@ +/** @file IAllocator_Xfer.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "AAllocator.hpp" + +namespace xo { + namespace mm { + /** @class IAllocator_Xfer + * + * @tparam DRepr target representation + * @tparam IAllocator_DRepr typed interface for @p DRepr + * + * Adapts typed allocator implementation @p IAllocator_DRepr + * to type-erased @ref AAllocator interface + **/ + template + struct IAllocator_Xfer : public AAllocator { + /** @defgroup mm-allocator-xfer-types **/ + ///@{ + // parallel interface to AAllocator, with specific data type + using Impl = IAllocator_DRepr; + using size_type = AAllocator::size_type; + using value_type = AAllocator::value_type; + using typeseq = AAllocator::typeseq; + ///@} + + /** @defgroup mm-allocator-xfer-methods IAllocator_Xfer methods **/ + ///@{ + + static const DRepr & _dcast(Copaque d) { return *(const DRepr *)d; } + static DRepr & _dcast(Opaque d) { return *(DRepr *)d; } + + // from AAllocator + + // builtin methods + /** return typeseq for @tparam DRepr **/ + typeseq _typeseq() const noexcept override { return s_typeseq; } + /** invoke native c++ dtor **/ + void _drop(Opaque d) const noexcept override { _dcast(d).~DRepr(); } + + // const methods + + std::string_view name(Copaque d) const noexcept override { return I::name(_dcast(d)); } + size_type reserved(Copaque d) const noexcept override { return I::reserved(_dcast(d)); } + size_type size(Copaque d) const noexcept override { return I::size(_dcast(d)); } + size_type committed(Copaque d) const noexcept override { return I::committed(_dcast(d)); } + size_type available(Copaque d) const noexcept override { return I::available(_dcast(d)); } + size_type allocated(Copaque d) const noexcept override { return I::allocated(_dcast(d)); } + bool contains(Copaque d, const void * p) const noexcept override { + return I::contains(_dcast(d), p); + } + void visit_pools(Copaque d, const MemorySizeVisitor & fn) const override { I::visit_pools(_dcast(d), fn); } + AllocError last_error(Copaque d) const noexcept override { return I::last_error(_dcast(d)); } + AllocInfo alloc_info(Copaque d, value_type mem) const noexcept override { + return I::alloc_info(_dcast(d), mem); + } + range_type alloc_range(Copaque d, DArena & mm) const noexcept override { return I::alloc_range(_dcast(d), mm); } + + // non-const methods + + bool expand(Opaque d, + std::size_t z) const noexcept override { return I::expand(_dcast(d), z); } + value_type alloc(Opaque d, + typeseq t, + std::size_t z) const override { return I::alloc(_dcast(d), t, z); } + value_type super_alloc(Opaque d, + typeseq t, + std::size_t z) const override { return I::super_alloc(_dcast(d), t, z); } + value_type sub_alloc(Opaque d, + std::size_t z, + bool complete_flag) const override { + return I::sub_alloc(_dcast(d), z, complete_flag); + } +#ifdef OBSOLETE + value_type alloc_copy(Opaque d, + value_type src) const override { return I::alloc_copy(_dcast(d), src); } +#endif + void clear(Opaque d) const override { return I::clear(_dcast(d)); } + void barrier_assign_aux(Opaque d, + void * parent, + AGCObject * lhs_iface, void ** lhs_data, + AGCObject * rhs_iface, void * rhs_data) const override { I::barrier_assign_aux(_dcast(d), parent, lhs_iface, lhs_data, rhs_iface, rhs_data); } + ///@} + + private: + using I = Impl; + + public: + static xo::facet::typeseq s_typeseq; + static bool _valid; + }; + + template + xo::facet::typeseq + IAllocator_Xfer::s_typeseq = facet::typeseq::id(); + + template + bool + IAllocator_Xfer::_valid = facet::valid_facet_implementation(); + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocator_Xfer.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/alloc/RAllocIterator.hpp b/xo-alloc2/include/xo/alloc2/alloc/RAllocIterator.hpp new file mode 100644 index 00000000..47d59f29 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/alloc/RAllocIterator.hpp @@ -0,0 +1,67 @@ +/** @file RAllocIterator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "AAllocIterator.hpp" +#include +//#include + +namespace xo { + namespace mm { + /* @class RAllocIterator */ + template + struct RAllocIterator : public Object { + private: + using O = Object; + public: + using ObjectType = Object; + using DataPtr = Object::DataPtr; + using typeseq = xo::reflect::typeseq; + + RAllocIterator() {} + RAllocIterator(Object::DataPtr data) : Object{std::move(data)} {} + + typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); } + AllocInfo deref() const noexcept { return O::iface()->deref(O::data()); } + cmpresult compare(const obj & other) const noexcept { + return O::iface()->compare(O::data(), other); } + void next() noexcept { O::iface()->next(O::data()); } + + AllocInfo operator*() const noexcept { return deref(); } + + /** triggers operator++ in obj> **/ + void _preincrement() noexcept { this->next(); } + + static bool _valid; + }; + + template + bool + RAllocIterator::_valid = facet::valid_object_router(); + } /*namespace mm*/ + + namespace facet { + template + struct RoutingFor { + using RoutingType = xo::mm::RAllocIterator; + }; + + /* also provide comparison */ + template + inline bool operator==(obj lhs, + obj rhs) + { + return lhs.compare(rhs).is_equal(); + } + + template + inline bool operator!=(obj lhs, + obj rhs) + { + return !lhs.compare(rhs).is_equal(); + } + } +} /*namespace xo*/ + +/* end RAllocIterator.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/alloc/RAllocator.hpp b/xo-alloc2/include/xo/alloc2/alloc/RAllocator.hpp new file mode 100644 index 00000000..b44d0a24 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/alloc/RAllocator.hpp @@ -0,0 +1,112 @@ +/** @file RAllocator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "Allocator_basic.hpp" // omits RAllocator_aux +#include "AllocIterator.hpp" +#include +#include + +namespace xo { + namespace mm { + /** @class RAllocator **/ + template + struct RAllocator : public Object { + private: + using O = Object; + public: + using ObjectType = Object; + using DataPtr = Object::DataPtr; + using typeseq = xo::facet::typeseq; + using size_type = std::size_t; + using value_type = std::byte *; + using range_type = AAllocator::range_type; + + RAllocator() {} + RAllocator(Object::DataPtr data) : Object{std::move(data)} {} + RAllocator(const AAllocator * iface, void * data) + requires std::is_same_v + : Object(iface, data) {} + + template + void * alloc_for(size_type n = sizeof(T)) noexcept { + return O::iface()->alloc(O::data(), typeseq::id(), n); + } + + template + void * alloc_copy_for(const T * src) noexcept { + return O::iface()->alloc_copy(O::data(), (std::byte*)const_cast(src)); + } + + template + T * std_copy_for(T * src) noexcept { + void * mem = this->alloc_copy_for(src); + + if (mem) { + return new (mem) T(std::move(*src)); + } + + return nullptr; + } + + bool _has_null_vptr() const noexcept { return O::iface()->_has_null_vptr(); } + typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); } + void _drop() const noexcept { O::iface()->_drop(O::data()); } + std::string_view name() const noexcept { return O::iface()->name(O::data()); } + size_type reserved() const noexcept { return O::iface()->reserved(O::data()); } + size_type size() const noexcept { return O::iface()->size(O::data()); } + size_type committed() const noexcept { return O::iface()->committed(O::data()); } + size_type available() const noexcept { return O::iface()->available(O::data()); } + size_type allocated() const noexcept { return O::iface()->allocated(O::data()); } + void visit_pools(const MemorySizeVisitor & fn) const { O::iface()->visit_pools(O::data(), fn); } + bool contains(const void * p) const noexcept { return O::iface()->contains(O::data(), p); } + AllocError last_error() const noexcept { return O::iface()->last_error(O::data()); } + AllocInfo alloc_info(value_type mem) const noexcept { return O::iface()->alloc_info(O::data(), mem); } + range_type alloc_range(DArena & mm) const noexcept { return O::iface()->alloc_range(O::data(), mm); } + + bool expand(size_type z) { return O::iface()->expand(O::data(), z); } + value_type alloc(typeseq t, size_type z) noexcept { return O::iface()->alloc(O::data(), t, z); } + value_type super_alloc(typeseq t, size_type z) noexcept { return O::iface()->super_alloc(O::data(), t, z); } + value_type sub_alloc(size_type z, + bool complete_flag) noexcept { return O::iface()->sub_alloc(O::data(), + z, complete_flag); } + value_type alloc_copy(value_type src) noexcept { return O::iface()->alloc_copy(O::data(), src); } + void clear() { O::iface()->clear(O::data()); } + void barrier_assign_aux(void * parent, + AGCObject * lhs_iface, void ** lhs_data, + AGCObject * rhs_iface, void * rhs_data) noexcept { O::iface()->barrier_assign_aux(O::data(), parent, + lhs_iface, lhs_data, + rhs_iface, rhs_data); } + + // see [RAllocator_aux.hpp] for implementation. + void barrier_assign(void * parent, + obj * p_lhs, + obj rhs) noexcept; + + // Need _drepr suffix to distinguish from .barrier_assign() + // see [RAllocator_aux.hpp] for implementation + template + void barrier_assign_drepr(void * parent, + DRepr ** lhs_data, + DRepr * rhs_data); + + static bool _valid; + }; + + template + bool + RAllocator::_valid = facet::valid_object_router(); + } + + namespace facet { + template + struct RoutingFor { + using RoutingType = xo::mm::RAllocator; + }; + } +} + +/* end RAllocator.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/alloc/RAllocator_aux.hpp b/xo-alloc2/include/xo/alloc2/alloc/RAllocator_aux.hpp new file mode 100644 index 00000000..835b45fb --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/alloc/RAllocator_aux.hpp @@ -0,0 +1,71 @@ +/** @file RAllocator_aux.hpp + * + * Out-of-line definitions for RAllocator template methods + * that depend on RGCObject (avoiding #include cycle in RAllocator.hpp) + * + * Would aspire to include via user_hpp_includes in Allocator.json5, + * if/when that exists + * + * @author Roland Conybeare + **/ + +#pragma once + +#include "RAllocator.hpp" +#include "GCObject.hpp" + +namespace xo { + namespace mm { + + /** NOTE: + * this definition is incorporated into [Allocator.hpp], + * while consciously omitted from [Allocator_basic.hpp]. + * + * Some .hpp files in {xo-alloc2/, xo-gc/} can + * only include [Allocator_basic.hpp]. + * + * Translation units that want to invoke + * barrier_assign() must #include Allocator.hpp + **/ + template + void + RAllocator::barrier_assign(void * parent, + obj * p_lhs, + obj rhs) noexcept + { + if (this->data()) { + this->barrier_assign_aux(parent, + p_lhs->iface(), p_lhs->opaque_data_addr(), + rhs.iface(), rhs.opaque_data()); + } else { + // special case: for null allocator want no write-barrier + *p_lhs = rhs; + } + } + + template + template + void + RAllocator::barrier_assign_drepr(void * parent, + DRepr ** lhs_data, + DRepr * rhs_data) + { + // need to get AGCObject i/face that goes with DRepr. + obj rhs_gco(rhs_data); + + if (this->data()) { + this->barrier_assign_aux(parent, + nullptr /*not needed*/, + lhs_data, + rhs_gco.iface(), + rhs_data); + } else { + // special case: for null allocator want no write-barrier + *lhs_data = rhs_data; + } + } + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end RAllocator_aux.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/arena/IAllocIterator_DArenaIterator.hpp b/xo-alloc2/include/xo/alloc2/arena/IAllocIterator_DArenaIterator.hpp new file mode 100644 index 00000000..5af231ac --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/arena/IAllocIterator_DArenaIterator.hpp @@ -0,0 +1,36 @@ +/** @file IAllocIterator_DArenaIterator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "alloc/IAllocIterator_Xfer.hpp" +#include + +namespace xo { + namespace mm { struct IAllocIterator_DArenaIterator; } + + namespace facet { + template <> + struct FacetImplementation { + using ImplType = xo::mm::IAllocIterator_Xfer; + }; + } + + namespace mm { + /** @class IAllocIterator_DArena + * @brief alloc iteration for the DArena allocator + **/ + struct IAllocIterator_DArenaIterator { + static AllocInfo deref(const DArenaIterator &) noexcept; + static cmpresult compare(const DArenaIterator &, + const obj & other) noexcept; + static void next(DArenaIterator &) noexcept; + }; + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocIterator_DArenaIterator.cpp */ diff --git a/xo-alloc2/include/xo/alloc2/arena/IAllocator_DArena.hpp b/xo-alloc2/include/xo/alloc2/arena/IAllocator_DArena.hpp new file mode 100644 index 00000000..a178e5a0 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/arena/IAllocator_DArena.hpp @@ -0,0 +1,92 @@ +/** @file IAllocator_DArena.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "alloc/AAllocator.hpp" +#include "alloc/IAllocator_Xfer.hpp" +#include + +namespace xo { + namespace mm { struct IAllocator_DArena; } + + namespace facet { + template <> + struct FacetImplementation { + using ImplType = xo::mm::IAllocator_Xfer; + }; + } + + namespace mm { + /** @class IAllocator_DArena + * @brief Provide AAllocator interface for DArena state + **/ + struct IAllocator_DArena { + /* changes here coordinate with: + * AAllocator AAllocator.hpp + * IAllocator_Any IAllocator_Any.hpp + * IAllocator_Xfer IAllocator_Xfer.hpp + * RAllocator RAllocator.hpp + */ + using typeseq = xo::facet::typeseq; + using size_type = std::size_t; + using value_type = std::byte *; + using range_type = AAllocator::range_type; + + static std::string_view name(const DArena &) noexcept; + static size_type reserved(const DArena &) noexcept; + static size_type size(const DArena &) noexcept; + static size_type committed(const DArena &) noexcept; + static size_type available(const DArena &) noexcept; + static size_type allocated(const DArena &) noexcept; + static void visit_pools(const DArena &, + const MemorySizeVisitor & visitor); + static bool contains(const DArena &, const void * p) noexcept; + static AllocError last_error(const DArena &) noexcept; + /** retrieve allocation bookkeeping info for @p mem from arena @p d **/ + static AllocInfo alloc_info(const DArena &, value_type mem) noexcept; + /** create alloc-iterator range over allocs on @d, + * Iterators themselves allocated from @p ialloc. + **/ + static range_type alloc_range(const DArena & d, DArena & ialloc) noexcept; + + /** expand committed space in arena @p d + * to size at least @p z + * In practice will round up to a multiple of @ref page_z_. + **/ + static bool expand(DArena & d, size_type z) noexcept; + + static value_type alloc(DArena &, typeseq t, size_type z); + /** when store_header_flag enabled: + * like alloc(), but combine memory consumed by this alloc + * plus following consecutive sub_alloc()'s into a single header. + * otherwise equivalent to alloc() + **/ + static value_type super_alloc(DArena &, typeseq t, size_type z); + /** when store_header_flag enabled: + * follow preceding super_alloc() by one or more sub_allocs(). + * accumulate total allocated size (including padding) into + * single header. All sub_allocs() except the last must set + * @p complete_flag to false. The last sub_alloc() must set + * @p complete_flag to true. + **/ + static value_type sub_alloc(DArena &, size_type z, bool complete_flag); + static void clear(DArena &); + /** perform assignment {*lhs_iface, *lhs_data} = {*rhs_iface, rhs_data} **/ + static void barrier_assign_aux(DArena &, + void * parent, + AGCObject * lhs_iface, void ** lhs_data, + AGCObject * rhs_iface, void * rhs_data); + //static void destruct_data(DArena &); + }; + +// template <> +// struct IAllocator_Impl { +// using ImplType = IAllocator_DArena; +// }; + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocator_DArena.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/dp.hpp b/xo-alloc2/include/xo/alloc2/dp.hpp new file mode 100644 index 00000000..8f61b7e1 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/dp.hpp @@ -0,0 +1,128 @@ +/** @file dp.hpp + * + * @author Roland Conybeare, Feb 2026 + **/ + +#pragma once + +#include "Allocator.hpp" +#include +#include + +namespace xo { + namespace mm { + + /** unimorphic "data pointer" with known representation and owned memory. + * runs dtor *but not delete*. Does not store allocator! + * + * Compare with abox + **/ + template + struct dp { + using repr_type = DRepr; + + public: + dp() = default; + + /** dp takes ownership of data @p ptr; + * will run dtor when dp goes out of scope. + * + * Note this is not useful when DRepr=DVariablePlaceholder + **/ + explicit dp(DRepr * ptr) : ptr_{ptr} {} + + /** (copy ctor not supported -- ownership is unique) **/ + dp(const dp & other) = delete; + + /** Move constructor **/ + dp(dp && other) + { + ptr_ = other.ptr_; + other.ptr_ = nullptr; + } + + /** allocates for sizeof(DRepr), so DRepr must not use flexible array **/ + template + static dp make(obj alloc, Args&&... args) { + void * mem = alloc.alloc_for(); + + if (mem) { + DRepr * data = ::new (mem) DRepr(std::forward(args)...); + assert(data); + + return dp(data); + } else { + assert(false); + + return dp(); + } + } + + static constexpr bool is_gc_eligible() { return DRepr::is_gc_eligible(); } + + dp & operator=(const dp & x) = delete; + + /** move assignment **/ + dp & operator=(dp && x) { + ptr_ = x.ptr_; + x.ptr_ = nullptr; + + return *this; + } + + // -------------------------------- + + DRepr * data() const noexcept { return ptr_; } + + operator bool() const noexcept { return ptr_ != nullptr; } + + DRepr * operator->() const noexcept { return ptr_; } + DRepr & operator*() const noexcept { return *ptr_; } + + /** transfer responsibility for pointer to caller, + * setting dp container's copy to nullptr + **/ + DRepr * release() noexcept { + DRepr * retval = ptr_; + + this->ptr_ = nullptr; + + return retval; + } + +#ifdef NOT_YET + /** explicit conversion to obj **/ + obj to_op() const noexcept { + return obj(this->iface(), this->data()); + } +#endif + +#ifdef NOT_YET + /** Take ownership from unowned object **/ + template + dp & adopt(const obj & other) + requires (std::is_same_v + || std::is_same_v) + { + /* replace .iface_ along w/ .data_ */ + this->from_obj(other); + + return *this; + } +#endif + + ~dp() { + if (ptr_) { + ptr_->~DRepr(); + } + } + + private: + DRepr * ptr_ = nullptr; + }; + } /*namespace mm*/ + + using mm::dp; +} /*namespace xo*/ + +/* end dp.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/ACollector.hpp b/xo-alloc2/include/xo/alloc2/gc/ACollector.hpp new file mode 100644 index 00000000..19be0039 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/ACollector.hpp @@ -0,0 +1,146 @@ +/** @file ACollector.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/Collector.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [abstract_facet.hpp.j2] + * 3. idl for facet methods + * [idl/Collector.json5] + **/ + +#pragma once + +// includes (via {facet_includes}) +#include +#include +#include +#include +#include +#include + +namespace xo { namespace mm { class AGCObject; } } +namespace xo { namespace mm { class IGCObject_Any; } } +// more pretext here.. + +namespace xo { +namespace mm { + +using Copaque = const void *; +using Opaque = void *; + +/** +A collector must also suppose the @ref AAllocator facet, see also +**/ +class ACollector { +public: + /** @defgroup mm-collector-type-traits **/ + ///@{ + // types + /** integer identifying a type **/ + using typeseq = xo::facet::typeseq; + using Copaque = const void *; + using Opaque = void *; + /** allocation size type **/ + using size_type = std::size_t; + ///@} + + /** @defgroup mm-collector-methods **/ + ///@{ + // const methods + /** An uninitialized ACollector 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 **/ + virtual void _drop(Opaque d) const noexcept = 0; + /** memory in use for this collector **/ + virtual size_type allocated(Copaque data, Generation g, Role r) const noexcept = 0; + /** memory committed for this collector **/ + virtual size_type committed(Copaque data, Generation g, Role r) const noexcept = 0; + /** address space reserved for this collector **/ + virtual size_type reserved(Copaque data, Generation g, Role r) const noexcept = 0; + /** Location of object in collector. -1 if not in collector memory. +Other negative values represent collector error states (good luck!). +Exact meaning of non-negative values up to collector implementation **/ + virtual std::int32_t locate_address(Copaque data, const void * addr) const noexcept = 0; + /** true if gc responsible for data at @p addr, and data belongs to Role @p r **/ + 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; + /** Report gc object types, at discretion of collector implementation. +Creates dictionary using memory from @p report_mm. +If unable to comply (e.g. oom), return runtime error allocated from @p error_mm. +Avoiding obj return type to avoid #include cycle **/ + virtual bool report_object_types(Copaque data, obj report_mm, obj error_mm, obj * output) const noexcept = 0; + /** 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 +in collector @p d. + +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. + +Return false if installation fails (e.g. memory exhausted) **/ + virtual bool install_type(Opaque data, const AGCObject & iface) = 0; + /** add gc root with address @p p_root. gc will preserve subgraph at this address **/ + virtual void add_gc_root_poly(Opaque data, obj * p_root) = 0; + /** remove gc root with address @p p_root. Reverse effect of prior add_gc_root_poly call **/ + virtual void remove_gc_root_poly(Opaque data, obj * p_root) = 0; + /** 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. + **/ + virtual void request_gc(Opaque data, Generation upto) = 0; + /** Assign pointer @p p_lhs to destination @p rhs, within parent allocation @p parent +DEPRECATED. Only used in MockCollector for gc unit test + +Require: gc not in progress **/ + virtual void assign_member(Opaque data, void * parent, obj * p_lhs, obj & rhs) = 0; + /** allocate copy of source object at address @p src. +Source must be owned by this collector. +Increments object age **/ + virtual void * alloc_copy(Opaque data, std::byte * src) = 0; + ///@} +}; /*ACollector*/ + +/** Implementation ICollector_DRepr of ACollector for state DRepr + * should provide a specialization: + * + * template <> + * struct xo::facet::FacetImplementation { + * using Impltype = ICollector_DRepr; + * }; + * + * then ICollector_ImplType --> ICollector_DRepr + **/ +template +using ICollector_ImplType = xo::facet::FacetImplType; + +} /*namespace mm*/ +} /*namespace xo*/ + +/* ACollector.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/AGCObject.hpp b/xo-alloc2/include/xo/alloc2/gc/AGCObject.hpp new file mode 100644 index 00000000..fb0add50 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/AGCObject.hpp @@ -0,0 +1,95 @@ +/** @file AGCObject.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/GCObject.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [abstract_facet.hpp.j2] + * 3. idl for facet methods + * [idl/GCObject.json5] + **/ + +#pragma once + +// includes (via {facet_includes}) +#include +#include +#include +#include +#include +#include +#include + +namespace xo { namespace mm { class ACollector; }} + +namespace xo { +namespace mm { + +using Copaque = const void *; +using Opaque = void *; + +/** +GC hooks for collector-aware data +**/ +class AGCObject { +public: + /** @defgroup mm-gcobject-type-traits **/ + ///@{ + // types + /** integer identifying a type **/ + using typeseq = xo::facet::typeseq; + using Copaque = const void *; + using Opaque = void *; + /** type for an amount of memory **/ + using size_type = std::size_t; + /** fomo allocator type **/ + using AAllocator = xo::mm::AAllocator; + /** fomo collector type **/ + using AGCObjectVisitor = xo::mm::AGCObjectVisitor; + /** hint arg when navigating object graph **/ + using VisitReason = xo::mm::VisitReason; + ///@} + + /** @defgroup mm-gcobject-methods **/ + ///@{ + // const methods + /** An uninitialized AGCObject 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 **/ + virtual void _drop(Opaque d) const noexcept = 0; + + // nonconst methods + /** move instance using object visitor. +Arguably abusing the word 'visitor' here **/ + virtual Opaque gco_shallow_move(Opaque data, obj gc) const noexcept = 0; + /** Invoke fn.visit_child(iface,data) for each child GCObject pointer. +Context: provides address of data pointer so it can be updated in place +when @p fn invokes garbage collector reentry point **/ + virtual void visit_gco_children(Opaque data, VisitReason reason, obj fn) const noexcept = 0; + ///@} +}; /*AGCObject*/ + +/** Implementation IGCObject_DRepr of AGCObject for state DRepr + * should provide a specialization: + * + * template <> + * struct xo::facet::FacetImplementation { + * using Impltype = IGCObject_DRepr; + * }; + * + * then IGCObject_ImplType --> IGCObject_DRepr + **/ +template +using IGCObject_ImplType = xo::facet::FacetImplType; + +} /*namespace mm*/ +} /*namespace xo*/ + +/* AGCObject.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/AGCObjectVisitor.hpp b/xo-alloc2/include/xo/alloc2/gc/AGCObjectVisitor.hpp new file mode 100644 index 00000000..71a1367a --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/AGCObjectVisitor.hpp @@ -0,0 +1,97 @@ +/** @file AGCObjectVisitor.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/GCObjectVisitor.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [abstract_facet.hpp.j2] + * 3. idl for facet methods + * [idl/GCObjectVisitor.json5] + **/ + +#pragma once + +// includes (via {facet_includes}) +#include +#include +#include +#include +#include +#include +#include + +// see GCObject.hpp, also in xo-alloc2/ +namespace xo { namespace mm { class AGCObject; }} +namespace xo { namespace mm { class AllocInfo; }} +namespace xo { namespace mm { class Generation; }} + +namespace xo { +namespace mm { + +using Copaque = const void *; +using Opaque = void *; + +/** +Visit a gc-aware object. Visitor can traverse and update child pointers in-place. +**/ +class AGCObjectVisitor { +public: + /** @defgroup mm-gcobjectvisitor-type-traits **/ + ///@{ + // types + /** integer identifying a type **/ + using typeseq = xo::facet::typeseq; + using Copaque = const void *; + using Opaque = void *; + ///@} + + /** @defgroup mm-gcobjectvisitor-methods **/ + ///@{ + // const methods + /** An uninitialized AGCObjectVisitor 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 **/ + virtual void _drop(Opaque d) const noexcept = 0; + /** allocation metadata for gc-aware data at address @p gco. +@p gco must be the result of a call to collector's alloc() function +note: load-bearing for xo-gc/MutationLogStore **/ + virtual AllocInfo alloc_info(Copaque data, void * addr) const = 0; + /** generation to which pointer @p addr belongs, given role @p r; +sentinel if @p addr is not owned by collector. +note: load-bearing for xo-gc/MutationLogStore **/ + virtual Generation generation_of(Copaque data, Role r, const void * addr) const noexcept = 0; + + // nonconst methods + /** allocate copy of source object at address @p src. +Source must be owned by this collector. +Increments object age **/ + virtual void * alloc_copy(Opaque data, std::byte * src) const = 0; + /** visit child of a gc-aware object. May update child in-place! **/ + virtual void visit_child(Opaque data, VisitReason reason, AGCObject * iface, void ** pp_data) const noexcept = 0; + ///@} +}; /*AGCObjectVisitor*/ + +/** Implementation IGCObjectVisitor_DRepr of AGCObjectVisitor for state DRepr + * should provide a specialization: + * + * template <> + * struct xo::facet::FacetImplementation { + * using Impltype = IGCObjectVisitor_DRepr; + * }; + * + * then IGCObjectVisitor_ImplType --> IGCObjectVisitor_DRepr + **/ +template +using IGCObjectVisitor_ImplType = xo::facet::FacetImplType; + +} /*namespace mm*/ +} /*namespace xo*/ + +/* AGCObjectVisitor.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/ICollector2_Any.hpp b/xo-alloc2/include/xo/alloc2/gc/ICollector2_Any.hpp new file mode 100644 index 00000000..93a68154 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/ICollector2_Any.hpp @@ -0,0 +1,99 @@ +/** @file ICollector2_Any.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/Collector2.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/Collector2.json5] + **/ + +#pragma once + +#include "ACollector2.hpp" +#include + +namespace xo { namespace mm { class ICollector2_Any; } } + +namespace xo { +namespace facet { + +template <> +struct FacetImplementation +{ + using ImplType = xo::mm::ICollector2_Any; +}; + +} +} + +namespace xo { +namespace mm { + + /** @class ICollector2_Any + * @brief ACollector2 implementation for empty variant instance + **/ + class ICollector2_Any : public ACollector2 { + public: + /** @defgroup mm-collector2-any-type-traits **/ + ///@{ + + /** integer identifying a type **/ + using typeseq = xo::facet::typeseq; + using size_type = ACollector2::size_type; + + ///@} + /** @defgroup mm-collector2-any-methods **/ + ///@{ + + const ACollector2 * iface() const { return std::launder(this); } + + // from ACollector2 + + // builtin methods + typeseq _typeseq() const noexcept override { return s_typeseq; } + [[noreturn]] void _drop(Opaque) const noexcept override { _fatal(); } + + // const methods + [[noreturn]] size_type allocated(Copaque, Generation, role) const noexcept override { _fatal(); } + [[noreturn]] size_type committed(Copaque, Generation, role) const noexcept override { _fatal(); } + [[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(); } + + // nonconst methods + [[noreturn]] bool install_type(Opaque, const AGCObject &) override; + [[noreturn]] void add_gc_root_poly(Opaque, obj *) override; + [[noreturn]] void remove_gc_root_poly(Opaque, obj *) override; + [[noreturn]] void request_gc(Opaque, Generation) override; + [[noreturn]] void assign_member(Opaque, void *, obj *, obj &) override; + [[noreturn]] void forward_inplace(Opaque, AGCObject *, void **) override; + + ///@} + + private: + /** @defgraoup mm-collector2-any-private-methods **/ + ///@{ + + [[noreturn]] static void _fatal(); + + ///@} + + public: + /** @defgroup mm-collector2-any-member-vars **/ + ///@{ + + static typeseq s_typeseq; + static bool _valid; + + ///@} + }; + +} /*namespace mm */ +} /*namespace xo */ + +/* ICollector2_Any.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/ICollector2_Xfer.hpp b/xo-alloc2/include/xo/alloc2/gc/ICollector2_Xfer.hpp new file mode 100644 index 00000000..0760abd8 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/ICollector2_Xfer.hpp @@ -0,0 +1,116 @@ +/** @file ICollector2_Xfer.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/Collector2.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/Collector2.json5] + **/ + +#pragma once + +#include +#include +#include + +namespace xo { +namespace mm { + /** @class ICollector2_Xfer + **/ + template + class ICollector2_Xfer : public ACollector2 { + public: + /** @defgroup mm-collector2-xfer-type-traits **/ + ///@{ + /** actual implementation (not generated; often delegates to DRepr) **/ + using Impl = ICollector2_DRepr; + /** integer identifying a type **/ + using typeseq = ACollector2::typeseq; + using size_type = ACollector2::size_type; + ///@} + + /** @defgroup mm-collector2-xfer-methods **/ + ///@{ + + static const DRepr & _dcast(Copaque d) { return *(const DRepr *)d; } + static DRepr & _dcast(Opaque d) { return *(DRepr *)d; } + + // from ACollector2 + + // builtin methods + typeseq _typeseq() const noexcept override { return s_typeseq; } + void _drop(Opaque d) const noexcept override { _dcast(d).~DRepr(); } + + // const methods + size_type allocated(Copaque data, Generation g, role r) const noexcept override { + return I::allocated(_dcast(data), g, r); + } + size_type committed(Copaque data, Generation g, role r) const noexcept override { + return I::committed(_dcast(data), g, r); + } + size_type reserved(Copaque data, Generation g, role r) const noexcept override { + return I::reserved(_dcast(data), g, r); + } + bool contains(Copaque data, role r, const void * addr) const noexcept override { + return I::contains(_dcast(data), r, addr); + } + bool is_type_installed(Copaque data, typeseq tseq) const noexcept override { + return I::is_type_installed(_dcast(data), tseq); + } + + // non-const methods + bool install_type(Opaque data, const AGCObject & iface) override { + return I::install_type(_dcast(data), iface); + } + void add_gc_root_poly(Opaque data, obj * p_root) override { + return I::add_gc_root_poly(_dcast(data), p_root); + } + void remove_gc_root_poly(Opaque data, obj * p_root) override { + return I::remove_gc_root_poly(_dcast(data), p_root); + } + void request_gc(Opaque data, Generation upto) override { + return I::request_gc(_dcast(data), upto); + } + void assign_member(Opaque data, void * parent, obj * p_lhs, obj & rhs) override { + return I::assign_member(_dcast(data), parent, p_lhs, rhs); + } + void forward_inplace(Opaque data, AGCObject * lhs_iface, void ** lhs_data) override { + return I::forward_inplace(_dcast(data), lhs_iface, lhs_data); + } + + ///@} + + private: + using I = Impl; + + public: + /** @defgroup mm-collector2-xfer-member-vars **/ + ///@{ + + /** typeseq for template parameter DRepr **/ + static typeseq s_typeseq; + /** true iff satisfies facet implementation **/ + static bool _valid; + + ///@} + }; + + template + xo::facet::typeseq + ICollector2_Xfer::s_typeseq + = xo::facet::typeseq::id(); + + template + bool + ICollector2_Xfer::_valid + = xo::facet::valid_facet_implementation(); + +} /*namespace mm */ +} /*namespace xo*/ + +/* end ICollector2_Xfer.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp b/xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp new file mode 100644 index 00000000..f4ab6bb0 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/ICollector_Any.hpp @@ -0,0 +1,103 @@ +/** @file ICollector_Any.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/Collector.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/Collector.json5] + **/ + +#pragma once + +#include "ACollector.hpp" +#include + +namespace xo { namespace mm { class ICollector_Any; } } + +namespace xo { +namespace facet { + +template <> +struct FacetImplementation +{ + using ImplType = xo::mm::ICollector_Any; +}; + +} +} + +namespace xo { +namespace mm { + + /** @class ICollector_Any + * @brief ACollector implementation for empty variant instance + **/ + class ICollector_Any : public ACollector { + public: + /** @defgroup mm-collector-any-type-traits **/ + ///@{ + + /** integer identifying a type **/ + using typeseq = xo::facet::typeseq; + using size_type = ACollector::size_type; + + ///@} + /** @defgroup mm-collector-any-methods **/ + ///@{ + + const ACollector * iface() const { return std::launder(this); } + + // from ACollector + + // builtin methods + typeseq _typeseq() const noexcept override { return s_typeseq; } + [[noreturn]] void _drop(Opaque) const noexcept override { _fatal(); } + + // const methods + [[noreturn]] size_type allocated(Copaque, Generation, Role) const noexcept override { _fatal(); } + [[noreturn]] size_type committed(Copaque, Generation, Role) const noexcept override { _fatal(); } + [[noreturn]] size_type reserved(Copaque, Generation, Role) const noexcept override { _fatal(); } + [[noreturn]] std::int32_t locate_address(Copaque, const void *) 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(); } + [[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; + [[noreturn]] void add_gc_root_poly(Opaque, obj *) override; + [[noreturn]] void remove_gc_root_poly(Opaque, obj *) override; + [[noreturn]] void request_gc(Opaque, Generation) override; + [[noreturn]] void assign_member(Opaque, void *, obj *, obj &) override; + [[noreturn]] void * alloc_copy(Opaque, std::byte *) override; + + ///@} + + private: + /** @defgraoup mm-collector-any-private-methods **/ + ///@{ + + [[noreturn]] static void _fatal(); + + ///@} + + public: + /** @defgroup mm-collector-any-member-vars **/ + ///@{ + + static typeseq s_typeseq; + static bool _valid; + + ///@} + }; + +} /*namespace mm */ +} /*namespace xo */ + +/* ICollector_Any.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp b/xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp new file mode 100644 index 00000000..00881e8c --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/ICollector_Xfer.hpp @@ -0,0 +1,136 @@ +/** @file ICollector_Xfer.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/Collector.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/Collector.json5] + * + * variables: + * {facet_hpp_fname} -> Collector.hpp + * {impl_hpp_subdir} -> gc + * {facet_ns1} -> xo + * {facet_detail_subdir} -> gc + * {abstract_facet_fname} -> ACollector.hpp + **/ + +#pragma once + +#include "ACollector.hpp" +#include +#include +#include + +namespace xo { +namespace mm { + /** @class ICollector_Xfer + **/ + template + class ICollector_Xfer : public ACollector { + public: + /** @defgroup mm-collector-xfer-type-traits **/ + ///@{ + /** actual implementation (not generated; often delegates to DRepr) **/ + using Impl = ICollector_DRepr; + /** integer identifying a type **/ + using typeseq = ACollector::typeseq; + using size_type = ACollector::size_type; + ///@} + + /** @defgroup mm-collector-xfer-methods **/ + ///@{ + + static const DRepr & _dcast(Copaque d) { return *(const DRepr *)d; } + static DRepr & _dcast(Opaque d) { return *(DRepr *)d; } + + // from ACollector + + // builtin methods + typeseq _typeseq() const noexcept override { return s_typeseq; } + void _drop(Opaque d) const noexcept override { _dcast(d).~DRepr(); } + + // const methods + size_type allocated(Copaque data, Generation g, Role r) const noexcept override { + return I::allocated(_dcast(data), g, r); + } + size_type committed(Copaque data, Generation g, Role r) const noexcept override { + return I::committed(_dcast(data), g, r); + } + size_type reserved(Copaque data, Generation g, Role r) const noexcept override { + return I::reserved(_dcast(data), g, r); + } + std::int32_t locate_address(Copaque data, const void * addr) const noexcept override { + return I::locate_address(_dcast(data), addr); + } + bool contains(Copaque data, Role r, const void * addr) const noexcept override { + return I::contains(_dcast(data), r, addr); + } + 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); + } + 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 { + return I::install_type(_dcast(data), iface); + } + void add_gc_root_poly(Opaque data, obj * p_root) override { + return I::add_gc_root_poly(_dcast(data), p_root); + } + void remove_gc_root_poly(Opaque data, obj * p_root) override { + return I::remove_gc_root_poly(_dcast(data), p_root); + } + void request_gc(Opaque data, Generation upto) override { + return I::request_gc(_dcast(data), upto); + } + void assign_member(Opaque data, void * parent, obj * p_lhs, obj & rhs) override { + return I::assign_member(_dcast(data), parent, p_lhs, rhs); + } + void * alloc_copy(Opaque data, std::byte * src) override { + return I::alloc_copy(_dcast(data), src); + } + + ///@} + + private: + using I = Impl; + + public: + /** @defgroup mm-collector-xfer-member-vars **/ + ///@{ + + /** typeseq for template parameter DRepr **/ + static typeseq s_typeseq; + /** true iff satisfies facet implementation **/ + static bool _valid; + + ///@} + }; + + template + xo::facet::typeseq + ICollector_Xfer::s_typeseq + = xo::facet::typeseq::id(); + + template + bool + ICollector_Xfer::_valid + = xo::facet::valid_facet_implementation(); + +} /*namespace mm */ +} /*namespace xo*/ + +/* end ICollector_Xfer.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/IGCObjectVisitor_Any.hpp b/xo-alloc2/include/xo/alloc2/gc/IGCObjectVisitor_Any.hpp new file mode 100644 index 00000000..8dbece07 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/IGCObjectVisitor_Any.hpp @@ -0,0 +1,91 @@ +/** @file IGCObjectVisitor_Any.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/GCObjectVisitor.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/GCObjectVisitor.json5] + **/ + +#pragma once + +#include "AGCObjectVisitor.hpp" +#include + +namespace xo { namespace mm { class IGCObjectVisitor_Any; } } + +namespace xo { +namespace facet { + +template <> +struct FacetImplementation +{ + using ImplType = xo::mm::IGCObjectVisitor_Any; +}; + +} +} + +namespace xo { +namespace mm { + + /** @class IGCObjectVisitor_Any + * @brief AGCObjectVisitor implementation for empty variant instance + **/ + class IGCObjectVisitor_Any : public AGCObjectVisitor { + public: + /** @defgroup mm-gcobjectvisitor-any-type-traits **/ + ///@{ + + /** integer identifying a type **/ + using typeseq = xo::facet::typeseq; + + ///@} + /** @defgroup mm-gcobjectvisitor-any-methods **/ + ///@{ + + const AGCObjectVisitor * iface() const { return std::launder(this); } + + // from AGCObjectVisitor + + // builtin methods + typeseq _typeseq() const noexcept override { return s_typeseq; } + [[noreturn]] void _drop(Opaque) const noexcept override { _fatal(); } + + // const methods + [[noreturn]] AllocInfo alloc_info(Copaque, void *) const override { _fatal(); } + [[noreturn]] Generation generation_of(Copaque, Role, const void *) const noexcept override { _fatal(); } + + // nonconst methods + [[noreturn]] void * alloc_copy(Opaque, std::byte *) const override; + [[noreturn]] void visit_child(Opaque, VisitReason, AGCObject *, void **) const noexcept override; + + ///@} + + private: + /** @defgraoup mm-gcobjectvisitor-any-private-methods **/ + ///@{ + + [[noreturn]] static void _fatal(); + + ///@} + + public: + /** @defgroup mm-gcobjectvisitor-any-member-vars **/ + ///@{ + + static typeseq s_typeseq; + static bool _valid; + + ///@} + }; + +} /*namespace mm */ +} /*namespace xo */ + +/* IGCObjectVisitor_Any.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/IGCObjectVisitor_Xfer.hpp b/xo-alloc2/include/xo/alloc2/gc/IGCObjectVisitor_Xfer.hpp new file mode 100644 index 00000000..d09d1800 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/IGCObjectVisitor_Xfer.hpp @@ -0,0 +1,103 @@ +/** @file IGCObjectVisitor_Xfer.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/GCObjectVisitor.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/GCObjectVisitor.json5] + * + * variables: + * {facet_hpp_fname} -> GCObjectVisitor.hpp + * {impl_hpp_subdir} -> gc + * {facet_ns1} -> xo + * {facet_detail_subdir} -> gc + * {abstract_facet_fname} -> AGCObjectVisitor.hpp + **/ + +#pragma once + +#include "AGCObjectVisitor.hpp" +#include +#include +#include +#include + +namespace xo { +namespace mm { + /** @class IGCObjectVisitor_Xfer + **/ + template + class IGCObjectVisitor_Xfer : public AGCObjectVisitor { + public: + /** @defgroup mm-gcobjectvisitor-xfer-type-traits **/ + ///@{ + /** actual implementation (not generated; often delegates to DRepr) **/ + using Impl = IGCObjectVisitor_DRepr; + /** integer identifying a type **/ + using typeseq = AGCObjectVisitor::typeseq; + ///@} + + /** @defgroup mm-gcobjectvisitor-xfer-methods **/ + ///@{ + + static const DRepr & _dcast(Copaque d) { return *(const DRepr *)d; } + static DRepr & _dcast(Opaque d) { return *(DRepr *)d; } + + // from AGCObjectVisitor + + // builtin methods + typeseq _typeseq() const noexcept override { return s_typeseq; } + void _drop(Opaque d) const noexcept override { _dcast(d).~DRepr(); } + + // const methods + AllocInfo alloc_info(Copaque data, void * addr) const override { + return I::alloc_info(_dcast(data), addr); + } + Generation generation_of(Copaque data, Role r, const void * addr) const noexcept override { + return I::generation_of(_dcast(data), r, addr); + } + + // non-const methods + void * alloc_copy(Opaque data, std::byte * src) const override { + return I::alloc_copy(_dcast(data), src); + } + void visit_child(Opaque data, VisitReason reason, AGCObject * iface, void ** pp_data) const noexcept override { + return I::visit_child(_dcast(data), reason, iface, pp_data); + } + + ///@} + + private: + using I = Impl; + + public: + /** @defgroup mm-gcobjectvisitor-xfer-member-vars **/ + ///@{ + + /** typeseq for template parameter DRepr **/ + static typeseq s_typeseq; + /** true iff satisfies facet implementation **/ + static bool _valid; + + ///@} + }; + + template + xo::facet::typeseq + IGCObjectVisitor_Xfer::s_typeseq + = xo::facet::typeseq::id(); + + template + bool + IGCObjectVisitor_Xfer::_valid + = xo::facet::valid_facet_implementation(); + +} /*namespace mm */ +} /*namespace xo*/ + +/* end IGCObjectVisitor_Xfer.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/IGCObject_Any.hpp b/xo-alloc2/include/xo/alloc2/gc/IGCObject_Any.hpp new file mode 100644 index 00000000..c1e2854f --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/IGCObject_Any.hpp @@ -0,0 +1,93 @@ +/** @file IGCObject_Any.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/GCObject.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/GCObject.json5] + **/ + +#pragma once + +#include "AGCObject.hpp" +#include + +namespace xo { namespace mm { class IGCObject_Any; } } + +namespace xo { +namespace facet { + +template <> +struct FacetImplementation +{ + using ImplType = xo::mm::IGCObject_Any; +}; + +} +} + +namespace xo { +namespace mm { + + /** @class IGCObject_Any + * @brief AGCObject implementation for empty variant instance + **/ + class IGCObject_Any : public AGCObject { + public: + /** @defgroup mm-gcobject-any-type-traits **/ + ///@{ + + /** integer identifying a type **/ + using typeseq = xo::facet::typeseq; + using size_type = AGCObject::size_type; + using AAllocator = AGCObject::AAllocator; + using AGCObjectVisitor = AGCObject::AGCObjectVisitor; + using VisitReason = AGCObject::VisitReason; + + ///@} + /** @defgroup mm-gcobject-any-methods **/ + ///@{ + + const AGCObject * iface() const { return std::launder(this); } + + // from AGCObject + + // builtin methods + typeseq _typeseq() const noexcept override { return s_typeseq; } + [[noreturn]] void _drop(Opaque) const noexcept override { _fatal(); } + + // const methods + + // nonconst methods + [[noreturn]] Opaque gco_shallow_move(Opaque, obj) const noexcept override; + [[noreturn]] void visit_gco_children(Opaque, VisitReason, obj) const noexcept override; + + ///@} + + private: + /** @defgraoup mm-gcobject-any-private-methods **/ + ///@{ + + [[noreturn]] static void _fatal(); + + ///@} + + public: + /** @defgroup mm-gcobject-any-member-vars **/ + ///@{ + + static typeseq s_typeseq; + static bool _valid; + + ///@} + }; + +} /*namespace mm */ +} /*namespace xo */ + +/* IGCObject_Any.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/IGCObject_Xfer.hpp b/xo-alloc2/include/xo/alloc2/gc/IGCObject_Xfer.hpp new file mode 100644 index 00000000..f5a1ff47 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/IGCObject_Xfer.hpp @@ -0,0 +1,101 @@ +/** @file IGCObject_Xfer.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/GCObject.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/GCObject.json5] + * + * variables: + * {facet_hpp_fname} -> GCObject.hpp + * {impl_hpp_subdir} -> gc + * {facet_ns1} -> xo + * {facet_detail_subdir} -> gc + * {abstract_facet_fname} -> AGCObject.hpp + **/ + +#pragma once + +#include "AGCObject.hpp" +#include +#include +#include +#include + +namespace xo { +namespace mm { + /** @class IGCObject_Xfer + **/ + template + class IGCObject_Xfer : public AGCObject { + public: + /** @defgroup mm-gcobject-xfer-type-traits **/ + ///@{ + /** actual implementation (not generated; often delegates to DRepr) **/ + using Impl = IGCObject_DRepr; + /** integer identifying a type **/ + using typeseq = AGCObject::typeseq; + using size_type = AGCObject::size_type; + using AAllocator = AGCObject::AAllocator; + using AGCObjectVisitor = AGCObject::AGCObjectVisitor; + using VisitReason = AGCObject::VisitReason; + ///@} + + /** @defgroup mm-gcobject-xfer-methods **/ + ///@{ + + static const DRepr & _dcast(Copaque d) { return *(const DRepr *)d; } + static DRepr & _dcast(Opaque d) { return *(DRepr *)d; } + + // from AGCObject + + // builtin methods + typeseq _typeseq() const noexcept override { return s_typeseq; } + void _drop(Opaque d) const noexcept override { _dcast(d).~DRepr(); } + + // const methods + + // non-const methods + Opaque gco_shallow_move(Opaque data, obj gc) const noexcept override { + return I::gco_shallow_move(_dcast(data), gc); + } + void visit_gco_children(Opaque data, VisitReason reason, obj fn) const noexcept override { + return I::visit_gco_children(_dcast(data), reason, fn); + } + + ///@} + + private: + using I = Impl; + + public: + /** @defgroup mm-gcobject-xfer-member-vars **/ + ///@{ + + /** typeseq for template parameter DRepr **/ + static typeseq s_typeseq; + /** true iff satisfies facet implementation **/ + static bool _valid; + + ///@} + }; + + template + xo::facet::typeseq + IGCObject_Xfer::s_typeseq + = xo::facet::typeseq::id(); + + template + bool + IGCObject_Xfer::_valid + = xo::facet::valid_facet_implementation(); + +} /*namespace mm */ +} /*namespace xo*/ + +/* end IGCObject_Xfer.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/RCollector.hpp b/xo-alloc2/include/xo/alloc2/gc/RCollector.hpp new file mode 100644 index 00000000..2818957b --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/RCollector.hpp @@ -0,0 +1,170 @@ +/** @file RCollector.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/Collector.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/Collector.json5] + **/ + +#pragma once + +#include "ACollector.hpp" + +namespace xo { +namespace mm { + +/** @class RCollector + **/ +template +class RCollector : public Object { +private: + using O = Object; + +public: + /** @defgroup mm-collector-router-type-traits **/ + ///@{ + using ObjectType = Object; + using DataPtr = Object::DataPtr; + using typeseq = xo::reflect::typeseq; + using size_type = ACollector::size_type; + ///@} + + /** @defgroup mm-collector-router-ctors **/ + ///@{ + RCollector() {} + RCollector(Object::DataPtr data) : Object{std::move(data)} {} + RCollector(const ACollector * iface, void * data) + requires std::is_same_v + : Object(iface, data) {} + + ///@} + /** @defgroup mm-collector-router-methods **/ + ///@{ + + // explicit injected content + /** convenience template for gc object copy **/ + template + void * alloc_copy_for(const T * src) noexcept { + return O::iface()->alloc_copy(O::data(), (std::byte *)const_cast(src)); + } + + /** convenience template for move-constructible T (this is common) **/ + template + T * std_move_for(T * src) noexcept { + void * mem = this->alloc_copy_for(src); + if (mem) { + return new (mem) T(std::move(*src)); + } + return nullptr; + } + + /** forward faceted object pointer in place. Defined in GCObject.hpp to avoid #include cycle **/ + template + void forward_inplace(obj * p_obj); + + /** another convenience template for forwarding. + * Defined in RGCObject.hpp to avoid #include cycle. + **/ + template + void forward_inplace(DRepr ** pp_repr); + + /** convenience template where pointer requires pivot **/ + template + requires (!std::is_same_v) + void forward_pivot_inplace(obj * p_obj); + + /** add root @p p_root **/ + template + void add_gc_root(obj * p_root) { + O::iface()->add_gc_root_poly(O::data(), (obj *)p_root); + } + + /** remove root @p p_root **/ + template + void remove_gc_root(obj * p_root) { + O::iface()->remove_gc_root_poly(O::data(), (obj *)p_root); + } + + // builtin methods + bool _has_null_vptr() const noexcept { return O::iface()->_has_null_vptr(); } + typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); } + void _drop() const noexcept { O::iface()->_drop(O::data()); } + + // const methods + size_type allocated(Generation g, Role r) const noexcept { + return O::iface()->allocated(O::data(), g, r); + } + size_type committed(Generation g, Role r) const noexcept { + return O::iface()->committed(O::data(), g, r); + } + size_type reserved(Generation g, Role r) const noexcept { + return O::iface()->reserved(O::data(), g, r); + } + std::int32_t locate_address(const void * addr) const noexcept { + return O::iface()->locate_address(O::data(), addr); + } + bool contains(Role r, const void * addr) const noexcept { + return O::iface()->contains(O::data(), r, addr); + } + 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); + } + 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) { + return O::iface()->install_type(O::data(), iface); + } + void add_gc_root_poly(obj * p_root) { + return O::iface()->add_gc_root_poly(O::data(), p_root); + } + void remove_gc_root_poly(obj * p_root) { + return O::iface()->remove_gc_root_poly(O::data(), p_root); + } + void request_gc(Generation upto) { + return O::iface()->request_gc(O::data(), upto); + } + void assign_member(void * parent, obj * p_lhs, obj & rhs) { + return O::iface()->assign_member(O::data(), parent, p_lhs, rhs); + } + void * alloc_copy(std::byte * src) { + return O::iface()->alloc_copy(O::data(), src); + } + + ///@} + /** @defgroup mm-collector-member-vars **/ + ///@{ + + static bool _valid; + + ///@} +}; + +template +bool +RCollector::_valid = xo::facet::valid_object_router(); + +} /*namespace mm*/ +} /*namespace xo*/ + +namespace xo { namespace facet { + template + struct RoutingFor { + using RoutingType = xo::mm::RCollector; + }; +} } + +/* end RCollector.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/RCollector2.hpp b/xo-alloc2/include/xo/alloc2/gc/RCollector2.hpp new file mode 100644 index 00000000..facaf295 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/RCollector2.hpp @@ -0,0 +1,115 @@ +/** @file RCollector2.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/Collector2.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/Collector2.json5] + **/ + +#pragma once + +#include "ACollector2.hpp" + +namespace xo { +namespace mm { + +/** @class RCollector2 + **/ +template +class RCollector2 : public Object { +private: + using O = Object; + +public: + /** @defgroup mm-collector2-router-type-traits **/ + ///@{ + using ObjectType = Object; + using DataPtr = Object::DataPtr; + using typeseq = xo::reflect::typeseq; + using size_type = ACollector2::size_type; + ///@} + + /** @defgroup mm-collector2-router-ctors **/ + ///@{ + RCollector2() {} + RCollector2(Object::DataPtr data) : Object{std::move(data)} {} + RCollector2(const ACollector2 * iface, void * data) + requires std::is_same_v + : Object(iface, data) {} + + ///@} + /** @defgroup mm-collector2-router-methods **/ + ///@{ + + // explicit injected content + + // builtin methods + typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); } + void _drop() const noexcept { O::iface()->_drop(O::data()); } + + // const methods + size_type allocated(Generation g, role r) const noexcept { + return O::iface()->allocated(O::data(), g, r); + } + size_type committed(Generation g, role r) const noexcept { + return O::iface()->committed(O::data(), g, r); + } + size_type reserved(Generation g, role r) const noexcept { + return O::iface()->reserved(O::data(), g, r); + } + bool contains(role r, const void * addr) const noexcept { + return O::iface()->contains(O::data(), r, addr); + } + bool is_type_installed(typeseq tseq) const noexcept { + return O::iface()->is_type_installed(O::data(), tseq); + } + + // non-const methods (still const in router!) + bool install_type(const AGCObject & iface) { + return O::iface()->install_type(O::data(), iface); + } + void add_gc_root_poly(obj * p_root) { + return O::iface()->add_gc_root_poly(O::data(), p_root); + } + void remove_gc_root_poly(obj * p_root) { + return O::iface()->remove_gc_root_poly(O::data(), p_root); + } + void request_gc(Generation upto) { + return O::iface()->request_gc(O::data(), upto); + } + void assign_member(void * parent, obj * p_lhs, obj & rhs) { + return O::iface()->assign_member(O::data(), parent, p_lhs, rhs); + } + void forward_inplace(AGCObject * lhs_iface, void ** lhs_data) { + return O::iface()->forward_inplace(O::data(), lhs_iface, lhs_data); + } + + ///@} + /** @defgroup mm-collector2-member-vars **/ + ///@{ + + static bool _valid; + + ///@} +}; + +template +bool +RCollector2::_valid = xo::facet::valid_object_router(); + +} /*namespace mm*/ +} /*namespace xo*/ + +namespace xo { namespace facet { + template + struct RoutingFor { + using RoutingType = xo::mm::RCollector2; + }; +} } + +/* end RCollector2.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/RCollector_aux.hpp b/xo-alloc2/include/xo/alloc2/gc/RCollector_aux.hpp new file mode 100644 index 00000000..e115e9b4 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/RCollector_aux.hpp @@ -0,0 +1,15 @@ +/** @file RCollector_aux.hpp + * + * Out-of-line definitions for RCollector template methods + * that depend on RGCObject (avoiding #include cycle in RCollector.hpp). + * + * Included via user_hpp_includes in GCObject.json5. + * + * @author Roland Conybeare + **/ + +#pragma once + +//#include + +/* end RCollector_aux.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/RGCObject.hpp b/xo-alloc2/include/xo/alloc2/gc/RGCObject.hpp new file mode 100644 index 00000000..871a55ec --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/RGCObject.hpp @@ -0,0 +1,91 @@ +/** @file RGCObject.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/GCObject.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/GCObject.json5] + **/ + +#pragma once + +#include "AGCObject.hpp" + +namespace xo { +namespace mm { + +/** @class RGCObject + **/ +template +class RGCObject : public Object { +private: + using O = Object; + +public: + /** @defgroup mm-gcobject-router-type-traits **/ + ///@{ + using ObjectType = Object; + using DataPtr = Object::DataPtr; + using typeseq = xo::reflect::typeseq; + using size_type = AGCObject::size_type; + using AAllocator = AGCObject::AAllocator; + using AGCObjectVisitor = AGCObject::AGCObjectVisitor; + using VisitReason = AGCObject::VisitReason; + ///@} + + /** @defgroup mm-gcobject-router-ctors **/ + ///@{ + RGCObject() {} + RGCObject(Object::DataPtr data) : Object{std::move(data)} {} + RGCObject(const AGCObject * iface, void * data) + requires std::is_same_v + : Object(iface, data) {} + + ///@} + /** @defgroup mm-gcobject-router-methods **/ + ///@{ + + // explicit injected content + + // builtin methods + typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); } + void _drop() const noexcept { O::iface()->_drop(O::data()); } + + // const methods + + // non-const methods (still const in router!) + Opaque gco_shallow_move(obj gc) noexcept { + return O::iface()->gco_shallow_move(O::data(), gc); + } + void visit_gco_children(VisitReason reason, obj fn) noexcept { + return O::iface()->visit_gco_children(O::data(), reason, fn); + } + + ///@} + /** @defgroup mm-gcobject-member-vars **/ + ///@{ + + static bool _valid; + + ///@} +}; + +template +bool +RGCObject::_valid = xo::facet::valid_object_router(); + +} /*namespace mm*/ +} /*namespace xo*/ + +namespace xo { namespace facet { + template + struct RoutingFor { + using RoutingType = xo::mm::RGCObject; + }; +} } + +/* end RGCObject.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/RGCObjectVisitor.hpp b/xo-alloc2/include/xo/alloc2/gc/RGCObjectVisitor.hpp new file mode 100644 index 00000000..4972593b --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/RGCObjectVisitor.hpp @@ -0,0 +1,129 @@ +/** @file RGCObjectVisitor.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/GCObjectVisitor.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/GCObjectVisitor.json5] + **/ + +#pragma once + +#include "AGCObjectVisitor.hpp" + +namespace xo { +namespace mm { + +/** @class RGCObjectVisitor + **/ +template +class RGCObjectVisitor : public Object { +private: + using O = Object; + +public: + /** @defgroup mm-gcobjectvisitor-router-type-traits **/ + ///@{ + using ObjectType = Object; + using DataPtr = Object::DataPtr; + using typeseq = xo::reflect::typeseq; + ///@} + + /** @defgroup mm-gcobjectvisitor-router-ctors **/ + ///@{ + RGCObjectVisitor() {} + RGCObjectVisitor(Object::DataPtr data) : Object{std::move(data)} {} + RGCObjectVisitor(const AGCObjectVisitor * iface, void * data) + requires std::is_same_v + : Object(iface, data) {} + + ///@} + /** @defgroup mm-gcobjectvisitor-router-methods **/ + ///@{ + + // explicit injected content + /** convenience: allocate copy for typed pointer **/ + template + void * alloc_copy_for(const T * src) noexcept { + return O::iface()->alloc_copy(O::data(), (std::byte *)const_cast(src)); + } + + /** convenience: move typed pointer **/ + template + T * std_move_for(T * src) noexcept { + void * mem = this->alloc_copy_for(src); + if (mem) { + return new (mem) T(std::move(*src)); + } + return nullptr; + } + + /** visit a gcobject child pointer in place. + Defined in RCollector_aux.hpp to avoid #include cycle + (for historical reasons - coul d be in RGCObject_aux.hpp?) + **/ + template + void visit_child(VisitReason reason, xo::facet::obj * p_obj); + + /** visit typed child data pointer in place. + Defined in RGCObject.hpp to avoid #include cycle + **/ + template + void visit_child(VisitReason reason, DRepr ** pp_data); + + /** visit faceted object pointer stored using some facet + other than AGCObject + **/ + template + requires (!std::is_same_v) + void visit_poly_child(VisitReason reason, obj * p_pivot); + + + // builtin methods + typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); } + void _drop() const noexcept { O::iface()->_drop(O::data()); } + + // const methods + AllocInfo alloc_info(void * addr) const { + return O::iface()->alloc_info(O::data(), addr); + } + Generation generation_of(Role r, const void * addr) const noexcept { + return O::iface()->generation_of(O::data(), r, addr); + } + + // non-const methods (still const in router!) + void * alloc_copy(std::byte * src) { + return O::iface()->alloc_copy(O::data(), src); + } + void visit_child(VisitReason reason, AGCObject * iface, void ** pp_data) noexcept { + return O::iface()->visit_child(O::data(), reason, iface, pp_data); + } + + ///@} + /** @defgroup mm-gcobjectvisitor-member-vars **/ + ///@{ + + static bool _valid; + + ///@} +}; + +template +bool +RGCObjectVisitor::_valid = xo::facet::valid_object_router(); + +} /*namespace mm*/ +} /*namespace xo*/ + +namespace xo { namespace facet { + template + struct RoutingFor { + using RoutingType = xo::mm::RGCObjectVisitor; + }; +} } + +/* end RGCObjectVisitor.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/gc/RGCObjectVisitor_aux.hpp b/xo-alloc2/include/xo/alloc2/gc/RGCObjectVisitor_aux.hpp new file mode 100644 index 00000000..c393b40a --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/gc/RGCObjectVisitor_aux.hpp @@ -0,0 +1,61 @@ +/** @file RGCObjectVisitor_aux.hpp + * + * Out-of-line definitions for RCollector template methods + * that depend on RGCObject (avoiding #include cycle in RCollector.hpp). + * + * Included via user_hpp_includes in GCObject.json5. + * + * @author Roland Conybeare + **/ + +#pragma once + +#include "gc/RGCObjectVisitor.hpp" +#include + +namespace xo { + namespace mm { + class ACollector; + class AGCObject; + + /** defined here to avoid #include cycle, since + * template obj awkward to make available + * in RCollector.hpp + **/ + template + template + void + RGCObjectVisitor::visit_child(VisitReason reason, + xo::facet::obj * p_obj) + { + this->visit_child(reason, p_obj->iface(), (void **)&(p_obj->data_)); + } + + template + template + void + RGCObjectVisitor::visit_child(VisitReason reason, DRepr ** p_repr) + { + // fetch static interface for DRepr (strip const: FacetImplementation specializations use non-const DRepr) + auto iface = xo::facet::impl_for>(); + + this->visit_child(reason, &iface, (void **)p_repr); + } + + template + template + requires (!std::is_same_v) + void + RGCObjectVisitor::visit_poly_child(VisitReason reason, obj * p_objs) + { + if (*p_objs) { + auto e = xo::facet::FacetRegistry::instance().variant(*p_objs); + + this->visit_child(reason, e.iface(), (void **)&(p_objs->data_)); + } + } + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end RCollector_aux.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/init_alloc2.hpp b/xo-alloc2/include/xo/alloc2/init_alloc2.hpp new file mode 100644 index 00000000..c13160b8 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/init_alloc2.hpp @@ -0,0 +1,23 @@ +/** @file init_alloc2.hpp + * + * @author Roland Conybeare, Feb 2026 + **/ + +#pragma once + +#include + +namespace xo { + /* tag to represent the xo-alloc2/ subsystem within ordered initialization */ + enum S_alloc2_tag {}; + + template <> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; + + +} /*namespace xo*/ + +/* end init_alloc2.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/role.hpp b/xo-alloc2/include/xo/alloc2/role.hpp new file mode 100644 index 00000000..cb8b9137 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/role.hpp @@ -0,0 +1,40 @@ +/** @file role.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include +#include + +namespace xo { + namespace mm { + static constexpr uint32_t c_n_role = 2; + + /** @brief identify GC half-spaces + **/ + class Role { + public: + using value_type = std::uint32_t; + + explicit constexpr Role(value_type x) : role_{x} {} + + static constexpr Role to_space() { return Role{0}; } + static constexpr Role from_space() { return Role{1}; } + + static constexpr std::array all() { return {{to_space(), from_space()}}; } + + static constexpr Role begin() { return Role{0}; } + static constexpr Role end() { return Role{2}; } + + operator value_type() const { return role_; } + + Role next() const { return Role(role_ + 1); } + + value_type role_ = 0; + }; + } /*namespace mm*/ +} /*namespace xo*/ + +/* end role.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/visitor/AResourceVisitor.hpp b/xo-alloc2/include/xo/alloc2/visitor/AResourceVisitor.hpp new file mode 100644 index 00000000..64d9b136 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/visitor/AResourceVisitor.hpp @@ -0,0 +1,81 @@ +/** @file AResourceVisitor.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/ResourceVisitor.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [abstract_facet.hpp.j2] + * 3. idl for facet methods + * [idl/ResourceVisitor.json5] + **/ + +#pragma once + +// includes (via {facet_includes}) +#include "Allocator.hpp" +#include +#include +#include + +// {pretext} here + +namespace xo { +namespace mm { + +using Copaque = const void *; +using Opaque = void *; + +/** +Visitor to receive measured resource consumption +**/ +class AResourceVisitor { +public: + /** @defgroup mm-resourcevisitor-type-traits **/ + ///@{ + // types + /** integer identifying a type **/ + using typeseq = xo::facet::typeseq; + using Copaque = const void *; + using Opaque = void *; + /** type for length of a sequence **/ + using size_type = std::size_t; + ///@} + + /** @defgroup mm-resourcevisitor-methods **/ + ///@{ + // const methods + /** An uninitialized AResourceVisitor 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 **/ + virtual void _drop(Opaque d) const noexcept = 0; + /** report memory consumption **/ + virtual void on_allocator(Copaque data, obj mm) const noexcept = 0; + + // nonconst methods + ///@} +}; /*AResourceVisitor*/ + +/** Implementation IResourceVisitor_DRepr of AResourceVisitor for state DRepr + * should provide a specialization: + * + * template <> + * struct xo::facet::FacetImplementation { + * using Impltype = IResourceVisitor_DRepr; + * }; + * + * then IResourceVisitor_ImplType --> IResourceVisitor_DRepr + **/ +template +using IResourceVisitor_ImplType = xo::facet::FacetImplType; + +} /*namespace mm*/ +} /*namespace xo*/ + +/* AResourceVisitor.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/visitor/IResourceVisitor_Any.hpp b/xo-alloc2/include/xo/alloc2/visitor/IResourceVisitor_Any.hpp new file mode 100644 index 00000000..18ae11a0 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/visitor/IResourceVisitor_Any.hpp @@ -0,0 +1,89 @@ +/** @file IResourceVisitor_Any.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/ResourceVisitor.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/ResourceVisitor.json5] + **/ + +#pragma once + +#include "AResourceVisitor.hpp" +#include + +namespace xo { namespace mm { class IResourceVisitor_Any; } } + +namespace xo { +namespace facet { + +template <> +struct FacetImplementation +{ + using ImplType = xo::mm::IResourceVisitor_Any; +}; + +} +} + +namespace xo { +namespace mm { + + /** @class IResourceVisitor_Any + * @brief AResourceVisitor implementation for empty variant instance + **/ + class IResourceVisitor_Any : public AResourceVisitor { + public: + /** @defgroup mm-resourcevisitor-any-type-traits **/ + ///@{ + + /** integer identifying a type **/ + using typeseq = xo::facet::typeseq; + using size_type = AResourceVisitor::size_type; + + ///@} + /** @defgroup mm-resourcevisitor-any-methods **/ + ///@{ + + const AResourceVisitor * iface() const { return std::launder(this); } + + // from AResourceVisitor + + // builtin methods + typeseq _typeseq() const noexcept override { return s_typeseq; } + [[noreturn]] void _drop(Opaque) const noexcept override { _fatal(); } + + // const methods + [[noreturn]] void on_allocator(Copaque, obj) const noexcept override { _fatal(); } + + // nonconst methods + + ///@} + + private: + /** @defgraoup mm-resourcevisitor-any-private-methods **/ + ///@{ + + [[noreturn]] static void _fatal(); + + ///@} + + public: + /** @defgroup mm-resourcevisitor-any-member-vars **/ + ///@{ + + static typeseq s_typeseq; + static bool _valid; + + ///@} + }; + +} /*namespace mm */ +} /*namespace xo */ + +/* IResourceVisitor_Any.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/visitor/IResourceVisitor_Xfer.hpp b/xo-alloc2/include/xo/alloc2/visitor/IResourceVisitor_Xfer.hpp new file mode 100644 index 00000000..7d6a6f46 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/visitor/IResourceVisitor_Xfer.hpp @@ -0,0 +1,92 @@ +/** @file IResourceVisitor_Xfer.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/ResourceVisitor.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/ResourceVisitor.json5] + * + * variables: + * {facet_hpp_fname} -> ResourceVisitor.hpp + * {impl_hpp_subdir} -> visitor + * {facet_ns1} -> xo + * {facet_detail_subdir} -> visitor + * {abstract_facet_fname} -> AResourceVisitor.hpp + **/ + +#pragma once + +#include "AResourceVisitor.hpp" +#include "Allocator.hpp" + +namespace xo { +namespace mm { + /** @class IResourceVisitor_Xfer + **/ + template + class IResourceVisitor_Xfer : public AResourceVisitor { + public: + /** @defgroup mm-resourcevisitor-xfer-type-traits **/ + ///@{ + /** actual implementation (not generated; often delegates to DRepr) **/ + using Impl = IResourceVisitor_DRepr; + /** integer identifying a type **/ + using typeseq = AResourceVisitor::typeseq; + using size_type = AResourceVisitor::size_type; + ///@} + + /** @defgroup mm-resourcevisitor-xfer-methods **/ + ///@{ + + static const DRepr & _dcast(Copaque d) { return *(const DRepr *)d; } + static DRepr & _dcast(Opaque d) { return *(DRepr *)d; } + + // from AResourceVisitor + + // builtin methods + typeseq _typeseq() const noexcept override { return s_typeseq; } + void _drop(Opaque d) const noexcept override { _dcast(d).~DRepr(); } + + // const methods + void on_allocator(Copaque data, obj mm) const noexcept override { + return I::on_allocator(_dcast(data), mm); + } + + // non-const methods + + ///@} + + private: + using I = Impl; + + public: + /** @defgroup mm-resourcevisitor-xfer-member-vars **/ + ///@{ + + /** typeseq for template parameter DRepr **/ + static typeseq s_typeseq; + /** true iff satisfies facet implementation **/ + static bool _valid; + + ///@} + }; + + template + xo::facet::typeseq + IResourceVisitor_Xfer::s_typeseq + = xo::facet::typeseq::id(); + + template + bool + IResourceVisitor_Xfer::_valid + = xo::facet::valid_facet_implementation(); + +} /*namespace mm */ +} /*namespace xo*/ + +/* end IResourceVisitor_Xfer.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/visitor/RResourceVisitor.hpp b/xo-alloc2/include/xo/alloc2/visitor/RResourceVisitor.hpp new file mode 100644 index 00000000..6fa7a633 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/visitor/RResourceVisitor.hpp @@ -0,0 +1,85 @@ +/** @file RResourceVisitor.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/ResourceVisitor.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/ResourceVisitor.json5] + **/ + +#pragma once + +#include "AResourceVisitor.hpp" + +namespace xo { +namespace mm { + +/** @class RResourceVisitor + **/ +template +class RResourceVisitor : public Object { +private: + using O = Object; + +public: + /** @defgroup mm-resourcevisitor-router-type-traits **/ + ///@{ + using ObjectType = Object; + using DataPtr = Object::DataPtr; + using typeseq = xo::reflect::typeseq; + using size_type = AResourceVisitor::size_type; + ///@} + + /** @defgroup mm-resourcevisitor-router-ctors **/ + ///@{ + RResourceVisitor() {} + RResourceVisitor(Object::DataPtr data) : Object{std::move(data)} {} + RResourceVisitor(const AResourceVisitor * iface, void * data) + requires std::is_same_v + : Object(iface, data) {} + + ///@} + /** @defgroup mm-resourcevisitor-router-methods **/ + ///@{ + + // explicit injected content + + // builtin methods + typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); } + void _drop() const noexcept { O::iface()->_drop(O::data()); } + + // const methods + void on_allocator(obj mm) const noexcept { + return O::iface()->on_allocator(O::data(), mm); + } + + // non-const methods (still const in router!) + + ///@} + /** @defgroup mm-resourcevisitor-member-vars **/ + ///@{ + + static bool _valid; + + ///@} +}; + +template +bool +RResourceVisitor::_valid = xo::facet::valid_object_router(); + +} /*namespace mm*/ +} /*namespace xo*/ + +namespace xo { namespace facet { + template + struct RoutingFor { + using RoutingType = xo::mm::RResourceVisitor; + }; +} } + +/* end RResourceVisitor.hpp */ diff --git a/xo-alloc2/src/alloc2/AAllocator.cpp b/xo-alloc2/src/alloc2/AAllocator.cpp new file mode 100644 index 00000000..e0d90284 --- /dev/null +++ b/xo-alloc2/src/alloc2/AAllocator.cpp @@ -0,0 +1,10 @@ +/** @file AAllocator.cpp **/ + +#include "alloc/AAllocator.hpp" + +namespace xo { + namespace mm { + } +} + +/* end AAlocator.cpp */ diff --git a/xo-alloc2/src/alloc2/CMakeLists.txt b/xo-alloc2/src/alloc2/CMakeLists.txt new file mode 100644 index 00000000..8972e546 --- /dev/null +++ b/xo-alloc2/src/alloc2/CMakeLists.txt @@ -0,0 +1,32 @@ +# alloc2/CMakeLists.txt + +set(SELF_LIB xo_alloc2) +set(SELF_SRCS + + init_alloc2.cpp + SetupAlloc2.cpp + + CollectorTypeRegistry.cpp + GCObjectConversion.cpp + + facet/ICollector_Any.cpp + + IGCObject_Any.cpp + facet/IGCObjectVisitor_Any.cpp + + AAllocator.cpp + IAllocator_Any.cpp + IAllocator_DArena.cpp + + IAllocIterator_Any.cpp + IAllocIterator_DArenaIterator.cpp + + IResourceVisitor_Any.cpp + ) + +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_arena) +xo_dependency(${SELF_LIB} xo_facet) +xo_dependency(${SELF_LIB} subsys) +xo_dependency(${SELF_LIB} indentlog) diff --git a/xo-alloc2/src/alloc2/CollectorTypeRegistry.cpp b/xo-alloc2/src/alloc2/CollectorTypeRegistry.cpp new file mode 100644 index 00000000..33f307c1 --- /dev/null +++ b/xo-alloc2/src/alloc2/CollectorTypeRegistry.cpp @@ -0,0 +1,50 @@ +/** @file CollectorTypeRegistry.cpp + * + * @author Roland Conybeare, Mar 2026 + **/ + +#include "CollectorTypeRegistry.hpp" +#include + +namespace xo { + namespace mm { + CollectorTypeRegistry & + CollectorTypeRegistry::instance() + { + static CollectorTypeRegistry s_instance; + + return s_instance; + } + + void + CollectorTypeRegistry::register_types(init_function_type fn) + { + scope log(XO_DEBUG(true)); + + init_seq_v_.push_back(fn); + } + + bool + CollectorTypeRegistry::install_types(obj gc) + { + scope log(XO_DEBUG(true)); + + bool ok = true; + + size_t i = 0; + size_t n = init_seq_v_.size(); + log && log("run n init steps", xtag("n", n)); + + for (const auto & fn : init_seq_v_) { + log && log("do install fn (", i+1, "/", n, ")"); + + ok = ok & fn(gc); + } + + return ok; + } + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end CollectorTypeRegistry.cpp */ diff --git a/xo-alloc2/src/alloc2/GCObjectConversion.cpp b/xo-alloc2/src/alloc2/GCObjectConversion.cpp new file mode 100644 index 00000000..6df96b4b --- /dev/null +++ b/xo-alloc2/src/alloc2/GCObjectConversion.cpp @@ -0,0 +1,28 @@ +/** @file GCObjectConversion.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include "GCObjectConversion.hpp" + +namespace xo { + using xo::reflect::typeseq; + + namespace scm { + + void + GCObjectConversionUtil::_from_gco_fail_aux(obj gco, + typeseq tseq, + scope * p_log) + { + p_log->retroactively_enable(); + if (p_log) { + (*p_log)(xtag("gco.tseq", gco._typeseq())); + (*p_log)(xtag("DRepr.tseq", tseq)); + } + } + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end GCObjectConversion.cpp */ diff --git a/xo-alloc2/src/alloc2/IAllocIterator_Any.cpp b/xo-alloc2/src/alloc2/IAllocIterator_Any.cpp new file mode 100644 index 00000000..4ae6c575 --- /dev/null +++ b/xo-alloc2/src/alloc2/IAllocIterator_Any.cpp @@ -0,0 +1,41 @@ +/** @file IAllocIterator_Any.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "alloc/IAllocIterator_Any.hpp" +#include +#include + +namespace xo { + namespace mm { + using xo::facet::DVariantPlaceholder; + using xo::facet::valid_facet_implementation; + using xo::reflect::typeseq; + + void + IAllocIterator_Any::_fatal() { + /* control here on uninitialized IAllocator_Any. + * Initialized instance will have specific implementation type + * e.g. IAllocator_Xfer + */ + + std::cerr << "fatal" + << ": attempt to call uninitialized" + << " IAllocIterator_Any method" + << std::endl; + std::terminate(); + } + + typeseq + IAllocIterator_Any::s_typeseq = typeseq::id(); + + bool + IAllocIterator_Any::_valid = valid_facet_implementation(); + + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocIterator_Any.cpp */ diff --git a/xo-alloc2/src/alloc2/IAllocIterator_DArenaIterator.cpp b/xo-alloc2/src/alloc2/IAllocIterator_DArenaIterator.cpp new file mode 100644 index 00000000..e8091935 --- /dev/null +++ b/xo-alloc2/src/alloc2/IAllocIterator_DArenaIterator.cpp @@ -0,0 +1,52 @@ +/** @file IAllocIterator_DArenaIterator.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "arena/IAllocIterator_DArenaIterator.hpp" +#include "AllocIterator.hpp" +#include +#include + +namespace xo { + using std::byte; + + namespace mm { + AllocInfo + IAllocIterator_DArenaIterator::deref(const DArenaIterator & ix) noexcept + { + return ix.deref(); + } + + cmpresult + IAllocIterator_DArenaIterator::compare(const DArenaIterator & ix, + const obj & other_arg) noexcept + { + scope log(XO_DEBUG(false), + xtag("&ix", &ix), + xtag("ix.arena", ix.arena_), xtag("ix.pos", ix.pos_)); + + /* downcast from variant */ + auto other = obj::from(other_arg); + + if (!other) + return cmpresult::incomparable(); + + DArenaIterator & other_ix = *other.data(); + + log && log(xtag("&other_ix", &other_ix), + xtag("other_ix.arena", other_ix.arena_), + xtag("other_ix.pos", other_ix.pos_)); + + return ix.compare(other_ix); + } + + void + IAllocIterator_DArenaIterator::next(DArenaIterator & ix) noexcept + { + ix.next(); + } + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocIterator_DArenaIterator.cpp */ diff --git a/xo-alloc2/src/alloc2/IAllocator_Any.cpp b/xo-alloc2/src/alloc2/IAllocator_Any.cpp new file mode 100644 index 00000000..d8b1aabf --- /dev/null +++ b/xo-alloc2/src/alloc2/IAllocator_Any.cpp @@ -0,0 +1,44 @@ +/** @file IAllocator_Any.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "alloc/IAllocator_Any.hpp" +#include +#include + +namespace xo { + using xo::facet::DVariantPlaceholder; + using xo::facet::typeseq; + using xo::facet::valid_facet_implementation; + + namespace mm { + + // LCOV_EXCL_START + void + IAllocator_Any::_fatal() + { + /* control here on uninitialized IAllocator_Any. + * Initialized instance will have specific implementation type + * e.g. IAllocator_Xfer + */ + + std::cerr << "fatal" + << ": attempt to call uninitialized" + << " IAllocator_Any method" + << std::endl; + + std::terminate(); + } + // LCOV_EXCL_STOP + + typeseq + IAllocator_Any::s_typeseq = typeseq::id(); + + bool + IAllocator_Any::_valid = valid_facet_implementation(); + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocator_Any.cpp */ diff --git a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp new file mode 100644 index 00000000..ea60b69b --- /dev/null +++ b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp @@ -0,0 +1,186 @@ +/** @file IAllocator_DArena.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "AllocIterator.hpp" +#include "GCObject.hpp" +#include "arena/IAllocator_DArena.hpp" +#include "arena/IAllocIterator_DArenaIterator.hpp" // for alloc_range +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xo { + using xo::facet::with_facet; + using std::size_t; + using std::byte; + + namespace mm { + + std::string_view + IAllocator_DArena::name(const DArena & s) noexcept { + return s.config_.name_; + } + + size_t + IAllocator_DArena::reserved(const DArena & s) noexcept { + return s.reserved(); + } + + size_t + IAllocator_DArena::size(const DArena & s) noexcept { + return s.limit_ - s.lo_; + } + + size_t + IAllocator_DArena::committed(const DArena & s) noexcept { + return s.committed(); + } + + size_t + IAllocator_DArena::available(const DArena & s) noexcept { + return s.available(); + } + + size_t + IAllocator_DArena::allocated(const DArena & s) noexcept { + return s.allocated(); + } + + void + IAllocator_DArena::visit_pools(const DArena & s, + const MemorySizeVisitor & visitor) + { + s.visit_pools(visitor); + } + + bool + IAllocator_DArena::contains(const DArena & s, + const void * p) noexcept + { + return (s.lo_ <= p) && (p < s.hi_); + } + + AllocError + IAllocator_DArena::last_error(const DArena & s) noexcept { + return s.last_error_; + } + + AllocInfo + IAllocator_DArena::alloc_info(const DArena & s, value_type mem) noexcept + { + return s.alloc_info(mem); + } + + auto + IAllocator_DArena::alloc_range(const DArena & s, + DArena & ialloc) noexcept -> range_type + { + scope log(XO_DEBUG(false)); + + DArenaIterator * begin_ix = construct_with(ialloc, &s, s.begin_header()); + DArenaIterator * end_ix = construct_with(ialloc, &s, s.end_header()); + + obj begin_obj + = with_facet::mkobj(begin_ix); + obj end_obj + = with_facet::mkobj( end_ix); + + log && log(xtag("begin_obj.typeseq", begin_obj._typeseq())); + + obj begin_vt = begin_obj; + obj end_vt = end_obj; + + log && log(xtag("begin_vt.typeseq", begin_vt._typeseq())); + + log && log(xtag("begin_ix", begin_ix), + xtag("begin_ix.arena", begin_ix->arena_), + xtag("begin_ix.pos", begin_ix->pos_)); + + range_type retval = range_type(std::make_pair(begin_vt, end_vt)); + + log && log(xtag("1.retval.first.typeseq", + retval.begin()._typeseq())); + + return retval; + } + + bool + IAllocator_DArena::expand(DArena & s, size_t target_z) noexcept + { + return s.expand(target_z, __PRETTY_FUNCTION__); + } /*expand*/ + + std::byte * + IAllocator_DArena::alloc(DArena & s, + typeseq t, + std::size_t req_z) + { + return s.alloc(t, req_z); + } + + std::byte * + IAllocator_DArena::super_alloc(DArena & s, + typeseq t, + std::size_t req_z) + { + return s.super_alloc(t, req_z); + } + + std::byte * + IAllocator_DArena::sub_alloc(DArena & s, + std::size_t req_z, + bool complete_flag) + { + return s.sub_alloc(req_z, complete_flag); + } + + void + IAllocator_DArena::clear(DArena & s) + { + s.clear(); + //s.checkpoint_ = s.lo_; + } + + void + IAllocator_DArena::barrier_assign_aux(DArena & s, + void * parent, + AGCObject * lhs_iface, void ** lhs_data, + AGCObject * rhs_iface, void * rhs_data) + { + (void)s; + (void)parent; + + // usually would expect this to just forward to DArena. + // That's problematic in this case, because DArena is at lower level + // relative to obj; + // recall that DArena is used in the implementation of xo-facet/ + // + // In any case, for DArena no write barrier is applied. + // Instead just perform the fop assignment + + // replacing vtable pointer here + if (lhs_iface) { + ::memcpy((void *)lhs_iface, (void *)rhs_iface, sizeof(AGCObject)); + } + + *lhs_data = rhs_data; + } + +#ifdef OBSOLETE + void + IAllocator_DArena::destruct_data(DArena & s) + { + s.~DArena(); + } +#endif + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocator_DArena.cpp */ diff --git a/xo-alloc2/src/alloc2/IGCObject_Any.cpp b/xo-alloc2/src/alloc2/IGCObject_Any.cpp new file mode 100644 index 00000000..4e729e08 --- /dev/null +++ b/xo-alloc2/src/alloc2/IGCObject_Any.cpp @@ -0,0 +1,54 @@ +/** @file IGCObject_Any.cpp + * + **/ + +#include "gc/IGCObject_Any.hpp" +#include +#include + +namespace xo { +namespace mm { + +using xo::facet::DVariantPlaceholder; +using xo::facet::typeseq; +using xo::facet::valid_facet_implementation; + +void +IGCObject_Any::_fatal() +{ + /* control here on uninitialized IAllocator_Any. + * Initialized instance will have specific implementation type + */ + std::cerr << "fatal" + << ": attempt to call uninitialized" + << " IGCObject_Any method" + << std::endl; + std::terminate(); +} + +typeseq +IGCObject_Any::s_typeseq = typeseq::id(); + +bool +IGCObject_Any::_valid + = valid_facet_implementation(); + +// nonconst methods + +auto +IGCObject_Any::gco_shallow_move(Opaque, obj) const noexcept -> Opaque +{ + _fatal(); +} + +auto +IGCObject_Any::visit_gco_children(Opaque, VisitReason, obj) const noexcept -> void +{ + _fatal(); +} + + +} /*namespace mm*/ +} /*namespace xo*/ + +/* end IGCObject_Any.cpp */ diff --git a/xo-alloc2/src/alloc2/IResourceVisitor_Any.cpp b/xo-alloc2/src/alloc2/IResourceVisitor_Any.cpp new file mode 100644 index 00000000..0c9cf0e7 --- /dev/null +++ b/xo-alloc2/src/alloc2/IResourceVisitor_Any.cpp @@ -0,0 +1,42 @@ +/** @file IResourceVisitor_Any.cpp + * + **/ + +#include "visitor/IResourceVisitor_Any.hpp" +#include +#include + +namespace xo { +namespace mm { + +using xo::facet::DVariantPlaceholder; +using xo::facet::typeseq; +using xo::facet::valid_facet_implementation; + +void +IResourceVisitor_Any::_fatal() +{ + /* control here on uninitialized IAllocator_Any. + * Initialized instance will have specific implementation type + */ + std::cerr << "fatal" + << ": attempt to call uninitialized" + << " IResourceVisitor_Any method" + << std::endl; + std::terminate(); +} + +typeseq +IResourceVisitor_Any::s_typeseq = typeseq::id(); + +bool +IResourceVisitor_Any::_valid + = valid_facet_implementation(); + +// nonconst methods + + +} /*namespace mm*/ +} /*namespace xo*/ + +/* end IResourceVisitor_Any.cpp */ diff --git a/xo-alloc2/src/alloc2/SetupAlloc2.cpp b/xo-alloc2/src/alloc2/SetupAlloc2.cpp new file mode 100644 index 00000000..da14c0ff --- /dev/null +++ b/xo-alloc2/src/alloc2/SetupAlloc2.cpp @@ -0,0 +1,39 @@ +/** @file SetupAlloc2.cpp + * + * @author Roland Conybeare, Feb 2026 + **/ + +#include "SetupAlloc2.hpp" +#include +#include +#include +#include + +namespace xo { + using xo::facet::FacetRegistry; + //using xo::facet::TypeRegistry; + using xo::reflect::typeseq; + + namespace mm { + + bool + SetupAlloc2::register_facets() + { + scope log(XO_DEBUG(true)); + + FacetRegistry::register_impl(); + FacetRegistry::register_impl(); + + log && log(xtag("DArena.tseq", typeseq::id())); + log && log(xtag("DArenaIterator.tseq", typeseq::id())); + + log && log(xtag("AAllocator.tseq", typeseq::id())); + log && log(xtag("AAllocIterator.tseq", typeseq::id())); + + return true; + } + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end SetupAlloc2.cpp */ diff --git a/xo-alloc2/src/alloc2/facet/ICollector_Any.cpp b/xo-alloc2/src/alloc2/facet/ICollector_Any.cpp new file mode 100644 index 00000000..2743bfb4 --- /dev/null +++ b/xo-alloc2/src/alloc2/facet/ICollector_Any.cpp @@ -0,0 +1,78 @@ +/** @file ICollector_Any.cpp + * + **/ + +#include "gc/ICollector_Any.hpp" +#include +#include + +namespace xo { +namespace mm { + +using xo::facet::DVariantPlaceholder; +using xo::facet::typeseq; +using xo::facet::valid_facet_implementation; + +void +ICollector_Any::_fatal() +{ + /* control here on uninitialized IAllocator_Any. + * Initialized instance will have specific implementation type + */ + std::cerr << "fatal" + << ": attempt to call uninitialized" + << " ICollector_Any method" + << std::endl; + std::terminate(); +} + +typeseq +ICollector_Any::s_typeseq = typeseq::id(); + +bool +ICollector_Any::_valid + = valid_facet_implementation(); + +// nonconst methods + +auto +ICollector_Any::install_type(Opaque, const AGCObject &) -> bool +{ + _fatal(); +} + +auto +ICollector_Any::add_gc_root_poly(Opaque, obj *) -> void +{ + _fatal(); +} + +auto +ICollector_Any::remove_gc_root_poly(Opaque, obj *) -> void +{ + _fatal(); +} + +auto +ICollector_Any::request_gc(Opaque, Generation) -> void +{ + _fatal(); +} + +auto +ICollector_Any::assign_member(Opaque, void *, obj *, obj &) -> void +{ + _fatal(); +} + +auto +ICollector_Any::alloc_copy(Opaque, std::byte *) -> void * +{ + _fatal(); +} + + +} /*namespace mm*/ +} /*namespace xo*/ + +/* end ICollector_Any.cpp */ diff --git a/xo-alloc2/src/alloc2/facet/IGCObjectVisitor_Any.cpp b/xo-alloc2/src/alloc2/facet/IGCObjectVisitor_Any.cpp new file mode 100644 index 00000000..934aa946 --- /dev/null +++ b/xo-alloc2/src/alloc2/facet/IGCObjectVisitor_Any.cpp @@ -0,0 +1,54 @@ +/** @file IGCObjectVisitor_Any.cpp + * + **/ + +#include "gc/IGCObjectVisitor_Any.hpp" +#include +#include + +namespace xo { +namespace mm { + +using xo::facet::DVariantPlaceholder; +using xo::facet::typeseq; +using xo::facet::valid_facet_implementation; + +void +IGCObjectVisitor_Any::_fatal() +{ + /* control here on uninitialized IAllocator_Any. + * Initialized instance will have specific implementation type + */ + std::cerr << "fatal" + << ": attempt to call uninitialized" + << " IGCObjectVisitor_Any method" + << std::endl; + std::terminate(); +} + +typeseq +IGCObjectVisitor_Any::s_typeseq = typeseq::id(); + +bool +IGCObjectVisitor_Any::_valid + = valid_facet_implementation(); + +// nonconst methods + +auto +IGCObjectVisitor_Any::alloc_copy(Opaque, std::byte *) const -> void * +{ + _fatal(); +} + +auto +IGCObjectVisitor_Any::visit_child(Opaque, VisitReason, AGCObject *, void **) const noexcept -> void +{ + _fatal(); +} + + +} /*namespace mm*/ +} /*namespace xo*/ + +/* end IGCObjectVisitor_Any.cpp */ diff --git a/xo-alloc2/src/alloc2/init_alloc2.cpp b/xo-alloc2/src/alloc2/init_alloc2.cpp new file mode 100644 index 00000000..a10bc66a --- /dev/null +++ b/xo-alloc2/src/alloc2/init_alloc2.cpp @@ -0,0 +1,36 @@ +/** @file init_alloc2.cpp + * + * @author Roland Conybeare, Feb 2026 + **/ + +#include "init_alloc2.hpp" +#include "SetupAlloc2.hpp" + +namespace xo { + using xo::mm::SetupAlloc2; + // using xo::mm::alloc2_register_types; + // using xo::mm::CollectorTypeRegistry; + + void + InitSubsys::init() + { + SetupAlloc2::register_facets(); + } + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* direct subsystem deps for xo-alloc2/ (if/when) */ + //retval ^= InitSubsys>::require(); + + /* xo-alloc2/'s own initialization code */ + retval ^= Subsystem::provide("alloc2", &init); + + return retval; + } + +} /*namespace xo*/ + +/* end init_alloc2.cpp */ diff --git a/xo-alloc2/utest/CMakeLists.txt b/xo-alloc2/utest/CMakeLists.txt new file mode 100644 index 00000000..f7af4ff2 --- /dev/null +++ b/xo-alloc2/utest/CMakeLists.txt @@ -0,0 +1,30 @@ +# xo-alloc2/utest/CMakeLists.txt +# + +set(UTEST_EXE utest.alloc2) +set(UTEST_SRCS + alloc2_utest_main.cpp + objectmodel.test.cpp + arena.test.cpp + IAllocator_Any.test.cpp + DArenaIterator.test.cpp + Generation.test.cpp + Role.test.cpp + VisitReason.test.cpp + ResourceVisitor.test.cpp + dp.test.cpp + random_allocs.cpp +) + +if (ENABLE_TESTING) + xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) + xo_self_dependency(${UTEST_EXE} xo_alloc2) + xo_dependency(${UTEST_EXE} xo_testutil) + xo_headeronly_dependency(${UTEST_EXE} randomgen) + xo_headeronly_dependency(${UTEST_EXE} indentlog) + xo_headeronly_dependency(${UTEST_EXE} xo_facet) + xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) + xo_external_target_dependency(${UTEST_EXE} CLI11 CLI11::CLI11) +endif() + +# end CMakeLists.txt diff --git a/xo-alloc2/utest/DArenaIterator.test.cpp b/xo-alloc2/utest/DArenaIterator.test.cpp new file mode 100644 index 00000000..00f22bbe --- /dev/null +++ b/xo-alloc2/utest/DArenaIterator.test.cpp @@ -0,0 +1,305 @@ +/** @file AllocIterator.test.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include +#include +#include +#include +#include +#include "arena/IAllocIterator_DArenaIterator.hpp" +#include "padding.hpp" +#include +#include +#include + +namespace xo { + using xo::mm::AAllocator; + + using xo::mm::AllocInfo; + + using xo::mm::AAllocIterator; + using xo::mm::IAllocIterator_Any; + using xo::mm::IAllocIterator_Xfer; + using xo::mm::IAllocIterator_DArenaIterator; + using xo::mm::DArenaIterator; + + using xo::mm::ArenaConfig; + using xo::mm::DArena; + + using xo::mm::padding; + using xo::mm::error; + + using xo::facet::DVariantPlaceholder; + using xo::facet::obj; + using xo::facet::typeseq; + + using std::byte; + + namespace ut { + TEST_CASE("IAllocIterator_Xfer_DArenaIterator", "[alloc2]") + { + auto log = Utest::ut_scope(); + + /* verify IAllocIterator_Xfer is constructible + satisfies concept checks */ + IAllocIterator_Xfer xfer; + REQUIRE(IAllocIterator_Xfer::_valid); + } + + TEST_CASE("IAllocIterator_Any", "[alloc2]") + { + auto log = Utest::ut_scope(); + + /* verify IAllocIterator_Any is constructible + satisfies concept checks */ + IAllocIterator_Any any; + REQUIRE(IAllocIterator_Any::_valid); + } + + TEST_CASE("obj_IAllocIterator", "[alloc2]") + { + auto log = Utest::ut_scope(); + + /* verify variant obj constructible */ + obj obj_any; + REQUIRE(obj_any.iface()); + REQUIRE(obj_any.data() == nullptr); + } + + TEST_CASE("IAllocIterator-disabled-1", "[alloc2]") + { + auto log = Utest::ut_scope(); + + /* verify iteration over empty arena */ + /* typed allocator a1o */ + ArenaConfig cfg { .name_ = "testarena", + .size_ = 64*1024, + .debug_flag_ = false }; + DArena arena = DArena::map(cfg); + obj a1o{&arena}; + + REQUIRE(a1o.reserved() >= cfg.size_); + REQUIRE(a1o.committed() == 0); + REQUIRE(a1o.available() == 0); + REQUIRE(a1o.allocated() == 0); + + DArenaIterator ix = arena.begin(); + /* iteration not supported since we did not set + * ArenaConfig.store_header_flag_ + */ + REQUIRE(ix.is_invalid()); + REQUIRE(ix != ix); + REQUIRE(arena.error_count_ == 1); + REQUIRE(arena.last_error_.error_seq_ == 1); + REQUIRE(arena.last_error_.error_ == error::alloc_iterator_not_supported); + + DArenaIterator end_ix = arena.end(); + /* iteration not supported since we did not set + * ArenaConfig.store_header_flag_ + */ + REQUIRE(end_ix.is_invalid()); + REQUIRE(end_ix != end_ix); + REQUIRE(arena.error_count_ == 2); + REQUIRE(arena.last_error_.error_seq_ == 2); + REQUIRE(arena.last_error_.error_ == error::alloc_iterator_not_supported); + } + + TEST_CASE("IAllocIterator-emptyarena", "[alloc2]") + { + auto log = Utest::ut_scope(); + + /* verify iteration over empty arena */ + /* typed allocator a1o */ + ArenaConfig cfg { .name_ = "testarena", + .size_ = 64*1024, + .store_header_flag_ = true, + .debug_flag_ = false }; + DArena arena = DArena::map(cfg); + obj a1o{&arena}; + + REQUIRE(a1o.reserved() >= cfg.size_); + REQUIRE(a1o.committed() == 0); + REQUIRE(a1o.available() == 0); + REQUIRE(a1o.allocated() == 0); + + DArenaIterator ix = arena.begin(); + DArenaIterator end_ix = arena.end(); + + REQUIRE(ix.is_valid()); + REQUIRE(end_ix.is_valid()); + + /* verify obj 'fat pointer' packaging */ + obj ix_vt{&ix}; + obj end_ix_vt{&end_ix}; + + REQUIRE(ix_vt.iface()); + REQUIRE(ix_vt.data()); + REQUIRE(end_ix_vt.iface()); + REQUIRE(end_ix_vt.data()); + + /* arena is empty, so begin==end */ + REQUIRE(ix == end_ix); + + REQUIRE(arena.error_count_ == 0); + + /* empty iterator cannot be dereferenced */ + { + AllocInfo bad_info = *ix; + REQUIRE(!bad_info.is_valid()); + + REQUIRE(arena.error_count_ == 1); + REQUIRE(arena.last_error_.error_seq_ == 1); + REQUIRE(arena.last_error_.error_ == error::alloc_iterator_deref); + } + + /* empty iterator cannot be advanced */ + { + ix.next(); + + REQUIRE(arena.error_count_ == 2); + REQUIRE(arena.last_error_.error_seq_ == 2); + REQUIRE(arena.last_error_.error_ == error::alloc_iterator_next); + } + } + + TEST_CASE("IAllocIterator-singlearena", "[alloc2]") + { + auto log = Utest::ut_scope(); + + ArenaConfig cfg { .name_ = "testarena", + .size_ = 64*1024, + .store_header_flag_ = true, + .debug_flag_ = false }; + DArena arena = DArena::map(cfg); + obj a1o{&arena}; + + REQUIRE(arena.error_count_ == 0); + REQUIRE(a1o.reserved() >= cfg.size_); + REQUIRE(a1o.committed() == 0); + REQUIRE(a1o.available() == 0); + REQUIRE(a1o.allocated() == 0); + + /* arbitrary alloc size */ + size_t req_z = 13; + byte * mem = a1o.alloc(typeseq::sentinel(), req_z); + + REQUIRE(arena.error_count_ == 0); + REQUIRE(mem != nullptr); + + { + DArenaIterator ix = arena.begin(); + DArenaIterator end_ix = arena.end(); + + REQUIRE(ix.is_valid()); + REQUIRE(end_ix.is_valid()); + /* arena is non-empty, so begin!=end */ + REQUIRE (ix != end_ix); + + REQUIRE(arena.error_count_ == 0); + + /* valid iterator can be dereferenced */ + { + AllocInfo info = *ix; + + REQUIRE(arena.error_count_ == 0); + REQUIRE(info.is_valid()); + REQUIRE(info.size() == padding::with_padding(req_z)); + + auto [payload_lo, payload_hi] = info.payload(); + + REQUIRE(payload_lo == mem); + REQUIRE(payload_hi == mem + info.size()); + } + + /* valid iterator can be advanced */ + { + ix.next(); + + REQUIRE(arena.error_count_ == 0); + REQUIRE(ix == end_ix); + } + } + + // repeat, this time with generic iterators + { + log && log(xtag("section", "obj"), + xtag("arena", &arena), + xtag("arena.lo", arena.lo_), + xtag("arena.free", arena.free_)); + + DArena scratch_mm + = DArena::map( + ArenaConfig{ + .size_ = 4*1024, + .hugepage_z_ = 4*1024}); + + auto range = a1o.alloc_range(scratch_mm); + + obj ix = range.begin(); + obj end_ix = range.end(); + + REQUIRE(ix.iface()); + REQUIRE(ix.data()); + REQUIRE(end_ix.iface()); + REQUIRE(end_ix.data()); + + REQUIRE(scratch_mm.allocated() >= 2*sizeof(DArenaIterator)); + REQUIRE(scratch_mm.available() > 0); + + log && log(xtag("ix._typeseq", ix._typeseq()), + xtag("ix.data", ix.data())); + + log && log(xtag("typeseq", + typeseq::id())); + log && log(xtag("typeseq", + typeseq::id())); + log && log(xtag("typeseq", + typeseq::id())); + + REQUIRE(ix.compare(ix).is_equal()); + + //REQUIRE(ix.compare(end_ix).is_equal()); + + REQUIRE(ix != end_ix); + + { + REQUIRE(arena.error_count_ == 0); + REQUIRE(ix.deref().is_valid()); + REQUIRE(ix.deref().size() == padding::with_padding(req_z)); + + auto [payload_lo, payload_hi] = ix.deref().payload(); + + REQUIRE(payload_lo == mem); + REQUIRE(payload_hi == mem + ix.deref().size()); + } + + /* valid iterator can be advanced + reaches end */ + { + ++ix; //ix.next(); + + REQUIRE(arena.error_count_ == 0); + REQUIRE(ix == end_ix); + } + } + + // repeat, this time using range iteration + { + DArena scratch_mm + = DArena::map( + ArenaConfig{ + .size_ = 4*1024, + .hugepage_z_ = 4*1024}); + + for (const auto & info : a1o.alloc_range(scratch_mm)) { + REQUIRE(info.is_valid()); + REQUIRE(info.size() == padding::with_padding(req_z)); + REQUIRE(info.payload().first == mem); + REQUIRE(info.payload().second == mem + info.size()); + } + } + } + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end AllocIterator.test.cpp */ diff --git a/xo-alloc2/utest/Generation.test.cpp b/xo-alloc2/utest/Generation.test.cpp new file mode 100644 index 00000000..dbfe3f52 --- /dev/null +++ b/xo-alloc2/utest/Generation.test.cpp @@ -0,0 +1,45 @@ +/** @file Generation.test.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include +#include "Generation.hpp" +#include + +namespace xo { + using xo::mm::Generation; + using xo::mm::c_max_generation; + + namespace ut { + + TEST_CASE("Generation-1", "[Generation]") + { + auto log = Utest::ut_scope(); + + REQUIRE(Generation::nursery() == 0); + REQUIRE(Generation::g0() == 0); + REQUIRE(Generation::g1() == 1); + REQUIRE(Generation::sentinel() == c_max_generation); + + REQUIRE(Generation::g0().is_sentinel() == false); + REQUIRE(Generation::g1().is_sentinel() == false); + REQUIRE(Generation::sentinel().is_sentinel()); + + REQUIRE(Generation::g0() != Generation::sentinel()); + REQUIRE(Generation::g1() != Generation::sentinel()); + + REQUIRE(!(Generation::g0() > Generation::g1())); + REQUIRE(Generation::g1() > Generation::g0()); + + auto g = Generation::g0(); + ++g; + REQUIRE(g == Generation::g1()); + ++g; + REQUIRE(g > Generation::g1()); + } + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end Generation.test.cpp */ diff --git a/xo-alloc2/utest/IAllocator_Any.test.cpp b/xo-alloc2/utest/IAllocator_Any.test.cpp new file mode 100644 index 00000000..020b8bca --- /dev/null +++ b/xo-alloc2/utest/IAllocator_Any.test.cpp @@ -0,0 +1,32 @@ +/** @file IAllocator_Any.test.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include +#include +#include +#include +#include +#include + +namespace xo { + using xo::mm::AAllocator; + + namespace ut { + + TEST_CASE("IAllocator_Any", "[alloc2][death]") + { + auto log = Utest::ut_scope(); + + // null allocator + obj alloc_any; + + // NOTE: tried using a fork() strategy to verify termination, + // but child process doesn't get measured by gcov + } + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end IAllocator_Any.test.cpp */ diff --git a/xo-alloc2/utest/ResourceVisitor.test.cpp b/xo-alloc2/utest/ResourceVisitor.test.cpp new file mode 100644 index 00000000..7d0df87f --- /dev/null +++ b/xo-alloc2/utest/ResourceVisitor.test.cpp @@ -0,0 +1,28 @@ +/** @file ResourceVisitor.test.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include +#include +#include + +namespace xo { + using xo::mm::AResourceVisitor; + + namespace ut { + + TEST_CASE("ResourceVisitor-1", "[resourcevisitor]") + { + auto log = Utest::ut_scope(); + + obj v; + + REQUIRE(v.iface()); + REQUIRE(!v.iface()->_has_null_vptr()); + } + + } +} /*namespace xo*/ + +/* end ResourceVisitor.test.cpp */ diff --git a/xo-alloc2/utest/Role.test.cpp b/xo-alloc2/utest/Role.test.cpp new file mode 100644 index 00000000..f4db9cf5 --- /dev/null +++ b/xo-alloc2/utest/Role.test.cpp @@ -0,0 +1,49 @@ +/** @file Role.test.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include +#include "role.hpp" +#include + +namespace xo { + using xo::mm::Role; + + namespace ut { + + TEST_CASE("Role-1", "[Role]") + { + auto log = Utest::ut_scope(); + + /* 1. there are two distinct valid roles, 'to' and 'from', + * 2. valid roles fall in interval [begin, end) + */ + + REQUIRE(Role::to_space() == Role::to_space()); + REQUIRE(Role::from_space() == Role::from_space()); + REQUIRE(Role::to_space() != Role::from_space()); + + Role x0 = Role::begin(); + { + bool ok = (x0 == Role::to_space() || x0 == Role::from_space()); + REQUIRE(ok); + } + REQUIRE(x0 != Role::end()); + + Role x1 = x0.next(); + REQUIRE(x1 != x0); + { + bool ok = (x1 == Role::to_space() || x1 == Role::from_space()); + REQUIRE(ok); + } + REQUIRE(x1 != Role::end()); + + Role x2 = x1.next(); + REQUIRE(x2 == Role::end()); + } + + } +} /*namespace xo*/ + +/* end Role.test.cpp */ diff --git a/xo-alloc2/utest/VisitReason.test.cpp b/xo-alloc2/utest/VisitReason.test.cpp new file mode 100644 index 00000000..2b50159d --- /dev/null +++ b/xo-alloc2/utest/VisitReason.test.cpp @@ -0,0 +1,29 @@ +/** @file VisitReason.test.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include +#include +#include + +namespace xo { + using xo::mm::VisitReason; + + namespace ut { + + TEST_CASE("VisitReason-1", "[visitreason]") + { + auto log = Utest::ut_scope(); + + REQUIRE(VisitReason::unspecified() == VisitReason::unspecified()); + + REQUIRE(VisitReason::unspecified() != VisitReason::forward()); + REQUIRE(VisitReason::unspecified() != VisitReason::verify()); + REQUIRE(VisitReason::forward() != VisitReason::verify()); + } + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end VisitReason.test.cpp */ diff --git a/xo-alloc2/utest/alloc2_utest_main.cpp b/xo-alloc2/utest/alloc2_utest_main.cpp new file mode 100644 index 00000000..4ec10b7c --- /dev/null +++ b/xo-alloc2/utest/alloc2_utest_main.cpp @@ -0,0 +1,18 @@ +/* file alloc2_utest_main.cpp */ + +#define CATCH_CONFIG_RUNNER // before UtestListener.hpp + +#include +#include + +namespace xo { + CATCH_REGISTER_LISTENER(UtestListener); +} + +int +main(int argc, char* argv[]) +{ + return xo::UtestAppStart("utest.alloc2").run(argc, argv); +} + +/* end alloc2_utest_main.cpp */ diff --git a/xo-alloc2/utest/arena.test.cpp b/xo-alloc2/utest/arena.test.cpp new file mode 100644 index 00000000..68433211 --- /dev/null +++ b/xo-alloc2/utest/arena.test.cpp @@ -0,0 +1,422 @@ +/** @file arena.test.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xo { + using xo::mm::AAllocator; + using xo::mm::IAllocator_DArena; + using xo::mm::IAllocator_Xfer; + using xo::mm::AllocError; + using xo::mm::DArena; + using xo::mm::AllocHeaderConfig; + using xo::mm::ArenaConfig; + using xo::mm::AllocHeader; + using xo::mm::AllocInfo; + using xo::mm::padding; + using xo::mm::error; + using xo::facet::with_facet; + using xo::facet::obj; + using xo::facet::typeseq; + using xo::scope; + using std::byte; + using std::size_t; + + namespace ut { + TEST_CASE("IAllocator_Xfer_DArena", "[alloc2]") + { + auto log = Utest::ut_scope(); + + IAllocator_Xfer xfer; + + REQUIRE(IAllocator_Xfer::_valid); + } + + TEST_CASE("DArena-medium", "[alloc2][DArena]") + { + auto log = Utest::ut_scope(); + + ArenaConfig cfg { .name_ = "testarena", + .size_ = 10*1024*1024 }; + DArena arena = DArena::map(cfg); + + REQUIRE(arena.config_.name_ == cfg.name_); + REQUIRE(arena.lo_ != nullptr); + REQUIRE(arena.free_ == arena.lo_); + REQUIRE(arena.limit_ == arena.lo_); + REQUIRE(arena.hi_ != nullptr); + REQUIRE(arena.hi_ > arena.lo_); + REQUIRE(((size_t)arena.hi_ - (size_t)arena.lo_) % cfg.hugepage_z_ == 0); + REQUIRE(arena.lo_ + cfg.size_ <= arena.hi_); + + /* verify arena.lo_ is aligned on a page boundary */ + REQUIRE(((size_t)(arena.lo_) & (cfg.hugepage_z_ - 1)) == 0); + + /* verify arena.hi_ is aligned on a hugepage boundary */ + REQUIRE(((size_t)(arena.hi_) & (cfg.hugepage_z_ - 1)) == 0); + + byte * lo = arena.lo_; + byte * free = arena.free_; + byte * limit = arena.limit_; + byte * hi = arena.hi_; + size_t committed_z = arena.committed_z_; + + DArena arena2 = std::move(arena); + + REQUIRE(arena.lo_ == nullptr); + REQUIRE(arena.free_ == nullptr); + REQUIRE(arena.limit_ == nullptr); + REQUIRE(arena.hi_ == nullptr); + REQUIRE(arena.committed_z_ == 0); + + REQUIRE(arena.lo_ == nullptr); + REQUIRE(arena2.lo_ == lo); + REQUIRE(arena2.free_ == free); + REQUIRE(arena2.limit_ == limit); + REQUIRE(arena2.hi_ == hi); + REQUIRE(arena2.committed_z_ == committed_z); + } + + TEST_CASE("allocator-any-1", "[alloc2][AAllocator]") + { + auto log = Utest::ut_scope(); + + /* empty allocator alloc1 */ + obj alloc1; + + REQUIRE(!alloc1); + REQUIRE(alloc1.iface() != nullptr); + REQUIRE(alloc1.data() == nullptr); + + /* typed allocator a1o */ + ArenaConfig cfg { .name_ = "testarena", + .size_ = 1 }; + DArena arena = DArena::map(cfg); + //obj a1o{&arena}; + auto a1o = with_facet::mkobj(&arena); + + REQUIRE(!a1o._has_null_vptr()); + + REQUIRE(a1o); + REQUIRE(a1o.iface() != nullptr); + REQUIRE(a1o.data() != nullptr); + + REQUIRE(a1o._typeseq() + == xo::facet::FacetImplType::s_typeseq); + REQUIRE(a1o.name() == cfg.name_); + REQUIRE(a1o.reserved() >= cfg.size_); + REQUIRE(a1o.reserved() < cfg.size_ + a1o.data()->page_z_); + REQUIRE(a1o.reserved() % a1o.data()->page_z_ == 0); + REQUIRE(a1o.size() == 0); + REQUIRE(a1o.committed() == 0); + REQUIRE(a1o.allocated() == 0); + + a1o._drop(); + { + REQUIRE(a1o.allocated() == 0); + REQUIRE(a1o.committed() == 0); + } + } + + TEST_CASE("allocator-expand-1", "[alloc2][AAllocator]") + { + auto log = Utest::ut_scope(); + + /* typed allocator a1o */ + ArenaConfig cfg { .name_ = "testarena", + .size_ = 1, + .debug_flag_ = false }; + DArena arena = DArena::map(cfg); + //obj a1o{&arena}; + auto a1o = with_facet::mkobj(&arena); + + REQUIRE(!a1o._has_null_vptr()); + + REQUIRE(a1o.available() == 0); + REQUIRE(a1o.allocated() == 0); + + size_t z2 = 512; + bool ok = a1o.expand(z2); + + INFO(xtag("last_error", a1o.last_error())); + + REQUIRE(ok); + + REQUIRE(a1o.reserved() % a1o.data()->page_z_ == 0); + REQUIRE(a1o.committed() >= z2); + REQUIRE(a1o.committed() % a1o.data()->page_z_ == 0); + /* .size() is synonym for .committed() */ + REQUIRE(a1o.size() == a1o.committed()); + REQUIRE(a1o.available() >= z2); + REQUIRE(a1o.available() == a1o.committed()); + REQUIRE(a1o.allocated() == 0); + + a1o._drop(); + { + REQUIRE(a1o.allocated() == 0); + REQUIRE(a1o.committed() == 0); + } + } + + TEST_CASE("allocator-alloc-1", "[alloc2][AAllocator]") + { + auto log = Utest::ut_scope(); + + /* typed allocator a1o */ + ArenaConfig cfg { .name_ = "testarena", + .size_ = 64*1024, + .debug_flag_ = false }; + DArena arena = DArena::map(cfg); + auto a1o = with_facet::mkobj(&arena); + + REQUIRE(!a1o._has_null_vptr()); + + REQUIRE(a1o.reserved() >= cfg.size_); + REQUIRE(a1o.committed() == 0); + REQUIRE(a1o.available() == 0); + REQUIRE(a1o.allocated() == 0); + + size_t z0 = 1; + byte * m0 = a1o.alloc(typeseq::sentinel(), 1); + + REQUIRE(m0); + REQUIRE(a1o.last_error().error_ == error::ok); + REQUIRE(a1o.last_error().error_seq_ == 0); + REQUIRE(a1o.allocated() >= z0); + REQUIRE(a1o.allocated() < z0 + padding::c_alloc_alignment ); + REQUIRE(a1o.allocated() <= a1o.committed()); + REQUIRE(a1o.allocated() + a1o.available() == a1o.committed()); + REQUIRE(a1o.committed() <= a1o.reserved()); + + size_t z1 = 16; + byte * m1 = a1o.alloc(typeseq::sentinel(), z1); + + REQUIRE(m1); + REQUIRE(a1o.last_error().error_ == error::ok); + REQUIRE(a1o.last_error().error_seq_ == 0); + REQUIRE(a1o.allocated() >= z0 + z1); + REQUIRE(a1o.allocated() < z0 + z1 + 2 * padding::c_alloc_alignment ); + REQUIRE(a1o.allocated() <= a1o.committed()); + REQUIRE(a1o.allocated() + a1o.available() == a1o.committed()); + REQUIRE(a1o.committed() <= a1o.reserved()); + + a1o._drop(); + { + REQUIRE(a1o.allocated() == 0); + REQUIRE(a1o.committed() == 0); + } + } + + TEST_CASE("allocator-alloc-2", "[alloc2][Allocator]") + { + auto log = Utest::ut_scope(); + + using header_type = AllocHeader; + + /* typed allocator a1o, with object header */ + ArenaConfig cfg { .name_ = "testarena", + .size_ = 64*1024, + .store_header_flag_ = true, + /* up to 4GB */ + .header_ = AllocHeaderConfig(0 /*guard_z*/, + 0xfd /*guard_byte*/, + 0 /*tseq-bits*/, + 0 /*age-bits*/, + 32 /*size-bits*/), + .debug_flag_ = false, + }; + DArena arena = DArena::map(cfg); + //obj a1o{&arena}; + auto a1o = with_facet::mkobj(&arena); + + REQUIRE(!a1o._has_null_vptr()); + + REQUIRE(a1o.reserved() >= cfg.size_); + REQUIRE(a1o.committed() == 0); + REQUIRE(a1o.available() == 0); + REQUIRE(a1o.allocated() == 0); + + size_t z0 = 1; + byte * m0 = a1o.alloc(typeseq::sentinel(), 1); + + REQUIRE(m0); + + header_type* header = (header_type*)(m0 - sizeof(header_type)); + + REQUIRE(a1o.contains(header)); + REQUIRE(cfg.header_.size(*header) == padding::with_padding(z0)); + //REQUIRE(((*header) & cfg.header_size_mask_) == padding::with_padding(z0)); + REQUIRE(a1o.last_error().error_ == error::ok); + REQUIRE(a1o.last_error().error_seq_ == 0); + REQUIRE(a1o.allocated() >= z0); + REQUIRE(a1o.allocated() < sizeof(AAllocator::header_type) + z0 + padding::c_alloc_alignment ); + REQUIRE(a1o.allocated() <= a1o.committed()); + REQUIRE(a1o.allocated() + a1o.available() == a1o.committed()); + REQUIRE(a1o.committed() <= a1o.reserved()); + + auto committed0_z = a1o.committed(); + + // also test alloc info + AllocInfo m0_info = a1o.alloc_info(m0); + { + REQUIRE(m0_info.size() >= z0); + + // {tseq, age}: feature disabled must be zero + REQUIRE(m0_info.tseq() == 0); + REQUIRE(m0_info.age() == 0); + + REQUIRE(m0_info.payload().first == m0); + REQUIRE(m0_info.payload().second >= m0 + z0); + + REQUIRE(m0_info.guard_z() == cfg.header_.guard_z_); + REQUIRE((uint32_t)m0_info.guard_byte() == (uint32_t)cfg.header_.guard_byte_); + } + + a1o.clear(); + { + // allocated size got reset + REQUIRE(a1o.allocated() == 0); + // committed size unchanged + REQUIRE(a1o.committed() == committed0_z); + REQUIRE(a1o.last_error().error_ == error::ok); + REQUIRE(a1o.last_error().error_seq_ == 0); + + // allocator no longer contains m0 (now points to unallocated but committed memory + // (not exposed via AAllocator! + // REQUIRE(a1o.contains_allocated(m0) == false); + + REQUIRE(a1o.contains(m0)); + } + + a1o._drop(); + { + REQUIRE(a1o.allocated() == 0); + REQUIRE(a1o.committed() == 0); + } + } + + TEST_CASE("allocator-alloc-3", "[alloc2][Allocator]") + { + auto log = Utest::ut_scope(); + + using header_type = AllocHeader; + + /* typed allocator a1o, with object header + guard bytes */ + ArenaConfig cfg { .name_ = "testarena", + .size_ = 64*1024, + .store_header_flag_ = true, + /* up to 4GB */ + .header_ = AllocHeaderConfig(8 /*guard_z*/, + 0xfd /*guard-byte*/, + 0 /*tseq-bits*/, + 0 /*age-bits*/, + 32 /*size-bits*/), + .debug_flag_ = false, + }; + DArena arena = DArena::map(cfg); + //obj a1o{&arena}; + auto a1o = with_facet::mkobj(&arena); + + REQUIRE(!a1o._has_null_vptr()); + + REQUIRE(a1o.reserved() >= cfg.size_); + REQUIRE(a1o.committed() == 0); + REQUIRE(a1o.available() == 0); + REQUIRE(a1o.allocated() == 0); + + size_t z0 = 1; + byte * m0 = a1o.alloc(typeseq::sentinel(), 1); + + REQUIRE(m0); + + // + // > < + // < guard>
< pad >< guard> + // ++++++++0000zzzzXppppppp++++++++ + // ^ ^ ^ ^ + // guard0 header m0 guard1 + // + + byte * guard0 = m0 - sizeof(header_type) - cfg.header_.guard_z_; + header_type* header = (header_type*)(m0 - sizeof(header_type)); + size_t pad = padding::with_padding(z0) - z0; + byte * guard1 = m0 + z0 + pad; + { + REQUIRE(a1o.contains(guard0)); + REQUIRE(a1o.contains(header)); + REQUIRE(cfg.header_.size(*header) == padding::with_padding(z0)); + //REQUIRE(((*header) & cfg.header_size_mask_) == padding::with_padding(z0)); + + REQUIRE(a1o.last_error().error_ == error::ok); + REQUIRE(a1o.last_error().error_seq_ == 0); + + REQUIRE(a1o.allocated() == (cfg.header_.guard_z_ + + sizeof(header_type) + + z0 + + pad + + cfg.header_.guard_z_)); + REQUIRE(a1o.allocated() <= a1o.committed()); + REQUIRE(a1o.allocated() + a1o.available() == a1o.committed()); + REQUIRE(a1o.committed() <= a1o.reserved()); + } + + a1o._drop(); + { + REQUIRE(a1o.allocated() == 0); + REQUIRE(a1o.committed() == 0); + } + } + + TEST_CASE("allocator-fail-1", "[alloc2][AAllocator]") + { + auto log = Utest::ut_scope(); + + /* typed allocator a1o */ + ArenaConfig cfg { .name_ = "testarena", + .size_ = 64*1024, + .debug_flag_ = false }; + DArena arena = DArena::map(cfg); + auto a1o = with_facet::mkobj(&arena); + //obj a1o{&arena}; + + REQUIRE(cfg.size_ <= cfg.hugepage_z_); + + REQUIRE(!a1o._has_null_vptr()); + + REQUIRE(a1o.reserved() >= cfg.size_); + REQUIRE(a1o.committed() == 0); + REQUIRE(a1o.available() == 0); + REQUIRE(a1o.allocated() == 0); + + size_t z0 = cfg.hugepage_z_ + 1; + byte * m0 = a1o.alloc(typeseq::sentinel(), z0); + AllocError err = a1o.last_error(); + { + REQUIRE(!m0); + REQUIRE(err.error_ == error::reserve_exhausted); + REQUIRE(err.error_seq_ == 1); + REQUIRE(err.request_z_ >= z0); + REQUIRE(err.request_z_ < z0 + padding::c_alloc_alignment); + REQUIRE(err.committed_z_ == 0); + REQUIRE(err.reserved_z_ == arena.reserved()); + } + + a1o._drop(); + { + REQUIRE(a1o.allocated() == 0); + REQUIRE(a1o.committed() == 0); + } + } + } /*namespace ut*/ +} /*namespace xo*/ + +/* end arena.test.cpp */ diff --git a/xo-alloc2/utest/dp.test.cpp b/xo-alloc2/utest/dp.test.cpp new file mode 100644 index 00000000..842c7dc7 --- /dev/null +++ b/xo-alloc2/utest/dp.test.cpp @@ -0,0 +1,107 @@ +/** @file dp.test.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include +#include "dp.hpp" +#include +#include +#include + +namespace xo { + using xo::mm::AAllocator; + using xo::mm::DArena; + using xo::mm::ArenaConfig; + + namespace { + class Foo { + public: + explicit Foo(uint32_t * p_counter) : p_counter_{p_counter} {} + + static constexpr bool is_gc_eligible() { return false; } + + ~Foo() { + ++(*p_counter_); + } + + private: + uint32_t * p_counter_ = nullptr; + }; + } + + namespace ut { + + TEST_CASE("dp-1", "[dp]") + { + auto log = Utest::ut_scope(); + + //ArenaConfig cfg { .name_ = "testarena", .size_ = 1024 }; + //DArena arena = DArena::map(cfg); + //auto mm = obj(&arena); + + uint32_t counter = 0; + Foo foo(&counter); + + REQUIRE(counter == 0); + { + dp foo_dp(&foo); + + REQUIRE(foo_dp); + REQUIRE(foo_dp.data()); + REQUIRE(foo_dp.is_gc_eligible() == false); + + // foo_dp dtor runs here, increments counter + } + REQUIRE(counter == 1); + } + + TEST_CASE("dp-2", "[dp]") + { + auto log = Utest::ut_scope(); + + uint32_t counter = 0; + Foo foo(&counter); + + REQUIRE(counter == 0); + { + dp foo_dp(&foo); + + REQUIRE(foo_dp); + REQUIRE(foo_dp.data() == &foo); + + foo_dp.release(); + + REQUIRE(!foo_dp); + REQUIRE(!foo_dp.data()); + + // foo_dp dtor runs here, increments counter + } + REQUIRE(counter == 0); + } + + TEST_CASE("dp-DArena", "[dp][DArena]") + { + auto log = Utest::ut_scope(); + + ArenaConfig cfg { .name_ = "testarena", .size_ = 1024 }; + DArena arena = DArena::map(cfg); + //auto mm = obj(&arena); + + REQUIRE(arena.reserved() > 0); + REQUIRE(arena.is_mapped()); + { + dp arena_dp(&arena); + + REQUIRE(arena_dp); + REQUIRE(arena_dp.data() == &arena); + } + + REQUIRE(arena.reserved() == 0); + REQUIRE(!arena.is_mapped()); + } + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end dp.test.cpp */ diff --git a/xo-alloc2/utest/objectmodel.test.cpp b/xo-alloc2/utest/objectmodel.test.cpp new file mode 100644 index 00000000..43067b6e --- /dev/null +++ b/xo-alloc2/utest/objectmodel.test.cpp @@ -0,0 +1,724 @@ +/** @file objectmodel.test.cpp + * + * @author: Roland Conybeare, Dec 2025 + * + * Testing rust-like traits (split iface/data) object model. + * Analogous to: + * - rust traits + * - haskell type classes + * - go interfaces + * + * See xo-alloc2/README.md + * + * Ingredients: + * 1. abstract interface: all virtual methods. No assumptions about representation. + * No state (besides implict vtable pointer) + * + * Rules: + * 1. abstract interface must have no state besides implicit vtable pointer. + * This is a strongly-held principle, we're keeping data representation entirely + * separate + * 2. representations as passive as possible. No getters. All public members. + * Exceptions to this principle: + * - ctors (including copy/move ctors, when needed) + * - dtors + * + * Conventions: + * 1. abstract interface start with letter A, e.g. AComplex + * 2. representation struct names start with letter D, e.g. DPolar, DRect. + * Don't require "intended primary interface" in the name, + * since we're seeking ability to attach the same data to different interfaces + * 3. implementations start with letter I. They concatenate abstract interface name + * and representation name, e.g. IComplex_PolarCoords + * + * Example Class Diagram + * + * AComplex + * ^ + * | + * /------------------------+--------------------\ + * | | | + * IComplex_DRectCoords IComplex_DPolarCoords IComplex_Any + * = IComplex_Specific = IComplex_Specific + * + * ^ ^ + * | | + * ... OUniqueBox + * + * ^ + * | + * RComplex + * = RoutingFor::RoutingType + * ^ + * | + * ubox + * + * AComplex: abstract interface. + * explicit, type-erased, data pointer argument + * virtual AComplex::xcoord(void * data) + * + * DPolarCoords: passive representation + * + * IComplex_DPolarCoords: implement AComplex interface for representation DPolarCoords + * static methods with typed data pointer argument + * IComplex_DPolarCoords::xcoord(void * data) + * IComplex_DPolarCoords::_xcoord(DPolarCoords * data) + * + * OUniqueBox: + * a self-sufficient object, associating + * interface AComplex with representation DPolarCoords + * OUniqueBox .data() method is DPolarCoord* + * 'impure' in the sense that it mixes code+data + * + * RComplex: convenience interface for OUniqueBox + * + * ubox: + * self-sufficent object with convenient interface + * + * Application code will deal with ubox + **/ + +#include +#include +#include +#include +#include + +namespace xo { + namespace ut { + namespace { + struct PlaceholderAbstractInterface { + virtual double foo(void * data) const = 0; + }; + + static_assert(sizeof(PlaceholderAbstractInterface) == sizeof(void*)); + + /** Concept: abstract interface requirements + * Use: when inheriting an abstract interface + * (see also valid_abstract_interface() below) + **/ + template + concept abstract_interface = requires { + std::is_abstract_v, + std::is_polymorphic_v; + /** require no state, just a single vtable pointer **/ + sizeof(T) == sizeof(PlaceholderAbstractInterface); + !std::has_virtual_destructor_v; + std::is_trivially_destructible_v; + }; + + /** For example ISpecific = IComplex_DPolarCoords + **/ + template + concept implements_interface = requires { + std::is_base_of_v; + std::is_default_constructible_v; + std::is_standard_layout_v; + /** require no additional state **/ + sizeof(ISpecific) == sizeof(AInterface); + }; + + /** Router delivers data to interface implementation **/ + template + concept provides_router = requires { + std::is_base_of_v; + sizeof(Router) == sizeof(Object); + }; + + /** Use: when defining an abstract interface AMyInterface + * + * struct AMyInterface { + * virtual void foo(void * data) const = 0; + * }; + * + * static_assert(valid_abstract_interface()); + * + **/ + template + consteval bool valid_abstract_interface() + { + static_assert(std::is_abstract_v, + "Abstract interface expected to have all-abstract methods"); + static_assert(std::is_polymorphic_v, + "Abstract interface expected to have vtable"); + static_assert(sizeof(T) == sizeof(PlaceholderAbstractInterface), + "Abstract interface expected to have no state except for a single vtable pointer"); + static_assert(!std::has_virtual_destructor_v, + "Abstract interface does not benefit from virtual dtor since no state"); + static_assert(std::is_trivially_destructible_v, + "Abstract interface expected to have trivial dtor since no state"); + return true; + }; + + template + consteval bool valid_interface_implementation() + requires (valid_abstract_interface()) + { + static_assert(std::is_base_of_v, + "Interface implementation must inherit abstract interface"); + static_assert(std::is_default_constructible_v, + "Interface implementation must be default-constructible"); + static_assert(sizeof(ISpecific) == sizeof(AInterface), + "Interface implementation may no introduce state"); + static_assert(!std::has_virtual_destructor_v, + "Interface implementation may does not benefit from virtual dtor since no state"); + static_assert(std::is_trivially_destructible_v, + "Interface implementation expected to have trivial dtor since no state"); + + // don't need this test, it's covered by sizeof check + //static_assert(std::is_pointer_interconvertible_base_of_v, + // "Interface implementation must directly inherit interface (no base offset)"); + + return true; + }; + + template + consteval bool valid_object_traits() + { + static_assert(requires { typename OObject::AbstractInterface; }, + "Object type must provide typename Object::AbstractInterface"); + static_assert(requires { typename OObject::ISpecific; }, + "Object type must provide typename Object::ISpecific"); + static_assert(requires { typename OObject::DataType; }, + "Object type must provide typename Object::DataType"); + static_assert(valid_interface_implementation, + "Object::ISpecific must implement Object::AbstractInterface"); + static_assert(std::is_standard_layout_v, + "Object must have standard layout, i.e. no virtual methods. Virtual methods belong in OObject::AbstractInterface"); + static_assert(requires(const OObject & obj) { { obj.iface() } -> std::convertible_to; }, + "Object must have non-virtual method iface() returning const Object::AbstractInterface"); + static_assert(requires(const OObject & obj) { { obj.data() } -> std::convertible_to; }, + "Object must have non-virtual method data() returning Object::DataType*"); + + return true; + } + + template + consteval bool valid_object_router() + { + static_assert(requires { typename RRouter::ObjectType; }, + "Router type must provide typename Router::ObjectType"); + static_assert(valid_object_traits, + "Router::ObjectType must satisfy objectmodel traits"); + static_assert(std::is_standard_layout_v, + "Router must have standard laayout, i.e. no virtual methods. Virtual methods belong in OObject::AbstractInterface*>"); + return true; + }; + + // ---------------------------------------------------------------- + + /** Associates an interface with an representation. + * Specialize to record such associations. + **/ + + template + struct ISpecificFor; + + /** type-erased implementation of AComplex, see below **/ + struct IComplex_Any; + + /** abstract interface for a complex number **/ + struct AComplex { + using TypeErasedIface = IComplex_Any; + + virtual double xcoord(void * data) const = 0; + virtual double ycoord(void * data) const = 0; + virtual double argument(void * data) const = 0; + virtual double magnitude(void * data) const = 0; + + virtual void destruct_data(void * data) const = 0; + + private: + static bool _valid; + }; + + bool + AComplex::_valid = valid_abstract_interface(); + + // ---------------------------------------------------------------- + + /** type-erased implementation of AComplex, for runtime polymorphism + * Usable by (and only by) overwriting with a typed implementation, + * such as IComplex_RectCoords or IComplex_PolarCoords. + **/ + struct IComplex_Any : public AComplex { + virtual double xcoord(void *) const final override { assert(false); return 0.0; } + virtual double ycoord(void *) const final override { assert(false); return 0.0; } + virtual double argument(void *) const final override { assert(false); return 0.0; } + virtual double magnitude(void *) const final override { assert(false); return 0.0; } + + virtual void destruct_data(void *) const final override { assert(false); } + + private: + static bool _valid; + }; + + bool + IComplex_Any::_valid = valid_interface_implementation(); + + // ---------------------------------------------------------------- + + template + struct IComplex_Specific : public AComplex { + static double _xcoord(Repr *); + static double _ycoord(Repr *); + static double _argument(Repr *); + static double _magnitude(Repr *); + static void _destruct_data(Repr *); + + virtual double xcoord(void * data) const final override { return _xcoord((Repr*)data); } + virtual double ycoord(void * data) const final override { return _ycoord((Repr*)data); } + virtual double argument(void * data) const final override { return _argument((Repr*)data); } + virtual double magnitude(void * data) const final override { return _magnitude((Repr*)data); } + virtual void destruct_data(void * data) const final override { _destruct_data((Repr*)data); } + + public: + static bool _valid; + }; + + template + bool + IComplex_Specific::_valid = valid_interface_implementation; + + // ----- Placeholder for opaque data ----- + + // Placeholder used for template specialization + + struct DOpaquePlaceholder {}; + + using IComplex_DOpaquePlaceholder = IComplex_Any; + + template <> + struct ISpecificFor { + using ImplType = IComplex_Any; + }; + + // ----- Representation: Polar Coordinates ----- + + /** complex number, represented using polar coordinates **/ + struct DPolarCoords { + DPolarCoords(double arg, double mag) : arg_{arg}, mag_{mag} {} + + double arg_; + double mag_; + }; + + // ----- AComplex for DPolarCoords ----- + + /** implementation of AComplex interface with representation DPolarCoords **/ + using IComplex_DPolarCoords = IComplex_Specific; + + template <> + double + IComplex_Specific::_xcoord(DPolarCoords * data) { + return data->mag_ * std::cos(data->arg_); + }; + + template <> + double IComplex_Specific::_ycoord(DPolarCoords * data) { + return data->mag_ * std::sin(data->arg_); + }; + + template <> + double + IComplex_Specific::_argument(DPolarCoords * data) { + return data->arg_; + } + + template <> + double + IComplex_Specific::_magnitude(DPolarCoords * data) { + return data->mag_; + } + + template <> + void + IComplex_Specific::_destruct_data(DPolarCoords * data) { + data->~DPolarCoords(); + } + + template <> + struct ISpecificFor { + using ImplType = IComplex_Specific; + }; + + // ----- Representation: Rectangular Coordinates ----- + + /** complex number, represented using rectangular coordinates **/ + struct DRectCoords { + DRectCoords(double x, double y) : x_{x}, y_{y} {} + + double x_; + double y_; + }; + + // ----- AComplex for DRectCoords ----- + + /** implementation of AComplex interface with representation DRectCoords **/ + using IComplex_DRectCoords = IComplex_Specific; + + template <> + double + IComplex_Specific::_xcoord(DRectCoords * data) { + return data->x_; + }; + + template <> + double + IComplex_Specific::_ycoord(DRectCoords * data) { + return data->y_; + }; + + template <> + double + IComplex_Specific::_argument(DRectCoords * data) { + return std::atan(data->y_ / data->x_); + } + + template <> + double + IComplex_Specific::_magnitude(DRectCoords * data) { + double x = data->x_; + double y = data->y_; + + return std::sqrt(x*x + y*y); + } + + template <> + void + IComplex_Specific::_destruct_data(DRectCoords * data) { + data->~DRectCoords(); + } + + template <> + struct ISpecificFor { + using ImplType = IComplex_Specific; + }; + + // ----- polymorphic box ----- + + /** + * Unqiuely-owned instance with runtime polymorphism. + * + * Unlike OUniqueBox can use for variant data + * without additional overhead. Tradeoff is that avoiding such + * overhead excludes std::unique_ptr. + * + * We're going to instead rely on AInterface providing a destruct_data() method, + * so in practice get the deleter from interface state. + * + * Possibly means we need all abstract interfaces to share a common base + * + * Remarks: + * - when @tparam Data is supplied + **/ + template + struct OUniqueBox { + using AbstractInterface = AInterface; + using ISpecific = ISpecificFor::ImplType; + /* note: Data can be void here */ + using DataType = Data; + using DataBox = Data*; + + explicit OUniqueBox() {} + /* unsatisfactory b/c doesn't enforce that @p d is heap-allocated */ + explicit OUniqueBox(DataBox d) : data_{std::move(d)} {} + + ~OUniqueBox() { + if (data_ != nullptr) { + this->iface()->destruct_data(data_); + delete data_; + this->data_ = nullptr; + } + } + + const AInterface * iface() const + requires std::is_same_v + { + return std::launder(&iface_); + } + + const AInterface * iface() const + requires (!std::is_same_v) + { + return &iface_; + } + + /** note: would prefer this to be constexpr, but not simple asof gcc 14.3 **/ + static bool _valid; + + /** note: load-bearing for routing classes such as RComplex **/ + Data * data() const { return data_; } + + ISpecific iface_; + DataBox data_ = nullptr; + }; + + template + bool + OUniqueBox::_valid = valid_object_traits(); + + // ----- Router; RFoo pairs with AFoo ----- + + /** For example, inherit OUniqueBox + **/ + template + struct RComplex : public Object { + using ObjectType = Object; + + RComplex() {} + RComplex(Object::DataBox data) : Object{std::move(data)} {} + + double xcoord() const { return Object::iface()->xcoord(Object::data()); } + double ycoord() const { return Object::iface()->ycoord(Object::data()); } + double argument() const { return Object::iface()->argument(Object::data()); } + double magnitude() const { return Object::iface()->magnitude(Object::data()); } + + /** note: would prefer this to be constexpr, but seems infeasible asof gcc 14.3 **/ + static bool _valid; + }; + + template + bool + RComplex::_valid = valid_object_router(); + + template + requires abstract_interface + struct RoutingFor; + + template + struct RoutingFor { + using RoutingType = RComplex; + }; + + template + using RoutingType = RoutingFor::RoutingType; + + // ----- unique any; coordinates with OUniqueBox ----- + + /** boxed object, held by unique-pointer equivalent. + * - With default Data argument: + * type-erased polymorphic container + * - with specific Data argument: + * typed container. Trivially de-virtualizable + * + * Example: + * std::unique_ptr z1_in + * = std::make_unique(1.0, 0.0): + * ubox z1{z1_in.release()}; + * z1.xcoord(); + * + * + * +-----+ +-----------------+ + * Interface | x-------------->| vtable for | + * +-----+ | some descendant | + * Data | x--------\ | of AInterface | + * +-----+ | | | + * | +-----------------+ + * | + * | +--------------+ + * \----->| data :: Repr | + * +--------------+ + * + * Binary representation of unay + * is compatible for different values of @tparam Data + * as long as vtable pointer moves along with data pointer. + * + * In particular binary representation for + * ubox is as if it inherited ubox + * (even though it does not as far as compiler is concerned) + * + * This is load-bearing for @ref move2any see below + **/ + template + struct ubox : public RoutingType> { + using Super = RoutingType>; + + ubox() {} + explicit ubox(Super::DataBox d) : Super(d) {} + + /** copy contents of this instance into *dest. + **/ + void move2any(ubox * dest) { + static_assert(sizeof(ubox) + == sizeof(ubox)); + + ::memcpy((void*)dest, (void*)this, sizeof(ubox)); + // this is almost right. But would not copy vtable pointer + //*dest = *(reinterpret_cast*>(this)); + this->data_ = nullptr; + } + + /** move constructor from a different representation. + * allowed given: + * - same abstract interface + * - same strategy (unique / refcounted / ..) + **/ + template + ubox(ubox && other) + requires (std::is_same_v + || std::is_convertible_v) + : Super() + { + static_assert(sizeof(ubox) + == sizeof(ubox)); + + other.move2any(this); + + assert(other.data_ = nullptr); + } + }; + + } /*namespace*/ + + // ----- UNIT TESTS ----- + + TEST_CASE("objectmodel-specific-1", "[objectmodel]") + { + auto log = Utest::ut_scope(); + + /* arg=0, mag=1 -> 1+0i */ + DPolarCoords polar{0.0, 1.0}; + IComplex_Specific polar_iface; + + REQUIRE(polar_iface._xcoord(&polar) == 1.0); + REQUIRE(polar_iface._ycoord(&polar) == 0.0); + REQUIRE(polar_iface._argument(&polar) == 0.0); + REQUIRE(polar_iface._magnitude(&polar) == 1.0); + } + + TEST_CASE("objectmodel-specific-2", "[objectmodel]") + { + auto log = Utest::ut_scope(); + + /* arg=0, mag=1 -> 1+0i */ + DRectCoords rect{1.0, 0.0}; + IComplex_Specific rect_iface; + + REQUIRE(rect_iface._xcoord(&rect) == 1.0); + REQUIRE(rect_iface._ycoord(&rect) == 0.0); + REQUIRE(rect_iface._argument(&rect) == 0.0); + REQUIRE(rect_iface._magnitude(&rect) == 1.0); + } + + TEST_CASE("uniquebox-1", "[objectmodel]") + { + auto log = Utest::ut_scope(); + + auto tmp = std::make_unique(0.0, 1.0); + OUniqueBox box{tmp.release()}; + + REQUIRE(box.iface()->xcoord(box.data()) == 1.0); + REQUIRE(box.iface()->ycoord(box.data()) == 0.0); + REQUIRE(box.iface()->argument(box.data()) == 0.0); + REQUIRE(box.iface()->magnitude(box.data()) == 1.0); + } + + TEST_CASE("router-1", "[objectmodel]") + { + auto log = Utest::ut_scope(); + + using Object = OUniqueBox; + auto tmp = std::make_unique(0.0, 1.0); + + RComplex box{tmp.release()}; + + REQUIRE(box.xcoord() == 1.0); + REQUIRE(box.ycoord() == 0.0); + REQUIRE(box.argument() == 0.0); + REQUIRE(box.magnitude() == 1.0); + } + + TEST_CASE("routing-type-1", "[objectmodel]") + { + auto log = Utest::ut_scope(); + + using Object = OUniqueBox; + auto tmp = std::make_unique(0.0, 1.0); + + RoutingType box{tmp.release()}; + + REQUIRE(box.xcoord() == 1.0); + REQUIRE(box.ycoord() == 0.0); + REQUIRE(box.argument() == 0.0); + REQUIRE(box.magnitude() == 1.0); + } + + TEST_CASE("ubox-1", "[objectmodel]") + { + auto log = Utest::ut_scope(); + + auto tmp = std::make_unique(0.0, 1.0); + ubox box{tmp.release()}; + + REQUIRE(box.xcoord() == 1.0); + REQUIRE(box.ycoord() == 0.0); + REQUIRE(box.argument() == 0.0); + REQUIRE(box.magnitude() == 1.0); + } + + TEST_CASE("ubox-2", "[objectmodel]") + { + auto log = Utest::ut_scope(); + + auto tmp = std::make_unique(1.0, 0.0); + ubox box{tmp.release()}; + + REQUIRE(box.xcoord() == 1.0); + REQUIRE(box.ycoord() == 0.0); + REQUIRE(box.argument() == 0.0); + REQUIRE(box.magnitude() == 1.0); + } + + TEST_CASE("ubox-any-1", "[objectmodel]") + { + auto log = Utest::ut_scope(); + + /* default ctor */ + ubox any; + } + + TEST_CASE("ubox-any-2", "[objectmodel]") + { + auto log = Utest::ut_scope(); + + /* equivalent to ubox, but impl doesn't use std::unique_ptr */ + ubox any{new DRectCoords{1.0, 0.0}}; + + REQUIRE(any.xcoord() == 1.0); + REQUIRE(any.ycoord() == 0.0); + REQUIRE(any.argument() == 0.0); + REQUIRE(any.magnitude() == 1.0); + } + + TEST_CASE("ubox-any-3", "[objectmodel]") + { + auto log = Utest::ut_scope(); + + /* equivalent to ubox, but impl doesn't use std::unique_ptr */ + ubox z1{new DRectCoords{1.0, 0.0}}; + + DRectCoords * z1_data = z1.data(); + + REQUIRE(z1.data() != nullptr); + REQUIRE(z1.xcoord() == 1.0); + + /* can type-erase */ + ubox z1_any; + + REQUIRE(z1_any.data() == nullptr); + + z1.move2any(&z1_any); + + /* z1 should be empty, it moved itself */ + REQUIRE(z1.data() == nullptr); + REQUIRE((void*)z1_any.data() == (void*)z1_data); + + REQUIRE(z1_any.xcoord() == 1.0); + } + } /*namespace ut*/ +} /*namespace xo*/ + +/* end objectmodel.test.cp */ diff --git a/xo-alloc2/utest/random_allocs.cpp b/xo-alloc2/utest/random_allocs.cpp new file mode 100644 index 00000000..470ee840 --- /dev/null +++ b/xo-alloc2/utest/random_allocs.cpp @@ -0,0 +1,222 @@ +/** @file random_allocs.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "random_allocs.hpp" +#include +#include +#include +#include +#include +#include + +namespace utest { + using xo::mm::AllocInfo; + using xo::mm::DArena; + using xo::mm::ArenaConfig; + using xo::mm::padding; + using xo::rng::xoshiro256ss; + using xo::facet::obj; + using xo::scope; + using xo::xtag; + using std::uint32_t; + using std::byte; + + /* remember an allocation result. + * application owns memory in [lo, lo+z) + */ + struct Alloc { + Alloc() = default; + Alloc(byte * lo, size_t z) : lo_{lo}, z_{z} {} + + byte * lo() const { return lo_; } + byte * hi() const { return lo_ + z_; } + + byte * lo_ = nullptr; + size_t z_ = 0; + }; + + bool + AllocUtil::random_allocs(uint32_t n_alloc, + uint32_t max_alloc_z, + bool catch_flag, + xoshiro256ss * p_rgen, + obj mm) + { + using xo::facet::typeseq; + + scope log(XO_DEBUG(catch_flag), xtag("n-alloc", n_alloc)); + + /* track allocs. verify: + * - allocs are non-overlapping + * - allocs have valid alloc header + * - allocs surrounded by guard bytes + * + * allocs sorted on Alloc::lo + */ + std::map allocs_by_lo_map; + /* allocs sorted on Alloc::hi */ + std::map allocs_by_hi_map; + + for (uint32_t i_alloc = 0; i_alloc < n_alloc; ++i_alloc) { + std::normal_distribution ngen{5.0, 1.5}; + + double si = ngen(*p_rgen); + double zi = ::pow(2.0, si); + std::size_t z = ::ceil(zi); + if (z > max_alloc_z) + z = max_alloc_z; + + bool ok_flag = true; + + std::byte * mem = mm.alloc(typeseq::sentinel(), z); + + log && log(xtag("i_alloc", i_alloc), + xtag("si", si), + xtag("zi", zi), + xtag("mem", mem)); + log && log(xtag("used", mm.allocated()), + xtag("avail", mm.available()), + xtag("commit", mm.committed()), + xtag("resv", mm.reserved())); + + + REQUIRE_ORFAIL(ok_flag, catch_flag, mem != nullptr); + REQUIRE_ORFAIL(ok_flag, catch_flag, mm.contains(mem)); + REQUIRE_ORFAIL(ok_flag, catch_flag, mm.last_error().error_seq_ == 0); + REQUIRE_ORFAIL(ok_flag, catch_flag, mm.last_error().error_ == xo::mm::error::ok); + + { + auto ix = allocs_by_lo_map.lower_bound(mem); + if (ix != allocs_by_lo_map.end()) { + REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->first > mem + z)); + } + } + + { + auto ix = allocs_by_hi_map.upper_bound(mem); + if (ix != allocs_by_hi_map.end()) { + --ix; + REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->first < mem)); + } + } + + allocs_by_lo_map[mem] = Alloc(mem, z); + allocs_by_hi_map[mem + z] = &(allocs_by_lo_map[mem]); + + /* verify we can recover alloc info */ + AllocInfo info = mm.alloc_info(mem); + + REQUIRE_ORFAIL(ok_flag, catch_flag, info.is_valid()); + + REQUIRE_ORFAIL(ok_flag, catch_flag, + info.size() == padding::with_padding(z)); + + /* age isn't configured -> 0 = sentinel */ + REQUIRE_ORFAIL(ok_flag, catch_flag, info.age() == 0); + /* tseq isn't configured -> 0 = sentinel */ + REQUIRE_ORFAIL(ok_flag, catch_flag, info.tseq() == 0); + + if ((info.p_config_->guard_z_ > 0) + || info.guard_lo().first + || info.guard_lo().second + || info.guard_hi().first + || info.guard_hi().second) + { + REQUIRE_ORFAIL(ok_flag, catch_flag, + info.guard_lo().first != nullptr); + REQUIRE_ORFAIL(ok_flag, catch_flag, + info.guard_lo().second != nullptr); + REQUIRE_ORFAIL(ok_flag, catch_flag, + info.guard_lo().first + info.guard_z() + == info.guard_lo().second); + + for (const byte * p = info.guard_lo().first; + p != info.guard_lo().second; ++p) + { + REQUIRE_ORFAIL(ok_flag, catch_flag, (char)*p == info.guard_byte()); + } + + REQUIRE_ORFAIL(ok_flag, catch_flag, + info.guard_hi().first != nullptr); + REQUIRE_ORFAIL(ok_flag, catch_flag, + info.guard_hi().second != nullptr); + REQUIRE_ORFAIL(ok_flag, catch_flag, + info.guard_hi().first + info.guard_z() + == info.guard_hi().second); + + for (const byte * p = info.guard_hi().first; + p != info.guard_hi().second; ++p) + { + REQUIRE_ORFAIL(ok_flag, catch_flag, (char)*p == info.guard_byte()); + } + + + } else { + /* control here only if all of: + * - guard_z is zero + * - guard_lo empty + * - guard_hi empty + */ + + REQUIRE_ORFAIL(ok_flag, catch_flag, + info.guard_lo().first == nullptr); + REQUIRE_ORFAIL(ok_flag, catch_flag, + info.guard_lo().second == nullptr); + + REQUIRE_ORFAIL(ok_flag, catch_flag, + info.guard_hi().first == nullptr); + REQUIRE_ORFAIL(ok_flag, catch_flag, + info.guard_hi().second == nullptr); + + } + + /** scratch arena for iterators **/ + DArena scratch_mm = DArena::map(ArenaConfig{.name_ = "scratch", + .size_ = 4*1024, + .hugepage_z_ = 4*1024 }); + auto range = mm.alloc_range(scratch_mm); + + /* limit iteration test to a few cases: + * - 1st loop + * - median loop + * - last loop + */ + if (i_alloc == 0 || i_alloc == n_alloc || 2*i_alloc == n_alloc) + { + /* verify iteration visits all the allocs, exactly once */ + + /* temp copy; remove allocs from this map as we encounter + * them via range iteration below + */ + auto alloc_map = allocs_by_lo_map; + + if (log) { + log(xtag("allocs_by_lo_map.size", allocs_by_lo_map.size())); + + for (auto & kv : allocs_by_lo_map) { + log(xtag("key", kv.first), xtag("value", kv.second.lo()), xtag("hi", kv.second.hi())); + } + } + + for (AllocInfo info : range) { + INFO(tostr(xtag("alloc_map.size", alloc_map.size()), + xtag("i_alloc", i_alloc))); + INFO(tostr(xtag("payload.first", info.payload().first))); + + const std::byte * alloc_lo = info.payload().first; + + REQUIRE_ORFAIL(ok_flag, catch_flag, + alloc_map.find(alloc_lo) != alloc_map.end()); + + alloc_map.erase(alloc_lo); + } + } + } + + return true; + } +} + +/* end random_allocs.cpp */ diff --git a/xo-alloc2/utest/random_allocs.hpp b/xo-alloc2/utest/random_allocs.hpp new file mode 100644 index 00000000..5c726e40 --- /dev/null +++ b/xo-alloc2/utest/random_allocs.hpp @@ -0,0 +1,46 @@ +/** @file random_allocs.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "Allocator.hpp" +#include +#include + +namespace utest { + +/* note: trivial REQUIRE() call in else branch bc we still want + * catch2 to count assertions when verification succeeds + */ +# define REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr) \ + if (catch_flag) { \ + REQUIRE((expr)); \ + } else { \ + REQUIRE(true); \ + ok_flag &= (expr); \ + } + +# define REQUIRE_ORFAIL(ok_flag, catch_flag, expr) \ + REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr); \ + if (!ok_flag) \ + return ok_flag + + + + struct AllocUtil { + using AAllocator = xo::mm::AAllocator; + + /** generate a random sequence of allocations. + * verify allocator behavior + **/ + static bool random_allocs(std::uint32_t n_alloc, + std::uint32_t max_alloc_z, + bool catch_flag, + xo::rng::xoshiro256ss * p_rgen, + xo::facet::obj alloc); + }; +} + +/* end random_allocs.hpp */