diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a60760..9ce251c 100644 --- a/CMakeLists.txt +++ b/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/docs/AAllocator-reference.rst b/docs/AAllocator-reference.rst new file mode 100644 index 0000000..faa6f15 --- /dev/null +++ b/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/docs/ArenaConfig-reference.rst b/docs/ArenaConfig-reference.rst new file mode 100644 index 0000000..37932f4 --- /dev/null +++ b/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/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 0000000..55ceed6 --- /dev/null +++ b/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/docs/DArena-reference.rst b/docs/DArena-reference.rst new file mode 100644 index 0000000..8ea46f9 --- /dev/null +++ b/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/docs/README b/docs/README new file mode 100644 index 0000000..2fab639 --- /dev/null +++ b/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/docs/_static/README b/docs/_static/README new file mode 100644 index 0000000..7297d04 --- /dev/null +++ b/docs/_static/README @@ -0,0 +1 @@ +add any static {.html, .js, ..} files for sphinx to pickup here diff --git a/docs/_static/img/favicon.ico b/docs/_static/img/favicon.ico new file mode 100644 index 0000000..4163dd6 Binary files /dev/null and b/docs/_static/img/favicon.ico differ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..401b73e --- /dev/null +++ b/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/docs/glossary.rst b/docs/glossary.rst new file mode 100644 index 0000000..cb992ef --- /dev/null +++ b/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/docs/implementation.rst b/docs/implementation.rst new file mode 100644 index 0000000..d0d4812 --- /dev/null +++ b/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/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..aedc0ba --- /dev/null +++ b/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/include/xo/alloc2/AAllocator.hpp b/include/xo/alloc2/AAllocator.hpp index 5b81473..ada5184 100644 --- a/include/xo/alloc2/AAllocator.hpp +++ b/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/include/xo/alloc2/DArena.hpp b/include/xo/alloc2/DArena.hpp index 77bcf04..3058b64 100644 --- a/include/xo/alloc2/DArena.hpp +++ b/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/include/xo/alloc2/IAllocator_DArena.hpp b/include/xo/alloc2/IAllocator_DArena.hpp index 9619174..7a3d4c6 100644 --- a/include/xo/alloc2/IAllocator_DArena.hpp +++ b/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/include/xo/alloc2/RAllocator.hpp b/include/xo/alloc2/RAllocator.hpp new file mode 100644 index 0000000..76785ab --- /dev/null +++ b/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/src/alloc2/AAllocator.cpp b/src/alloc2/AAllocator.cpp new file mode 100644 index 0000000..d896b6d --- /dev/null +++ b/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/src/alloc2/CMakeLists.txt b/src/alloc2/CMakeLists.txt index 53f7542..f742158 100644 --- a/src/alloc2/CMakeLists.txt +++ b/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/src/alloc2/DArena.cpp b/src/alloc2/DArena.cpp index 860735f..b727849 100644 --- a/src/alloc2/DArena.cpp +++ b/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/src/alloc2/IAllocator_DArena.cpp b/src/alloc2/IAllocator_DArena.cpp index 26c8901..5dffefc 100644 --- a/src/alloc2/IAllocator_DArena.cpp +++ b/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/utest/CMakeLists.txt b/utest/CMakeLists.txt index 1065ae8..e21c3cd 100644 --- a/utest/CMakeLists.txt +++ b/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/utest/arena.test.cpp b/utest/arena.test.cpp index ea9187f..c9a24f2 100644 --- a/utest/arena.test.cpp +++ b/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*/