diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cfaa5b3..becae3d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,6 @@ add_subdirectory(xo-imgui) # ---------------------------------------------------------------- # documentation. must follow add_subdirectory() for satellite projects -xo_umbrella_doxygen_deps(xo_facet xo_alloc indentlog xo_flatstring xo_ratio xo_unit xo_tokenizer xo_reader xo_interpreter xo_jit) +xo_umbrella_doxygen_deps(xo_facet xo_alloc xo_alloc2 indentlog xo_flatstring xo_ratio xo_unit xo_tokenizer xo_reader xo_interpreter xo_jit) xo_umbrella_doxygen_config() xo_umbrella_sphinx_config(index.rst docs/install.rst docs/glossary.rst) diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. diff --git a/README.md b/README.md index 6a61f562..ac5b9738 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,40 @@ $ cmake -B .build -S . -DXO_ENABLE_VULKAN=1 -DXO_ENABLE_EXAMPLES=1 -DCMAKE_INSTA Currently not supporting a nix sandbox build for xo-imgui +## Directory Layout + +(not in alphabetical order) + +``` +xo-umbrella2/ +| ++- CMakeLists.txt top-level cmake config ++- cmake +| \- xo-bootstrap-macros.cmake configure xo cmake support for build ++- compile_commands.json symlink to path/to/build/compile_commands.json for LSP +| ++- conf.py sphinx config for project documentation ++- index.rst root of xo-umbrella2 doc tree ++- Doxyfile.in doxygen config template; cmake will prepare Doxyfile in build dir ++- _static/ static inputs to sphinx +| ++- etc +| +- hostegl/ sample video driver symlinks for WSL2 +| \- hostubuntu/ sample video driver symlinks for ubuntu +| ++- default.nix top-level nix build (works w/ stock nixpkgs 25.05) ++- pkgs/ per-satellite nix builds. see xo-umbrella2/default.nix +| +- xo-callback.nix nix build for xo-umbrella2/xo-callback +| ..etc.. +| \- xo-webutil.nix +| ++- xo-alloc/ xo-alloc subproject. independent git repo, using git subtree ++- xo-alloc2/ xo-alloc2 subproject. +..etc.. +\- xo-webutil/ + +``` + ## To view docs from WSL 1. find wsl IP address diff --git a/conf.py b/conf.py index d6535873..2cafc981 100644 --- a/conf.py +++ b/conf.py @@ -6,7 +6,7 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'xo documentation' +project = 'xo umbrella2 documentation' copyright = '2025, Roland Conybeare' author = 'Roland Conybeare' @@ -17,7 +17,7 @@ author = 'Roland Conybeare' extensions = [ "breathe", "sphinx.ext.mathjax", # inline math "sphinx.ext.autodoc", # generate info from docstrings - # "sphinxcontrib.ditaa", # diagrams-through-ascii-art + "sphinxcontrib.ditaa", # diagrams-through-ascii-art "sphinxcontrib.plantuml", # text -> uml diagrams ] diff --git a/index.rst b/index.rst index 3e955649..bb9bc97d 100644 --- a/index.rst +++ b/index.rst @@ -13,6 +13,7 @@ Some features: kalman filters, stochastic processes, complex event processing, s docs/install xo-facet/docs/index + xo-alloc2/docs/index xo-alloc/docs/index xo-indentlog/docs/index xo-flatstring/docs/index diff --git a/xo-alloc2/CMakeLists.txt b/xo-alloc2/CMakeLists.txt index 9a607607..9ce251c7 100644 --- a/xo-alloc2/CMakeLists.txt +++ b/xo-alloc2/CMakeLists.txt @@ -27,6 +27,6 @@ add_subdirectory(utest) # docs targets depend on other library/utest/exec targets above, # --> must come after them. # -#add_subdirectory(docs) +add_subdirectory(docs) # end CMakeLists.txt diff --git a/xo-alloc2/docs/AAllocator-reference.rst b/xo-alloc2/docs/AAllocator-reference.rst new file mode 100644 index 00000000..faa6f15a --- /dev/null +++ b/xo-alloc2/docs/AAllocator-reference.rst @@ -0,0 +1,39 @@ +.. _AAllocator-reference: + +AAllocator Reference +==================== + +Abstract interface facet for arena allocator. +Provides simple arena allocation. + +Context +------- + +.. ditaa:: + :--scale: 0.99 + + +--------------------------------+ + | IAllocator_DArena | + +--------------------------------+ + | IAllocator_Xfer | + +--------------------------------+ + | IAllocator_ImplType | + +--------------+-----------------+ + |cBLU | DArena | + | AAllocator +-----------------+ + | | ArenaConfig | + +--------------+-----------------+ + +.. code-block:: cpp + + #incldue + +Class +----- + +.. doxygenclass:: xo::mm::AAllocator + +Methods +------- + +.. doxygengroup:: mm-allocator-methods diff --git a/xo-alloc2/docs/ArenaConfig-reference.rst b/xo-alloc2/docs/ArenaConfig-reference.rst new file mode 100644 index 00000000..37932f4c --- /dev/null +++ b/xo-alloc2/docs/ArenaConfig-reference.rst @@ -0,0 +1,35 @@ +.. _ArenaConfig-reference: + +ArenaConfig Reference +===================== + +Configuration for an arena allocator + +Context +------- + +.. ditaa:: + :--scale: 0.99 + + +--------------------------------+ + | IAllocator_DArena | + +--------------------------------+ + | IAllocator_Xfer | + +--------------------------------+ + | IAllocator_ImplType | + +--------------+-----------------+ + | | DArena | + | AAllocator +-----------------+ + | | ArenaConfig cBLU| + +--------------+-----------------+ + +.. code-block:: cpp + + #include + +Class +----- + +.. doxygenclass:: xo::mm::ArenaConfig + +.. doxygenclass:: mm-arenaconfig-instance-vars diff --git a/xo-alloc2/docs/CMakeLists.txt b/xo-alloc2/docs/CMakeLists.txt new file mode 100644 index 00000000..55ceed69 --- /dev/null +++ b/xo-alloc2/docs/CMakeLists.txt @@ -0,0 +1,17 @@ +# xo-alloc2/docs/CMakeLists.txt + +xo_doxygen_collect_deps() +xo_docdir_doxygen_config() +xo_docdir_sphinx_config( + index.rst + glossary.rst + implementation.rst + ArenaConfig-reference.rst + DArena-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..8ea46f98 --- /dev/null +++ b/xo-alloc2/docs/DArena-reference.rst @@ -0,0 +1,64 @@ +.. _DArena-reference: + +DArena Reference +================ + +Native representation for arena allocator + +Context +------- + +.. ditaa:: + :--scale: 0.99 + + +--------------------------------+ + | IAllocator_DArena | + +--------------------------------+ + | IAllocator_Xfer | + +--------------------------------+ + | IAllocator_ImplType | + +--------------+-----------------+ + | | DArena cBLU| + | AAllocator +-----------------+ + | | ArenaConfig | + +--------------+-----------------+ + +.. code-block:: cpp + + #include + + +Arena memory layout:: + + <----------------------------size--------------------------> + <------------committed-----------><-------uncommitted------> + <--allocated--> + + XXXXXXXXXXXXXXX___________________.......................... + ^ ^ ^ ^ + lo free limit hi + + [X] allocated: in use + [_] committed: physical memory obtained + [.] uncommitted: mapped in virtual memory, not backed by memory + + +Class +----- + +.. doxygenclass:: xo::mm::DArena + +Member Variables +---------------- + +.. doxygengroup:: mm-arena-instance-vars + +Type Traits +----------- + +.. doxygengroup:: mm-arena-traits + +Constructors +------------ + +.. doxygengroup:: mm-arena-ctors 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-facet/docs/_static/favicon.ico b/xo-alloc2/docs/_static/img/favicon.ico similarity index 100% rename from xo-facet/docs/_static/favicon.ico rename to xo-alloc2/docs/_static/img/favicon.ico 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/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..d0d48127 --- /dev/null +++ b/xo-alloc2/docs/implementation.rst @@ -0,0 +1,51 @@ +.. _implementation: + +Components +========== + +Library dependency tower for *xo-alloc2* + +.. ditaa:: + + +-----------------+ + | xo_alloc2 | + +-----------------+ + | xo_facet | + +-----------------+ + | xo_cmake | + +-----------------+ + +Abstraction tower for *xo-alloc2* components + +.. ditaa:: + :--scale: 0.99 + + +--------------------------------+ + | IAllocator_DArena | + +--------------------------------+ + | IAllocator_Xfer | + +--------------------------------+ + | IAllocator_ImplType | + +--------------+-----------------+ + | | DArena | + | AAllocator +-----------------+ + | | ArenaConfig | + +--------------+-----------------+ + +.. list-table:: Descriptions + :header-rows: 1 + :widths: 20 90 + + * - Component + - Description + * - ``AAllocator`` + - allocator facet (abstract interface) + * - ``DArena`` + - arena representation + * - ``IAllocator_ImplType`` + - lookup implementation for allocator A + with representation D. + * - ``IAllocator_Xfer`` + - transfer interface. downcast to native state. + * - ``IAllocator_DArena`` + - allocator implementation for ``DArena`` diff --git a/xo-alloc2/docs/index.rst b/xo-alloc2/docs/index.rst new file mode 100644 index 00000000..aedc0ba5 --- /dev/null +++ b/xo-alloc2/docs/index.rst @@ -0,0 +1,28 @@ +# xo-alloc2 documentation master file + +xo-alloc2 documentation +======================= + +xo-alloc2 is intended to provide fast vm-aware arena allocation. +Next-generation version of xo-alloc. +At present (Dec 2025) xo-alloc is fully functional, +while xo-alloc2 is aspirational. + +Features: + +* allocates uncommitted virtual memory, and commits on demand. +* ses THP (Transparent Huge Pages) when available. + +Implemented using FOMO (faceted rust-like object model) from xo-facet + +.. toctree:: + :maxdepth: 2 + :caption: xo-alloc2 contents + + implementation + AAllocator-reference + ArenaConfig-reference + DArena-reference + glossary + genindex + search diff --git a/xo-alloc2/include/xo/alloc2/AAllocator.hpp b/xo-alloc2/include/xo/alloc2/AAllocator.hpp index 5b814737..ada51844 100644 --- a/xo-alloc2/include/xo/alloc2/AAllocator.hpp +++ b/xo-alloc2/include/xo/alloc2/AAllocator.hpp @@ -14,52 +14,86 @@ namespace xo { using Copaque = const void *; using Opaque = void *; - - /** Abstract facet for allocation + /** @class AAllocator + * @brief Abstract facet for allocation * - * <----------------------------size--------------------------> - * <------------committed-----------><-------uncommitted------> - * <--allocated--> - * - * XXXXXXXXXXXXXXX___________________.......................... - * - * allocated: in use - * committed: physical memory obtained - * uncommitted: mapped in virtual memory, not backed by memory **/ struct AAllocator { - /** RTTI: unique id# for actual runtime data repr **/ + /* + * <----------------------------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 **/ + ///@{ + + /** RTTI: unique id# for actual runtime data representation **/ virtual int32_t _typeseq() = 0; - /** optional name for this allocator. + /** optional name for allocator @p d * Labeling, for diagnostics. **/ virtual const std::string & name(Copaque d) = 0; /** allocator size in bytes (up to reserved limit) - * includes allocated and uncomitted memory + * for allocator @p d. + * Includes allocated and uncomitted memory **/ virtual std::size_t size(Copaque d) = 0; /** committed size (physical addresses obtained) + * for allocator @p d. **/ virtual std::size_t committed(Copaque d) = 0; - /** true iff pointer @p in range of this allocator + /** true iff allocator @p d is responsible for memory at address @p p. **/ virtual bool contains(Copaque d, const void * p) = 0; - /** allocate @p z bytes of memory. **/ + /** allocate @p z bytes of memory from allocator @p d. **/ virtual std::byte * alloc(Opaque d, std::size_t z) = 0; - /** reset allocator to empty state **/ + /** reset allocator @p d to empty state **/ virtual void clear(Opaque d) = 0; - /** **/ + /** destruct allocator @p d **/ virtual void destruct_data(Opaque d) = 0; - }; + + ///@} + }; /*AAllocator*/ template struct IAllocator_Impl; + template + using IAllocator_ImplType = IAllocator_Impl::ImplType; + + struct IAllocator_Any : public AAllocator { + using Impl = Allocator_ImplType; + + // from AAllocator + int32_t _typeseq() override { return s_typeseq; } + + const std::string & name(Copaque d) override { assert(false); static std::string x; return x; } + std::size_t size(Copaque d) override { assert(false); return 0ul; } + std::size_t committed(Copaque d) override { assert(false); reutrn 0ul; } + bool contains(Copaque d, const void * p) override { assert(false); return false; } + + std::byte * alloc(Opaque d, std::size_t z) override { assert(false); return nullptr; } + void clear(Opaque d) override { assert(false); } + void destruct_data(Opaque d) override { assert(false); } + + static int32_t s_typeseq; + static bool _valid; + } + + /** @class IAllocator_Xfer + **/ template struct IAllocator_Xfer : public AAllocator { // parallel interface to AAllocator, with specific data type - using Impl = IAllocator_Impl; + using Impl = IAllocator_ImplType; static const DRepr & _dcast(Copaque d) { return *(const DRepr *)d; } diff --git a/xo-alloc2/include/xo/alloc2/DArena.hpp b/xo-alloc2/include/xo/alloc2/DArena.hpp index 77bcf04d..3058b64f 100644 --- a/xo-alloc2/include/xo/alloc2/DArena.hpp +++ b/xo-alloc2/include/xo/alloc2/DArena.hpp @@ -10,12 +10,17 @@ namespace xo { namespace mm { - /** ArenaConfig + /** @class ArenaConfig + * + * @brief configuration for a @ref DArena instance **/ struct ArenaConfig { + /** @defgroup mm-arenaconfig-instance-vars ArenaConfig members **/ + ///@{ + /** optional name, for diagnostics **/ std::string name_; - /** arena size -- hard max = reserved virtual memory **/ + /** desired arena size -- hard max = reserved virtual memory **/ std::size_t size_; /** hugepage size -- using huge pages relieves some TLB pressure * (provided you use their full extent :) @@ -23,31 +28,70 @@ namespace xo { std::size_t hugepage_z_ = 2 * 1024 * 1024; /** true to enable debug logging **/ bool debug_flag_ = false; + + ///@} }; - /** Arena allocator state + /** @class DArena * - * <----------------------------size--------------------------> - * <------------committed-----------><-------uncommitted------> - * <--allocated--> + * @brief represent arena allocator state * - * XXXXXXXXXXXXXXX___________________.......................... - * - * allocated: in use - * committed: physical memory obtained - * uncommitted: mapped in virtual memory, not backed by memory + * Provides minimal RAII functionality around memory mapping. + * For allocation see @ref IAllocator_DArena **/ struct DArena { - /** [lo, hi) already-mapped address range **/ - DArena(const ArenaConfig & cfg, - std::byte * lo, - std::byte * hi); + /* + * <----------------------------size--------------------------> + * <------------committed-----------><-------uncommitted------> + * <--allocated--> + * + * XXXXXXXXXXXXXXX___________________.......................... + * + * [X] allocated: in use + * [_] committed: physical memory obtained + * [.] uncommitted: mapped in virtual memory, not backed by memory + */ + /** @defgroup mm-arena-traits arena type traits **/ + ///@{ + + /** @brief an amount of memory **/ + using size_type = std::size_t; + /** @brief a contiguous memory range **/ + using range_type = std::pair; + + ///@} + + /** @defgroup mm-arena-ctors arena constructors and destructors **/ + ///@{ + + /** create arena per configuration @p cfg. **/ + static DArena map(const ArenaConfig & cfg); + + /** ctor from already-mapped (but not committed) address range **/ + DArena(const ArenaConfig & cfg, size_type page_z, std::byte * lo, std::byte * hi); + /** DArena is not copyable **/ + DArena(const DArena & other) = delete; + /** move ctor **/ + DArena(DArena && other); + /** dtor releases mapped memory **/ ~DArena(); + ///@} + + /** obtain uncommitted contiguous memory range comprising + * a whole multiple of @p hugepage_z bytes, of at least size @p req_z, + * aligned on a @p hugepage_z boundary + **/ + static range_type map_aligned_range(size_type req_z, size_type hugepage_z); + + /** @defgroup mm-arena-instance-vars **/ + ///@{ + + /** arena configuration **/ ArenaConfig config_; - /** size of a VM page (via getpagesize()). Likely 4k **/ + /** size of a VM page (obtained automatically via getpagesize()). Likely 4k **/ std::size_t page_z_ = 0; /** arena owns memory in range [@ref lo_, @ref hi_) @@ -74,6 +118,8 @@ namespace xo { * Memory in range [@ref limit_, @ref hi_) is uncommitted **/ std::byte * hi_ = nullptr; + + ///@} }; } /*namespace mm*/ diff --git a/xo-alloc2/include/xo/alloc2/IAllocator_DArena.hpp b/xo-alloc2/include/xo/alloc2/IAllocator_DArena.hpp index 96191749..7a3d4c6d 100644 --- a/xo-alloc2/include/xo/alloc2/IAllocator_DArena.hpp +++ b/xo-alloc2/include/xo/alloc2/IAllocator_DArena.hpp @@ -9,12 +9,8 @@ namespace xo { namespace mm { - template <> - struct IAllocator_Impl { - static const std::string & name(const DArena & s) { - return s.name_; - } - + struct IAllocator_DArena { + static const std::string & name(const DArena & s); static std::size_t size(const DArena & s); static std::size_t committed(const DArena & s); static bool contains(const DArena & s, const void * p); @@ -23,6 +19,10 @@ namespace xo { static void destruct_data(DArena & s); }; + template <> + struct IAllocator_Impl { + using ImplType = IAllocator_DArena; + }; } /*namespace mm*/ } /*namespace xo*/ diff --git a/xo-alloc2/include/xo/alloc2/RAllocator.hpp b/xo-alloc2/include/xo/alloc2/RAllocator.hpp new file mode 100644 index 00000000..76785abc --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/RAllocator.hpp @@ -0,0 +1,38 @@ +/** @file RAllocator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "xo/facet/RRouter.hpp" + +namespace xo { + namespace mm { + /** @class RAllocator **/ + template + struct RAllocator : public Object { + using ObjectType = Object; + + RAllocator() {} + RAllocator(Object::DataPtr data) : Object{std::move(data)} {} + + int32_t _typeseq() const { return Object::iface()->_typeseq(); } + + 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/src/alloc2/AAllocator.cpp b/xo-alloc2/src/alloc2/AAllocator.cpp new file mode 100644 index 00000000..d896b6d4 --- /dev/null +++ b/xo-alloc2/src/alloc2/AAllocator.cpp @@ -0,0 +1,19 @@ +/** @file AAllocator.cpp **/ + +#include "xo/alloc2/AAllocator.hpp" + +namespace xo { + using xo::facet::DVariantPlaceholder; + using xo::facet::typeseq; + using xo::facet::valid_facet_implementation; + + namespace mm { + int32_t + IAllocator_Any::s_typeseq = typeseq::id; + + bool + IAllocator_Any::_valid = valid_facet_implementation; + } +} + +/* end AAlocator.cpp */ diff --git a/xo-alloc2/src/alloc2/CMakeLists.txt b/xo-alloc2/src/alloc2/CMakeLists.txt index 53f75425..f7421582 100644 --- a/xo-alloc2/src/alloc2/CMakeLists.txt +++ b/xo-alloc2/src/alloc2/CMakeLists.txt @@ -2,6 +2,7 @@ set(SELF_LIB xo_alloc2) set(SELF_SRCS + AAllocator.cpp DArena.cpp IAllocator_DArena.cpp ) diff --git a/xo-alloc2/src/alloc2/DArena.cpp b/xo-alloc2/src/alloc2/DArena.cpp index 860735f2..b7278492 100644 --- a/xo-alloc2/src/alloc2/DArena.cpp +++ b/xo-alloc2/src/alloc2/DArena.cpp @@ -9,107 +9,135 @@ #include #include // for ::munmap() #include // for ::getpagesize() +#include // for ::memset() namespace xo { using std::byte; + using std::size_t; namespace mm { - DArena::DArena(const ArenaConfig & cfg, - std::byte * lo, - std::byte * hi - ) + /** Map a contiguous uncommitted memory range comprising + * a whole multiple of @p hugepage_z, with at least + * @p req_z bytes. + * + * Memory will also be aligned on @p hugepage_z boundary + * (2MB in practice) + * + * - @p req_z is rounded up to a multiple of @p hugepage_z + * - Resulting uncommitted address range not backed by + * physical memory. + * - since hugpage-aligned, can find base of mapped range + * by masking off the bottom log2(align_z) bits. + * May rely on this for GC metadata + * - opt-in to transparent huge pages (THP). + * Reduces page-fault time by a lot, in return for + * lower VM granularity + * - rejecting inferior MAP_HUGETLB|MAP_HUGE_2MB flags on ::mmap here: + * - requires previously-reserved memory in /proc/sys/vm/nr_hugepages + * - reserved pages permenently resident in RAM, never swapped + * - memory cost incurred even if no application is using said pages + * + * TODO: for OSX -> need something else here. + * MAP_ALIGNED_SUPER with mmap() and/or + * use mach_vm_allocate() + * + * @return pair giving mapped address range [lo, hi) + **/ + auto + DArena::map_aligned_range(size_t req_z, size_t hugepage_z) -> range_type { - //scope log(XO_DEBUG(debug_flag), xtag("name", name)); + // 1. round up to multiple of hugepage_z + size_t target_z = padding::with_padding(req_z, hugepage_z); // 4. - this->page_z_ = getpagesize(); - - // 1. need k pagetable entries where k is lub {k | k * .page_z >= z} - // 2. base will be aligned with .page_z but likely not with .hugepage_z - // 3. bad to have misalignment, because misaligned {prefix, suffix} of [base, base+z) - // will use 4k pages instead of 2mb pages + // 2. mmap() will give us page-aligned memory, + // but not hugepage-aligned. // - // strategy: - // 4. round up z to multiple of hugepage_z_ - // 5. over-request so reserved range contains an aligned subrange of size z - // 6. unmap misaligned prefix - // 7. unmap misaligned suffix. - // 8. enable huge pages for now-aligned remainder of reserved range + // Over-request by hugepage_z to ensure + // hugepage-aligned subrange of size target_z // - // Z. note: rejecting inferior MAP_HUGETLB|MAP_HUGE_2MB flags on ::mmap here: - // Za. requires previously-reserved memory in /proc/sys/vm/nr_hugepages - // Zb. reserved pages permenently resident in RAM, never swapped - // Zc. memory cost incurred even if no application is using said pages + byte * base = (byte *)(::mmap(nullptr, + target_z + hugepage_z, + PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0)); - std::size_t z = cfg.size_; - - z = padding::with_padding(z, config_.hugepage_z_); // 4. - - // 5. - byte * base = reinterpret_cast( - ::mmap(nullptr, - z + config_.hugepage_z_, - PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS, - -1, 0)); + // on mmap success: upper limit of mapped address range + byte * hi = base + (target_z + hugepage_z); + // lowest hugepage-aligned address in [base, hi) + byte * aligned_base = (byte *)(padding::with_padding((size_t)base, hugepage_z)); + // end of hugeppage-aligned range starting at aligned_base + byte * aligned_hi = aligned_base + target_z; #ifdef NOT_YET log && log("acquired memory [lo,hi) using mmap", xtag("lo", base), - xtag("z", z), - xtag("hi", reinterpret_cast(base) + z)); + xtag("req_z", req_z), + xtag("target_z", target_z), + xtag("hi", (byte *)(base) + z)); #endif - if (base == MAP_FAILED) { - assert(false); + + // 3. assess mmap success + { + if (base == MAP_FAILED) { + assert(false); #ifdef NOPE - throw std::runtime_error(tostr("ArenaAlloc: uncommitted allocation failed", - xtag("size", z))); + throw std::runtime_error(tostr("ArenaAlloc: uncommitted allocation failed", + xtag("size", z))); #endif + } + + assert((size_t)aligned_base % hugepage_z == 0); + assert(aligned_base >= base); + assert(aligned_base < base + hugepage_z); } - byte * aligned_base = reinterpret_cast - (padding::with_padding(reinterpret_cast(base), - config_.hugepage_z_)); - - assert(reinterpret_cast(aligned_base) % config_.hugepage_z_ == 0); - assert(aligned_base >= base); - assert(aligned_base < base + config_.hugepage_z_); - + // 4. release unaligned prefix if (base < aligned_base) { - size_t prefix = aligned_base - base; + size_t ua_prefix = aligned_base - base; - ::munmap(base, prefix); // 6. + ::munmap(base, ua_prefix); } - byte * aligned_hi = aligned_base + z; - byte * hi = base + z + config_.hugepage_z_; - + // 5. release unaligned suffix if (aligned_hi < hi) { size_t suffix = hi - aligned_hi; - ::munmap(aligned_hi, suffix); // 7. + ::munmap(aligned_hi, suffix); } #ifdef __linux__ - /** opt-in to huge pages, provided they're available. - * otherwise fallback gracefully + /** linux: + * opt-in to transparent huge pages (THP) + * provided OS configured to support them. + * otherwise fallback gracefully. + * + * Huge pages -> use fewer TLB entries + faster + * shorter path through page table. + * + * When we commit (i.e. obtain physical memory on page fault), + * typically expect to pay ~1us per superpage. + * Much better than ~500us to commit 512 4k VM pages. + * + * But wasted if we don't use the memory. **/ - ::madvise(aligned_base, z, MADV_HUGEPAGE); // 8. + ::madvise(aligned_base, target_z, MADV_HUGEPAGE); // 8. #endif - // TODO: for OSX -> need something else here. - // MAP_ALIGNED_SUPER with mmap() and/or - // use mach_vm_allocate() - // - this->lo_ = aligned_base; - this->committed_z_ = 0; - //this->checkpoint_ = lo_; - this->free_ = lo_; - this->limit_ = lo_; - this->hi_ = lo_ + z; + return std::make_pair(aligned_base, aligned_hi); + } + + DArena + DArena::map(const ArenaConfig & cfg) + { + //scope log(XO_DEBUG(debug_flag), xtag("name", name)); + + auto [lo, hi] = map_aligned_range(cfg.size_, cfg.hugepage_z_); + + if (!lo) { + // control here implies mmap() failed silently - if (!lo_) { assert(false); #ifdef NOPE throw std::runtime_error(tostr("ArenaAlloc: allocation failed", @@ -117,11 +145,47 @@ namespace xo { #endif } + size_t page_z = getpagesize(); + + #ifdef NOPE log && log(xtag("lo", (void*)lo_), xtag("page_z", page_z_), xtag("hugepage_z", hugepage_z_)); #endif + + return DArena(cfg, page_z, lo, hi); + } /*map*/ + + DArena::DArena(const ArenaConfig & cfg, + size_type page_z, + byte * lo, + byte * hi) : config_{cfg}, + page_z_{page_z}, + lo_{lo}, + committed_z_{0}, + free_{lo}, + limit_{lo}, + hi_{hi} + { + //retval.checkpoint_ = lo_; + } + + DArena::DArena(DArena && other) { + config_ = other.config_; + page_z_ = other.page_z_; + lo_ = other.lo_; + committed_z_ = other.committed_z_; + free_ = other.free_; + limit_ = other.limit_; + hi_ = other.hi_; + + other.config_ = ArenaConfig(); + other.lo_ = nullptr; + other.committed_z_ = 0; + other.free_ = nullptr; + other.limit_ = nullptr; + other.hi_ = nullptr; } DArena::~DArena() @@ -136,12 +200,12 @@ namespace xo { } // hygiene - lo_ = nullptr; - committed_z_ = 0; + this->lo_ = nullptr; + this->committed_z_ = 0; // checkpoint_ = nullptr; - free_ = nullptr; - limit_ = nullptr; - hi_ = nullptr; + this->free_ = nullptr; + this->limit_ = nullptr; + this->hi_ = nullptr; } } } /*namespace xo*/ diff --git a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp index 26c8901d..5dffefcf 100644 --- a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp +++ b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp @@ -9,25 +9,30 @@ namespace xo { namespace mm { + const std::string & + IAllocator_DArena::name(const DArena & s) { + return s.config_.name_; + } + std::size_t - IAllocator_Impl::size(const DArena & s) { + IAllocator_DArena::size(const DArena & s) { return s.limit_ - s.lo_; } std::size_t - IAllocator_Impl::committed(const DArena & s) { + IAllocator_DArena::committed(const DArena & s) { return s.committed_z_; } bool - IAllocator_Impl::contains(const DArena & s, + IAllocator_DArena::contains(const DArena & s, const void * p) { return (s.lo_ <= p) && (p < s.hi_); } std::byte * - IAllocator_Impl::alloc(const DArena & s, + IAllocator_DArena::alloc(const DArena & s, std::size_t z) { (void)s; @@ -39,14 +44,14 @@ namespace xo { } void - IAllocator_Impl::clear(DArena & s) + IAllocator_DArena::clear(DArena & s) { s.free_ = s.lo_; //s.checkpoint_ = s.lo_; } void - IAllocator_Impl::destruct_data(DArena & s) + IAllocator_DArena::destruct_data(DArena & s) { s.~DArena(); } diff --git a/xo-alloc2/utest/CMakeLists.txt b/xo-alloc2/utest/CMakeLists.txt index 1065ae81..e21c3cd8 100644 --- a/xo-alloc2/utest/CMakeLists.txt +++ b/xo-alloc2/utest/CMakeLists.txt @@ -9,6 +9,7 @@ set(UTEST_SRCS if (ENABLE_TESTING) xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) + xo_self_dependency(${UTEST_EXE} xo_alloc2) xo_headeronly_dependency(${UTEST_EXE} xo_facet) xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) endif() diff --git a/xo-alloc2/utest/arena.test.cpp b/xo-alloc2/utest/arena.test.cpp index ea9187f0..c9a24f24 100644 --- a/xo-alloc2/utest/arena.test.cpp +++ b/xo-alloc2/utest/arena.test.cpp @@ -6,12 +6,19 @@ #include "xo/alloc2/AAllocator.hpp" #include "xo/alloc2/DArena.hpp" #include "xo/alloc2/IAllocator_DArena.hpp" +#include "xo/alloc2/RAllocator.hpp" #include "xo/alloc2/padding.hpp" +#include "xo/facet/obj.hpp" #include namespace xo { + using xo::mm::AAllocator; using xo::mm::IAllocator_Xfer; using xo::mm::DArena; + using xo::mm::ArenaConfig; + using xo::facet::obj; + using std::byte; + using std::size_t; namespace ut { TEST_CASE("IAllocator_Xfer_DArena", "[alloc2]") @@ -20,6 +27,65 @@ namespace xo { REQUIRE(IAllocator_Xfer::_valid); } + + TEST_CASE("DArena", "[alloc2][DArena]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 1 }; + 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 hugepage 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]") + { +#ifdef NOPE + ArenaConfig cfg { .name_ = "testarena", + .size_ = 1 }; + DArena arena = DArena::map(cfg); +#endif + + /* empty allocator */ + obj alloc1; + + REQUIRE(!alloc1); + REQUIRE(alloc1.iface() != nullptr); + REQUIRE(alloc1.data() == nullptr); + } } /*namespace ut*/ } /*namespace xo*/ diff --git a/xo-facet/docs/implementation.rst b/xo-facet/docs/implementation.rst index 92891c9e..ed24aece 100644 --- a/xo-facet/docs/implementation.rst +++ b/xo-facet/docs/implementation.rst @@ -19,57 +19,39 @@ Abstraction tower for *xo-facet* components. :--scale: 0.85 +--------------------------------+ - | obj(A,D) | + | obj(A,D) | +--------------------------------+ - | RRouter(A,D) | + | RRouter(A,D) | +--------------------------------+ - | OObject(A,D) | + | OObject(A,D) | +--------------------------------+ - | FacetImplmentationType(A,D) | + | FacetImplType(A,D) | +----------------+---------------+ | facet [A] | data [D] | +----------------+---------------+ - -Decorated with sample method calls, to reveal type recovery - -.. ditaa:: - :--scale: 0.85 - - +--------------------------------+ - | obj(A,D) | x.foo() - +--------------------------------+ - | RRouter(A,D) | x.foo() - +--------------------------------+ - | OObject(A,D) | x.iface_.foo(x.data_) - +--------------------------------+ - | FacetImplmentationType(A,D) | x.foo(void*data) - +----------------+---------------+ - | facet A | data D | virtual x.foo(void* data) - +----------------+---------------+ - .. list-table:: Descriptions :header-rows: 1 - :widths: 30 30 60 + :widths: 18 30 50 * - Component - Use - Description - * - obj - - x.foo() + * - ``obj`` + - ``x.foo()`` - convenience wrapper with interface A, with state D* - * - RRouter - - x.foo() + * - ``RRouter`` + - ``x.foo()`` - auto injects data pointer - * - OObject - - x.iface()->foo(x.data(), ..) + * - ``OObject`` + - ``x.iface()->foo(x.data(), ..)`` - fat object pointer. combine i/face + data pointer. - * - FacetImplementationType - - x.foo(void* data, ..) + * - ``FacetImplType`` + - ``x.foo(void* data, ..)`` - implement facet for a particular state datatype; explicit type-erased state * - facet - - x.foo(void* data, ..)=0 + - ``x.foo(void* data, ..)=0`` - fully abstract interface; explicit type-erased state .. uml:: diff --git a/xo-ordinaltree/utest/CMakeLists.txt b/xo-ordinaltree/utest/CMakeLists.txt index 5762446f..a829d1ee 100644 --- a/xo-ordinaltree/utest/CMakeLists.txt +++ b/xo-ordinaltree/utest/CMakeLists.txt @@ -4,27 +4,29 @@ set(SELF_EXE utest.tree) set(SELF_SOURCE_FILES tree_utest_main.cpp redblacktree.cpp bplustree.cpp RedBlackTree-gc.test.cpp) -add_executable(${SELF_EXE} ${SELF_SOURCE_FILES}) -xo_include_options2(${SELF_EXE}) +if (ENABLE_TESTING) + add_executable(${SELF_EXE} ${SELF_SOURCE_FILES}) + xo_include_options2(${SELF_EXE}) -add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) -# need coverage on unit test b/c header-only libraries -#target_code_coverage(${SELF_EXE} EXCLUDE */nix/store* utest/*) -#target_code_coverage(${SELF_EXE} AUTO ALL) + add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) + # need coverage on unit test b/c header-only libraries + #target_code_coverage(${SELF_EXE} EXCLUDE */nix/store* utest/*) + #target_code_coverage(${SELF_EXE} AUTO ALL) -# ---------------------------------------------------------------- -# internal dependencies: refcnt, ... + # ---------------------------------------------------------------- + # internal dependencies: refcnt, ... -xo_self_dependency(${SELF_EXE} xo_ordinaltree) -xo_dependency(${SELF_EXE} xo_object) -xo_dependency(${SELF_EXE} xo_alloc) -xo_dependency(${SELF_EXE} refcnt) -xo_dependency(${SELF_EXE} indentlog) -xo_dependency(${SELF_EXE} randomgen) + xo_self_dependency(${SELF_EXE} xo_ordinaltree) + xo_dependency(${SELF_EXE} xo_object) + xo_dependency(${SELF_EXE} xo_alloc) + xo_dependency(${SELF_EXE} refcnt) + xo_dependency(${SELF_EXE} indentlog) + xo_dependency(${SELF_EXE} randomgen) -# ---------------------------------------------------------------- -# 3rd part dependency: catch2: + # ---------------------------------------------------------------- + # 3rd part dependency: catch2: -xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) +endif() # end ordinaltree/utest/CMakeLists.txt