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 00000000..4163dd69 Binary files /dev/null and b/xo-alloc2/docs/_static/img/favicon.ico differ diff --git a/xo-alloc2/docs/cmpresult-reference.rst b/xo-alloc2/docs/cmpresult-reference.rst new file mode 100644 index 00000000..00c27ae9 --- /dev/null +++ b/xo-alloc2/docs/cmpresult-reference.rst @@ -0,0 +1,55 @@ +.. _cmpresult-reference: + +cmpresult +========= + +Represent the result of a partially ordered comparison + +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 | + +----------------------+-------------------------+-----------------------------------+ + +-----------------+----------------------------------------------+-------------------+ + | | AllocInfo | | + | +----------------------------------------------+ | + | AllocError | AllocHeaderConfig | cmpresult | + | +----------------------------------------------+ | + | | AllocHeader | cBLU | + +-----------------+----------------------------------------------+-------------------+ + +.. code-block:: cpp + + #include + +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 */