From 6fc83036026aed83cae0421fa3f3272ca182ab3a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 6 Jun 2026 21:44:12 -0400 Subject: [PATCH] kill tmp .xo-arena --- .xo-arena/CMakeLists.txt | 36 - .xo-arena/cmake/xo-bootstrap-macros.cmake | 41 - .xo-arena/cmake/xo_arenaConfig.cmake.in | 14 - .xo-arena/docs/AllocInfo-reference.rst | 26 - .xo-arena/docs/ArenaConfig-reference.rst | 55 -- .xo-arena/docs/CMakeLists.txt | 22 - .xo-arena/docs/DArena-reference.rst | 96 --- .xo-arena/docs/DArenaIterator-reference.rst | 50 -- .xo-arena/docs/README | 72 -- .xo-arena/docs/_static/README | 1 - .xo-arena/docs/_static/img/favicon.ico | Bin 309936 -> 0 bytes .xo-arena/docs/cmpresult-reference.rst | 50 -- .xo-arena/docs/conf.py | 39 - .xo-arena/docs/examples.rst | 125 --- .xo-arena/docs/glossary.rst | 24 - .xo-arena/docs/implementation.rst | 153 ---- .xo-arena/docs/index.rst | 31 - .xo-arena/include/xo/arena/.gitkeep | 0 .xo-arena/include/xo/arena/AllocError.hpp | 84 -- .xo-arena/include/xo/arena/AllocHeader.hpp | 35 - .../include/xo/arena/AllocHeaderConfig.hpp | 180 ---- .xo-arena/include/xo/arena/AllocInfo.hpp | 98 --- .xo-arena/include/xo/arena/ArenaConfig.hpp | 71 -- .../include/xo/arena/ArenaHashMapConfig.hpp | 58 -- .../include/xo/arena/CircularBufferConfig.hpp | 63 -- .xo-arena/include/xo/arena/DArena.hpp | 338 -------- .xo-arena/include/xo/arena/DArenaHashMap.hpp | 768 ------------------ .xo-arena/include/xo/arena/DArenaIterator.hpp | 127 --- .xo-arena/include/xo/arena/DArenaVector.hpp | 358 -------- .../include/xo/arena/DCircularBuffer.hpp | 262 ------ .xo-arena/include/xo/arena/ErrorArena.hpp | 54 -- .xo-arena/include/xo/arena/MemorySizeInfo.hpp | 71 -- .../include/xo/arena/arena_streambuf.hpp | 236 ------ .xo-arena/include/xo/arena/backtrace.hpp | 13 - .xo-arena/include/xo/arena/cmpresult.hpp | 87 -- .../include/xo/arena/hashmap/ControlGroup.hpp | 83 -- .../arena/hashmap/DArenaHashMapIterator.hpp | 153 ---- .../xo/arena/hashmap/DArenaHashMapUtil.hpp | 117 --- .../include/xo/arena/hashmap/HashMapStore.hpp | 163 ---- .../xo/arena/hashmap/verify_policy.hpp | 59 -- .xo-arena/include/xo/arena/mmap_util.hpp | 49 -- .xo-arena/include/xo/arena/padding.hpp | 60 -- .xo-arena/include/xo/arena/print.hpp | 39 - .xo-arena/include/xo/arena/span.hpp | 329 -------- .xo-arena/src/arena/AllocError.cpp | 45 - .xo-arena/src/arena/AllocInfo.cpp | 45 - .xo-arena/src/arena/CMakeLists.txt | 35 - .xo-arena/src/arena/DArena.cpp | 655 --------------- .xo-arena/src/arena/DArenaIterator.cpp | 144 ---- .xo-arena/src/arena/DCircularBuffer.cpp | 394 --------- .xo-arena/src/arena/ErrorArena.cpp | 43 - .xo-arena/src/arena/arena_streambuf.cpp | 214 ----- .xo-arena/src/arena/backtrace.cpp | 172 ---- .xo-arena/src/arena/cmpresult.cpp | 38 - .xo-arena/src/arena/mmap_util.cpp | 105 --- .xo-arena/utest/CMakeLists.txt | 24 - .xo-arena/utest/DArena.test.cpp | 219 ----- .xo-arena/utest/DArenaHashMap.test.cpp | 360 -------- .xo-arena/utest/DArenaVector.test.cpp | 539 ------------ .xo-arena/utest/DCircularBuffer.test.cpp | 62 -- .xo-arena/utest/arena_utest_main.cpp | 6 - .xo-arena/utest/random_hash_ops.hpp | 714 ---------------- 62 files changed, 8604 deletions(-) delete mode 100644 .xo-arena/CMakeLists.txt delete mode 100644 .xo-arena/cmake/xo-bootstrap-macros.cmake delete mode 100644 .xo-arena/cmake/xo_arenaConfig.cmake.in delete mode 100644 .xo-arena/docs/AllocInfo-reference.rst delete mode 100644 .xo-arena/docs/ArenaConfig-reference.rst delete mode 100644 .xo-arena/docs/CMakeLists.txt delete mode 100644 .xo-arena/docs/DArena-reference.rst delete mode 100644 .xo-arena/docs/DArenaIterator-reference.rst delete mode 100644 .xo-arena/docs/README delete mode 100644 .xo-arena/docs/_static/README delete mode 100644 .xo-arena/docs/_static/img/favicon.ico delete mode 100644 .xo-arena/docs/cmpresult-reference.rst delete mode 100644 .xo-arena/docs/conf.py delete mode 100644 .xo-arena/docs/examples.rst delete mode 100644 .xo-arena/docs/glossary.rst delete mode 100644 .xo-arena/docs/implementation.rst delete mode 100644 .xo-arena/docs/index.rst delete mode 100644 .xo-arena/include/xo/arena/.gitkeep delete mode 100644 .xo-arena/include/xo/arena/AllocError.hpp delete mode 100644 .xo-arena/include/xo/arena/AllocHeader.hpp delete mode 100644 .xo-arena/include/xo/arena/AllocHeaderConfig.hpp delete mode 100644 .xo-arena/include/xo/arena/AllocInfo.hpp delete mode 100644 .xo-arena/include/xo/arena/ArenaConfig.hpp delete mode 100644 .xo-arena/include/xo/arena/ArenaHashMapConfig.hpp delete mode 100644 .xo-arena/include/xo/arena/CircularBufferConfig.hpp delete mode 100644 .xo-arena/include/xo/arena/DArena.hpp delete mode 100644 .xo-arena/include/xo/arena/DArenaHashMap.hpp delete mode 100644 .xo-arena/include/xo/arena/DArenaIterator.hpp delete mode 100644 .xo-arena/include/xo/arena/DArenaVector.hpp delete mode 100644 .xo-arena/include/xo/arena/DCircularBuffer.hpp delete mode 100644 .xo-arena/include/xo/arena/ErrorArena.hpp delete mode 100644 .xo-arena/include/xo/arena/MemorySizeInfo.hpp delete mode 100644 .xo-arena/include/xo/arena/arena_streambuf.hpp delete mode 100644 .xo-arena/include/xo/arena/backtrace.hpp delete mode 100644 .xo-arena/include/xo/arena/cmpresult.hpp delete mode 100644 .xo-arena/include/xo/arena/hashmap/ControlGroup.hpp delete mode 100644 .xo-arena/include/xo/arena/hashmap/DArenaHashMapIterator.hpp delete mode 100644 .xo-arena/include/xo/arena/hashmap/DArenaHashMapUtil.hpp delete mode 100644 .xo-arena/include/xo/arena/hashmap/HashMapStore.hpp delete mode 100644 .xo-arena/include/xo/arena/hashmap/verify_policy.hpp delete mode 100644 .xo-arena/include/xo/arena/mmap_util.hpp delete mode 100644 .xo-arena/include/xo/arena/padding.hpp delete mode 100644 .xo-arena/include/xo/arena/print.hpp delete mode 100644 .xo-arena/include/xo/arena/span.hpp delete mode 100644 .xo-arena/src/arena/AllocError.cpp delete mode 100644 .xo-arena/src/arena/AllocInfo.cpp delete mode 100644 .xo-arena/src/arena/CMakeLists.txt delete mode 100644 .xo-arena/src/arena/DArena.cpp delete mode 100644 .xo-arena/src/arena/DArenaIterator.cpp delete mode 100644 .xo-arena/src/arena/DCircularBuffer.cpp delete mode 100644 .xo-arena/src/arena/ErrorArena.cpp delete mode 100644 .xo-arena/src/arena/arena_streambuf.cpp delete mode 100644 .xo-arena/src/arena/backtrace.cpp delete mode 100644 .xo-arena/src/arena/cmpresult.cpp delete mode 100644 .xo-arena/src/arena/mmap_util.cpp delete mode 100644 .xo-arena/utest/CMakeLists.txt delete mode 100644 .xo-arena/utest/DArena.test.cpp delete mode 100644 .xo-arena/utest/DArenaHashMap.test.cpp delete mode 100644 .xo-arena/utest/DArenaVector.test.cpp delete mode 100644 .xo-arena/utest/DCircularBuffer.test.cpp delete mode 100644 .xo-arena/utest/arena_utest_main.cpp delete mode 100644 .xo-arena/utest/random_hash_ops.hpp diff --git a/.xo-arena/CMakeLists.txt b/.xo-arena/CMakeLists.txt deleted file mode 100644 index 0b6c7020..00000000 --- a/.xo-arena/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -# xo-arena/CMakeLists.txt - -cmake_minimum_required(VERSION 3.10) - -project(xo_arena VERSION 1.0) -enable_language(CXX) - -include(GNUInstallDirs) -include(cmake/xo-bootstrap-macros.cmake) - -xo_cxx_toplevel_options3() - -# ---------------------------------------------------------------- -# c++ settings - -# one-time project-specific c++ flags. usually empty -set(PROJECT_CXX_FLAGS "") -add_definitions(${PROJECT_CXX_FLAGS}) - -# ---------------------------------------------------------------- -# output targets - -add_subdirectory(src/arena) -add_subdirectory(utest) - -# ---------------------------------------------------------------- -# cmake export - -xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) - -# ---------------------------------------------------------------- -# docs targets depend on all the other library/utest targets -# -add_subdirectory(docs) - -# end CMakeLists.txt diff --git a/.xo-arena/cmake/xo-bootstrap-macros.cmake b/.xo-arena/cmake/xo-bootstrap-macros.cmake deleted file mode 100644 index 592272c0..00000000 --- a/.xo-arena/cmake/xo-bootstrap-macros.cmake +++ /dev/null @@ -1,41 +0,0 @@ -# ---------------------------------------------------------------- -# 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 (XO_SUBMODULE_BUILD) - if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) - # local version of xo-cmake macros - set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/xo-cmake/cmake") - message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") - endif() -else() - 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-arena/cmake/xo_arenaConfig.cmake.in b/.xo-arena/cmake/xo_arenaConfig.cmake.in deleted file mode 100644 index 20b8f877..00000000 --- a/.xo-arena/cmake/xo_arenaConfig.cmake.in +++ /dev/null @@ -1,14 +0,0 @@ -@PACKAGE_INIT@ - -include(CMakeFindDependencyMacro) - -# note: changes to find_dependency() calls here -# must coordinate with xo_dependency() calls -# in CMakeLists.txt -# -find_dependency(xo_reflectutil) -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-arena/docs/AllocInfo-reference.rst b/.xo-arena/docs/AllocInfo-reference.rst deleted file mode 100644 index 05da0eac..00000000 --- a/.xo-arena/docs/AllocInfo-reference.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. _AllocInfo-reference: - -AllocInfo Reference -=================== - -Describes a single allocation. -Requires allocator configured to store per-allocation headers - -Context -------- - -.. ditaa:: - :--scale: 0.99 - - +-----------------------------------------------------+ - | DArena | - | DArenaIterator | - +-----------------------------------------------------+ - | ArenaConfig | - +--------------+------------------------+-------------+ - | | AllocInfo cBLU| | - | +------------------------+ | - | AllocError | AllocHeaderConfig | cmpresult | - | +------------------------+ | - | | AllocHeader | | - +--------------+------------------------+-------------+ diff --git a/.xo-arena/docs/ArenaConfig-reference.rst b/.xo-arena/docs/ArenaConfig-reference.rst deleted file mode 100644 index 48a38ac1..00000000 --- a/.xo-arena/docs/ArenaConfig-reference.rst +++ /dev/null @@ -1,55 +0,0 @@ -.. _ArenaConfig-reference: - -ArenaConfig Reference -===================== - -Configuration for an arena allocator - -Context -------- - -.. ditaa:: - :--scale: 0.99 - - +-----------------------------------------------------+ - | DArena | - | DArenaIterator | - +-----------------------------------------------------+ - | 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-arena/docs/CMakeLists.txt b/.xo-arena/docs/CMakeLists.txt deleted file mode 100644 index 7fc9ee26..00000000 --- a/.xo-arena/docs/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# xo-arena/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 -) - -# see xo-reader/doc or xo-unit/doc for working examples -# example.rst install.rst implementation.rst diff --git a/.xo-arena/docs/DArena-reference.rst b/.xo-arena/docs/DArena-reference.rst deleted file mode 100644 index 50ad44f5..00000000 --- a/.xo-arena/docs/DArena-reference.rst +++ /dev/null @@ -1,96 +0,0 @@ -.. _DArena-reference: - -DArena -====== - -Native arena allocator - -Context -------- - -.. ditaa:: - :--scale: 0.99 - - +-----------------------------------------------------+ - | DArena cBLU| - | DArenaIterator | - +-----------------------------------------------------+ - | 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-arena/docs/DArenaIterator-reference.rst b/.xo-arena/docs/DArenaIterator-reference.rst deleted file mode 100644 index d41a1590..00000000 --- a/.xo-arena/docs/DArenaIterator-reference.rst +++ /dev/null @@ -1,50 +0,0 @@ -.. _DArenaIterator-reference: - -DArenaIterator -============== - -Iterator for allocs obtained from a :cpp:class:`xo::mm::DArena`. - -Context -------- - -.. ditaa:: - :--scale: 0.99 - - +-----------------------------------------------------+ - | DArena | - | DArenaIterator cBLU| - +-----------------------------------------------------+ - | 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-arena/docs/README b/.xo-arena/docs/README deleted file mode 100644 index bdd13316..00000000 --- a/.xo-arena/docs/README +++ /dev/null @@ -1,72 +0,0 @@ -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 - +- examples.rst - +- ArenaConfig-reference.rst - +- DArena-reference.rst - +- DArenaIterator-reference.rst - +- AllocInfo-reference.rst - +- cmpresult-reference.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-arena/docs/_static/README b/.xo-arena/docs/_static/README deleted file mode 100644 index 7297d046..00000000 --- a/.xo-arena/docs/_static/README +++ /dev/null @@ -1 +0,0 @@ -add any static {.html, .js, ..} files for sphinx to pickup here diff --git a/.xo-arena/docs/_static/img/favicon.ico b/.xo-arena/docs/_static/img/favicon.ico deleted file mode 100644 index 4163dd69c734f8186cf1ce5e726213cebd231c31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 309936 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 - -Class ------ - -.. doxygenclass:: xo::mm::cmpresult - -Constructors ------------- - -.. doxygengroup:: mm-cmpresult-ctors - -Methods -------- - -.. doxygengroup:: mm-cmpresult-methods - -Member Variables ----------------- - -.. doxygengroup:: mm-cmpresult-instance-vars diff --git a/.xo-arena/docs/conf.py b/.xo-arena/docs/conf.py deleted file mode 100644 index 72dafcf4..00000000 --- a/.xo-arena/docs/conf.py +++ /dev/null @@ -1,39 +0,0 @@ -# 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 arena 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-arena/docs/examples.rst b/.xo-arena/docs/examples.rst deleted file mode 100644 index 5e78bbba..00000000 --- a/.xo-arena/docs/examples.rst +++ /dev/null @@ -1,125 +0,0 @@ -.. _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-arena/docs/glossary.rst b/.xo-arena/docs/glossary.rst deleted file mode 100644 index 5878417c..00000000 --- a/.xo-arena/docs/glossary.rst +++ /dev/null @@ -1,24 +0,0 @@ -.. _glossary: - -Glossary --------- - -.. glossary:: - FOMO - | faceted 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-arena/docs/implementation.rst b/.xo-arena/docs/implementation.rst deleted file mode 100644 index 69f92de6..00000000 --- a/.xo-arena/docs/implementation.rst +++ /dev/null @@ -1,153 +0,0 @@ -.. _implementation: - -Implementation -============== - -Library dependency tower for *xo-arena* - -.. ditaa:: - - +------------------------------------+ - | xo_arena | - +-----------------+------------------+ - | xo_indentlog | xo_reflectutil | - +-----------------+------------------+ - | xo_cmake | - +------------------------------------+ - -Abstraction tower for *xo-arena* components (simplified) - -.. ditaa:: - :--scale: 0.99 - - +-------------------+ - | DArena | - | DArenaIterator | - +-------------------+ - | ArenaConfig | - +-------------------+ - | auxiliary types | - +-------------------+ - - -Abstraction tower for *xo-arena* components (detailed) - -.. ditaa:: - :--scale: 0.99 - - +-----------------------------------------------------+ - | DArena | - | DArenaIterator | - +-----------------------------------------------------+ - | ArenaConfig | - +--------------+------------------------+-------------+ - | | AllocInfo | | - | +------------------------+ | - | AllocError | AllocHeaderConfig | cmpresult | - | +------------------------+ | - | | AllocHeader | | - +--------------+------------------------+-------------+ - -.. 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 - -.. 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 darena1<> - darena1 : config - darena1 : lo - darena1 : hi - darena1 : free - darena1 : limit - darena1 : last_error - - object header1<> - header1 : size - header1 : header - - object hconfig1<> - hconfig1 : guard_z - hconfig1 : guard_byte - hconfig1 : tseq_bits - hconfig1 : age_bits - hconfig1 : size_bits - - darena1 o-- header1 - header1 o-- hconfig1 - -.. uml:: - :caption: memory layout - :scale: 99% - :align: center - - object darena1<> - darena1 : config - darena1 : lo - darena1 : hi - darena1 : free - darena1 : limit - darena1 : last_error - - rectangle "allocated" #90EE90 { - note as n1 - lo -> free - objects here - end note - } - - rectangle "available" #FFFFE0 { - note as n2 - free -> limit - alloc from here - end note - } - - rectangle "uncommitted" #D3D3D3 { - note as n3 - limit -> hi - not mapped yet - end note - } - - darena1 -[hidden]down- n1 - n1 -[hidden]down- n2 - n2 -[hidden]down- n3 - -Remarks: - -* See xo-alloc2 for abstract allocator trait *AAllocator* - along with its application to *DArena*. -* We split these because in *xo-facet* we rely on *DArena* to implement - double-dispatch (two-dimensional vtables, as seen for example in CLOS, Julia, Mathematica). diff --git a/.xo-arena/docs/index.rst b/.xo-arena/docs/index.rst deleted file mode 100644 index b5aeafbc..00000000 --- a/.xo-arena/docs/index.rst +++ /dev/null @@ -1,31 +0,0 @@ -# xo-arena documentation master file - -xo-arena documentation -====================== - -xo-arena 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. - -.. toctree:: - :maxdepth: 2 - :caption: xo-arena contents - - examples - implementation - ArenaConfig-reference - DArena-reference - DArenaIterator-reference - AllocInfo-reference - cmpresult-reference - glossary - genindex - search diff --git a/.xo-arena/include/xo/arena/.gitkeep b/.xo-arena/include/xo/arena/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/.xo-arena/include/xo/arena/AllocError.hpp b/.xo-arena/include/xo/arena/AllocError.hpp deleted file mode 100644 index 0b302768..00000000 --- a/.xo-arena/include/xo/arena/AllocError.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/** @file AllocError.hpp - * - * @author Roland Conybeare, Dec 2025 - **/ - -#pragma once - -#include -#include - -namespace xo { - namespace mm { - enum class error : int32_t { - /** sentinel **/ - invalid = -1, - /** not an error **/ - ok, - /** reserved size exhauged **/ - reserve_exhausted, - /** unable to commit (i.e. mprotect failure) **/ - commit_failed, - /** allocation size too big (See @ref ArenaConfig::header_size_mask_) **/ - header_size_mask, - /** sub_alloc not preceded by super alloc (or another sub_alloc) **/ - orphan_sub_alloc, - /** attempt to call alloc_info for allocator with alloc header feature disabled - * (e.g. @ref see ArenaConfig::store_header_flag_) - **/ - alloc_info_disabled, - /** attempt to call alloc_info for address not owned by allocator **/ - alloc_info_address, - /** for example: alloc iteration not supported in arenas with - * AllocConfig.store_header_flag_ = false - **/ - alloc_iterator_not_supported, - /** attempt to deref an iterator that does not refer to an alloc **/ - alloc_iterator_deref, - /** attempt to advance an iterator that does not refer to an alloc **/ - alloc_iterator_next, - }; - - struct AllocError { - using size_type = std::size_t; - using value_type = std::byte*; - - AllocError() = default; - explicit AllocError(error err, - uint32_t seq) : error_{err}, - error_seq_{seq} {} - AllocError(error err, - const char * src_fn, - uint32_t seq, - size_type req_z, - size_type com_z, - size_type rsv_z) : error_{err}, - src_fn_{src_fn}, - error_seq_{seq}, - request_z_{req_z}, - committed_z_{com_z}, - reserved_z_{rsv_z} {} - - static const char * error_description(error x); - - /** error code **/ - error error_ = error::ok; - /** source function. Typically injected with __PRETTY_FUNCTION__ - * somewhere suitable on stack - **/ - const char * src_fn_ = nullptr; - /** sequence# of this error. - * Each error event within an allocator gets next sequence number - **/ - uint32_t error_seq_ = 0; - /** reqeust size assoc'd with errror **/ - size_type request_z_ = 0; - /** committed allocator memory at time of error **/ - size_type committed_z_ = 0; - /** reserved allocator memory at time of error **/ - size_type reserved_z_ = 0; - }; - } /*namespace mm*/ -} /*namespace xo*/ - -/* end AllocError.hpp */ diff --git a/.xo-arena/include/xo/arena/AllocHeader.hpp b/.xo-arena/include/xo/arena/AllocHeader.hpp deleted file mode 100644 index db8b6785..00000000 --- a/.xo-arena/include/xo/arena/AllocHeader.hpp +++ /dev/null @@ -1,35 +0,0 @@ -/** @file AllocHeader.hpp - * - * @author Roland Conybeare, Dec 2025 - **/ - -#pragma once - -#include -#include -#include - -namespace xo { - namespace mm { - /** @brief per-alloc header - * - * Appears immediately before each allocation when - * ArenaConfig.store_header_flag_ is set. - * - * See AllocInfo.hpp for encoding of @ref repr_ - **/ - struct AllocHeader { - using repr_type = std::uintptr_t; - using size_type = std::size_t; - - explicit AllocHeader(repr_type x) : repr_{x} {} - - repr_type repr_; - }; - - static_assert(sizeof(AllocHeader) == sizeof(AllocHeader::repr_type)); - static_assert(std::is_standard_layout_v); - } -} - -/* end AllocHeader.hpp */ diff --git a/.xo-arena/include/xo/arena/AllocHeaderConfig.hpp b/.xo-arena/include/xo/arena/AllocHeaderConfig.hpp deleted file mode 100644 index d574b292..00000000 --- a/.xo-arena/include/xo/arena/AllocHeaderConfig.hpp +++ /dev/null @@ -1,180 +0,0 @@ -/** @file AllocHeaderConfig.hpp -* - * @author Roland Conybeare, Dec 2025 - **/ - -#pragma once - -#include "AllocHeader.hpp" -#include "padding.hpp" -#include - -namespace xo { - namespace mm { - /** - * @brief specifies alloc header layout - * - * Each allocation is preceded by a 64-bit header. - * Header is split into 3 configurable-width bit fields, - * labelled (from hi to lo bit order) {tseq, age, size}. - * - * 1. tseq. seq# identifying object types; needed for gc. - * 2. gen. age cohort; increases when alloc survives gc. - * 3. size. alloc size. - * - * Arena allocator only uses size. - * X1 collector uses {tseq, gen, size} - * - * alloc header - * - * TTTTTTTTTTTTGGGGGZZZZZZZZZZZZ - * < tseq >< size > - * - * masking - * - * ..432107654321076543210 bit - * - * > < .gen_bits - * 0..............01111111 gen_mask_unshifted - * 0..011111110..........0 gen_mask_shifted - * > < gen_shift - **/ - struct AllocHeaderConfig { - using repr_type = AllocHeader; - using span_type = std::pair; - - AllocHeaderConfig() = default; - AllocHeaderConfig(std::uint32_t gz, - std::uint8_t guard_byte, - std::uint8_t t, - std::uint8_t a, - std::uint8_t z) noexcept : guard_z_{gz}, - guard_byte_{guard_byte}, - tseq_bits_{t}, - age_bits_{a}, - size_bits_{z} {} - - /** create header tuple (@p t, @p a, @p z) - * with typeseq @p t, age @p a, size @p z - **/ - std::uint64_t mkheader(std::uint64_t t, - std::uint64_t a, - std::uint64_t z) const noexcept { - - // don't let age wrap around. - // Expect std::min() to compile to cmov (no branch) - // - a = std::min(a, this->max_age()); - - uint64_t tseq_bits = (t << (age_bits_ + size_bits_)) & tseq_mask(); - uint64_t age_bits = (a << size_bits_) & age_mask(); - uint64_t size_bits = z & size_mask();; - - return (tseq_bits | age_bits | size_bits); - } - - std::uint64_t tseq_mask() const noexcept { - // e.g. - // FF FF FF 00 00 00 00 00 - // with tseq_bits=24, age_bits=8, size_bits=32 - // - return ((1ul << tseq_bits_) - 1) << (age_bits_ + size_bits_); - } - - std::uint64_t max_age() const noexcept { - return ((1ul << age_bits_) - 1); - } - - std::uint64_t age_mask() const noexcept { - // e.g. - // 00 00 00 FF 00 00 00 00 - // with age_bits=8, size_bits=32 - // - return this->max_age() << size_bits_; - } - - std::uint64_t size_mask() const noexcept { - // e.g. - // 00 00 00 00 FF FF FF FF - // with size_bits=32 - // - return ((1ul << size_bits_) - 1); - } - - /** extract type id from alloc header @p hdr **/ - std::uint32_t tseq(repr_type hdr) const noexcept { - // e.g. - // 0x302010 - // for header - // 30 20 10 -- -- -- -- -- - // with tseq_bits_ = 24, age_bits_ + size_bits_ = 40 - // - return (hdr.repr_ & tseq_mask()) >> (age_bits_ + size_bits_); - } - - /** extract age from alloc header @p hdr **/ - std::uint32_t age(repr_type hdr) const noexcept { - // e.g. - // 0xa0 - // for header - // -- -- -- a0 -- -- -- -- - // with age_bits_ = 8, size_bits_ = 32 - // - return (hdr.repr_ & age_mask()) >> size_bits_; - } - - /** extract size from alloc header @p hdr **/ - std::size_t size(repr_type hdr) const noexcept { - // e.g. - // 0x01020300 - // for header - // -- -- -- -- 01 02 03 00 - // with size_bits_ = 32 - // - return (hdr.repr_ & size_mask()); - } - - /** extract padded size from alloc header @p hdr **/ - std::size_t size_with_padding(repr_type hdr) const noexcept { - return padding::with_padding(this->size(hdr)); - } - - /** true iff sentinel tseq, flagging a forwarding pointer **/ - bool is_forwarding_tseq(repr_type hdr) const noexcept { - // e.g. - // 0xFFFFFF - // i.e. header - // FF FF FF -- -- -- -- -- - // with tseq_bits_ = 24, age_bits + size_bits_ = 40 - // - return (hdr.repr_ & tseq_mask()) == tseq_mask(); - } - - bool is_size_enabled() const noexcept { return size_bits_ > 0; } - - /** construct alloc header for a forwarding object **/ - AllocHeader mark_forwarding_tseq(AllocHeader hdr) const noexcept { - return AllocHeader((hdr.repr_ & ~tseq_mask()) | tseq_mask()); - } - - /** if non-zero, allocate extra space between allocs, and fill - * with fixed test-pattern contents. Allows for simple - * runtime arena sanitizing checks. - * Will be rounded up to multiple of @ref padding::c_alloc_alignment - **/ - std::uint32_t guard_z_ = 0; - /** if guard_z_ > 0, write at least that many copies - * of this guard byte following each complete allocation - **/ - std::uint8_t guard_byte_ = 0xfd; - /** number of bits for tseq **/ - std::uint8_t tseq_bits_ = 24; - /** number of bits for age **/ - std::uint8_t age_bits_ = 8; - /** number of bits for size **/ - std::uint8_t size_bits_ = 32; - }; - } /*namespace mm*/ -} /*namespace xo*/ - -/* end AllocHeaderConfig.hpp */ diff --git a/.xo-arena/include/xo/arena/AllocInfo.hpp b/.xo-arena/include/xo/arena/AllocInfo.hpp deleted file mode 100644 index 16878b9b..00000000 --- a/.xo-arena/include/xo/arena/AllocInfo.hpp +++ /dev/null @@ -1,98 +0,0 @@ -/** @file AllocInfo.hpp - * - * @author Roland Conybeare, Dec 2025 - **/ - -#pragma once - -#include "AllocHeaderConfig.hpp" -#include - -namespace xo { - namespace mm { - /** @class AllocInfo - * @brief bookkeeping information for an allocation - * - * AllocInfo instances are 1:1 with sum of calls to - * {@ref AAllocator::alloc, @ref AAllocator::alloc_super} - * - **/ - class AllocInfo { - public: - /** @defgroup mm-allocinfo-traits **/ - ///@{ - - using size_type = AllocHeader::size_type; - using byte = std::byte; - using span_type = std::pair; - - ///@} - - /** @defgroup mm-allocinfo-ctors **/ - ///@{ - - AllocInfo(const AllocHeaderConfig * p_cfg, - const byte * p_guard_lo, - const AllocHeader * p_hdr, - const byte * p_guard_hi) : p_config_{p_cfg}, - p_guard_lo_{p_guard_lo}, - p_header_{p_hdr}, - p_guard_hi_{p_guard_hi} {} - - /** error when alloc-header not configured **/ - static AllocInfo error_not_configured(const AllocHeaderConfig * p_cfg) { - return AllocInfo(p_cfg, nullptr, nullptr, nullptr); - } - /** error on deref empty iterator **/ - static AllocInfo error_invalid_iterator(const AllocHeaderConfig * p_cfg) { - return AllocInfo(p_cfg, nullptr, nullptr, nullptr); - } - - ///@} - - /** @defgroup mm-allocinfo-methods **/ - ///@{ - - AllocHeader header() const noexcept { return *p_header_; } - - /** true for non-sentinel AllocInfo instance **/ - bool is_valid() const noexcept { return ((p_config_ != nullptr) - && (p_header_ != nullptr)); } - /** true iff sentinel tseq, flagging a forwarding pointer **/ - bool is_forwarding_tseq() const noexcept { - return p_config_->is_forwarding_tseq(*p_header_); - } - - /** Guard bytes preceding allocation-header **/ - span_type guard_lo() const noexcept; - /** Type sequence number in garbage collector **/ - std::uint32_t tseq() const noexcept { return p_config_->tseq(*p_header_); } - /** Allocation age in garbage collector **/ - std::uint32_t age() const noexcept { return p_config_->age (*p_header_); } - /** Allocation size (including allocator-supplied padding, excluding alloc header) **/ - size_type size() const noexcept { return p_config_->size(*p_header_); } - /** Payload for this allocation. This is the memory available to application **/ - span_type payload() const noexcept; - /** Guard bytes immediately following allocation **/ - span_type guard_hi() const noexcept; - /** Number of guard bytes **/ - size_type guard_z() const noexcept { return p_config_->guard_z_; } - /** Value (fixed test pattern) of guard byte **/ - uint8_t guard_byte() const noexcept { return p_config_->guard_byte_; } - - ///@} - - /** @defgroup mm-allocinfo-instance-vars **/ - ///@{ - - const AllocHeaderConfig * p_config_ = nullptr; - const byte * p_guard_lo_ = nullptr; - const AllocHeader * p_header_ = nullptr; - const byte * p_guard_hi_ = nullptr; - - ///@} - }; - } /*namespace mm*/ -} /*namespace xo*/ - -/* end AllocInfo.hpp */ diff --git a/.xo-arena/include/xo/arena/ArenaConfig.hpp b/.xo-arena/include/xo/arena/ArenaConfig.hpp deleted file mode 100644 index 05904f90..00000000 --- a/.xo-arena/include/xo/arena/ArenaConfig.hpp +++ /dev/null @@ -1,71 +0,0 @@ -/** @file ArenaConfig.hpp - * - * @author Roland Conybeare, Dec 2025 - **/ - -#pragma once - -#include "AllocHeaderConfig.hpp" -#include -#include - -namespace xo { - namespace mm { - - /** @class ArenaConfig - * - * @brief configuration for a @ref DArena instance - **/ - struct ArenaConfig { - /** @defgroup mm-arenaconfig-ctors **/ - ///@{ - - /** NOTE: not providing explicit ctors so we can use designated initializers **/ - - ArenaConfig with_name(std::string name) const { - ArenaConfig copy(*this); - copy.name_ = name; - return copy; - } - - ArenaConfig with_size(std::size_t z) const { - ArenaConfig copy(*this); - copy.size_ = z; - return copy; - } - - ArenaConfig with_store_header_flag(bool x) const { - ArenaConfig copy(*this); - copy.store_header_flag_ = x; - return copy; - } - - ///@} - /** @defgroup mm-arenaconfig-instance-vars ArenaConfig members **/ - ///@{ - - /** optional name, for diagnostics **/ - std::string name_; - /** desired arena size -- hard max = reserved virtual memory **/ - std::size_t size_ = 0; - /** hugepage size -- using huge pages relieves some TLB pressure - * (provided you use their full extent :) - **/ - std::size_t hugepage_z_ = 2 * 1024 * 1024; - /** true to store header (8 bytes) at the beginning of each allocation. - * necessary and sufficient to allows iterating over allocs - * present in arena. - **/ - bool store_header_flag_ = false; - /** configuration for per-alloc header **/ - AllocHeaderConfig header_{}; - /** true to enable debug logging **/ - bool debug_flag_ = false; - - ///@} - }; - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end ArenaConfig.hpp */ diff --git a/.xo-arena/include/xo/arena/ArenaHashMapConfig.hpp b/.xo-arena/include/xo/arena/ArenaHashMapConfig.hpp deleted file mode 100644 index 69174141..00000000 --- a/.xo-arena/include/xo/arena/ArenaHashMapConfig.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/** @file ArenaHashMapConfig.hpp - * - * @author Roland Conybeare, Feb 2026 - **/ - -#pragma once - -#include -#include - -namespace xo { - namespace map { - /** @class ArenaHashMapConfig - * - * @brief configuration for a @ref DArenaHashMap instance - **/ - struct ArenaHashMapConfig { - /** @defgroup map-arenahashmapconfig-ctors **/ - ///@{ - - ArenaHashMapConfig with_name(std::string name) const { - ArenaHashMapConfig copy(*this); - copy.name_ = name; - return copy; - } - - ArenaHashMapConfig with_hint_max_capacity(std::size_t z) const { - ArenaHashMapConfig copy(*this); - copy.hint_max_capacity_ = z; - return copy; - } - - ArenaHashMapConfig with_debug_flag(bool x) const { - ArenaHashMapConfig copy(*this); - copy.debug_flag_ = x; - return copy; - } - - ///@} - /** @defgroup mm-arenahashmapconfig-instance-vars ArenaHashMapConfig members **/ - ///@{ - - /** optional name, for diagnostics **/ - std::string name_; - /** desired hard max hashmap size -> reserved virtual memory - * hint: actual max may be larger, because of power-of-2 considerations. - **/ - std::size_t hint_max_capacity_ = 0; - /** true to enable debug logging **/ - bool debug_flag_ = false; - - ///@} - }; - - } /*namespace map*/ -} /*namespace xo*/ - -/* end ArenaHashMapConfig.hpp */ diff --git a/.xo-arena/include/xo/arena/CircularBufferConfig.hpp b/.xo-arena/include/xo/arena/CircularBufferConfig.hpp deleted file mode 100644 index 587b9d22..00000000 --- a/.xo-arena/include/xo/arena/CircularBufferConfig.hpp +++ /dev/null @@ -1,63 +0,0 @@ -/** @file CircularBufferConfig.hpp -* - * @author Roland Conybeare, Jan 2026 - **/ - -#pragma once - -#include - -namespace xo { - namespace mm { - /** @class CircularBufferConfig - * - * @brief configuration for a @ref DCircularBuffer instance - **/ - struct CircularBufferConfig { - /** @defgroup mm-circularbufferconfig-instance-vars CircularBufferConfig members **/ - ///@{ - - /** optional name, for diagnostics **/ - std::string name_; - /** hard maximum buffer size = reserved virtual memory. - * However actual max will be this value rounded up to at least page size. - * Buffer will generally map much less than this amount of memory - **/ - std::size_t max_capacity_ = 0; - /** hugepage size -- using huge pages relieves some TLB pressure, - * at expense of inefficient memory consumption for (up to two) - * partially used superpages. - **/ - std::size_t hugepage_z_ = 2 * 1024 * 1024; - /** Threshold 'move efficeincy' = (move_distance / move_qty) - * applies to moving unread input to the beginning of mapped range, - * when not prevented by pinned ranges. - * - * Higher numbers reduce cpu consumption but increase memory consumption - * Reciprocal loose ceiling on relative effort that may be spent on - * moving fractional input - **/ - float threshold_move_efficiency_ = 50.0; - /** lower bound for hard maximum number of capture spans. - * - * Expected use case is to track spans that are currently referenced - * (rather than copied) from outside a DCircularBuffer instance. - * Circular buffer will not unmap or overwrite memory for such spans. - * - * Expect to generally release captured spans in the same order they - * were captured. Out of order release is supported, but cost - * of out-of-order release grows - * like O(r) for r remembered spans. - * - * A typical parser will need spans to remember one line of input - **/ - std::size_t max_captured_span_ = 0; - /** true to enable debug logging **/ - bool debug_flag_ = false; - - ///@} - }; - } /*namespace mm*/ -} /*namespace xo*/ - -/* end CircularBufferConfig.hpp */ diff --git a/.xo-arena/include/xo/arena/DArena.hpp b/.xo-arena/include/xo/arena/DArena.hpp deleted file mode 100644 index 5148ba6e..00000000 --- a/.xo-arena/include/xo/arena/DArena.hpp +++ /dev/null @@ -1,338 +0,0 @@ -/** @file DArena.hpp - * - * @author Roland Conybeare, Dec 2025 - **/ - -#pragma once - -#include "ArenaConfig.hpp" -#include "AllocError.hpp" -#include "MemorySizeInfo.hpp" -#include "AllocInfo.hpp" -#include - -namespace xo { - namespace mm { - struct DArenaIterator; // see DArenaIterator.hpp - - /** @class DArena - * - * @brief represent arena allocator state - * - * Provides minimal RAII functionality around memory mapping. - * For allocation implementation see @ref IAllocator_DArena - **/ - struct DArena { - /* - * <----------------------------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 allocation pointer; use for allocation results **/ - using value_type = std::byte*; - /** @brief a contiguous memory range **/ - using range_type = std::pair; - /** @brief type for allocation header (if enabled) **/ - using header_type = AllocHeader; - /** integer identifying a type (see xo::facet::typeid()) **/ - using typeseq = xo::reflect::typeseq; - - /** @brief mode argument for @ref _alloc **/ - enum class alloc_mode : uint8_t { - /** ordinary alloc. Most common mode **/ - standard, - /** begin a sequence of suballocs that share a single alloc header **/ - super, - /** make a subsidiary allocation on behalf of a preceding super alloc. - * Will be followed by at least one more suballoc call. - **/ - sub_incomplete, - /** make a subsidiary allocation that completes preceding super alloc. **/ - sub_complete, - }; - - /** @brief Checkpoint for unwinding arena state **/ - struct Checkpoint { - Checkpoint() = default; - explicit Checkpoint(std::byte * x) : free_{x} {} - std::byte * free_ = nullptr; - }; - - ///@} - - /** @defgroup mm-arena-ctors arena constructors and destructors **/ - ///@{ - - /** create arena per configuration @p cfg. **/ - static DArena map(const ArenaConfig & cfg); - - /** null ctor **/ - DArena() = default; - /** create arena from @p cfg. Will reserve memory for allocation **/ - DArena(const ArenaConfig & cfg); - /** ctor from already-mapped (but not committed) address range [lo,hi] **/ - DArena(const ArenaConfig & cfg, - size_type page_z, - size_type arena_align_z, - value_type lo, - value_type hi); - /** DArena is not copyable **/ - DArena(const DArena & other) = delete; - /** move ctor **/ - DArena(DArena && other); - /** dtor releases mapped memory **/ - ~DArena(); - - /** move-assignment **/ - DArena & operator=(DArena && other); - - ///@} - - /** @defgroup mm-arena-methods **/ - ///@{ - - /** false -> not eligible for GC (allocates own memory + not moveable) **/ - static constexpr bool is_gc_eligible() { return false; } - - /** Reserved memory, in bytes. This is the maximum size of this arena. **/ - size_type reserved() const noexcept { return hi_ - lo_; } - /** Allocated memory in bytes: memory consumed by allocs from this arena, - * including administrative overhead (alloc headers + guard bytes) - **/ - size_type allocated() const noexcept { return free_ - lo_; } - /** Committed memory in bytes: amount of memory actually backed by physical memory **/ - size_type committed() const noexcept { return committed_z_; } - /** Available committed memory. - * This is the amount of memory guaranteed to be usable for future allocs from this arena. - **/ - size_type available() const noexcept { return limit_ - free_; } - /** VM page size for this arena (likely 4KB) **/ - size_type page_z() const noexcept { return page_z_; } - /** Last error encountered by this arena **/ - const AllocError & last_error() const noexcept { return last_error_; } - - /** True iff address @p addr is owned by this arena, - * i.e. falls within [@ref lo_, @ref hi_) - **/ - bool contains(const void * addr) const noexcept { return (lo_ <= addr) && (addr < hi_); } - - /** True iff address @p addr is owned by this arena and in allocated regions **/ - bool contains_allocated(const void * addr) const noexcept { return (lo_ <= addr) && (addr < free_); } - - /** true if arena is mapped i.e. has a reserved address range **/ - bool is_mapped() const noexcept { return (lo_ != nullptr) && (hi_ != nullptr); } - - /** @ret iterator pointing to the first allocation in this arena **/ - DArenaIterator begin() const noexcept; - /** @ret iterator pointing to just after the last allocation in this arena **/ - DArenaIterator end() const noexcept; - - /** @ret header for first allocation in this arena **/ - AllocHeader * begin_header() const noexcept; - /** @ret location of header for next (not yet performed!) - * allocation in this arena - **/ - AllocHeader * end_header() const noexcept; - - /** report memory use for this arena to @p fn. - * For DArena reporting just one pool = arena's memory range - **/ - void visit_pools(const MemorySizeVisitor & fn) const; - - /** get header from allocated object address **/ - header_type * obj2hdr(void * obj) noexcept; - /** get header from allocated object address (const version) **/ - const header_type * obj2hdr(void * obj) const noexcept; - - /** report alloc book-keeping info for allocation at @p mem - * - * Require: - * 1. @p mem is address returned by allocation on this arena - * i.e. by @ref IAllocator_DArena::alloc() or - * @ref IAllocator_DArena::alloc_super() - * 2. @p mem has not been invalidated since it was allocated - * i.e. by call to @ref DArena::clear - * - * Note: non-const, may stash error details - **/ - AllocInfo alloc_info(value_type mem) const noexcept; - - /** convenience template for allocating for a T-instance **/ - template - void * alloc_for(size_type n = sizeof(T)) { - return this->alloc(typeseq::id(), n); - } - - /** allocate at least @p z bytes of memory. - * Return nullptr and capture error if unable to satisfy request. - * May expand committed memory, as long as resulting committed size - * is no larger than reserved size - **/ - value_type alloc(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() - **/ - value_type super_alloc(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. - **/ - value_type sub_alloc(size_type z, bool complete_flag); - - /** alloc copy of @p src **/ - value_type alloc_copy(value_type src); - - /** capture error information: advance error count + set last_error **/ - void capture_error(error err, - const char * src_fn, - size_type target_z = 0) const; - - /** alloc driver. shared by alloc(), super_alloc(), sub_alloc() **/ - value_type _alloc(std::size_t req_z, - alloc_mode mode, - typeseq tseq, - uint32_t age, - const char * src_fn); - - /** expand committed space in arena @p d - * to size at least @p z, on behalf of @p src_fn - * In practice will round up to a multiple of @ref page_z_. - **/ - bool expand(size_type z, const char * src_fn) noexcept; - - /** create initial guard **/ - void establish_initial_guard() noexcept; - - /** checkpoint arena state. Revert to the same state with - * @ref restore - **/ - Checkpoint checkpoint() noexcept { return Checkpoint(free_); } - - /** restore arena state to previously-established checkpoint **/ - void restore(Checkpoint ckp) noexcept { free_ = ckp.free_; } - - - /** zero out all allocated memory. Likely use case is diagnostics **/ - void scrub() noexcept; - - /** discard all allocated memory, return to empty state - * Promise: - * - committed memory unchanged - * - available memory = committed memory - **/ - void clear() noexcept; - - /** release backing memory and reset bookkeeping to the empty state. - * - * Unmaps [@ref lo_, @ref hi_) (if mapped) and zeroes the bookkeeping - * fields {lo_, committed_z_, last_header_, free_, limit_, hi_, - * error_count_, last_error_}. @ref config_ (and page_z_/arena_align_z_) - * are left intact. - * - * Idempotent: a second call is a no-op (lo_ has been cleared). - * Invoked by ~DArena(); also safe to call on a live arena. - * - * Note: application code must not rely on observing the zeroed state - * after destruction (that would be UB) -- the zeroing is defensive, - * to avoid leaving dangling pointers behind. - **/ - void unmap() noexcept; - - /** swap contents (including configuration) with another arena **/ - void swap(DArena & other) noexcept; - - ///@} - - /** @defgroup mm-arena-instance-vars **/ - ///@{ - - /** arena configuration **/ - ArenaConfig config_; - - /** size of a VM page (obtained automatically via getpagesize()). Likely 4k **/ - size_type page_z_ = 0; - - /** alignment for this arena. In practice will be either page_z_ or cfg.hugepage_z_ **/ - size_type arena_align_z_ = 0; - - /** arena owns memory in range [@ref lo_, @ref hi_) - **/ - std::byte * lo_ = nullptr; - - /** prefix of this size is committed. - * Remainder mapped but uncommitted. - **/ - size_type committed_z_ = 0; - - /** if config_.store_header_flag_: - * Pointer to header for last allocation. - **/ - header_type * last_header_ = nullptr; - - /** free pointer. - * Memory in range [@ref lo_, @ref free_) current in use - **/ - std::byte * free_ = nullptr; - - /** soft limit; end of committed virtual memory - * Memory in range [@ref lo_, @ref limit_) is committed - * (backed by physical memory) - **/ - std::byte * limit_ = nullptr; - - /** hard limit; end of reserved virtual memory - * Memory in range [@ref limit_, @ref hi_) is uncommitted - **/ - std::byte * hi_ = nullptr; - - /** count runtime errors. Each error updates @ref last_error_ **/ - uint32_t error_count_ = 0; - - /** capture some error details if/when error **/ - AllocError last_error_; - - ///@} - }; - - /** construct a @tparam T instance from arguments @p args - * using memory obtained from arena @p ialloc - **/ - template - static T * - construct_with(DArena & ialloc, Args&&... args) - { - using xo::reflect::typeseq; - - typeseq t = typeseq::id(); - std::byte * mem = ialloc.alloc(t, sizeof(T)); - - if (mem) - return new (mem) T(std::forward(args)...); - - return nullptr; - } - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end DArena.hpp */ diff --git a/.xo-arena/include/xo/arena/DArenaHashMap.hpp b/.xo-arena/include/xo/arena/DArenaHashMap.hpp deleted file mode 100644 index c97f7bad..00000000 --- a/.xo-arena/include/xo/arena/DArenaHashMap.hpp +++ /dev/null @@ -1,768 +0,0 @@ -/** @file DArenaHashMap.hpp - * - * @author Roland Conybeare, Jan 2026 - **/ - -#pragma once - -#include "ArenaHashMapConfig.hpp" -#include "DArenaVector.hpp" -#include "hashmap/verify_policy.hpp" -#include "hashmap/HashMapStore.hpp" -#include "hashmap/DArenaHashMapIterator.hpp" -#include -#include -#include -#include -#include - -namespace xo { - namespace map { -#ifdef NOT_YET - enum class insert_error : int32_t { - /** sentinel **/ - invalid = -1, - /** not an error **/ - ok, - }; -#endif - - /** @brief flat hash map of key-value pairs using dedicated DArenas for storage - * - * Replicates (to the extent feasible) std::unordered_map - * - * @tparam Key key type. - * @tparam Value value type. - * @tparam Hash hash function for keys - * @tparam Equal equality function for keys - **/ - template , - typename Equal = std::equal_to> - struct DArenaHashMap : DArenaHashMapUtil { - public: - using size_type = DArenaHashMapUtil::size_type; - using key_type = Key; - using mapped_type = Value; - using value_type = std::pair; - using key_hash = Hash; - using key_equal = Equal; - using MemorySizeVisitor = xo::mm::MemorySizeVisitor; - using byte = std::byte; - using group_type = detail::ControlGroup; - using store_type = detail::HashMapStore; - using insert_value_type = std::pair; - using iterator = detail::DArenaHashMapIterator; - using const_iterator = detail::DArenaHashMapConstIterator; - - public: - /** create hash map **/ - DArenaHashMap(const ArenaHashMapConfig & cfg); - DArenaHashMap(const std::string & name, - size_type hint_max_capacity, - bool debug_flag = false); - DArenaHashMap(const std::string & name, - Hash && hash = Hash(), - Equal && eq = Equal(), - size_type hint_max_capacity = 0, - bool debug_flag = false); - - /** true for types that support the AGCObject facet; DArenaHashMap gets its own memory! **/ - static constexpr bool is_gc_eligible() { return false; } - - size_type empty() const noexcept { return store_.empty(); } - size_type groups() const noexcept { return store_.n_group_; } - size_type size() const noexcept { return store_.size_; } - size_type capacity() const noexcept { return store_.capacity(); } - float load_factor() const noexcept { return store_.load_factor(); } - - const_iterator cbegin() const { return this->_begin_aux(); } - const_iterator cend() const { return this->_end_aux(); } - - const_iterator begin() const { return this->_begin_aux(); } - const_iterator end() const { return this->_end_aux(); } - - iterator begin() { return _promote_iterator(_begin_aux()); } - iterator end() { return _promote_iterator(_end_aux()); } - - void visit_pools(const MemorySizeVisitor & visitor) const { - return store_.visit_pools(visitor); - } - - /** insert @p kv_pair into hash map. - * Replaces any previous value stored under the same key. - * - * Return pair retval with: - * retval.first: address of slots_[p] at which pair inserted/updated - * retval.second: true if size incremented; - * - * When table is full retval.second will be nullptr, - * with error captured in last_error_ - **/ - insert_value_type try_insert(const value_type & kv_pair); - - /** insert @p kv_pair into hash map. - * Increase table size if necessary - **/ - bool insert(const value_type & kv_pair); - - /** reset to empty state **/ - void clear(); - - /** find element with key @p key. - * @return iterator to element if found, end() otherwise - **/ - const_iterator find(const key_type & key) const { return _find(key); } - iterator find(const key_type & key) { return _promote_iterator(_find(key)); } - - /** establish kv pair for @p key in this table; return address of value part **/ - mapped_type & operator[](const key_type & key); - - /** verify DArenaHashMap invariants - * Act on failure according to policy @p - * (combination of throw|log bits) - **/ - bool verify_ok(verify_policy p = verify_policy::throw_only()) const; - - store_type * _store() noexcept { return &store_; } - - auto _hash(const key_type & key) const { - size_type h = hash_(key); - size_type h1 = h >> 7; // slot# - size_type h2 = h % 0x7f; // fingerprint - - size_type N = store_.capacity(); - - size_type slot_ix = h1 % (N - 1); - - return std::make_pair(slot_ix, h2); - } - - private: - iterator _promote_iterator(const_iterator ix) { - return iterator(const_cast(ix._ctrl()), - const_cast(ix._pos())); - } - - const_iterator _begin_aux() const { - if (this->empty()) [[unlikely]] { - return this->end(); - } - - const_iterator ix(&(store_.control_[c_control_stub]), - &(store_.slots_[0])); - - if (ix._at_slot_sentinel()) { - /* advance to first occupied position in table */ - ++ix; - } - - return ix; - } - - const_iterator _end_aux() const { - const_iterator ix(&(store_.control_[c_control_stub + store_.capacity()]), - &(store_.slots_[store_.capacity()])); - - return ix; - } - - /** search hash map on key @p key, return iterator to table member. - * return end-iterator if @p key not found - **/ - const_iterator _find(const key_type & key) const; - - /** insert @p kv_pair, - * where key hashes to @p hash_value, into @p *store - **/ - insert_value_type _try_insert_aux(size_type hash_value, - const value_type & kv_pair, - store_type * p_store); - - /** increase hash table size (invoke when max load factor reached) **/ - bool _try_grow(); - - /** load group abstraction from control bytes starting at @p ix **/ - group_type _load_group(size_type ix) { return store_._load_group(ix); } - - /** like ctrl_[ix] = h2, but maintain overflow copy - * at end of ctrl_[] array - **/ - void _update_control(size_type ix, uint8_t h2) { - return store_._update_control(ix, h2); - } - - private: - /** hash function **/ - key_hash hash_; - /** key equal **/ - key_equal equal_; - /** hash table state contents + size-related attributes **/ - store_type store_; - /** true to enable debug logging **/ - bool debug_flag_ = false; - }; - - template - DArenaHashMap::DArenaHashMap(const ArenaHashMapConfig & cfg) - : DArenaHashMap(cfg.name_, Hash(), Equal(), cfg.hint_max_capacity_, cfg.debug_flag_) - { - } - - template - DArenaHashMap::DArenaHashMap(const std::string & name, - size_type hint_max_capacity, - bool debug_flag) - : DArenaHashMap(name, Hash(), Equal(), hint_max_capacity, debug_flag) - { - } - - /* remarks: - * - control: extra 16 slots for safe wraparound. - * last 16 bytes will be copy of first 16 bytes - */ - template - DArenaHashMap::DArenaHashMap(const std::string & name, - Hash && hash, - Equal && eq, - size_type hint_max_capacity, - bool debug_flag) - : hash_{std::move(hash)}, - equal_{std::move(eq)}, - store_{name, lub_exp2(lub_group_mult(hint_max_capacity))}, - debug_flag_{debug_flag} - { - } - - template - auto - DArenaHashMap::try_insert(const value_type & kv_pair) -> insert_value_type - { - size_type h = hash_(kv_pair.first); - - return _try_insert_aux(h, kv_pair, &store_); - } - - template - auto - DArenaHashMap::_try_insert_aux(size_type hash_value, - const std::pair & kv_pair, - store_type * p_store) - -> std::pair - - { - scope log(XO_DEBUG(false)); - - size_type h = hash_value; - // h1: hi bits: probe sequence - size_type h1 = h >> 7; - // h2: lo bits: store in control byte - uint8_t h2 = h & 0x7f; - - size_type N = p_store->capacity(); - - if (N == 0) [[unlikely]] { - return std::make_pair(nullptr, false); - } - - // same as: - // ix = h1 % N - // since N is power of 2 - size_type ix = h1 & (N - 1); - - // will make series of probes - for (;;) { - auto grp = p_store->_load_group(ix); - - { - // look for matching slot to update - uint16_t m = grp.all_matches(h2); - - // process each match. - // matches are encountered in the same order they - // appear in ctrl_[] - while (m) { - // zeroes: #of 0 before least-significant 1 bit - int skip = __builtin_ctz(m); - size_type slot_ix = (ix + skip) & (N - 1); - - // invariant: slot_ix in [0 .. N) - - auto & slot = p_store->slots_[slot_ix]; - - if (equal_(slot.first, kv_pair.first)) { - // we have match on existing key; - // replace associated value - slot.second = kv_pair.second; - - // false: did not change table size - return std::make_pair(&slot, false); - } - - // e.g: - // /-- lowest 1 bit gets cleared - // v - // m = b01101000 - // m-1 = b01100111 - // & = b01100000 - - m &= (m - 1); - } - } - - { - // look for empty slot to insert - uint16_t e = grp.empty_matches(); - - // process each empty slot - if (e) { - // check that table is below max load factor (0.875). - // Check here so that table can stay at max load factor - // indefinitely as long as updates only - // - if (p_store->load_factor() >= c_max_load_factor) { - return std::make_pair(nullptr, false); - } - - // zeroes: #of 0 before least significant 1 bit - int skip = __builtin_ctz(e); - size_type slot_ix = (ix + skip) & (N - 1); - - // invariant: slot_ix in [0 .. N) - - auto & slot = p_store->slots_[slot_ix]; - - // mark slot occupied in control space; - // maintain copy-at-end for overflow - p_store->_update_control(slot_ix, h2); - new (&slot) value_type(kv_pair); - - ++(p_store->size_); - - // true: increased table size - return std::make_pair(&slot, true); - } - } - - // slot range associated with grp - // has no room, and does not contain target key - // -> move on to next group. - // - // note: relying on c_group_size overflow bytes here - // when ix is close to N - - ix = (ix + c_group_size) & (N - 1); - } - } - - template - bool - DArenaHashMap::_try_grow() - { - scope log(XO_DEBUG(false)); - - size_type n_group_exponent_2x = 0; - size_type n_group_2x = 0; - - if (store_.n_group_ == 0) [[unlikely]] { - // special case: grow from hard empty state - n_group_exponent_2x = 0; - n_group_2x = 1; - } else { - n_group_exponent_2x = store_.n_group_exponent_ + 1; - n_group_2x = 2 * n_group_exponent_2x; - } - - // optimization when table is empty. in that case can resize - // arenas in place - - if (this->empty()) { - log && log("resize-from-empty branch"); - - this->store_.resize_from_empty(std::make_pair(n_group_exponent_2x, n_group_2x)); - } else { - log && log("duplicate-and-replace branch"); - - detail::HashMapStore store_2x("arenahashmap", - std::make_pair(n_group_exponent_2x, - n_group_2x)); - /* rehash everything in store_, - * into store_2x - */ - - for (size_type i = 0, n = store_.capacity(); i < n; ++i) { - uint8_t ctrl = store_.control_[c_control_stub + i]; - value_type & kv_pair = store_.slots_[i]; - - if (DArenaHashMapUtil::is_data(ctrl)) { - size_type h = hash_(kv_pair.first); - auto chk = this->_try_insert_aux(h, kv_pair, &store_2x); - - if (!chk.second) { - // shenanigans - something isn't right. - // - may have run out of memory - assert(false); - - return false; - } - } - } - - this->store_ = std::move(store_2x); - } - - return true; - } - - template - bool - DArenaHashMap::insert(const std::pair & kv_pair) - { - scope log(XO_DEBUG(false)); - - auto [slot_addr, ins_flag] = this->try_insert(kv_pair); - - if (slot_addr) { - log && log("fast", xtag("slot_addr", (void*)slot_addr), xtag("ins_flag", ins_flag)); - - return ins_flag; - } - - assert((store_.size_ + 1) / static_cast(store_.n_slot_) >= c_max_load_factor); - - if (this->_try_grow()) { - /* retry insert, with bigger table */ - auto [slot_addr, ins_flag] = this->try_insert(kv_pair); - - return ins_flag; - } else { - assert(false); - - // TODO: set last error. Presumeably reached max size - return false; - } - } - - template - void - DArenaHashMap::clear() - { - this->store_.clear(); - } - - template - auto - DArenaHashMap::_find(const key_type & key) const -> const_iterator - { - size_type N = store_.capacity(); - - if (N == 0) [[unlikely]] { - return this->cend(); - } - - size_type h = hash_(key); - size_type h1 = h >> 7; - uint8_t h2 = h & 0x7f; - - size_type ix = h1 & (N - 1); - - for (;;) { - auto grp = store_._load_group(ix); - - { - uint16_t m = grp.all_matches(h2); - - while (m) { - int skip = __builtin_ctz(m); - size_type slot_ix = (ix + skip) & (N - 1); - - auto & slot = store_.slots_[slot_ix]; - - if (equal_(slot.first, key)) { - return const_iterator(&(store_.control_[c_control_stub + slot_ix]), - &slot); - } - - m &= (m - 1); - } - } - - { - uint16_t e = grp.empty_matches(); - - if (e) { - return this->end(); - } - } - - ix = (ix + c_group_size) & (N - 1); - } - } - - template - auto - DArenaHashMap::operator[](const key_type & key) -> mapped_type & - { - { - auto ix = this->find(key); - - if (ix != this->end()) - return ix->second; - } - - // key-value pair - value_type kv_pair = std::make_pair(key, mapped_type{}); - - auto [slot_addr, ins_flag] = this->try_insert(kv_pair); - - if (slot_addr) - return slot_addr->second; - - if (!this->_try_grow()) { - // we are out of room - - throw std::runtime_error("DArenaHashMap::operator[]: table capacity exhausted"); - } - - /* retry insert, now with bigger capacity */ - std::tie(slot_addr, ins_flag) = this->try_insert(kv_pair); - - assert(slot_addr); - - return slot_addr->second; - } - - /** - * Verify DArenaHashMap class invariants. - * - * SM1. size consistency - * - SM1.1 size_ <= n_slot_ - * - SM1.2 control_[] size consistent with slots_[] size - * - SM1.3 n_group_ consistent with n_group_exponent_ - * - SM1.4 n_slot_ consistent with n_group_ - * - SM1.5 n_slot_ a power of 2 - * SM2. load factor - * - SM2.1 load_factor() <= c_max_load_factor - * SM3. control_ - * - SM3.1 control_[i] = c_iterator_bookend for i in [0, c_control_stub) - * - SM3.2 control_[stub+i] = control_[stub+N+i] for i in [0, c_group_size) - * - SM3.3 {number of control_[i] spots with non-sentinel values} = size_ - * - SM3.4 control_[stub+N+c_group_size+i] = c_iterator_bookend for i in [0, c_control_stub) - * SM4. slots_ - * - SM4.1 if control_[i] is non-sentinel: - * - SM4.1.1 control_[i] = hash_(slots_[i].first) & 0x7f - * - SM4.1.2 all slots in range [h .. i] are non-empty, - * where h is hash_(slots_[i].first >> 7 - * - SM4.2 if control_[i] is empty or tombstone: - * - slots_[i].first = key_type() - * - **/ - template - bool - DArenaHashMap::verify_ok(verify_policy policy) const - { - using xo::scope; - using xo::tostr; - using xo::xtag; - - constexpr const char * c_self = "DArenaHashMap::verify_ok"; - scope log(XO_DEBUG(debug_flag_), - xtag("size", store_.size_)); - - /* SM1.1: size_ <= n_slot_ */ - if (store_.size_ > store_.n_slot_) { - return policy.report_error(log, - c_self, ": expect .size <= .n_slot", - xtag("size", store_.size_), - xtag("n_slot", store_.n_slot_)); - } - - /* SM1.2: control_[] size consistent with slots_[] size */ - if (store_.control_.size() != control_size(store_.n_slot_)) { - return policy.report_error - (log, - c_self, ": expect .control_.size = .n_slot + c_group_size + 2 * c_control_stub", - xtag("control_.size", store_.control_.size()), - xtag("n_slot", store_.n_slot_), - xtag("c_group_size", c_group_size), - xtag("c_control_stub", c_control_stub)); - } - if (store_.slots_.size() != store_.n_slot_) { - return policy.report_error(log, - c_self, ": expect .slots_.size = .n_slot", - xtag("slots_.size", store_.slots_.size()), - xtag("n_slot", store_.n_slot_)); - } - - /* SM1.3: n_group_ consistent with n_group_exponent_ */ - if ((store_.n_group_ > 0) - && (store_.n_group_ != (size_type{1} << store_.n_group_exponent_))) - { - return policy.report_error(log, - c_self, ": expect .n_group = 2^.n_group_exponent", - xtag("n_group", store_.n_group_), - xtag("n_group_exponent", store_.n_group_exponent_)); - } - - /* SM1.4: n_slot_ consistent with n_group_ */ - if (store_.n_slot_ != store_.n_group_ * c_group_size) { - return policy.report_error(log, - c_self, ": expect .n_slot = .n_group * c_group_size", - xtag("n_slot", store_.n_slot_), - xtag("n_group", store_.n_group_), - xtag("c_group_size", c_group_size)); - } - - /* SM1.5: n_slot_ a power of 2 */ - if ((store_.n_slot_ & (store_.n_slot_ - 1)) != 0) { - return policy.report_error(log, - c_self, ": expect .n_slot is power of 2", - xtag("n_slot", store_.n_slot_)); - } - - /* SM2.1: load_factor() <= c_max_load_factor */ - if (load_factor() > c_max_load_factor) { - return policy.report_error(log, - c_self, ": expect .load_factor <= c_max_load_factor", - xtag("load_factor", load_factor()), - xtag("c_max_load_factor", c_max_load_factor)); - } - - /* SM3.1: control_[i] = c_iterator_bookend for i in [0, c_control_stub) */ - for (size_type i = 0; i < c_control_stub; ++i) { - if (store_.control_[i] != c_iterator_bookend) { - return policy.report_error(log, - c_self, ": expect control_[i] = c_iterator_bookend for front stub", - xtag("i", i), - xtag("control_[i]", (int)(store_.control_[i])), - xtag("c_iterator_bookend", (int)c_iterator_bookend)); - } - } - - /* SM3.2: control_[N+i] = control_[i] for i in [0, c_group_size) */ - for (size_type i = 0; i < c_group_size; ++i) { - if (store_.control_[store_.n_slot_ + i + c_control_stub] != store_.control_[i + c_control_stub]) { - return policy.report_error(log, - c_self, ": expect control_[N+i] = control_[i]", - xtag("i", i), - xtag("control_[i]", (int)(store_.control_[i + c_control_stub])), - xtag("control_[N+i]", (int)(store_.control_[store_.n_slot_ + i + c_control_stub]))); - } - } - - /* SM3.3: {number of control_[i] spots with non-sentinel values} = size_ */ - { - size_type occupied_count = 0; - for (size_type i = 0; i < store_.n_slot_; ++i) { - uint8_t c = store_.control_[i + c_control_stub]; - if (DArenaHashMapUtil::is_data(c)) { - ++occupied_count; - } - } - if (occupied_count != store_.size_) { - return policy.report_error(log, - c_self, ": expect occupied control count = size", - xtag("occupied_count", occupied_count), - xtag("size", store_.size_)); - } - } - - /* SM3.4: control_[stub+N+c_group_size+i] = c_iterator_bookend for i in [0, c_control_stub) */ - if (store_.n_slot_ > 0) { - for (size_type i = 0; i < c_control_stub; ++i) { - size_type ix = c_control_stub + store_.n_slot_ + c_group_size + i; - if (store_.control_[ix] != c_iterator_bookend) { - return policy.report_error - (log, - c_self, ": expect control_[stub+N+group+i] = c_iterator_bookend for end stub", - xtag("i", i), - xtag("N", store_.n_slot_), - xtag("ix", ix), - xtag("control_[ix]", (int)(store_.control_[ix])), - xtag("c_iterator_bookend", (int)c_iterator_bookend)); - } - } - } - - /* SM4.1.1: if control_[i] is non-sentinel, control_[i] = hash_(slots_[i].first) & 0x7f */ - for (size_type i = 0; i < store_.n_slot_; ++i) { - uint8_t c = store_.control_[i + c_control_stub]; - if (DArenaHashMapUtil::is_data(c)) { - uint8_t expected_h2 = hash_(store_.slots_[i].first) & 0x7f; - if (c != expected_h2) { - return policy.report_error(log, - c_self, ": expect control[i] = hash(key) & 0x7f", - xtag("i", i), - xtag("control[i+stub]", c), - xtag("expected_h2", expected_h2)); - } - } - } - - /* SM4.1.2: if control_[i] is non-sentinel, all slots in range [h .. i] are non-empty, - * where h = (hash_(slots_[i].first) >> 7) & (n_slot_ - 1) - */ - for (size_type i = 0; i < store_.n_slot_; ++i) { - uint8_t c = store_.control_[i + c_control_stub]; - if (DArenaHashMapUtil::is_data(c)) { - size_type h = (hash_(store_.slots_[i].first) >> 7) & (store_.n_slot_ - 1); - size_type j = h; - while (j != i) { - uint8_t cj = store_.control_[j + c_control_stub]; - if (DArenaHashMapUtil::is_sentinel(cj)) { - return policy.report_error(log, - c_self, ": expect non-empty slot in probe range [h..i]", - xtag("i", i), - xtag("h", h), - xtag("j", j), - xtag("control[j+stub]", cj)); - } - j = (j + 1) & (store_.n_slot_ - 1); - } - } - } - - /* SM4.2: if control_[i] is empty or tombstone, slots_[i].first = key_type() */ - for (size_type i = 0; i < store_.n_slot_; ++i) { - uint8_t c = store_.control_[i + c_control_stub]; - if (DArenaHashMapUtil::is_sentinel(c)) { - if (!(store_.slots_[i].first == key_type())) { - return policy.report_error(log, - c_self, ": expect empty/tombstone slot has default key", - xtag("i", i), - xtag("control[i+stub]", c)); - } - } - } - - return true; - } - } /*namespace map*/ -} /*namespace xo*/ - -/* end DArenaHashMap.hpp */ diff --git a/.xo-arena/include/xo/arena/DArenaIterator.hpp b/.xo-arena/include/xo/arena/DArenaIterator.hpp deleted file mode 100644 index cb277d6c..00000000 --- a/.xo-arena/include/xo/arena/DArenaIterator.hpp +++ /dev/null @@ -1,127 +0,0 @@ -/** @file DArenaIterator.hpp - * - * @author Roland Conybeare, Dec 2025 - **/ - -#pragma once - -#include "AllocInfo.hpp" -#include "AllocHeader.hpp" -#include "cmpresult.hpp" - -namespace xo { - namespace mm { - struct DArena; - - /** @class DArenaIterator - * @brief Representation for alloc iterator over arena - * - * Map showing an arena allocation: - * - * @verbatim - * - * <-------------z1---------------> - * < guard >< hz >< req_z >< dz >< guard > - * - * +++++++++0000zzzz@@@@@@@@@@@@@@@@@ppppppp+++++++++ - * - * ^ ^ ^ - * header mem header - * ^ (next alloc) - * DArenaIterator::pos_ - * - * guard [+] guard before+after each allocation, for simple sanitize checks - * header [0] alloc header (non-size bits) - * [z] alloc header (size bits) - * mem [@] app-requested memory, including padding [p] - * dz [p] padding (to uintptr_t alignment. req_z+dz recorded in header) - * free_ DArena::free_ just after guard bytes for last allocation - * - * @endverbatim - **/ - struct DArenaIterator { - /** @defgroup mm-arenaiterator-ctors DArenaIterator instance vars **/ - ///@{ - DArenaIterator() = default; - DArenaIterator(const DArena * arena, - AllocHeader * pos) : arena_{arena}, - pos_{pos} {} - - /** Create iterator in invalid state **/ - static DArenaIterator invalid() { return DArenaIterator(); } - - /** Create iterator pointing to the beginning of @p arena - * Iterator cannot modify memory, but can capture - * an iterator error in @p *arena - **/ - static DArenaIterator begin(const DArena * arena); - /** Create iterator pointing to the end of @p arena - * Iterator cannot modify memory, but can capture - * an iterator error in @p *arena - **/ - static DArenaIterator end(const DArena * arena); - ///@} - - /** @defgroup mm-arenaiterator-methods DArenaIterator methods **/ - ///@{ - /** Address of allocation header for beginning of alloc range in @p arena **/ - static AllocHeader * begin_header(const DArena * arena); - /** Address of allocation header for end of alloc range. - * This is the address of header for _next_ allocation in @p arena - * i.e. free pointer - **/ - static AllocHeader * end_header(const DArena * arena); - - /** A valid iterator can be compared, at least with itself - * It can be dereferenced if is also non-empty - **/ - bool is_valid() const noexcept { return (arena_ != nullptr) && (pos_ != nullptr); } - /** An invalid (or sentinel) iterator is incomparable with all - * iterators including itself - **/ - bool is_invalid() const noexcept { return !is_valid(); } - - /** fetch contents at current iterator position **/ - AllocInfo deref() const noexcept; - /** compare two iterators. To be comparable, - * iterators must refer to the same arena - **/ - cmpresult compare(const DArenaIterator & other) const noexcept; - /** advance iterator to next allocation **/ - void next() noexcept; - - /** cast iterator position to byte* */ - std::byte * pos_as_byte() const { return (std::byte *)pos_; } - - /** *ix synonym for ix.deref() **/ - AllocInfo operator*() const noexcept { return this->deref(); } - /** ++ix synonym for ix.next() **/ - DArenaIterator & operator++() noexcept { this->next(); return *this; } - ///@} - - /** @defgroup mm-arenaiterator-instance-vars **/ - ///@{ - /** iterator visits allocations from this arena **/ - const DArena * arena_ = nullptr; - /** current iterator position **/ - AllocHeader * pos_ = nullptr; - ///@} - }; - - inline bool - operator==(const DArenaIterator & x, - const DArenaIterator & y) - { - return x.compare(y).is_equal(); - } - - inline bool - operator!=(const DArenaIterator & x, - const DArenaIterator & y) - { - return !x.compare(y).is_equal(); - } - } /*namespace mm*/ -} /*namespace xo*/ - -/* end DArenaIterator.hpp */ diff --git a/.xo-arena/include/xo/arena/DArenaVector.hpp b/.xo-arena/include/xo/arena/DArenaVector.hpp deleted file mode 100644 index 7b017268..00000000 --- a/.xo-arena/include/xo/arena/DArenaVector.hpp +++ /dev/null @@ -1,358 +0,0 @@ -/** @file DArenaVector.hpp - * - * @author Roland Conybeare, Jan 2026 - **/ - -#pragma once - -#include "DArena.hpp" -#include -#include // for ::memset() - -namespace xo { - namespace mm { - /** @brief vector of T using dedicated DArena for storage - * - * Replicate (to the extent feasible) std::vector - * behavior, but using a dedicated DArena to provide storage - * - * Unlike std::vector: - * 1. does not support copying - * 2. capacity fixed at construction time - * - * @tparam T element type. Must be Erasable - **/ - template - struct DArenaVector { - public: - using value_type = T; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using reference = value_type &; - using const_reference = const value_type &; - using iterator = value_type *; - using const_iterator = const value_type *; - - /** null ctor **/ - DArenaVector() = default; - /** ctor from already-mapped (but not committed) address range_type - * vector has size zero - **/ - DArenaVector(const ArenaConfig & cfg, - size_type page_z, - size_type arena_align_z, - DArena::value_type lo, - DArena::value_type hi); - /** not intended to be copyable **/ - DArenaVector(const DArenaVector &) = delete; - /** move ctor **/ - DArenaVector(DArenaVector && other); - /** releases mapped memory **/ - ~DArenaVector(); - - /** create empty vector using @p cfg to configure backing store **/ - static DArenaVector map(const ArenaConfig & cfg); - - /** true iff vector is emtpy **/ - bool empty() const { return size_ == 0; } - size_type size() const { return size_; } - size_type max_size() const { return capacity(); } - size_type capacity() const { return store_.reserved() / sizeof(T); } - - /** get reference to element at zero-based index @p i. Do not check bounds **/ - T & operator[](size_t i) noexcept { return *(this->_address_of(i)); } - const T & operator[](size_t i) const noexcept { return *(this->_address_of(i)); } - - /** get reference to element at zero-based index @p i. Do check bounds **/ - T & at(size_type i) { _check_valid_index(i); return *(this->_address_of(i)); } - const T & at(size_type i) const { _check_valid_index(i); return *(this->_address_of(i)); } - - /** get to at first element of vector. Same as @p end if vector is empty **/ - iterator begin() noexcept { return this->_address_of(0); } - /** get iterator to end of vector - "one past the last element" **/ - iterator end() noexcept { return this->_address_of(size_); } - const_iterator cbegin() const noexcept { return this->_address_of(0); } - const_iterator begin() const noexcept { return this->cbegin(); } - const_iterator cend() const noexcept { return this->_address_of(size_); } - const_iterator end() const noexcept { return this->cend(); } - - constexpr const DArena * store() const { return &store_; } - constexpr T * data() { return reinterpret_cast(store_.lo_); } - constexpr const T * data() const { return reinterpret_cast(store_.lo_); } - - /** arena used for element storage - * (Might prefer obj here; refrain to avoid leveling violation) - **/ - void visit_pools(const MemorySizeVisitor & fn) const { store_.visit_pools(fn); } - - /** reserve space, if possible, for at least @p z elements. - * Always limited by ArenaConfig.size_ - **/ - void reserve(size_type z); - /** resize to size @p z. Return true on success. May fail iff oom. **/ - bool resize(size_type z); - void shrink_to_fit(); - /** reset vector to empty state **/ - void clear(); - - T & insert(size_type pos, T && x); - T & insert(size_type pos, const T & x); - - void erase(size_type pos); - - void push_back(T && x); - void push_back(const T & x); - - void swap(DArenaVector & other) noexcept; - - DArenaVector & operator=(DArenaVector && x) noexcept; - - private: - T * _address_of(size_type i) { return ((T *)store_.lo_) + i; } - const T * _address_of(size_type i) const { return ((const T *)store_.lo_) + i; } - - void _check_valid_index(size_type i) const; - - private: - size_type size_ = 0; - DArena store_; - DArena::Checkpoint zero_ckp_; - }; - - template - DArenaVector::DArenaVector(const ArenaConfig & cfg, - size_type page_z, - size_type arena_align_z, - DArena::value_type lo, - DArena::value_type hi) - : store_{cfg, page_z, arena_align_z, lo, hi}, - zero_ckp_{store_.checkpoint()} - {} - - template - DArenaVector::DArenaVector(DArenaVector && other) - : size_{other.size_}, store_{std::move(other.store_)}, zero_ckp_{std::move(other.zero_ckp_)} - { - other.size_ = 0; - other.zero_ckp_ = DArena::Checkpoint(); - } - - template - DArenaVector::~DArenaVector() - { - if constexpr (std::is_trivially_destructible_v) { - // nothing to do - } else { - // invoke destructor for each element - for (size_type i = 0, n = size(); i < n; ++i) { - T & x = (*this)[i]; - - x.~T(); - } - } - } - - template - DArenaVector & - DArenaVector::operator=(DArenaVector && other) noexcept - { - this->size_ = other.size_; - this->store_ = std::move(other.store_); - this->zero_ckp_ = std::move(other.zero_ckp_); - - other.size_ = 0; - other.zero_ckp_ = DArena::Checkpoint(); - - return *this; - } - - template - DArenaVector - DArenaVector::map(const ArenaConfig & cfg) - { - DArenaVector retval; - - retval.store_ = std::move(DArena::map(cfg)); - retval.zero_ckp_ = retval.store_.checkpoint(); - - return retval; - } - - template - void - DArenaVector::reserve(size_type z) { - store_.expand(z * sizeof(T), __PRETTY_FUNCTION__); - } - - template - bool - DArenaVector::resize(size_type z) { - // new arena size in bytes - size_t req_z = z * sizeof(T); - - if (z > size_) { - // expand arena to accomodate - - if (!store_.expand(req_z, __PRETTY_FUNCTION__)) - return false; - - // run ctors - if constexpr (std::is_trivially_constructible_v) { - ::memset(this->_address_of(size_), 0, req_z - (size_ * sizeof(T))); - } else { - for (size_type i = size_; i < z; ++i) { - void * addr = &(*this)[i]; - - new (addr) T(); - } - } - } else { - if constexpr (std::is_trivially_destructible_v) { - // nothing to do - } else { - // invoke destructor for each discarded element - for (size_type i = z; i < size_; ++i) { - T & x = (*this)[i]; - - x.~T(); - } - } - } - - // rewind to checkpoint, then reallocate. - // This is for form's sake, so that DArena considers memory - // to be 'allocated'. DArenaVector doesn't care for itself, - // but this preserves expected behavior of visit_pools(). - // - store_.restore(zero_ckp_); - store_.alloc(xo::reflect::typeseq::id(), req_z); - - this->size_ = z; - - return true; - } - - template - void - DArenaVector::shrink_to_fit() { - // could in principle release unused mapped pages here - } - - template - void - DArenaVector::clear() { - this->resize(0); - } - - template - void - DArenaVector::_check_valid_index(size_type i) const { - if (size_ <= i) - throw std::out_of_range("DArenaVector index out of bounds"); - } - - template - T & - DArenaVector::insert(size_type pos, T && x) { - { - size_type new_z = size_ + 1; - size_type req_z = new_z * sizeof(T); - - store_.expand(req_z, __PRETTY_FUNCTION__); - } - - // move elements [i .. z-1] right by one position. - // must proceed in reverse order! - for (size_type ip1 = size_; ip1 > pos; --ip1) { - (*this)[ip1] = std::move((*this)[ip1-1]); - } - - T * addr = this->_address_of(pos); - - new (addr) T{std::move(x)}; - - this->size_ = size_ + 1; - - return *addr; - } - - template - T & - DArenaVector::insert(size_type pos, const T & x) { - { - size_type new_z = size_ + 1; - size_type req_z = new_z * sizeof(T); - - store_.expand(req_z, __PRETTY_FUNCTION__); - } - - // move elements [i .. z-1] right by one position. - // must proceed in reverse order! - for (size_type ip1 = size_; ip1 > pos; --ip1) { - (*this)[ip1] = std::move((*this)[ip1-1]); - } - - T * addr = this->_address_of(pos); - - new (addr) T{x}; - - this->size_ = size_ + 1; - - return *addr; - } - - template - void - DArenaVector::erase(size_type pos) { - // move elements [pos+1 .. z-1] left by one position. - - if (pos >= size_) [[unlikely]] - return; - - for (size_type i = pos; i+1 < size_; ++i) { - (*this)[i] = std::move((*this)[i+1]); - } - - --(this->size_); - } - - template - void - DArenaVector::push_back(T && x) { - size_type z = size_ + 1; - size_type req_z = z * sizeof(T); - - if (this->store_.expand(req_z, __PRETTY_FUNCTION__)) { - T * addr = this->_address_of(size_); - - new (addr) T{std::move(x)}; - - this->size_ = z; - } - } - - template - void - DArenaVector::push_back(const T & x) { - size_type z = size_ + 1; - - if (this->store_.expand(z * sizeof(T), __PRETTY_FUNCTION__)) { - T * addr = this->_address_of(size_); - - new (addr) T{x}; - - this->size_ = z; - } - } - - template - void - DArenaVector::swap(DArenaVector & other) noexcept { - std::swap(size_, other.size_); - std::swap(store_, other.store_); - } - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end DArenaVector.hpp */ diff --git a/.xo-arena/include/xo/arena/DCircularBuffer.hpp b/.xo-arena/include/xo/arena/DCircularBuffer.hpp deleted file mode 100644 index ba744531..00000000 --- a/.xo-arena/include/xo/arena/DCircularBuffer.hpp +++ /dev/null @@ -1,262 +0,0 @@ -/** @file DCircularBuffer.hpp -* - * @author Roland Conybeare, Jan 2026 - **/ - -#include "CircularBufferConfig.hpp" -#include "DArenaVector.hpp" -#include "hashmap/verify_policy.hpp" -#include "span.hpp" -#include - -namespace xo { - namespace mm { - /** @class DCircularBuffer - * - * @brief high performance vm-aware circular buffer - * - * Circular buffer implementation with parsing-friendly performance features. - * - generalization of DArena. - * Like DArena, maps superpages as needed. - * Unlike DArena memory at the beginning of reserved range can be unmapped. - * - allows address range >> physical range - * - admits "Cheney on the MTA" strategy. - * May be feasible to reserve a lifetime address range (say 1TB) - * as long as buffer only every maps a subrange that fits in physical memory. - * - zero copy support for parsing / protocol trnaslation: - * provides capture/release semantics for a fixed number - * of remembered spans. Will never unmap memory for a remembered span, - * until that span is released. - * - automatically resets to beginning of reserved range - * whenever occupied range is empty - **/ - struct DCircularBuffer { - public: - /** @defgroup mm-circularbuffer-types CircularBuffer type traits **/ - ///@{ - - /** an amount of memory **/ - using size_type = std::size_t; - using byte = std::byte; - /** a contiguous addres range **/ - using span_type = span; - using const_span_type = span; - - ///@} - - public: - /** @defgroup mm-cicrularbuffer-ctors CircularBuffer constructors **/ - ///@{ - - /** contruct instance - * @p config circular buffer configuration - * @p page_z o/s page size (via getpagesize()) - * @p buffer_align_z alignment for buffer memory - * @p reserved_range reserved virtual address range - **/ - DCircularBuffer(const CircularBufferConfig & config, - size_type page_z, - size_type buffer_align_z, - span_type reserved_range); -#ifdef NOT_YET - /** constructor */ - DCircularBuffer(const CircularBufferConfig & config); -#endif - /** non-copyable **/ - DCircularBuffer(const DCircularBuffer & other) = delete; - /** move ctor **/ - DCircularBuffer(DCircularBuffer && other); - - /** - * allocate virtual memory address (uncommitted!) for circular buffer - * with configuration @p config. - **/ - static DCircularBuffer map(const CircularBufferConfig & config); - - ///@} - - /** @defgroup mm-circularbuffer-const-methods CircularBuffer const methods **/ - ///@{ - - const_span_type reserved_range() const noexcept { return reserved_range_; } - const_span_type mapped_range() const noexcept { return mapped_range_; } - const_span_type occupied_range() const noexcept { return occupied_range_; } - const_span_type input_range() const noexcept { return input_range_; } - - /** report memory-size info for this buffer to @p fn **/ - void visit_pools(const MemorySizeVisitor & fn) const; - - /** verify DCircularBuffer invariants. - * Act on failure according to policy @p p - * (combination of throw|log bits) - * - * verify invariants: - * CB1: mapped_range_ is subrange of reserved_range_ - * CB2: occupied_range_ is subrange of mapped_range_ - * CB3: each remembered_spans_[i] is subrange of occupied_range_ - * CB4: buffer_align_z_ > 0 when buffer is mapped - * CB5: reserved_range_.lo() aligned on buffer_align_z_ boundary - **/ - bool verify_ok(verify_policy p = verify_policy::throw_only()) const; - - ///@} - - /** @defgroup mm-circularbuffer-nonconst-methods CircularBuffer non-const methods **/ - ///@{ - - span_type input_range() noexcept { return input_range_; } - - /** copy memory in span @p r into buffer starting at the end of - * @ref occupied_range_. Map new physical memory as needed. - * On success returns empty suffix of @p r. - * If buffer memory exhausted, may copy a prefix of @p r. - * In that case returns the remaining suffix of @p r. - **/ - const_span_type append(const_span_type r); - - /** DMA version of @ref append_span : get mapped span A at which - * buffer will receive new content. Upstream may write into - * A. It must then coordinate with buffer by calling - * @ref report_append(P) for some prefix P of A - * - * Example: - * @code - * CircularBuffer buf = ...; - * constexpr size_type z = 64*1024; - * auto span = buf.get_append_span(z); - * ssize_t nr = read(FD, span.lo(), span.size()); - * if (nr > 0) - * buf.report_append(span.prefix(nr)); - * @endcode - **/ - span_type get_append_span(size_type desired_z); - - /** update bookkeeping as if caller had invoked append(r); - * however caller has already written to mapped memory - * after using get_append_span(); so omit copy - **/ - void report_append(span_type r); - - /** consume span (or prefix thereof) previously obtained from @ref occupied_range() - * Caller represents that it won't need to read this memory again - * unless overlaps with a pinned span. - **/ - void consume(const_span_type input); - - /** pin memory range @p r. circular buffer will not touch - * addresses that appear in any pinned range. - * use to - **/ - void pin_range(span_type r); - - /** unwind a previous pin_range call on range @p r. - * both start and end or @p r should exactly match a pinned range. - **/ - void unpin_range(span_type r); - - ///@} - - private: - - /** @defgroup mm-circularbuffer-private-methods CircularBuffer non-const methods **/ - ///@{ - - /** expand hi end of mapped memory range to at least @p hi. - * - * Require: @p hi < @ref reserved_range_.hi - **/ - bool _expand_to(char * hi); - - /** shrink occupied rnage to the smallest contiguous range that contains both: - * all of .input_range_, and all pinned ranges in .pinned_spans_ - **/ - void _shrink_occupied_to_fit(); - - /** check for edge condition in which there are no pinned ranges. **/ - void _check_reset_map_start(); - - ///@} - - private: - /** @defgroup mm-circularbuffer-instance-vars CircularBuffer member variables **/ - ///@{ - - /* memory layout - * - * reserved_range_ : entire address range owned by buffer (may be huge, e.g., 1TB) - * mapped_range_ : subrange backed by physical memory (fits in RAM) - * occupied_range_ : subrange currently containing data - * input_range_ : subrange containing unread input - * pinned_spans_ : pinned subranges within occupied (prevents alteration or unmap) - * - * <------------------- .reserved_range ---------------------> - * . <------------- .mapped_range -------------> . - * . . <----- .occupied_range -----> . . - * . . . <- .input_range -----> . . - * . . . . . . . - * ........------XXXXXXXIIIIIIIIIIIIIIIIIIIIII--------........ - * pp ppp pp - * Legend: - * [.] reserved : uncommitted memory. may be huge (e.g. 1TB) - * [-] mapped : range backed by physical memory - * [X] consumed : preserved until last overlapping pin removed - * [I] input : unread content, waiting to be read - * [p] pinned : pinned memory will not be altered (let alone unmapped) - * - * Invariants: - * - .input_range <= .occupied_range <= .mapped_range <= .reserved_range - * - mapped_range_ cannot shrink to exclude any portion of a pinned span - */ - - /** buffer configuration **/ - CircularBufferConfig config_; - - /** size of a VM page (obtained automatically via getpagesize()). 4k on ubuntu. 16k on osx **/ - size_type page_z_; - - /** alignment for buffer address range. - * In practice will be either page_z_ or config_.hugepage_z_ - **/ - size_type buffer_align_z_; - - /** Circular buffer owns address range defined by this span. - * Aligned on @ref buffer_align_z_. - * Always a whole number of @ref page_z_ or @ref config_.hugepage_z_ - **/ - span_type reserved_range_; - - /** buffer owns memory defined by this span. - * Always a subrange of reserved_range - * These addresses backed by physical memory. - * Always a whole number of @ref page_z_ or @ref config_.hugepage_z_ - **/ - span_type mapped_range_; - - /** currently occupied buffer memory. - * Always a subrange of @ref mapped_range_ - **/ - span_type occupied_range_; - - /** portion of occupied buffer memory waiting to be read. - * Always represents a subspan of @ref occupied_range_, with the same - * hi endpoint. - * conversely @ref consume shrinks @ref input_range_ by increasing its lo endpoint. - **/ - span_type input_range_; - - /** remembered spans. For anticipated use cases expect one vm page sufficient. - * Spans in this vector always represent subranges of @ref occupied_range_ - * - * @ref pinned_spans_ is confined to @ref occupied_range_. - * (In particular it's *not* confined to @ref input_range_) - * - * sorted on increasing span.lo() - **/ - DArenaVector pinned_spans_; - - ///@} - }; - } -} /*namespace xo*/ - -/* end DCircularBuffer.hpp */ diff --git a/.xo-arena/include/xo/arena/ErrorArena.hpp b/.xo-arena/include/xo/arena/ErrorArena.hpp deleted file mode 100644 index 0236c325..00000000 --- a/.xo-arena/include/xo/arena/ErrorArena.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/** @file ErrorArena.hpp -* - * @author Roland Conybeare, Feb 2026 - **/ - -#pragma once - -#include "DArena.hpp" - -namespace xo { - namespace mm { - - /** @brief Dedicated arena for error reporting - * - * Reserving memory for error messaages. - * Motivation - * 1. so we have room to report an out-of-memory condition - * 2. so we have place to allocate for an error that - * doesn't interfere with other allocator state - * - * Expect to reset arena between errors, so only need - * enough room to report one error. - * - * To initialize explicitly: - * @code - * // before any other ErrorArena method calls: - * ErrorArena::init_once(cfg...); - * - * // do stuff with ErrorArena.. - * ErrorArena::instance() - * @endcode - * - * Reminder: can't use obj here, - * would be leveling violation. - **/ - class ErrorArena { - public: - /** default configuration for error arena **/ - static ArenaConfig default_config(); - - /** idempotent initialization **/ - static void init_once(const ArenaConfig & cfg = default_config()); - - /** get initialized instnace **/ - static DArena * instance(); - - private: - static DArena s_instance; - }; - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end ErrorArena.hpp */ diff --git a/.xo-arena/include/xo/arena/MemorySizeInfo.hpp b/.xo-arena/include/xo/arena/MemorySizeInfo.hpp deleted file mode 100644 index c626201c..00000000 --- a/.xo-arena/include/xo/arena/MemorySizeInfo.hpp +++ /dev/null @@ -1,71 +0,0 @@ -/** @file MemorySizeInfo.hpp -* - * @author Roland Conybeare, Feb 2026 - **/ - -#pragma once - -#include -#include -#include -#include - -namespace xo { - namespace mm { - - struct MemorySizeDetail { - using typeseq = xo::reflect::typeseq; - - /** identifies a c++ type T. See xo/facet/TypeRegistry **/ - typeseq tseq_; - /** number of T-instances **/ - uint32_t n_alloc_ = 0; - /** bytes used by T-instances **/ - uint32_t z_alloc_ = 0; - }; - - struct MemorySizeInfo { - using size_type = std::size_t; - using DetailArrayType = std::array; - - MemorySizeInfo() = default; - MemorySizeInfo(std::string_view name, - std::size_t u, std::size_t a, std::size_t c, std::size_t r, - const void * lo, const void * hi, - DetailArrayType * detail) - : resource_name_{name}, - used_{u}, allocated_{a}, committed_{c}, reserved_{r}, lo_{lo}, hi_{hi}, detail_{detail} - {} - - static MemorySizeInfo sentinel() { return MemorySizeInfo(); } - - /** resource name **/ - std::string_view resource_name_; - /** memory used (excluding wasted space) **/ - std::size_t used_ = 0; - /** memory allocated (including wasted space e.g. empty slots in hash tables **/ - std::size_t allocated_ = 0; - /** memory committed (backed by physical memory) **/ - std::size_t committed_ = 0; - /** memory reserved: - * virtual memory addresses range obtained, whether or not committed - **/ - std::size_t reserved_ = 0; - /** start address (optional) **/ - const void * lo_ = 0; - /** end address (optional) **/ - const void * hi_ = 0; - - /** optional histogram with per-data-type counts **/ - DetailArrayType * detail_ = nullptr; - }; - - /** function that visits MemorySizeInfo for a collection of @p n memory pools. - * Each pool reported with index @p i in [0, n), with associated - * size record @p info. - **/ - using MemorySizeVisitor = std::function; - } -} - -/* end MemorySizeInfo.hpp */ diff --git a/.xo-arena/include/xo/arena/arena_streambuf.hpp b/.xo-arena/include/xo/arena/arena_streambuf.hpp deleted file mode 100644 index 2b364622..00000000 --- a/.xo-arena/include/xo/arena/arena_streambuf.hpp +++ /dev/null @@ -1,236 +0,0 @@ -/** @file arena_streambuf.hpp -* - * @author Roland Conybeare, Feb 2026 - **/ - -#pragma once - -#include "DArena.hpp" -//#include "print/quoted_char.hpp" -#include -#include -#include -#include // e.g. for std::memcpy() -#include -#include - -namespace xo { - namespace mm { - /** @brief Arena-based buffer for logging and pretty-printing - * - * Arena-based using mmap - * Write to self-extending storage array - * Track position relative to start of line - **/ - class arena_streambuf : public std::streambuf { - public: - struct rewind_state { - explicit rewind_state(std::size_t solpos, std::size_t color_esc, std::uint32_t p) - : solpos{solpos}, color_escape_chars{color_esc}, pos{p} {} - - std::size_t solpos = 0; - std::size_t color_escape_chars = 0; - std::uint32_t pos = 0; - }; - - public: - /** arena should be ready-to-allocate i.e. have committed > 0 **/ - arena_streambuf(DArena * arena, bool debug_flag = false) : arena_{arena}, debug_flag_{debug_flag} { - this->reset_stream(); - } /*ctor*/ - - std::streamsize capacity() const { return arena_->committed(); } - const char * lo() const { return this->pbase(); } - const char * hi() const { return this->lo() + this->capacity(); } - std::uint32_t pos() const { return this->pptr() - this->pbase(); } - - /** output position (relative to pbase) when local state last computed. Exposed here for unit tests **/ - std::size_t _local_ppos() const { return local_ppos_; } - /** position (relative to pbase) one character after last \n or \r. For unit tests **/ - std::uint32_t _solpos() const { return solpos_; } - /** start of incomplete color-escape sequence **/ - const char * _color_escape_start() const { return color_escape_start_; } - /** number of non-printing chars after @ref solpos_ from completed color-escape sequences **/ - std::uint32_t _color_escape_chars() const { return color_escape_chars_; } - - /** number of visible characters since start of line (last \n or \r) **/ - std::uint32_t lpos() const; - - rewind_state checkpoint() const; - - bool debug_flag() const { return debug_flag_; } - - operator std::string_view () const { return std::string_view(this->pbase(), this->pptr()); } - - void reset_stream(); - - void rewind_to(rewind_state s); - - protected: - /** expand buffer storage (by 2x), preserve current contents **/ - void expand_to(std::size_t new_z); - - virtual std::streamsize xsputn(const char * s, std::streamsize n) override; - - virtual int_type overflow(int_type new_ch) override; - - /* off. offset, relative to starting point dir. - * dir. - * which. in|out|both - * - * Note that off=0,dir=cur,which=out reads offset - */ - virtual pos_type seekoff(off_type off, - std::ios_base::seekdir dir, - std::ios_base::openmode which) override; - - private: - void _update_local_state_char(const char * p_lo, const char * p) - { - if ((*p == '\n') || (*p == '\r')) { - this->solpos_ = (p+1 - this->pbase()); - /* reset, since these chars relevant as correction to solpos */ - this->color_escape_chars_ = 0; - /* -> incomplete color escape, broken by newline */ - this->color_escape_start_ = nullptr; - } else if (*p == '\033') { - if (debug_flag_) [[unlikely]] { - std::cout << "xsputn: \\033 at p-p_lo=" << (p - p_lo) << std::endl; - } - this->color_escape_start_ = p; - } else if (this->color_escape_start_ != nullptr) { - if (*p == 'm') { - /* escape seq non-printing including both endpoints */ - std::int64_t esc_chars = (p+1 - color_escape_start_); - - this->color_escape_chars_ += esc_chars; - - if (debug_flag_) [[unlikely]] { - std::cout << "xsputn: m at p-p_lo" << (p - p_lo) << " +" << esc_chars - << " -> color_escape_chars=" << color_escape_chars_ << std::endl; - } - this->color_escape_start_ = nullptr; - } else if (!isdigit(*p) && (*p != '[') && (*p != ';')) { - /* not color escape after all */ - this->color_escape_start_ = nullptr; - } - } - } - - /** recognize stale local state vars: - * @ref solpos_, @ref color_escape_chars_, @ref color_escape_start_. - * - * Require: - * - {pbase, pptr} in consistent state - * Promise: - * - @c local_ppos_ + @c pbase = @c pptr - * - @c solpos_, @c color_escape_chars_, @c color_escape_start_ all up-to-date - **/ - void _check_update_local_state() { - const char * p0 = this->pbase(); - const char * pn = this->pptr(); - - if (debug_flag_) { - std::cerr << "_check_update_local_state:" << std::endl; - std::cerr << " buf: (p0=" << (void*)p0 << ", pn=" << (void*)pn << ")" << std::endl; - std::cerr << " solpos_=" << solpos_ << ", color_escape_chars_=" << color_escape_chars_ << std::endl; - } - - if (p0 + local_ppos_ == pn) [[likely]] { - // solpos_, color_escape_chars_, color_escape_start_ all up-to-date - } else { - // [pnew, pn): input that hasn't been incorporated into - // {solpos_, color_escape_chars_, color_escape_start_) - - const char * pnew = this->pbase() + this->local_ppos_; - - if (debug_flag_) { - std::cerr << "_check_update_local_state: range: (pnew=" << (void*)pnew << ", pn=" << (void*)pn << ")" << std::endl; - } - - for(const char * p = pnew; p < pn; ++p) { - this->_update_local_state_char(p0, p); - } - } - - // solpos_, color_escape_chars_, color_escape_start_ all up-to-date - // for current buffered contents - - this->local_ppos_ = pn - p0; - - if (debug_flag_) { - std::cerr << "_check_update_local_state: pos=" << pos(); - std::cerr << ", solpos=" << solpos_; - std::cerr << ", color_escape_chars=" << color_escape_chars_ << std::endl; - } - - assert(pos() >= solpos_ + color_escape_chars_); - } - - private: - /* - * pbase: start of buffered text. Thils will be arena_->lo_ - * - * - * pbase pptr epptr - * v >e1< >e2< v v - * |xx\xxEEExxx\xxxxxxxEExxxxEExxxxxxxEExxx\xEExxxxxx..................| - * ^ ^<------new-------> - * solpos local_ppos - * - * solpos : first character after newline (stale) - * color_escape_pos : e1+e2+.. (stale) - * new : new characters not reflected - * in local_ppos_, color_escape_chars_ etc. - * - * Legend: - * [\] newline - * [x] visible character - * [E] color escape chars - * - * - * after _check_update_local_state(): - * - * - * pbase pptr epptr - * v >e1< v v - * |xx\xxEEExxx\xxxxxxxEExxxxEExxxxxxxEExxx\xEExxxxxx..................| - * ^ ^ - * solpos local_ppos - * - */ - - /** @defgroup logstreambuf-instance-vars **/ - ///@{ - - /** value of pptr (relative to pbase) when _check_update_local_state() last ran **/ - std::size_t local_ppos_ = 0; - /** position (relative to pbase) one character after last \n or \r. - * Use to drive @ref lpos. This _has_ to be lazy, since - * xsputn() isn'g guaranteed to be called when there's room in - * in buffer. - **/ - std::size_t solpos_ = 0; - /** number of non-printing chars after @ref solpos_, from - * completed color escape sequences. - * (ansi color escapes = text between '\033' and 'm') - **/ - std::size_t color_escape_chars_ = 0; - /** non-null: start of incomplete color escape sequence **/ - const char * color_escape_start_ = nullptr; - - /** buffered output stored here. - * We don't use arena's allocation api, just treat as a block of available memory - **/ - DArena * arena_ = nullptr;; - /** true to debug log_streambuf itself **/ - bool debug_flag_ = false; - - ///@} - }; /*log_streambuf*/ - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end arena_streambuf.hpp */ - diff --git a/.xo-arena/include/xo/arena/backtrace.hpp b/.xo-arena/include/xo/arena/backtrace.hpp deleted file mode 100644 index d29bcb60..00000000 --- a/.xo-arena/include/xo/arena/backtrace.hpp +++ /dev/null @@ -1,13 +0,0 @@ -/** @file backtrace.hpp - * - * @author Roland Conybeare, Apr 2026 - **/ - -#pragma once - -namespace xo { - void print_backtrace(bool demangle_flag); - void print_backtrace_dwarf(bool demangle_flag); -} - -/* end backtrace.hpp */ diff --git a/.xo-arena/include/xo/arena/cmpresult.hpp b/.xo-arena/include/xo/arena/cmpresult.hpp deleted file mode 100644 index bf09af57..00000000 --- a/.xo-arena/include/xo/arena/cmpresult.hpp +++ /dev/null @@ -1,87 +0,0 @@ -/** @file cmpresult.hpp -* - * @author Roland Conybeare, Dec 2025 - **/ - -#pragma once - -#include -#include - -namespace xo { - namespace mm { - enum class comparison : int32_t { - invalid = -1, - comparable = 0, - incomparable = +1, - }; - - extern const char * comparison2str(comparison x); - - inline std::ostream & - operator<<(std::ostream & os, comparison x) { - os << comparison2str(x); - return os; - } - - /** @brief result of a generic comparison operation - **/ - struct cmpresult { - /** @defgroup mm-cmpresult-ctors cmpresult ctors **/ - ///@{ - cmpresult() : err_{comparison::invalid}, cmp_{0} {} - cmpresult(comparison err, std::int16_t cmp) : err_{err}, cmp_{cmp} {} - - static cmpresult incomparable() { return cmpresult(comparison::incomparable, 0); } - static cmpresult lesser() { return cmpresult(comparison::comparable, -1); } - static cmpresult equal() { return cmpresult(comparison::comparable, 0); } - static cmpresult greater() { return cmpresult(comparison::comparable, +1); } - template - static cmpresult from_cmp(T && x, T && y) { - if (x < y) - return cmpresult::lesser(); - else if (x == y) - return cmpresult::equal(); - else - return cmpresult::greater(); - } - - ///@} - - /** @defgroup mm-cmpresult-methods cmpresult methods **/ - ///@{ - - /** print to stream **/ - void display(std::ostream & os) const; - - bool is_lesser() const { - return (err_ == comparison::comparable) && (cmp_ < 0); - } - bool is_equal() const { - return (err_ == comparison::comparable) && (cmp_ == 0); - } - ///@} - - /** @defgroup mm-cmpresult-instance-vars cmpresult instance vars **/ - ///@{ - /** -1 -> invalid (sentinel) - * 0 -> comparable - * +1 -> incomparable (e.g. iterators from different arenas) - **/ - comparison err_ = comparison::invalid; - /** <0 -> lesser; 0 -> equal, >0 -> greater **/ - std::int16_t cmp_ = 0; - ///@} - }; - - inline std::ostream & operator<<(std::ostream & os, - const cmpresult & x) - { - x.display(os); - return os; - } - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end cmpresult.hpp */ diff --git a/.xo-arena/include/xo/arena/hashmap/ControlGroup.hpp b/.xo-arena/include/xo/arena/hashmap/ControlGroup.hpp deleted file mode 100644 index 744ddb5b..00000000 --- a/.xo-arena/include/xo/arena/hashmap/ControlGroup.hpp +++ /dev/null @@ -1,83 +0,0 @@ -/** @file ControlGroupo - * - * @author Roland Conybeare, Jan 2026 - **/ - -#pragma once - -#include "DArenaHashMapUtil.hpp" -#include -#include -#include - -namespace xo { - namespace map { - namespace detail { - /** @brief 16x 8-bit control bytes. - * - * Support optimization using SIMD operations - **/ - struct ControlGroup { - std::array ctrl_; - - /** Require: lo is aligned on c_group_size (probably 16 bytes) **/ - explicit ControlGroup(const uint8_t * lo) { - ::memcpy(ctrl_.data(), lo, DArenaHashMapUtil::c_group_size); - } - - /** find all exact matches in ctrl_[0..15] for @p h2. - * for each match set corresponding bit in return value. - * Bits {0x1, 0x2, 0x4, ...} set iff exact match on - * {ctrl_[0], ctrl_[1], ctrl_2[], ...} respectively - **/ - uint16_t all_matches(uint8_t h2) const { - uint16_t retval = 0; - uint16_t bit = 1; - for (auto xi : ctrl_) { - if (xi == h2) - retval |= bit; - bit = bit << 1; - } - - return retval; - } - - /** find all empty sentinels in ctrl_[0..15]. - * for each empty, set corresponding bit in return value. - * Bits {0x1, 0x2, 0x4, ...} set iff empty spot - * {ctrl_[0], ctrl_[1], ctrl_[2], ...} respectively - **/ - uint16_t empty_matches() const { - uint16_t retval = 0; - uint16_t bit = 1; - - for (auto xi : ctrl_) { - if (xi == DArenaHashMapUtil::c_empty_slot) - retval |= bit; - bit = bit << 1; - } - - return retval; - } - -#ifdef NOT_YET - __m128i ctrl; // 16 bytes loaded via SSE2 - - // Find all slots matching h2 - uint16_t Match(uint8_t h2) const { - __m128i pattern = _mm_set1_epi8(h2); - __m128i result = _mm_cmpeq_epi8(ctrl, pattern); - return _mm_movemask_epi8(result); // 16-bit mask - } - - // Find all empty slots (0xFF) - uint16_t MatchEmpty() const { - return _mm_movemask_epi8(_mm_cmpeq_epi8(ctrl, _mm_set1_epi8(0xFF))); - } -#endif - }; - } - } /*namespace map*/ -} /*namespace xo*/ - -/* end ControlGroup.hpp */ diff --git a/.xo-arena/include/xo/arena/hashmap/DArenaHashMapIterator.hpp b/.xo-arena/include/xo/arena/hashmap/DArenaHashMapIterator.hpp deleted file mode 100644 index 77c450d7..00000000 --- a/.xo-arena/include/xo/arena/hashmap/DArenaHashMapIterator.hpp +++ /dev/null @@ -1,153 +0,0 @@ -/** @file DArenaHashMapIterator.hpp -* - * @author Roland Conybeare, Jan 2026 - **/ - -#pragma once - -#include - -namespace xo { - namespace map { - namespace detail { - - template - struct DArenaHashMapIterator : public DArenaHashMapUtil { - using value_type = std::pair; - - public: - DArenaHashMapIterator(uint8_t * c, value_type * p) - : ctrl_{c}, pos_{p} {} - - value_type & operator*() const { return *pos_; } - value_type * operator->() const { return pos_; } - - uint8_t * _ctrl() const { return ctrl_; } - value_type * _pos() const { return pos_; } - - /** true iff iterator at sentinel position (not dereferencable state !) **/ - bool _at_slot_sentinel() const { return is_sentinel(*ctrl_) && (*ctrl_ != c_iterator_bookend); } - - bool operator==(const DArenaHashMapIterator & x) const { - return this->pos_ == x.pos_; - } - - bool operator!=(const DArenaHashMapIterator & x) const { - return this->pos_ != x.pos_; - } - - DArenaHashMapIterator & operator++() { - do { - ++(this->ctrl_); - ++(this->pos_); - - /** end condition: iterator ends at last non-wrapped position. - * relyin on bookend sentinel values at known offset from 'wrap' section - * - * ctrl_ ctrl_ + c_group_size - * | | - * v v - * <----------------- control_size(n_slot) ----------------> - * <-stub-> <----------- n_slot ----------> <-stub-> - * +--------+-------------------------------+-------+--------+ - * | 0xF0 | empty / data / tombstone | wrap | 0xF0 | - * +--------+-------------------------------+-------+--------+ - **/ - } while (is_sentinel(*ctrl_) - && (*(ctrl_ + c_group_size) != c_iterator_bookend)); - - return *this; - } - - DArenaHashMapIterator & operator--() { - /* simpler than forward iteration, since bookend immediately - * precedes control byte for first slot - */ - do { - --(this->ctrl_); - --(this->pos_); - } while (is_sentinel(*ctrl_) - && (*ctrl_ != c_iterator_bookend)); - - return *this; - } - - private: - uint8_t * ctrl_ = nullptr; - value_type * pos_ = nullptr; - }; - - template - struct DArenaHashMapConstIterator : public DArenaHashMapUtil { - using value_type = std::pair; - - public: - DArenaHashMapConstIterator(const uint8_t * c, const value_type * p) - : ctrl_{c}, pos_{p} {} - - const value_type & operator*() const { return *pos_; } - const value_type * operator->() const { return pos_; } - - const uint8_t * _ctrl() const { return ctrl_; } - const value_type * _pos() const { return pos_; } - - /** true iff iterator at sentinel position (not dereferencable state !) **/ - bool _at_slot_sentinel() const { - return is_sentinel(*ctrl_) && (*ctrl_ != c_iterator_bookend); - } - - bool operator==(const DArenaHashMapConstIterator & x) const { - return this->pos_ == x.pos_; - } - - bool operator!=(const DArenaHashMapConstIterator & x) const { - return this->pos_ != x.pos_; - } - - DArenaHashMapConstIterator & operator++() { - do { - ++(this->ctrl_); - ++(this->pos_); - - /** end condition: iterator ends at last non-wrapped position. - * relyin on bookend sentinel values at known offset from 'wrap' section - * - * ctrl_ ctrl_ + c_group_size - * | | - * v v - * <----------------- control_size(n_slot) ----------------> - * <-stub-> <----------- n_slot ----------> <-stub-> - * +--------+-------------------------------+-------+--------+ - * | 0xF0 | empty / data / tombstone | wrap | 0xF0 | - * +--------+-------------------------------+-------+--------+ - **/ - } while (is_sentinel(*ctrl_) - && (*(ctrl_ + c_group_size) != c_iterator_bookend)); - - return *this; - } - - DArenaHashMapConstIterator & operator--() { - /* simpler than forward iteration, since bookend immediately - * precedes control byte for first slot - */ - do { - --(this->ctrl_); - --(this->pos_); - } while (is_sentinel(*ctrl_) - && (*ctrl_ != c_iterator_bookend)); - - return *this; - } - - private: - const uint8_t * ctrl_ = nullptr; - const value_type * pos_ = nullptr; - }; - } - } /*namespace map*/ -} /*namespace xo*/ - -/* end DArenaHashMapIterator.hpp */ diff --git a/.xo-arena/include/xo/arena/hashmap/DArenaHashMapUtil.hpp b/.xo-arena/include/xo/arena/hashmap/DArenaHashMapUtil.hpp deleted file mode 100644 index 372c99e0..00000000 --- a/.xo-arena/include/xo/arena/hashmap/DArenaHashMapUtil.hpp +++ /dev/null @@ -1,117 +0,0 @@ -/** @file DArenaHashMapUtil.hpp - * - * @author Roland Conybeare, Jan 2026 - **/ - -#pragma once - -#include - -namespace xo { - namespace map { - /** @class DArenaHashMapUtil - * - * @pre - * - * control - * - * <----------------- control_size(n_slot) ----------------> - * <-stub-> <----------- n_slot ----------> <-stub-> - * +--------+-------------------------------+-------+--------+ - * | 0xF0 | empty / data / tombstone | wrap | 0xF0 | - * +--------+-------------------------------+-------+--------+ - * ^ ^ - * | ... | control_[stub+i] <--> slots_[i] - * slots v v - * +-------------------------------+ - * | {k,v} pairs | - * +-------------------------------+ - * <--- n_slot key-value pairs --> - * - * sizes: - * - stub before+after bookends. c_control_stub bytes (16) - * - group c_group_size. power of 2 (16 bytes) - * - n_slot hash table slots. power of 2 multiple of c_group_size. - * - * control bytes: - * - 0b1xxxxxxx sentinel bitmask - * - 0xf0 sentinel for before/after stubs (iterator bookends) - * - 0xff sentinel for empty slot. - * - 0xfe sentinel for tombstone - * - 0b0xxxxxxx high bit clear; remainder hold low 7 bits of hash - * - wrap duplicate first c_group_size bytes (after front stub) - * for SIMD convenience - * - * @endpre - **/ - struct DArenaHashMapUtil { - using size_type = std::size_t; - using control_type = std::uint8_t; - - /** control: mask for sentinel states **/ - static constexpr uint8_t c_sentinel_mask = 0x80; - /** control: sentinel for empty slot **/ - static constexpr uint8_t c_empty_slot = 0xFF; - /** control: tombstone for deleted slot **/ - static constexpr uint8_t c_tombstone = 0xFE; - /** control: bookends around control array, - * for iterator edge support - **/ - static constexpr uint8_t c_iterator_bookend = 0xF0; - - /** group size **/ - static constexpr size_type c_group_size = 16; - - /** max load factor **/ - static constexpr float c_max_load_factor = 0.875; - - /** Iterator sentinel at begin/end of control array. - * Load-bearing for bidirectional iterator implementation - **/ - static constexpr size_type c_control_stub = c_group_size; //c_group_size; - - /** control: true for sentinel values **/ - static constexpr bool is_sentinel(control_type ctrl) { - return c_sentinel_mask == (ctrl & c_sentinel_mask); - } - - /** control; true for non-sentinel values **/ - static constexpr bool is_data(control_type ctrl) { - return 0 == (ctrl & c_sentinel_mask); - } - - /** control: compute size of control array for swiss hash map with @p n_slot cells **/ - static constexpr size_type control_size(size_type n_slot) { - if (n_slot == 0) - return 0; - - /* control: - * - c_group_size overflow slots - * - 2x c_control_stub begin/end sentinels - */ - - return n_slot + c_group_size + (2 * c_control_stub); - } - - /** find smallest multiple k : k * c_group_size >= n **/ - static size_type lub_group_mult(size_t n) { - return (n + c_group_size - 1) / c_group_size; - } - - /** find smallest x such that 2^x >= n. Return {x, 2^x} **/ - static std::pair lub_exp2(size_t n) { - size_type ngx = 0; - size_type ng = 1; - - while (ng < n) { - ++ngx; - ng *= 2; - } - - return std::make_pair(ngx, ng);; - } - }; - } /*namespace map*/ -} /*namespace xo*/ - -/* end DArenaHashMapUtil.hpp */ diff --git a/.xo-arena/include/xo/arena/hashmap/HashMapStore.hpp b/.xo-arena/include/xo/arena/hashmap/HashMapStore.hpp deleted file mode 100644 index 23b9d31f..00000000 --- a/.xo-arena/include/xo/arena/hashmap/HashMapStore.hpp +++ /dev/null @@ -1,163 +0,0 @@ -/** @file HashMapStore.hpp -* - * @author Roland Conybeare, Jan 2026 - **/ - -#pragma once - -#include -#include - -namespace xo { - namespace map { - namespace detail { - template - struct HashMapStore : DArenaHashMapUtil { - public: - using value_type = std::pair; - using group_type = detail::ControlGroup; - using control_vector_type = xo::mm::DArenaVector; - using slot_vector_type = xo::mm::DArenaVector; - using MemorySizeVisitor = xo::mm::MemorySizeVisitor; - using MemorySizeInfo = xo::mm::MemorySizeInfo; - - public: - /** group_exp2: number of groups {x, 2^x} **/ - explicit HashMapStore(const std::string & name, - const std::pair & group_exp2) - : size_{0}, - n_group_exponent_{group_exp2.first}, - n_group_{group_exp2.second}, - n_slot_{group_exp2.second * c_group_size}, - control_{control_vector_type::map - (xo::mm::ArenaConfig{ - .name_ = name + "-ctl", - .size_ = control_size(n_slot_), - .store_header_flag_ = false})}, - slots_{slot_vector_type::map - (xo::mm::ArenaConfig{ - .name_ = name + "-slots", - .size_ = n_slot_ * sizeof(value_type), - .store_header_flag_ = false})} - { - /* here: arenas have allocated address range, but no committed memory yet */ - - this->_init(); - } - - size_type empty() const noexcept { return size_ == 0; } - size_type capacity() const noexcept { return n_group_ * c_group_size; } - float load_factor() const noexcept { return size_ / static_cast(n_slot_); } - - void visit_pools(const MemorySizeVisitor & visitor) const { - // complexity here in service of HashMapStore-specific value for MemorySizeInfo.used - - MemorySizeInfo ctl_info; - MemorySizeInfo slot_info; - - control_.visit_pools([&ctl_info](const auto & x) { ctl_info = x; }); - slots_.visit_pools([&slot_info](const auto & x) { slot_info = x; }); - - // control: 1 byte per (key,value) pair - ctl_info.used_ = size_; - slot_info.used_ = size_ * sizeof(value_type); - - visitor(ctl_info); - visitor(slot_info); - } - - void resize_from_empty(const std::pair & group_exp2) - { - assert(size_ == 0); - - this->n_group_exponent_ = group_exp2.first; - this->n_group_ = group_exp2.second; - this->n_slot_ = group_exp2.second * c_group_size; - - this->_init(); - } - - void clear() { - /* remark: discontinuity in the sense that we lose n_group_ = 2 ^ n_group_epxonent_ - * - * juice may not be worth the squeeze here, - * since DArena doesn't yet (Jan 2026) unmap on clear - */ - - this->size_ = 0; - this->n_group_exponent_ = 0; - this->n_group_ = 0; - this->n_slot_ = 0; - - this->control_.resize(0); - this->slots_.resize(0); - } - - public: - void _init() { - this->control_.resize(control_size(n_slot_)); - - /* front stub: iterator bookend */ - std::fill(this->control_.begin(), - this->control_.begin() + c_control_stub, - c_iterator_bookend); - - /* all slots marked empty initially */ - std::fill(this->control_.begin() + c_control_stub, - this->control_.end() - c_control_stub, - c_empty_slot); - - /* end stub: iterator bookend */ - std::fill(this->control_.end() - c_control_stub, - this->control_.end(), - c_iterator_bookend); - - this->slots_.resize(n_slot_); - } - - /** load control group for slot range [ix .. ix+c_group_size) **/ - group_type _load_group(size_type ix) const { - return group_type(&(control_[ix + c_control_stub])); - } - - /** update control group for slot number @p ix, replace with @p h2 **/ - void _update_control(size_type ix, uint8_t h2) { - this->control_[ix + c_control_stub] = h2; - - if (ix < c_group_size) { - size_type N = this->capacity(); - - // refresh end-of-array copy - std::memcpy(&(control_[N + c_control_stub]), - &(control_[c_control_stub]), - c_group_size); - } - } - - public: - /** number of pairs in this table **/ - size_type size_ = 0; - /** base-2 logarithm of n_group_ **/ - size_type n_group_exponent_ = 0; - /** table has capacity for this number of groups. - * always an exact power of two. - * number of slots is n_group_ * c_group_size - **/ - size_type n_group_ = (1 << n_group_exponent_); - /** table has capacity for this number of {key,value} pairs **/ - size_type n_slot_ = n_group_ * c_group_size; - /** control_[] partitioned into groups of - * c_group_size (16) consecutive elements - **/ - control_vector_type control_; - /** slots_[] holds {key,value} pairs **/ - slot_vector_type slots_; - }; - } - } /*namespace map*/ -} /*namespace xo*/ - -/* end HashMapStore.hpp */ diff --git a/.xo-arena/include/xo/arena/hashmap/verify_policy.hpp b/.xo-arena/include/xo/arena/hashmap/verify_policy.hpp deleted file mode 100644 index 4d0d32aa..00000000 --- a/.xo-arena/include/xo/arena/hashmap/verify_policy.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/** @file verify_policy.hpp -* - * @author Roland Conybeare, Jan 2026 - **/ - -#pragma once - -#include -#include - -namespace xo { - // TODO: move xo/indentlog - - /** @brief policy for verify_ok behavior. - * - * Remarke: wrote this for DArenaHashMap, - * want to incorporate into other subsystems - * that provide a verify_ok() method. - * e.g. RedBlackTree - **/ - struct verify_policy { - static verify_policy log_only() { - return verify_policy{.flags_ = 0x01}; - } - static verify_policy throw_only() { - return verify_policy{.flags_ = 0x02}; - } - static verify_policy chatty() { - return verify_policy{.flags_ = 0x03}; - } - - bool is_silent() const noexcept { return flags_ == 0; } - bool log_flag() const noexcept { return flags_ & 0x01; } - bool throw_flag() const noexcept { return flags_ & 0x02; } - - template - bool report_error(scope & log, Tn&&... args) - { - if (!this->is_silent()) { - // TODO: consider global arena here for string - std::string msg = tostr(std::forward(args)...); - - if (this->log_flag()) { - log.retroactively_enable(); - log(msg); - } - if (this->throw_flag()) { - throw std::runtime_error(msg); - } - } - return false; - } - - const char * c_self_ = "anonymous"; - uint8_t flags_; - }; -} /*namespace xo*/ - -/* end verify_policy.hpp */ diff --git a/.xo-arena/include/xo/arena/mmap_util.hpp b/.xo-arena/include/xo/arena/mmap_util.hpp deleted file mode 100644 index 64cd4d25..00000000 --- a/.xo-arena/include/xo/arena/mmap_util.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/** @file mmap_util.hpp -* - * @author Roland Conybeare, Jan 2026 - **/ - -#pragma once - -#include "span.hpp" - -namespace xo { - namespace mm { - struct mmap_util { - using byte = std::byte; - using span_type = span; - using size_type = std::size_t; - - /** obtain uncommitted contiguous memory range comprising - * a whole multiple of @p align_z bytes, of at least size @p req_z, - * aligned on a @p align_z boundary. Uncommitted memory is not (yet) - * backed by physical memory. - * - * If @p enable_hugepage_flag is true and THP - * (transparent huge pages) are available, use THP for arena memory. - * This relieves TLB and page table memory when @p req_z is a lot larger than - * page size (likely 4KB). Cost is that arena will consum physical memory in unit - * of @p align_z. Arena may waste up to @p align_z bytes of memory as a result. - * - * If @p enable_hugepage_flag is true, @p align_z should be huge page size - * (probably 2MB) for optimal performance. - * - * At present the THP feature is not supported on OSX. - * May be supportable through mach_vm_allocate(). - * - * Note that we reject MAP_HUGETLB|MAP_HUGE_2MB flags to mmap here, - * since requires previously-reserved memory in /proc/sys/vm/nr_hugepages. - * - * Write log messages iff @p debug_flag is true. - * - * @return spqn giving reserved memory address range [lo,hi) - **/ - static span_type map_aligned_range(size_type req_z, - size_type align_z, - bool enable_hugepage_flag, - bool debug_flag); - }; - } /*namespace mm*/ -} /*namespace xo*/ - -/* end mmap_util.hpp */ diff --git a/.xo-arena/include/xo/arena/padding.hpp b/.xo-arena/include/xo/arena/padding.hpp deleted file mode 100644 index 10d03b82..00000000 --- a/.xo-arena/include/xo/arena/padding.hpp +++ /dev/null @@ -1,60 +0,0 @@ -/** @file padding.hpp - * - * @author Roland Conybeare, Dec 2025 - **/ - -#pragma once - -#include -#include - -namespace xo { - namespace mm { - - struct padding { - /** word size for alignment**/ - static constexpr std::size_t c_alloc_alignment = sizeof(std::uintptr_t); - - static inline std::size_t is_aligned(std::size_t n, - std::size_t align = c_alloc_alignment) { - return n % align == 0; - } - - /** how much to add to @p z to get a multiple of - * @ref c_alloc_alignment - **/ - static inline std::size_t alloc_padding(std::size_t z, - std::size_t align = c_alloc_alignment) - { - - /* round up to multiple of c_bpw, but map 0 -> 0 - * (table assuming c_bpw==8) - * - * z%c_bpw dz - * ------------ - * 0 0 - * 1 7 - * 2 6 - * .. .. - * 7 1 - */ - std::size_t dz = (align - (z % align)) % align; - - return dz; - } - - /** @p z rounded up to an exact multiple - * of @ref c_alloc_alignment - **/ - static inline - std::size_t with_padding(std::size_t z, - std::size_t align = c_alloc_alignment) - { - return z + alloc_padding(z, align); - } - }; - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end padding.hpp */ diff --git a/.xo-arena/include/xo/arena/print.hpp b/.xo-arena/include/xo/arena/print.hpp deleted file mode 100644 index 4618d5d4..00000000 --- a/.xo-arena/include/xo/arena/print.hpp +++ /dev/null @@ -1,39 +0,0 @@ -/** @file print.hpp -* - * @author Roland Conybeare, Dec 2025 - **/ - -#pragma once - -#include "AllocError.hpp" -#include -#include - -namespace xo { - namespace mm { - inline std::ostream & - operator<<(std::ostream & os, const error & x) { - os << AllocError::error_description(x); - return os; - } - - inline std::ostream & - operator<<(std::ostream & os, const AllocError & x) { - os << ""; - - return os; - } - } -} - -/* end print.hpp */ diff --git a/.xo-arena/include/xo/arena/span.hpp b/.xo-arena/include/xo/arena/span.hpp deleted file mode 100644 index b9192662..00000000 --- a/.xo-arena/include/xo/arena/span.hpp +++ /dev/null @@ -1,329 +0,0 @@ -/** @file span.hpp - * - * @author Roland Conybeare, Jul 2024 - **/ - -#pragma once - -#include "xo/indentlog/scope.hpp" -#include "xo/indentlog/print/ppdetail_atomic.hpp" -#include -#include -#include - -namespace xo { - namespace mm { - /** @class span compression/span.hpp - * - * @brief A contiguous range of characters, without ownership. - * - * @tparam CharT type for elements referred to by this span. - **/ - template - class span { - public: - /** @defgroup span-type-traits span type traits **/ - ///@{ - - /** typealias for span size (in units of CharT) **/ - using size_type = std::uint64_t; - - /** typealias for span elements **/ - using value_type = CharT; - - ///@} - - public: - /** @defgroup span-ctors span constructors **/ - ///@{ - - /** null span **/ - span() : lo_{nullptr}, hi_{nullptr} {} - - /** Create span for the contiguous memory range [@p lo, @p hi) **/ - span(CharT * lo, CharT * hi) : lo_{lo}, hi_{hi} {} - - /** Create span for the contiguous memory range [@p lo, @p lo + z) **/ - span(CharT * lo, size_t z) : lo_{lo}, hi_{lo + z} {} - - /** explicit conversion from span **/ - template - span(const span & other, - std::enable_if_t - && !std::is_same_v> * = nullptr) - : lo_{other.lo()}, hi_{other.hi()} {} - - /** copy ctor (explicit to avoid ambiguity with template ctor) **/ - span(const span & other) = default; - span & operator=(const span & other) = default; - - /** Create a null span (i.e. with null @p lo, @p hi pointers) - * A null span can be concatenated with any other span - * without triggering matching-endpoint asserts. - **/ - static span make_null() { return span(static_cast(nullptr), - static_cast(nullptr)); } - - /** @brief create span for C-style string @p cstr **/ - static span from_cstr(const CharT * cstr) { - CharT * lo = cstr; - CharT * hi = cstr ? cstr + strlen(cstr) : nullptr; - - return span(lo, hi); - } - - /** @brief create span from std::string @p str **/ - static span from_string(const std::string & str) { - CharT * lo = &(*str.begin()); - CharT * hi = &(*str.end()); - - return span(lo, hi); - } - - /** @brief create span from std::string @p str **/ - static span from_string_view(const std::string_view & sv) { - CharT * lo = &(*sv.begin()); - CharT * hi = &(*sv.end()); - - return span(lo, hi); - } - - /** @brief create span from raw memory **/ - static span from_memory(span span_memory) { - CharT * lo = (CharT *)span_memory.lo(); - CharT * hi = (CharT *)span_memory.hi(); - - return span(lo, hi); - } - - /** @brief concatenate two contiguous spans */ - static span concat(const span & span1, const span & span2) { - if (span1.is_null()) - return span2; - if (span2.is_null()) - return span1; - - if (span1.hi() != span2.lo()) { - scope log(XO_DEBUG(true)); - - log && log(xtag("span1.hi", (void*)span1.hi()), xtag("span2.lo", (void*)span2.lo())); - } - - assert(span1.hi() == span2.lo()); - - CharT * lo = span1.lo(); - CharT * hi = span2.hi(); - - return span(lo, hi); - } - - ///@} - - /** @defgroup span-access-methods **/ - ///@{ - - CharT * lo() const { return lo_; } /* get member span::lo_ */ - CharT * hi() const { return hi_; } /* get member span::hi_ */ - - /** true iff this span is null. distinct from empty. **/ - bool is_null() const { return lo_ == nullptr && hi_ == nullptr; } - /** true iff this span is empty (comprises 0 elements). **/ - bool empty() const { return lo_ == hi_; } - /** report the number of elements (of type CharT) in this span. **/ - size_type size() const { return hi_ - lo_; } - - /** true iff this span is a subspan of @p other. - * i.e. other.lo() <= this->lo() && this->hi() <= other.hi() - **/ - bool is_subspan_of(const span & other) const noexcept { - return (other.lo() <= lo_) && (hi_ <= other.hi()); - } - - /** convert to string view **/ - std::string_view to_string_view() const { - return std::string_view((const char *)lo_, (const char *)hi_); - } - - ///@} - - /** @defgroup span-general-methods **/ - ///@{ - - /** @brief strip prefix until first occurence of '\n', including the newline **/ - void discard_until_newline() { - for (const CharT * p = lo_; p < hi_; ++p) { - if (*p == '\n') { - lo_ = p + 1; - return; - } - } - - lo_ = hi_; - } - - /** Create new span over supplied type, - * with identical (possibly misaligned) endpoints. - * - * @warning - * 1. New span uses exactly the same memory addresses. - * Endpoint pointers may not be aligned. - * 2. Implementation assumes code compiled with - * @code -fno-strict-aliasing @endcode enabled. - * - * @tparam OtherT element type for new span - **/ - template - span - cast() const { return span(reinterpret_cast(lo_), - reinterpret_cast(hi_)); } - - /** @brief create span including the first @p z members of this span. **/ - span prefix(size_type z) const { return span(lo_, lo_ + z); } - - /** @brief create span representing prefix up to (but not including) @p *p - **/ - span prefix_upto(CharT * p) const { - if (p <= hi_) - return span(lo_, p); - else - return span(lo_, hi_); - } - - /** @brief create span with first @p z members of this span removed **/ - span after_prefix(size_type z) const { - if (lo_ + z > hi_) - z = hi_ - lo_; - - return span(lo_ + z, hi_); - } - - /** @brief create span with @p prefix of this span removed **/ - span after_prefix(const span & prefix) const { - if (!prefix.is_null() && (prefix.lo() != lo_)) { - throw std::runtime_error - ("after_prefix: expected prefix of this span"); - } - - return after_prefix(prefix.size()); - } - - /** Create span starting with position @p p. - * Does boundary checking; will return empty span if @p p is outside @c [lo_,hi) - **/ - span suffix_from(CharT * p) const { - if ((lo_ <= p) && (p <= hi_)) - return span(p, hi_); - else - return span(hi_, hi_); - } - - /** increase extent of this spans to include @p x. - * Requires @c hi() == @c x.lo() - **/ - span & operator+=(const span & x) { - if (hi_ == x.lo_) { - hi_ = x.hi_; - } else if (!x.is_null()) { - assert(false); - } - - return *this; - } - - /** print representation for this span on stream @p os **/ - void print(std::ostream & os) const { - os << ""; - } - ///@} - - private: - /** @defgroup span-instance-vars **/ - ///@{ - - /** start of span. - Span comprises memory address between @p lo (inclusive) and @p hi (exclusive) - **/ - CharT * lo_ = nullptr; - - /** @brief end of span. - Span comprises memory address between @p lo (inclusive) and @p hi (exclusive) - **/ - CharT * hi_ = nullptr; - - ///@} - }; /*span*/ - - /** @defgroup span-operators **/ - ///@{ - - /** compare spans for equality. - * Two spans are equal iff both endpoints match exactly. - **/ - template - inline bool - operator==(const span & lhs, const span & rhs) { - return ((lhs.lo() == rhs.lo()) - && (lhs.hi() == rhs.hi())); - } - - /** compare spans for inequality. - * Two spans are unequal if either paired endpoint differs. - **/ - template - inline bool - operator!=(const span & lhs, const span & rhs) { - return ((lhs.lo() != rhs.lo()) - || (lhs.hi() != rhs.hi())); - } - - /** print a summary of @p x on stream @p os. Intended for diagnostics **/ - template - inline std::ostream & - operator<<(std::ostream & os, - const span & x) { - x.print(os); - return os; - } - - ///@} - } /*namespace scm*/ - - namespace print { - template - class printspan_impl { - public: - printspan_impl(xo::mm::span x) : span_{x} {} - - xo::mm::span span_; - }; - - template - printspan_impl printspan(const xo::mm::span& span) { - return printspan_impl(span); - } - - template - inline std::ostream & - operator<< (std::ostream & os, - const printspan_impl & x) - { - for (const CharT * p = x.span_.lo(); p < x.span_.hi(); ++p) - os << *p; - - return os; - } - -#ifndef ppdetail_atomic - template \ - PPDETAIL_ATOMIC_BODY(printspan_impl); - - template \ - PPDETAIL_ATOMIC_BODY(xo::scm::span); -#endif - - } /*namespace mm*/ -} /*namespace xo*/ diff --git a/.xo-arena/src/arena/AllocError.cpp b/.xo-arena/src/arena/AllocError.cpp deleted file mode 100644 index 43c16783..00000000 --- a/.xo-arena/src/arena/AllocError.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/** @file AllocError.cpp - * - * @author Roland Conybeare, Dec 2025 - **/ - -#include "AllocError.hpp" - -namespace xo { - namespace mm { - - const char * - AllocError::error_description(error x) - { - switch (x) { - case error::invalid: - break; - case error::ok: - return "ok"; - case error::reserve_exhausted: - return "reserve-exhausted"; - case error::commit_failed: - return "commit-failed"; - case error::header_size_mask: - return "header-size-mask"; - case error::orphan_sub_alloc: - return "orphan-sub-alloc"; - case error::alloc_info_disabled: - return "alloc-info-disabled"; - case error::alloc_info_address: - return "alloc-info-address"; - case error::alloc_iterator_not_supported: - return "alloc-iterator-not-supported"; - case error::alloc_iterator_deref: - return "alloc-iterator-deref"; - case error::alloc_iterator_next: - return "alloc-iterator-next"; - } - - return "?error"; - } - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end AllocError.cpp */ diff --git a/.xo-arena/src/arena/AllocInfo.cpp b/.xo-arena/src/arena/AllocInfo.cpp deleted file mode 100644 index 9c4c8e7c..00000000 --- a/.xo-arena/src/arena/AllocInfo.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/** @file AllocInfo.cpp - * - * @author Roland Conybeare, Dec 2025 - **/ - -#include "AllocInfo.hpp" - -namespace xo { - namespace mm { - auto - AllocInfo::guard_lo() const noexcept -> span_type - { - if (!p_guard_lo_) - return span_type(nullptr, nullptr); - - return span_type(p_guard_lo_, - p_guard_lo_ + p_config_->guard_z_); - } - - auto - AllocInfo::payload() const noexcept -> span_type - { - if (!p_header_) - return span_type(nullptr, nullptr); - - byte * lo = (byte *)(p_header_ + 1); - size_type z = this->size(); - - return span_type(lo, lo+z); - } - - auto - AllocInfo::guard_hi() const noexcept -> span_type - { - if (!p_guard_hi_) - return span_type(nullptr, nullptr); - - return span_type(p_guard_hi_, - p_guard_hi_ + p_config_->guard_z_); - } - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end AllocInfo.cpp */ diff --git a/.xo-arena/src/arena/CMakeLists.txt b/.xo-arena/src/arena/CMakeLists.txt deleted file mode 100644 index 34832560..00000000 --- a/.xo-arena/src/arena/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -# xo-arena/src/CMakeLists.txt - -set(SELF_LIB xo_arena) -set(SELF_SRCS - arena_streambuf.cpp - ErrorArena.cpp - cmpresult.cpp - mmap_util.cpp - AllocError.cpp - AllocInfo.cpp - DArena.cpp - DArenaIterator.cpp - DCircularBuffer.cpp - backtrace.cpp -) - -xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) -xo_install_include_tree3(include/xo/arena) - -# ---------------------------------------------------------------- -# input dependencies -# -# NOTE: dependency set here must be kept consistent with -# xo-arena/cmake/xo_arenaConfig.cmake.in - -xo_dependency(${SELF_LIB} xo_reflectutil) -xo_dependency(${SELF_LIB} indentlog) - -if (NOT APPLE) -xo_external_pkgconfig_dependency(${SELF_LIB} LIBUNWIND libunwind-generic) -xo_external_pkgconfig_dependency(${SELF_LIB} LIBDW libdw) -else() -endif() - -# end src/CMakeLists.txt diff --git a/.xo-arena/src/arena/DArena.cpp b/.xo-arena/src/arena/DArena.cpp deleted file mode 100644 index ccd6d52e..00000000 --- a/.xo-arena/src/arena/DArena.cpp +++ /dev/null @@ -1,655 +0,0 @@ -/** @file DArena.cpp - * - * @author Roland Conybeare, Dec 2025 - **/ - -//#include "alloc/AAllocator.hpp" -#include "DArena.hpp" -#include "DArenaIterator.hpp" -#include "mmap_util.hpp" -#include "backtrace.hpp" -#include -#include -#include -#include -#include -#include // for std::launder() -#include // for ::munmap() -#include // for ::getpagesize() -#include // for ::memset() - -namespace xo { - using xo::reflect::typeseq; - using std::byte; - using std::cerr; - using std::endl; - using std::size_t; - - namespace mm { - - DArena - DArena::map(const ArenaConfig & cfg) - { - scope log(XO_DEBUG(cfg.debug_flag_)); - - /* vm page size. 4KB, probably */ - size_t page_z = getpagesize(); - - bool enable_hugepage_flag = (cfg.size_ >= cfg.hugepage_z_); - - /* Align start of arena memory on this boundary. - * Will use THP (transparent huge pages) if available - * and arena size is at least as large as hugepage size (2MB, probably) - */ - size_t align_z = (enable_hugepage_flag ? cfg.hugepage_z_ : page_z); - - log && log(xtag("page_z", page_z), - xtag("align_z", align_z)); - - auto span = mmap_util::map_aligned_range(cfg.size_, - align_z, - enable_hugepage_flag, - cfg.debug_flag_); - - if (!span.lo()) { - // control here implies mmap() failed silently - - throw std::runtime_error(tostr("ArenaAlloc: reserve address range failed", - xtag("size", cfg.size_))); - } - - -#ifdef NOPE - log && log(xtag("lo", (void*)lo_), - xtag("page_z", page_z_), - xtag("hugepage_z", hugepage_z_)); -#endif - - return DArena(cfg, page_z, align_z, span.lo(), span.hi()); - } /*map*/ - - DArena::DArena(const ArenaConfig & cfg) - { - *this = map(cfg); - } - - DArena::DArena(const ArenaConfig & cfg, - size_type page_z, - size_type arena_align_z, - byte * lo, - byte * hi) : config_{cfg}, - page_z_{page_z}, - arena_align_z_{arena_align_z}, - lo_{lo}, - committed_z_{0}, - free_{lo}, - limit_{lo}, - hi_{hi}, - error_count_{0}, - last_error_{} - { - //retval.checkpoint_ = lo_; - - /** make sure guard size is aligned **/ - config_.header_.guard_z_ - = padding::with_padding(config_.header_.guard_z_); - } - - DArena::DArena(DArena && other) { - config_ = other.config_; - page_z_ = other.page_z_; - arena_align_z_ = other.arena_align_z_; - lo_ = other.lo_; - committed_z_ = other.committed_z_; - free_ = other.free_; - limit_ = other.limit_; - hi_ = other.hi_; - error_count_ = other.error_count_; - last_error_ = other.last_error_; - - other.config_ = ArenaConfig(); - other.lo_ = nullptr; - other.committed_z_ = 0; - other.free_ = nullptr; - other.limit_ = nullptr; - other.hi_ = nullptr; - other.error_count_ = 0; - other.last_error_ = AllocError(); - } - - DArena & - DArena::operator=(DArena && other) - { - config_ = other.config_; - page_z_ = other.page_z_; - arena_align_z_ = other.arena_align_z_; - lo_ = other.lo_; - committed_z_ = other.committed_z_; - free_ = other.free_; - limit_ = other.limit_; - hi_ = other.hi_; - error_count_ = other.error_count_; - last_error_ = other.last_error_; - - other.config_ = ArenaConfig(); - other.lo_ = nullptr; - other.committed_z_ = 0; - other.free_ = nullptr; - other.limit_ = nullptr; - other.hi_ = nullptr; - other.error_count_ = 0; - other.last_error_ = AllocError(); - - return *this; - } - - void - DArena::unmap() noexcept - { - if (lo_) { - //log && log("unmap [lo,hi)", - // xtag("lo", lo_), - // xtag("z", hi_ - lo_), - // xtag("hi", hi_)); - - ::munmap(lo_, hi_ - lo_); - } - - /* Mandatory hygiene: zero the bookkeeping tail so no dangling - * pointers survive (e.g. after ~DArena() runs the dtor of a - * stack-owned arena). config_, page_z_, arena_align_z_ are - * preserved -- only {lo_ .. last_error_} get cleared. - * - * The memset goes through a std::launder'd self pointer so the - * compiler cannot prove it writes to the dying object and elide - * the stores as dead (gcc>=15 does that with a plain member - * assignment / un-laundered memset at -O1). All cleared fields - * are trivially-copyable, so byte-zeroing them is well-defined. - */ - DArena * self = std::launder(this); - byte * tail = reinterpret_cast(&self->lo_); - byte * end = reinterpret_cast(self) + sizeof(DArena); - - ::memset(tail, 0, end - tail); - } - - DArena::~DArena() - { - this->unmap(); - } - - auto - DArena::obj2hdr(void * obj) noexcept -> header_type * - { - assert(config_.store_header_flag_); - - return (header_type *)((byte *)obj - sizeof(header_type)); - } - - auto - DArena::obj2hdr(void * obj) const noexcept -> const header_type * - { - assert(config_.store_header_flag_); - - return (const header_type *)((byte *)obj - sizeof(header_type)); - } - - void - DArena::visit_pools(const MemorySizeVisitor & fn) const - { - /** arena can't tell purpose of allocated memory; - * must assume it's all used - **/ - - // assemble histogram - MemorySizeInfo::DetailArrayType detail_v; - MemorySizeInfo::DetailArrayType * p_detail = nullptr; - - if (config_.store_header_flag_) { - p_detail = &detail_v; - - for (const auto & ix : *this) { - typeseq ix_tseq(ix.tseq()); - - // totals in detail_v[0] - MemorySizeDetail & d = detail_v[0]; - ++d.n_alloc_; - d.z_alloc_ += ix.size(); - - // O(n) insertion here - for (size_t i = 1; i < detail_v.size(); ++i) { - if (detail_v[i].tseq_.is_sentinel() - || (detail_v[i].tseq_ == ix_tseq)) - { - MemorySizeDetail & d = detail_v[i]; - - d.tseq_ = ix_tseq; - ++d.n_alloc_; - d.z_alloc_ += ix.size(); - break; - } - } - } - } - - fn(MemorySizeInfo(config_.name_, - this->allocated() /*used*/, - this->allocated(), - this->committed(), - this->reserved(), - lo_, - hi_, - p_detail)); - } - - AllocInfo - DArena::alloc_info(value_type mem) const noexcept - { - if (!config_.store_header_flag_) [[unlikely]] { - this->capture_error(error::alloc_info_disabled, __PRETTY_FUNCTION__); - - return AllocInfo::error_not_configured(&config_.header_); - } - - byte * header_mem = mem - sizeof(AllocHeader); - -#ifdef OBSOLETE // relying on cross-alloc header shenanigans in DX1Collector - if (!this->contains(header_mem)) { - this->capture_error(error::alloc_info_address); - } -#endif - - AllocHeader * header = (AllocHeader *)header_mem; - - const byte * guard_lo - = header_mem - config_.header_.guard_z_; - const byte * guard_hi - = mem + config_.header_.size(*header); - - return AllocInfo(&config_.header_, - guard_lo, - (AllocHeader *)header_mem, - guard_hi); - } - - DArenaIterator - DArena::begin() const noexcept - { - return DArenaIterator::begin(this); - } - - DArenaIterator - DArena::end() const noexcept - { - return DArenaIterator::end(this); - } - - AllocHeader * - DArena::begin_header() const noexcept - { - if (config_.store_header_flag_ == false) { - this->capture_error(error::alloc_iterator_not_supported, __PRETTY_FUNCTION__); - - return nullptr; - } - - return (AllocHeader *)(lo_ + config_.header_.guard_z_); - } - - AllocHeader * - DArena::end_header() const noexcept - { - if (config_.store_header_flag_ == false) { - this->capture_error(error::alloc_iterator_not_supported, __PRETTY_FUNCTION__); - - return nullptr; - } - - return (AllocHeader *)free_; - } - - std::byte * - DArena::alloc(typeseq t, std::size_t req_z) - { - /* - primary allocation path: - * exactly 1 header per alloc() call. - * - store_header_flag follows configuration - */ - return _alloc(req_z, - alloc_mode::standard, - t, - 0 /*age*/, - __PRETTY_FUNCTION__); - } - - std::byte * - DArena::super_alloc(typeseq t, std::size_t req_z) - { - /* - (uncommon) pattern for parent alloc immediately followed by - * zero-or-more susidiary allocs, all sharing a single header. - * - collapses into alloc() behavior when - * ArenaConfig.store_header_flag_ disabled - */ - - (void)t; - - return _alloc(req_z, - alloc_mode::super, - t, - 0 /*age*/, - __PRETTY_FUNCTION__); - } - - std::byte * - DArena::sub_alloc(std::size_t req_z, - bool complete_flag) - { - /* - (uncommon) pattern for subsidiary allocs: - * that piggyback onto preceding super_alloc() - * - collapses into alloc() behavior when - * ArenaConfig.store_header_flag_ disabled - */ - - return _alloc(req_z, - (complete_flag - ? alloc_mode::sub_complete - : alloc_mode::sub_incomplete), - typeseq::sentinel() /*typeseq: ignored*/, - 0 /*age - ignored */, - __PRETTY_FUNCTION__); - } - - std::byte * - DArena::alloc_copy(std::byte * src) - { - /* NOTE: allocator that owns src must have the same header configuration */ - - assert(config_.store_header_flag_); - - /* src will come from an allocator other than this one; - * we rely on header layout from destination - * allocator -> assumes compatible header config - */ - AllocInfo src_info = alloc_info(src); - - size_t req_z = src_info.size(); - typeseq tseq = typeseq(src_info.tseq()); - uint32_t age = src_info.age(); - - return _alloc(req_z, alloc_mode::standard, tseq, age + 1, - __PRETTY_FUNCTION__); - } - - void - DArena::capture_error(error err, - const char * src_fn, - size_type target_z) const - { - DArena * self = const_cast(this); - - ++(self->error_count_); - self->last_error_ = AllocError(err, - src_fn, - error_count_, - target_z, - committed_z_, - reserved()); - } - - byte * - DArena::_alloc(std::size_t req_z, - alloc_mode mode, - typeseq tseq, - uint32_t age, - const char * src_fn) - { - scope log(XO_DEBUG(config_.debug_flag_)); - - /* - * sub_complete - * sub_incomplete | - * standard super | | - * v v v v - */ - std::array store_header_v = {{ true, true, false, false }}; - std::array retain_header_v = {{ false, true, false, false }}; - std::array store_guard_v = {{ true, false, false, true }}; - - /* -> write header at free_ */ - bool store_header_flag = false; - /* -> stash last_header_*/ - bool retain_header_flag = false; - /* -> write guard bytes */ - bool store_guard = false; - - if (config_.store_header_flag_) { - store_header_flag = store_header_v[(int)mode]; - retain_header_flag = retain_header_v[(int)mode]; - store_guard = store_guard_v[(int)mode]; - } - - assert(padding::is_aligned((size_t)free_)); - - /* - * free_(pre) - * v - * - * <-------------z1---------------> - * < guard >< hz >< req_z >< dz >< guard > - * - * used <== +++++++++0000zzzz@@@@@@@@@@@@@@@@@ppppppp+++++++++ ==> avail - * - * ^ ^ ^ - * header mem | - * ^ | - * last_header_ free_(post) - * - * [+] guard after each allocation, for simple sanitize checks - * [0] unused header bits (avail to application) - * [z] record allocation size - * [@] new allocated memory - * [p] padding (to uintptr_t alignment) - */ - - /* non-zero if header feature enabled */ - size_t hz = 0; - /* dz: pad req_z to alignment size (multiple of 8 bytes, probably) */ - size_t dz = padding::alloc_padding(req_z); - size_t z0 = req_z + dz; - /* if non-zero: - * will store padded alloc size at the beginning of each allocation - * reminder: - * important to store padded size for correct arena iteration - */ - uint64_t header = (req_z + dz); - - if (store_header_flag) - { - if (config_.header_.is_size_enabled()) [[likely]] { - header = this->config_.header_.mkheader(tseq.seqno(), age, req_z + dz); - hz = sizeof(header); - } else { - /* req_z doesn't fit in configured header_size_mask bits */ - capture_error(error::header_size_mask, src_fn); - return nullptr; - } - } - - size_t z1 = hz + z0; - - assert(padding::is_aligned(z1)); - - if (!this->expand(this->allocated() + z1, src_fn)) [[unlikely]] { - /* (error state already captured) */ - return nullptr; - } - - if (store_header_flag) { - /* capturing header */ - *(uint64_t *)free_ = header; - - if (retain_header_flag) { - /* and rembering for subsequent - * sub_alloc() - */ - last_header_ = (AllocHeader *)free_; - } - } - - byte * mem = free_ + hz; - - this->free_ += z1; - - if (store_guard) { - /* write guard bytes for overrun detection */ - ::memset(free_, - config_.header_.guard_byte_, - config_.header_.guard_z_); - - this->free_ += config_.header_.guard_z_; - } - - log && log(xtag("self", config_.name_), - xtag("hz", hz), - xtag("z0", req_z), - xtag("+pad", dz), - xtag("z1", z1), - xtag("size", this->committed()), - xtag("avail", this->available())); - log && log(xtag("mem", mem), - xtag("free", free_)); - - return mem; - } - - void - DArena::establish_initial_guard() noexcept - { - assert(free_ == lo_); - - ::memset(this->free_, - config_.header_.guard_byte_, - config_.header_.guard_z_); - - this->free_ += config_.header_.guard_z_; - } - - bool - DArena::expand(size_t target_z, const char * src_fn) noexcept - { - scope log(XO_DEBUG(config_.debug_flag_), - xtag("target_z", target_z), - xtag("committed_z", committed_z_), - xtag("src_fn", src_fn)); - - if (target_z <= committed_z_) [[likely]] { - log && log("trivial success, offset within committed range", - xtag("target_z", target_z), - xtag("committed_z", committed_z_)); - return true; - } - - if (lo_ + target_z > hi_) [[unlikely]] { - this->capture_error(error::reserve_exhausted, src_fn, target_z); - - //fprintf(stderr, "DArena::expand: reserve exhausted"); - //print_backtrace_dwarf(true /*demangle_flag*/); - //std::terminate(); - - return false; - } - - /* - * pre: - * - * _______________................................... - * ^ ^ ^ - * lo limit hi - * - * < committed_z > - * <----------target_z-----------> - * > <- z: 0 <= z < hugepage_z - * <---------aligned_target_z---------> - * <--- add_commit_z --> - * - * post: - * ____________________________________.............. - * ^ ^ ^ - * lo limit hi - * - */ - - std::size_t aligned_target_z = padding::with_padding(target_z, arena_align_z_); - std::byte * commit_start = limit_; // = lo_ + committed_z_; - std::size_t add_commit_z = aligned_target_z - committed_z_; - - assert(limit_ == lo_ + committed_z_); - - if (::mprotect(commit_start, - add_commit_z, - PROT_READ | PROT_WRITE) != 0) [[unlikely]] - { - if (log) { - log("commit failed!"); - log(xtag("aligned_target_z", aligned_target_z), - xtag("commit_start", commit_start), - xtag("add_commit_z", add_commit_z), - xtag("commit_end", commit_start + add_commit_z) - ); - } - - this->capture_error(error::commit_failed, src_fn, add_commit_z); - - fprintf(stderr, "DArena::expand: mprotect failed (system oom?)"); - print_backtrace_dwarf(false /*!demangle_flag*/); - - return false; - } - - committed_z_ = aligned_target_z; - limit_ = lo_ + committed_z_; - - if (commit_start == lo_) [[unlikely]] { - /* first expand() for this allocator - start with guard_z_ bytes */ - - this->establish_initial_guard(); - } - - assert(committed_z_ % arena_align_z_ == 0); - assert(reinterpret_cast(limit_) % arena_align_z_ == 0); - - return true; - } /*expand*/ - - void - DArena::scrub() noexcept - { - ::memset(this->lo_, 0, this->free_ - this->lo_); - } - - void - DArena::clear() noexcept - { - this->free_ = lo_; - this->establish_initial_guard(); - } - - void - DArena::swap(DArena & other) noexcept - { - std::swap(config_, other.config_); - std::swap(page_z_, other.page_z_); - std::swap(arena_align_z_, other.arena_align_z_); - std::swap(lo_, other.lo_); - std::swap(committed_z_, other.committed_z_); - std::swap(last_header_, other.last_header_); - std::swap(free_, other.free_); - std::swap(limit_, other.limit_); - std::swap(hi_, other.hi_); - std::swap(error_count_, other.error_count_); - std::swap(last_error_, other.last_error_); - } - } -} /*namespace xo*/ - -/* end DArena.cpp */ diff --git a/.xo-arena/src/arena/DArenaIterator.cpp b/.xo-arena/src/arena/DArenaIterator.cpp deleted file mode 100644 index 78769b7a..00000000 --- a/.xo-arena/src/arena/DArenaIterator.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/** @file DArenaIterator.cpp -* - * @author Roland Conybeare, Dec 2025 - **/ - -#include "DArenaIterator.hpp" -#include "DArena.hpp" -#include -#include -#include - -namespace xo { - using std::byte; - - namespace mm { - DArenaIterator - DArenaIterator::begin(const DArena * arena) - { - constexpr bool c_debug_flag = false; - scope log(XO_DEBUG(c_debug_flag)); - - AllocHeader * begin_hdr = begin_header(arena); - - if (!begin_hdr) - return DArenaIterator::invalid(); - - log && log(xtag("begin_hdr", begin_hdr)); - - return DArenaIterator(arena, begin_hdr); - } - - DArenaIterator - DArenaIterator::end(const DArena * arena) - { - constexpr bool c_debug_flag = false; - scope log(XO_DEBUG(c_debug_flag)); - - AllocHeader * end_hdr = end_header(arena); - - if (!end_hdr) - return DArenaIterator::invalid(); - - log && log(xtag("end_hdr", end_hdr)); - - return DArenaIterator(arena, end_hdr); - } - - AllocHeader * - DArenaIterator::begin_header(const DArena * arena) - { - assert(arena); - - return arena->begin_header(); - } - - AllocHeader * - DArenaIterator::end_header(const DArena * arena) - { - assert(arena); - - return arena->end_header(); - } - - AllocInfo - DArenaIterator::deref() const noexcept - { - constexpr bool c_debug_flag = false; - scope log(XO_DEBUG(c_debug_flag)); - - bool contains_flag = arena_->contains(this->pos_as_byte()); - bool bounds_flag = (this->pos_as_byte() < arena_->free_); - - log && log(xtag("contains_flag", contains_flag), - xtag("bounds_flag", bounds_flag)); - - if (!contains_flag || !bounds_flag) { - arena_->capture_error(error::alloc_iterator_deref, __PRETTY_FUNCTION__); - - return AllocInfo::error_invalid_iterator(&(arena_->config_.header_)); - } - - /* iterator points to beginning of header. - * memory given to application start immediately followed header - */ - - byte * mem = (byte *)(pos_ + 1); - - return arena_->alloc_info(mem); - } - - cmpresult - DArenaIterator::compare(const DArenaIterator & other_ix) const noexcept - { - scope log(XO_DEBUG(false), - xtag("arena", arena_), - xtag("pos", pos_), - xtag("other.arena", other_ix.arena_), - xtag("other.pos", other_ix.pos_)); - - if (is_invalid() || (arena_ != other_ix.arena_)) - return cmpresult::incomparable(); - - if (pos_ < other_ix.pos_) { - return cmpresult::lesser(); - } else if(pos_ == other_ix.pos_) { - return cmpresult::equal(); - } else { - return cmpresult::greater(); - } - } - - void - DArenaIterator::next() noexcept - { - constexpr bool c_debug_flag = false; - scope log(XO_DEBUG(c_debug_flag)); - - bool contains_flag = arena_->contains(this->pos_as_byte()); - bool bounds_flag = (this->pos_as_byte() < arena_->free_); - - log && log(xtag("contains_flag", contains_flag), - xtag("bounds_flag", bounds_flag)); - - if (!contains_flag || !bounds_flag) { - arena_->capture_error(error::alloc_iterator_next, __PRETTY_FUNCTION__); - return; - } - - size_t mem_z = arena_->config_.header_.size(*pos_); - size_t guard_z = arena_->config_.header_.guard_z_; - - byte * next_as_byte = ((byte *)pos_ + sizeof(AllocHeader) + mem_z + guard_z); - /* next == ix.arena_free_ --> iterator is at end of allocator */ - assert(next_as_byte <= arena_->free_); - - AllocHeader * next = (AllocHeader *)next_as_byte; - - this->pos_ = next; - } - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end DArenaIterator.cpp */ diff --git a/.xo-arena/src/arena/DCircularBuffer.cpp b/.xo-arena/src/arena/DCircularBuffer.cpp deleted file mode 100644 index 23bc0684..00000000 --- a/.xo-arena/src/arena/DCircularBuffer.cpp +++ /dev/null @@ -1,394 +0,0 @@ -/** @file DCircularBuffer.cpp - * - * @author Roland Conybeare, Jan 2026 - **/ - -#include "DCircularBuffer.hpp" -#include "mmap_util.hpp" -#include -#include -#include -#include // for ::getpagesize() on osx - -namespace xo { - using xo::print::operator<<; - using xo::print::printspan; - - namespace mm { - - DCircularBuffer::DCircularBuffer(DCircularBuffer && other) - : config_{other.config_}, - page_z_{other.page_z_}, - buffer_align_z_{other.buffer_align_z_}, - reserved_range_{other.reserved_range_}, - mapped_range_{other.mapped_range_}, - occupied_range_{other.occupied_range_}, - pinned_spans_{std::move(other.pinned_spans_)} - { - other.reserved_range_ = span_type(); - other.mapped_range_ = span_type(); - other.occupied_range_ = span_type(); - } - - DCircularBuffer - DCircularBuffer::map(const CircularBufferConfig & config) - { - scope log(XO_DEBUG(config.debug_flag_)); - - /* vm page size. 4KB (probably if linux) or 16KB (probably if osx) */ - size_t page_z = getpagesize(); - - bool enable_hugepage_flag = (config.max_capacity_ >= config.hugepage_z_); - - /* Align start of arena memory on this boundary. - * Will use THP (transparent huge pages) if available - * and arena size is at least as large as hugepage size (2MB, probably) - */ - size_t align_z = (enable_hugepage_flag ? config.hugepage_z_ : page_z); - - log && log(xtag("page_z", page_z), - xtag("align_z", align_z)); - - auto mapped_span - = span::from_memory(mmap_util::map_aligned_range - (config.max_capacity_, - align_z, - enable_hugepage_flag, - config.debug_flag_)); - - if (!mapped_span.lo()) { - throw std::runtime_error(tostr("DCircularBuffer: reserve address range failed", - xtag("size", config.max_capacity_))); - } - - return DCircularBuffer(config, page_z, align_z, mapped_span); - } - - DCircularBuffer::DCircularBuffer(const CircularBufferConfig & config, - size_type page_z, - size_type buffer_align_z, - span_type reserved_range) - : config_{config}, - page_z_{page_z}, - buffer_align_z_{buffer_align_z}, - reserved_range_{reserved_range}, - mapped_range_{reserved_range_.prefix(0)}, - occupied_range_{mapped_range_.prefix(0)}, - input_range_{occupied_range_.prefix(0)}, - pinned_spans_{DArenaVector::map(ArenaConfig().with_name(config.name_ + "-pins"))} - { - } - - void - DCircularBuffer::visit_pools(const MemorySizeVisitor & visitor) const - { - visitor(MemorySizeInfo(config_.name_, - occupied_range_.size() /*used*/, - occupied_range_.size(), - mapped_range_.size(), - reserved_range_.size(), - reserved_range_.lo(), - reserved_range_.hi(), - nullptr /*detail*/)); - - pinned_spans_.visit_pools(visitor); - } - - bool - DCircularBuffer::verify_ok(verify_policy policy) const - { - using xo::xtag; - - constexpr const char * c_self = "DCircularBuffer::verify_ok"; - scope log(XO_DEBUG(false)); - - /* CB1: mapped_range_ is subrange of reserved_range_ */ - if ((mapped_range_.lo() < reserved_range_.lo()) - || (mapped_range_.hi() > reserved_range_.hi())) - { - return policy.report_error(log, - c_self, ": expect mapped_range subset of reserved_range", - xtag("mapped.lo", (void*)mapped_range_.lo()), - xtag("mapped.hi", (void*)mapped_range_.hi()), - xtag("reserved.lo", (void*)reserved_range_.lo()), - xtag("reserved.hi", (void*)reserved_range_.hi())); - } - - /* CB2: occupied_range_ is subrange of mapped_range_ */ - if ((occupied_range_.lo() < mapped_range_.lo()) - || (occupied_range_.hi() > mapped_range_.hi())) - { - return policy.report_error(log, - c_self, ": expect occupied_range subset of mapped_range", - xtag("occupied.lo", (void*)occupied_range_.lo()), - xtag("occupied.hi", (void*)occupied_range_.hi()), - xtag("mapped.lo", (void*)mapped_range_.lo()), - xtag("mapped.hi", (void*)mapped_range_.hi())); - } - - /* CB3: each remembered span is subrange of occupied_range_ */ - for (size_type i = 0, n = pinned_spans_.size(); i < n; ++i) { - const const_span_type & pin = pinned_spans_[i]; - - if ((pin.lo() < occupied_range_.lo()) - || (pin.hi() > occupied_range_.hi())) - { - return policy.report_error(log, - c_self, ": expect remembered_span subset of occupied_range", - xtag("i", i), - xtag("pin.lo", (void*)pin.lo()), - xtag("pin.hi", (void*)pin.hi()), - xtag("occupied.lo", (void*)occupied_range_.lo()), - xtag("occupied.hi", (void*)occupied_range_.hi())); - } - } - - /* CB4: buffer_align_z_ is non-zero (when buffer is mapped) */ - if (!reserved_range_.is_null() && (buffer_align_z_ == 0)) { - return policy.report_error(log, - c_self, ": expect buffer_align_z > 0 when buffer is mapped", - xtag("buffer_align_z", buffer_align_z_)); - } - - /* CB5: reserved_range_ aligned on buffer_align_z_ boundary */ - if (!reserved_range_.is_null() && (buffer_align_z_ > 0)) { - if (((size_type)(reserved_range_.lo()) % buffer_align_z_) != 0) { - return policy.report_error(log, - c_self, ": expect reserved_range.lo aligned on buffer_align_z", - xtag("reserved.lo", (void*)reserved_range_.lo()), - xtag("buffer_align_z", buffer_align_z_)); - } - } - - return true; - } - - auto - DCircularBuffer::append(const_span_type src) -> const_span_type - { - span_type dest = get_append_span(src.size()); - - size_t copy_z = std::min(src.size(), dest.size()); - - ::memcpy(occupied_range_.hi(), src.lo(), copy_z); - - this->occupied_range_ += span_type(dest.lo(), copy_z); - this->input_range_ += span_type(dest.lo(), copy_z); - - return src.after_prefix(copy_z); - } - - auto - DCircularBuffer::get_append_span(size_type desired_z) -> span_type - { - span_type dest = span_type(occupied_range_.hi(), desired_z); - - if (dest.hi() > reserved_range_.hi()) { - /* under no circumstances go past the end of reserved range */ - dest = span_type(dest.lo(), reserved_range_.hi()); - } - - /* establish mapped range at least to dest.hi */ - this->_expand_to(dest.hi()); - - /* report available memory */ - return span_type(occupied_range_.hi(), mapped_range_.hi()); - } - - void - DCircularBuffer::report_append(span_type r) - { - if (r.lo() != occupied_range_.hi()) { - // error! - - // this->capture_error(error::bad_append_report, r.size()) - assert(false); - - return; - } - - if (r.hi() > mapped_range_.hi()) { - // error! - - // this->capture_error(error::bad_append_report, r.size()) - assert(false); - - return; - } - - this->occupied_range_ += r; - } - - void - DCircularBuffer::consume(const_span_type input) - { - scope log(XO_DEBUG(false), xtag("input", input.to_string_view())); - - if (input.lo() != input_range_.lo()) { - assert(false); - - return; - } - - if (input.hi() > occupied_range_.hi()) { - assert(false); - - return; - } - - if (occupied_range_.lo() < input_range_.lo()) { - log && log("pinned range prevents shrinking occupied range"); - - /* here: a pinned range prevents shrinking occupied_range */ - - this->input_range_ - = input_range_.suffix_from((span_type::value_type *)input.hi()); - } else { - log && log(xtag("msg", "will shrink occupied range"), - xtag("input.lo", (void*)input.lo()), - xtag("input.hi", (void*)input.hi()), - xtag("stored.lo", (void*)input_range_.lo()), - xtag("stored.hi", (void*)input_range_.hi()) - ); - - /* here: input; recompute occupied boundary */ - - this->input_range_ - = input_range_.suffix_from((span_type::value_type *)input.hi()); - - log && log(xtag("occupied", occupied_range_.size()), - xtag("input", input_range_.size())); - - this->_shrink_occupied_to_fit(); - - log && log(xtag("occupied", occupied_range_.size()), - xtag("input", input_range_.size())); - } - - this->_check_reset_map_start(); - } - - void - DCircularBuffer::pin_range(span_type r) - { - // loop optimized for case where r falls - // _after_ any existing pinned ranges - - size_type z = pinned_spans_.size(); - size_type ip1 = z; // ip1 = i + 1 - - for (; ip1 > 0; --ip1) { - if (r.lo() > pinned_spans_[ip1 - 1].lo()) - break; - - // insert at i to maintain sorted order - pinned_spans_.insert(ip1 - 1, r); - return; - } - - pinned_spans_.push_back(r); - } - - void - DCircularBuffer::unpin_range(span_type r) - { - // loop optimized for case where r - // is the first pinned range - - assert(pinned_spans_.size() > 0); - - if (r == pinned_spans_[0]) { - this->pinned_spans_.erase(0); - - /* removing pinned span means can perhaps shrink - * occupied range - */ - this->_shrink_occupied_to_fit(); - this->_check_reset_map_start(); - } else { - for (size_type i = 1; i < pinned_spans_.size(); ++i) { - if (r == pinned_spans_[i]) { - this->pinned_spans_.erase(i); - - /* since this isn't the first pinned span, - * won't be able to shrink occupied range. - */ - return; - } - } - } - } - - bool - DCircularBuffer::_expand_to(char * hi) - { - scope log(XO_DEBUG(config_.debug_flag_)); - - if (hi < mapped_range_.hi()) { - /* nothing todo */ - return true; - } - - size_t add_z = hi - mapped_range_.hi(); - size_t add_commit_z = padding::with_padding(add_z, buffer_align_z_); - char * commit_start = mapped_range_.hi(); - - if (::mprotect(commit_start, - add_commit_z, - PROT_READ | PROT_WRITE) != 0) - { - if (log) { - log("commit failed"); - log(xtag("commit_start", commit_start), - xtag("add_z", add_z), - xtag("add_commit_z", add_commit_z)); - } - - // this->capture_error(error::commit_failed, add_commit_z); - return false; - } - - this->mapped_range_ += span(commit_start, add_commit_z); - return true; - } - - void - DCircularBuffer::_shrink_occupied_to_fit() - { - if (pinned_spans_.empty()) { - this->occupied_range_ = input_range_; - } else if (occupied_range_.lo() < pinned_spans_[0].lo()) { - this->occupied_range_ = occupied_range_.suffix_from(pinned_spans_[0].lo()); - } - } - - void - DCircularBuffer::_check_reset_map_start() - { - if (pinned_spans_.empty() - && (input_range_ == occupied_range_)) { - - // here: permissible to move input range to the beginning of mapped range. - // decide (heuristically) whether we think this is optimal - - std::size_t input_z = input_range_.size(); - - // 1st clause checks efficiency. - // 2nd clause (probably redundant) check non-overlapping - if ((input_range_.lo() > (mapped_range_.lo() - + std::max(page_z_, - static_cast(config_.threshold_move_efficiency_ * input_z)))) - && (mapped_range_.lo() + input_z < input_range_.lo())) { - - ::memmove(mapped_range_.lo(), input_range_.lo(), input_z); - - this->occupied_range_ = mapped_range_.prefix(input_z); - this->input_range_ = mapped_range_.prefix(input_z); - } - } - } - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end DCircularBuffer.cpp */ diff --git a/.xo-arena/src/arena/ErrorArena.cpp b/.xo-arena/src/arena/ErrorArena.cpp deleted file mode 100644 index d1d3d62b..00000000 --- a/.xo-arena/src/arena/ErrorArena.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/** @file ErrorArena.cpp -* - * @author Roland Conybeare, Feb 2026 - **/ - -#include "ErrorArena.hpp" - -namespace xo { - namespace mm { - DArena - ErrorArena::s_instance; - - ArenaConfig - ErrorArena::default_config() - { - return ArenaConfig().with_name("error-arena").with_size(16 * 1024); - } - - namespace { - bool s_init_done = false; - } - - void - ErrorArena::init_once(const ArenaConfig & cfg) - { - if (!s_init_done) { - s_init_done = true; - s_instance = DArena::map(cfg); - } - } - - DArena * - ErrorArena::instance() - { - init_once(default_config()); - - return &s_instance; - } - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end ErrorArena.cpp */ diff --git a/.xo-arena/src/arena/arena_streambuf.cpp b/.xo-arena/src/arena/arena_streambuf.cpp deleted file mode 100644 index 7947da0d..00000000 --- a/.xo-arena/src/arena/arena_streambuf.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/** @file arena_streambuf.cpp - * - * @author Roland Conybeare, Feb 2026 - **/ - -#include "arena_streambuf.hpp" - -namespace xo { - namespace mm { - - std::uint32_t - arena_streambuf::lpos() const - { - if (debug_flag_) { - std::cerr << "log_streambuf::lpos: enter" << std::endl; - } - - // logically-const. lazy implementation - arena_streambuf * self = const_cast(this); - - self->_check_update_local_state(); - - return pos() - solpos_ - color_escape_chars_; - } - - auto - arena_streambuf::checkpoint() const -> rewind_state - { - // logically-const. lazy implementation - arena_streambuf * self = const_cast(this); - - self->_check_update_local_state(); - - return rewind_state(solpos_, color_escape_chars_, pos()); - } - - void - arena_streambuf::reset_stream() - { - assert(arena_); - assert(arena_->committed() > 0); - - char * p_lo = (char *)(arena_->lo_); - char * p_hi = (char *)(arena_->limit_); - - /* tells parent our buffer extent */ - this->setp(p_lo, p_hi); - - this->local_ppos_ = 0; - this->solpos_ = 0; - this->color_escape_chars_ = 0; - this->color_escape_start_ = nullptr; - } - - void - arena_streambuf::rewind_to(rewind_state s) - { - if (debug_flag_) { - std::cout << "rewind_to: pos " << pos() << "->" << s.pos - << " solpos " << solpos_ << "->" << s.solpos - << " color_esc " << color_escape_chars_ << "->" << s.color_escape_chars - << std::endl; - } - - /* .setp(): using just for side effect: sets .pptr to .pbase */ - this->setp(this->pbase(), this->epptr()); - /* advance pptr to saved position */ - this->pbump(s.pos); - - this->local_ppos_ = this->pptr() - this->pbase(); - this->solpos_ = s.solpos; - this->color_escape_chars_ = s.color_escape_chars; - /* assuming we never try to capture rewind state with incomplete color escape */ - this->color_escape_start_ = nullptr; - } - - void - arena_streambuf::expand_to(std::size_t new_z) - { - char * old_pptr = pptr(); - std::streamsize old_n = old_pptr - pbase(); - - assert(old_n <= static_cast(arena_->allocated())); - assert(new_z > arena_->committed()); - - /* note: local_ppos_ invariant across expand_to() */ - - arena_->expand(new_z, __PRETTY_FUNCTION__); - - char * p_base = (char *)(arena_->lo_); - char * p_hi = (char *)(arena_->limit_); - - this->setp(p_base, p_hi); - this->pbump(old_n); - } - - std::streamsize - arena_streambuf::xsputn(const char * s, std::streamsize n) - { - /* s must be an address in [this->lo() .. this->lo() + capacity()] */ - - assert(hi() >= pptr()); - - if (pptr() + n > hi()) { - std::size_t new_z = std::max(2 * arena_->committed(), std::size_t(this->pos() + n + 1)); - - if (new_z > arena_->reserved()) - new_z = arena_->reserved(); - - this->expand_to(new_z); - } - - if (debug_flag_) { - std::cout << "xsputn: pbase=" << (void *)(this->pbase()) - << ", pptr=" << (void*)(this->pptr()) - << "(+" << (this->pptr() - this->lo()) << ")" - << ", n=" << n << " -> (+" << (this->pptr() + n - this->lo()) << ")" - << ", arena.size=" << this->arena_->committed() - << std::endl; - } - - std::streamsize ncopied = 0; - - if (this->pptr() + n > this->hi()) { - ncopied = this->hi() - this->pptr(); - } else { - ncopied = n; - } - - if (false /*debug_flag_*/) { - std::cout << "xsputn: copying ncopied=" << ncopied << " (/n=" << n << ") bytes into range [lo,hi)" - << ", lo=" << (void*)this->pptr() - << ", hi=" << (void*)(this->pptr() + n) - << std::endl; - } - - std::memcpy(this->pptr(), s, ncopied); - - this->pbump(ncopied); - - /* now {pbase, pptr} consistent with new input */ - - this->_check_update_local_state(); - - return ncopied; - } - - auto - arena_streambuf::overflow(int_type new_ch) -> int_type - { - char * old_base = this->pbase(); - char * old_pptr = this->pptr(); - /* #of chars buffered */ - std::streamsize old_n = old_pptr - old_base; - - assert(old_n <= static_cast(arena_->committed())); - - // if (debug_flag_) { - // std::cout << "overflow: new_ch=" << quoted_char(new_ch) << std::endl; - // } - - /* increase buffer size */ - this->expand_to(2 * arena_->committed()); - - arena_->lo_[old_n] = static_cast(new_ch); - this->pbump(1); - - if ((new_ch == static_cast('\n')) || (new_ch == static_cast('\r'))) { - this->solpos_ = this->pos(); - - // what if new_ch starts color escape ? - } - - if (new_ch == std::char_traits::eof()) { - /* reminder: returning eof sets badbit on ostream */ - return std::char_traits::not_eof(new_ch); - } else { - return new_ch; - } - } - - auto - arena_streambuf::seekoff(off_type off, - std::ios_base::seekdir dir, - std::ios_base::openmode which) -> pos_type - { - //std::cout << "seekoff: off=" << off << ", dir=" << dir << ", which=" << which << std::endl; - if (debug_flag_) { - std::cout << "seekoff(off,dir,which)" << std::endl; - } - - // Only output stream is supported - if (which != std::ios_base::out) - throw std::runtime_error("log_streambuf: only output mode supported"); - - if (dir == std::ios_base::cur) { - this->pbump(off); - } else if (dir == std::ios_base::end) { - /* .setp(): using for side effect: sets .pptr to .pbase */ - this->setp(this->pbase(), this->epptr()); - this->pbump(off); - } else if (dir == std::ios_base::beg) { - /* .setp(): using for side effect: sets .pptr to .pbase */ - this->setp(this->pbase(), this->epptr()); - this->pbump(this->capacity() + off); - } - - return this->pptr() - this->pbase(); - } /*seekoff*/ - - } /*namespace mm*/ -} /*namespace xo*/ - -/* end arena_streambuf.cpp */ diff --git a/.xo-arena/src/arena/backtrace.cpp b/.xo-arena/src/arena/backtrace.cpp deleted file mode 100644 index 708ba5e6..00000000 --- a/.xo-arena/src/arena/backtrace.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/** @file backtrace.cpp - * - * @author Roland Conybeare, Apr 2026 - **/ - -#include "backtrace.hpp" -#include -#include -#include -#include -#include -#include -#include -#ifndef __APPLE__ -# include -#endif - -namespace xo { - void - print_backtrace(bool demangle_flag) { - unw_cursor_t cursor; - unw_context_t cx; - - // capture cpu register state at this call site - unw_getcontext(&cx); - - // stack frame iterator for current thread. - // local -> this process - // - unw_init_local(&cursor, &cx); - - // depth relative to top of call stack - int depth = 0; - while (unw_step(&cursor) > 0) { - unw_word_t pc = 0; - - // read return address of current frame into pc. - // This determines the function that is executing - // when print_backtrace() invoked - // - unw_get_reg(&cursor, UNW_REG_IP, &pc); - - std::array name; - unw_word_t offset = 0; - - // mangled function name for current frame's pc. - // - if (unw_get_proc_name(&cursor, name.data(), sizeof(name), &offset) == 0) { - int status = 0; - - // we are resaponsible for calling ::free() on non-null demangled value - char * demangled = nullptr; - - if (demangle_flag) - demangled = abi::__cxa_demangle(name.data(), nullptr, nullptr, &status); - - if ((status == 0) && demangled) { - fprintf(stderr, "#%d 0x%lx %s+0x%lx\n", - depth, (long)pc, demangled, (long)offset); - free(demangled); - } else { - // demangle failed (or disabled) - fprintf(stderr, "#%d 0x%lx %s+0x%lx\n", - depth, (long)pc, name.data(), (long)offset); - } - } else { - // unable to get function name - fprintf(stderr, "#%d 0x%lx ???\n", depth, (long)pc); - } - } - } - namespace { -#ifndef __APPLE__ - // libdwfl requires callbacks for find_elf and find_debuginfo. - // The offline defaults work for the current process. - // - static const Dwfl_Callbacks dwfl_callbacks = { - .find_elf = dwfl_linux_proc_find_elf, - .find_debuginfo = dwfl_standard_find_debuginfo, - .section_address = nullptr, - .debuginfo_path = nullptr, - }; -#endif - } - - void - print_backtrace_dwarf(bool demangle_flag) - { - -#ifdef __APPLE__ - (void)demangle_flag; - - std::cerr << "backtrace with dwarf symbols not supported on osx" << std::endl; -#else - unw_cursor_t cursor; - unw_context_t cx; - - unw_getcontext(&cx); - unw_init_local(&cursor, &cx); - - // set up dwfl for resolving addresses to source locations. - // - Dwfl * dwfl = dwfl_begin(&dwfl_callbacks); - - if (dwfl) { - // populate module list from /proc/self/maps - dwfl_linux_proc_report(dwfl, getpid()); - dwfl_report_end(dwfl, nullptr, nullptr); - } - - int depth = 0; - while (unw_step(&cursor) > 0) { - unw_word_t pc = 0; - unw_get_reg(&cursor, UNW_REG_IP, &pc); - - std::array name; - unw_word_t offset = 0; - - // resolve function name via libunwind - // - const char * func_name = "???"; - char * demangled = nullptr; - int status = -1; - - if (unw_get_proc_name(&cursor, name.data(), name.size(), &offset) == 0) { - if (demangle_flag) - demangled = abi::__cxa_demangle(name.data(), nullptr, nullptr, &status); - - func_name = ((status == 0) && demangled) ? demangled : name.data(); - } - - // resolve source file + line via DWARF debug info - // - const char * source_file = nullptr; - int line = 0; - - if (dwfl) { - Dwfl_Module * module = dwfl_addrmodule(dwfl, pc); - - if (module) { - Dwfl_Line * dwfl_line = dwfl_module_getsrc(module, pc); - - if (dwfl_line) { - source_file = dwfl_lineinfo(dwfl_line, nullptr, &line, - nullptr, nullptr, nullptr); - } - } - } - - if (source_file) { - fprintf(stderr, "#%d 0x%lx %s+0x%lx at %s:%d\n", - depth, (long)pc, func_name, (long)offset, - source_file, line); - } else { - fprintf(stderr, "#%d 0x%lx %s+0x%lx\n", - depth, (long)pc, func_name, (long)offset); - } - - if (demangled) - free(demangled); - - ++depth; - } - - if (dwfl) - dwfl_end(dwfl); -#endif - - } -} /*namespace xo*/ - -/* end backtrace.cpp */ diff --git a/.xo-arena/src/arena/cmpresult.cpp b/.xo-arena/src/arena/cmpresult.cpp deleted file mode 100644 index cd7c2998..00000000 --- a/.xo-arena/src/arena/cmpresult.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/** @file cmpresult.cpp - * - * @author Roland Conybeare, Dec 2025 - **/ - -#include "cmpresult.hpp" -#include -#include - -namespace xo { - namespace mm { - const char * - comparison2str(comparison x) - { - switch (x) { - case comparison::invalid: - break; - case comparison::comparable: - return "cmp"; - case comparison::incomparable: - return "!cmp"; - } - - return "?comparison"; - } - - void - cmpresult::display(std::ostream & os) const - { - os << ""; - } - } /*namespace mm*/ -} /*namespace xo*/ - -/* end cmpresult.cpp */ diff --git a/.xo-arena/src/arena/mmap_util.cpp b/.xo-arena/src/arena/mmap_util.cpp deleted file mode 100644 index 37db7149..00000000 --- a/.xo-arena/src/arena/mmap_util.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/** @file mmap_util.cpp -* - * @author Roland Conybeare, Jan 2026 - **/ - -#include "mmap_util.hpp" -#include "padding.hpp" -#include // for mmap - -namespace xo { - namespace mm { - auto - mmap_util::map_aligned_range(size_t req_z, - size_t align_z, - bool enable_hugepage_flag, - bool debug_flag) -> span_type - { - scope log(XO_DEBUG(debug_flag), - xtag("req_z", req_z), - xtag("align_z", align_z)); - - // 1. round up to multiple of align_z - size_t target_z = padding::with_padding(req_z, align_z); // 4. - - // 2. mmap() will give us page-aligned memory, - // but not hugepage-aligned. - // - // Over-request by align_z to ensure - // aligned subrange of size target_z - // - byte * base = (byte *)(::mmap(nullptr, - target_z + align_z, - PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS, - -1, 0)); - - // on mmap success: upper limit of mapped address range - byte * hi = base + (target_z + align_z); - // lowest hugepage-aligned address in [base, hi) - byte * aligned_base = (byte *)(padding::with_padding((size_t)base, align_z)); - // end of hugeppage-aligned range starting at aligned_base - byte * aligned_hi = aligned_base + target_z; - - log && log("acquired memory [lo,hi) using mmap", - xtag("lo", base), - xtag("aligned_lo", aligned_base), - xtag("req_z", req_z), - xtag("target_z", target_z), - xtag("aligned_hi", aligned_hi), - xtag("hi", hi)); - - // 3. assess mmap success - { - if (base == MAP_FAILED) { - throw std::runtime_error(tostr("ArenaAlloc: uncommitted allocation failed", - xtag("size", req_z))); - } - - assert((size_t)aligned_base % align_z == 0); - assert(aligned_base >= base); - assert(aligned_base < base + align_z); - } - - // 4. release unaligned prefix - if (base < aligned_base) { - size_t ua_prefix = aligned_base - base; - - ::munmap(base, ua_prefix); - } - - // 5. release unaligned suffix - if (aligned_hi < hi) { - size_t suffix = hi - aligned_hi; - - ::munmap(aligned_hi, suffix); - } - - if (enable_hugepage_flag) { -#ifdef __linux__ - /** 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. - * - * Page table has a handful of levels - **/ - ::madvise(aligned_base, target_z, MADV_HUGEPAGE); // 8. -#endif - } - - return span_type(aligned_base, aligned_hi); - } - } /*namespace mm*/ -} /*namespace xo*/ - -/* end mmap_util.cpp */ diff --git a/.xo-arena/utest/CMakeLists.txt b/.xo-arena/utest/CMakeLists.txt deleted file mode 100644 index 886ecb7a..00000000 --- a/.xo-arena/utest/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -# xo-alloc2/utest/CMakeLists.txt -# - -set(UTEST_EXE utest.arena) -set(UTEST_SRCS - arena_utest_main.cpp -# objectmodel.test.cpp - DArena.test.cpp - DArenaVector.test.cpp - DArenaHashMap.test.cpp - DCircularBuffer.test.cpp -# DArenaIterator.test.cpp -# random_allocs.cpp -) - -if (ENABLE_TESTING) - xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) - xo_self_dependency(${UTEST_EXE} xo_arena) - xo_headeronly_dependency(${UTEST_EXE} randomgen) -# xo_headeronly_dependency(${UTEST_EXE} indentlog) - xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) -endif() - -# end CMakeLists.txt diff --git a/.xo-arena/utest/DArena.test.cpp b/.xo-arena/utest/DArena.test.cpp deleted file mode 100644 index 9ac40384..00000000 --- a/.xo-arena/utest/DArena.test.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/** @file DArena.test.cpp - * - * @author Roland Conybeare, Jan 2026 - **/ - -#include "DArena.hpp" -#include "print.hpp" -#include -#include - -namespace xo { - using xo::mm::DArena; - using xo::mm::AllocHeader; - using xo::mm::AllocHeaderConfig; - using xo::mm::ArenaConfig; - using xo::mm::padding; - using xo::mm::error; - using xo::reflect::typeseq; - using xo::xtag; - using std::byte; - - namespace ut { - TEST_CASE("DArena-tiny", "[arena][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_) % arena.page_z_ == 0); - REQUIRE(arena.lo_ + cfg.size_ <= arena.hi_); - - /* verify arena.lo_ is aligned on a page boundary */ - REQUIRE(((size_t)(arena.lo_) & (arena.page_z_ - 1)) == 0); - - /* verify arena.hi_ is aligned on a hugepage boundary */ - REQUIRE(((size_t)(arena.hi_) & (arena.page_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("DArena-medium", "[arena][DArena]") - { - 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("DArena-expand-1", "[arena][DArena]") - { - /* typed allocator a1o */ - ArenaConfig cfg { .name_ = "testarena", - .size_ = 1, - .debug_flag_ = false }; - DArena arena = DArena::map(cfg); - - REQUIRE(arena.available() == 0); - REQUIRE(arena.allocated() == 0); - - size_t z2 = 512; - bool ok = arena.expand(z2, __PRETTY_FUNCTION__); - - INFO(xtag("last_error", arena.last_error())); - - REQUIRE(ok); - - REQUIRE(arena.reserved() % arena.page_z() == 0); - REQUIRE(arena.committed() >= z2); - REQUIRE(arena.committed() % arena.page_z() == 0); - REQUIRE(arena.available() >= z2); - REQUIRE(arena.available() == arena.committed()); - REQUIRE(arena.allocated() == 0); - - } - - TEST_CASE("arena-alloc-1", "[arena][DArena]") - { - /* typed allocator a1o */ - ArenaConfig cfg { .name_ = "testarena", - .size_ = 64*1024, - .debug_flag_ = false }; - DArena arena = DArena::map(cfg); - - REQUIRE(arena.reserved() >= cfg.size_); - REQUIRE(arena.committed() == 0); - REQUIRE(arena.available() == 0); - REQUIRE(arena.allocated() == 0); - - size_t z0 = 1; - byte * m0 = arena.alloc(typeseq::sentinel(), 1); - - REQUIRE(m0); - REQUIRE(arena.last_error().error_ == error::ok); - REQUIRE(arena.last_error().error_seq_ == 0); - REQUIRE(arena.allocated() >= z0); - REQUIRE(arena.allocated() < z0 + padding::c_alloc_alignment ); - REQUIRE(arena.allocated() <= arena.committed()); - REQUIRE(arena.allocated() + arena.available() == arena.committed()); - REQUIRE(arena.committed() <= arena.reserved()); - - size_t z1 = 16; - byte * m1 = arena.alloc(typeseq::sentinel(), z1); - - REQUIRE(m1); - REQUIRE(arena.last_error().error_ == error::ok); - REQUIRE(arena.last_error().error_seq_ == 0); - REQUIRE(arena.allocated() >= z0 + z1); - REQUIRE(arena.allocated() < z0 + z1 + 2 * padding::c_alloc_alignment ); - REQUIRE(arena.allocated() <= arena.committed()); - REQUIRE(arena.allocated() + arena.available() == arena.committed()); - REQUIRE(arena.committed() <= arena.reserved()); - } - - TEST_CASE("arena-alloc-2", "[arena][DArena]") - { - 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); - - REQUIRE(arena.reserved() >= cfg.size_); - REQUIRE(arena.committed() == 0); - REQUIRE(arena.available() == 0); - REQUIRE(arena.allocated() == 0); - - size_t z0 = 1; - byte * m0 = arena.alloc(typeseq::sentinel(), 1); - - REQUIRE(m0); - - header_type* header = (header_type*)(m0 - sizeof(header_type)); - - REQUIRE(arena.contains(header)); - REQUIRE(cfg.header_.size(*header) == padding::with_padding(z0)); - //REQUIRE(((*header) & cfg.header_size_mask_) == padding::with_padding(z0)); - REQUIRE(arena.last_error().error_ == error::ok); - REQUIRE(arena.last_error().error_seq_ == 0); - REQUIRE(arena.allocated() >= z0); - REQUIRE(arena.allocated() < sizeof(DArena::header_type) + z0 + padding::c_alloc_alignment ); - REQUIRE(arena.allocated() <= arena.committed()); - REQUIRE(arena.allocated() + arena.available() == arena.committed()); - REQUIRE(arena.committed() <= arena.reserved()); - } - - } -} - -/* end DArena.test.cpp */ diff --git a/.xo-arena/utest/DArenaHashMap.test.cpp b/.xo-arena/utest/DArenaHashMap.test.cpp deleted file mode 100644 index 02ac3fc3..00000000 --- a/.xo-arena/utest/DArenaHashMap.test.cpp +++ /dev/null @@ -1,360 +0,0 @@ -/** @file DArenaHashMap.test.cpp -* - * @author Roland Conybeare, Jan 2026 - **/ - -#include "DArenaHashMap.hpp" -#include "random_hash_ops.hpp" -#include -#include -#include -#include -#include - -namespace xo { - using xo::map::DArenaHashMapUtil; - using xo::map::DArenaHashMap; - using xo::rng::random_seed; - using xo::rng::xoshiro256ss; - using utest::UtestTools; - using utest::HashMapUtil; - - namespace ut { - TEST_CASE("DArenaHashMap-ctor", "[arena][DArenaHashMap]") - { - using HashMap = DArenaHashMap; - - HashMap map("utest"); - - REQUIRE(map.empty()); - REQUIRE(map.size() == 0); - REQUIRE(map.groups() == 1); - REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size); - } - - TEST_CASE("DArenaHashMap-ctor2", "[arena][DArenaHashMap]") - { - using HashMap = DArenaHashMap; - - HashMap map("utest", 257); - - REQUIRE(map.empty()); - REQUIRE(map.size() == 0); - REQUIRE(map.capacity() == map.groups() * DArenaHashMapUtil::c_group_size); - REQUIRE(map.capacity() == std::max(512ul, - DArenaHashMapUtil::c_group_size)); - } - - TEST_CASE("DArenaHashMap-try-insert", "[arena][DArenaHashMap]") - { - using HashMap = DArenaHashMap; - - HashMap map("utest"); - - REQUIRE(map.empty()); - REQUIRE(map.size() == 0); - REQUIRE(map.groups() == 1); - REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size); - - { - auto x = map.try_insert(std::make_pair(1, 11)); - - REQUIRE(x.first); - REQUIRE(x.second); - REQUIRE(!map.empty()); - REQUIRE(map.size() == 1); - REQUIRE(map.groups() == 1); - REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size); - REQUIRE(map.load_factor() == 1/16.0); - - /* verify iteration */ - { - size_t n = 0; - for (auto & ix : map) { - REQUIRE(ix.first == 1); - REQUIRE(ix.second == 11); - ++n; - } - REQUIRE(n == map.size()); - } - - REQUIRE(map.verify_ok(verify_policy::chatty())); - } - - { - auto x = map.try_insert(std::make_pair(2, 9)); - - REQUIRE(x.first); - REQUIRE(x.second); - REQUIRE(!map.empty()); - REQUIRE(map.size() == 2); - REQUIRE(map.groups() == 1); - REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size); - REQUIRE(map.load_factor() == 2/16.0); - - /* verify iteration */ - { - size_t n = 0; - for (auto & ix : map) { - ++n; - } - REQUIRE(n == map.size()); - } - - REQUIRE(map.verify_ok(verify_policy::chatty())); - } - - { - auto x = map.try_insert(std::make_pair(259, 12)); - - REQUIRE(x.first); - REQUIRE(x.second); - REQUIRE(!map.empty()); - REQUIRE(map.size() == 3); - REQUIRE(map.groups() == 1); - REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size); - REQUIRE(map.load_factor() == 3/16.0); - - /* verify iteration */ - { - size_t n = 0; - for (auto & ix : map) { - switch (ix.first) { - case 1: - REQUIRE(ix.second == 11); - break; - case 2: - REQUIRE(ix.second == 9); - break; - case 259: - REQUIRE(ix.second == 12); - break; - default: - REQUIRE(false); - } - ++n; - } - REQUIRE(n == map.size()); - } - - REQUIRE(map.verify_ok(verify_policy::chatty())); - } - - { - map.clear(); - - REQUIRE(map.empty()); - REQUIRE(map.size() == 0); - REQUIRE(map.groups() == 0); - REQUIRE(map.capacity() == 0); - - REQUIRE(map.verify_ok(verify_policy::chatty())); - } - - /* slightly different starting point, 0 capacity! */ - { - auto x = map.try_insert(std::make_pair(1, 11)); - - /* try_insert should fail - no capacity */ - REQUIRE(!x.first); - REQUIRE(!x.second); - - REQUIRE(map.verify_ok(verify_policy::chatty())); - } - - { - /* insert will grow hash table */ - auto x = map.insert(std::make_pair(1, 11)); - - CHECK(x); - REQUIRE(!map.empty()); - REQUIRE(map.size() == 1); - REQUIRE(map.groups() == 1); - REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size); - REQUIRE(map.load_factor() == 1/16.0); - - /* verify iteration */ - { - size_t n = 0; - for (auto & ix : map) { - REQUIRE(ix.first == 1); - REQUIRE(ix.second == 11); - ++n; - } - REQUIRE(n == map.size()); - } - - REQUIRE(map.verify_ok(verify_policy::chatty())); - } - - } - - TEST_CASE("DArenaHashMap-try-insert2", "[arena][DArenaHashMap]") - { - using HashMap = DArenaHashMap; - - std::uint64_t seed = 17747889312058974961ul; - //random_seed(&seed); // to get new random seed - //log && log(xtag("seed", seed)); - - auto rgen = xoshiro256ss(seed); - - /* 1. Perform series of tests with increasing scale - * 2. Each test may run in two modes: - * a. silent fast fail. just report success. - * In this mode avoid catch2 REQUIRE - * b. noisy. run with logging enabled - * This mode automatically invoked when silent mode - * observes test failure - */ - - for (std::uint32_t n = 0; n <= 8; ) { - HashMap hash_map("utest"); - - auto test_fn = [&rgen, &hash_map](bool dbg_flag, - std::uint32_t n) - { - bool ok_flag = true; - - ok_flag &= HashMapUtil::random_inserts(n, dbg_flag, &rgen, &hash_map); - - ok_flag &= HashMapUtil::check_forward_iterator(0.0 /*dvalue*/, - dbg_flag, hash_map); - /* regular forward iterator, but start at hash_map.end() and use operator-- */ - ok_flag &= HashMapUtil::check_backward_iterator(0.0 /*dvalue*/, - dbg_flag, hash_map); - - ok_flag &= HashMapUtil::random_lookups(0.0 /*dvalue*/, - dbg_flag, &rgen, hash_map); - - return ok_flag; - }; - - bool ok_flag = UtestTools::bimodal_test("DArenaHashMap-try-insert2", test_fn, n); - - if (n == 0) - n = 1; - else - n = 2*n; - } - } - - TEST_CASE("DArenaHashMap-operator-bracket", "[arena][DArenaHashMap]") - { - scope log(XO_DEBUG(false)); - - using HashMap = DArenaHashMap; - - HashMap map("utest"); - - // copy keys here so we can print stuff - std::vector key_v; - - // insert via operator[] - map[1] = 100; - key_v.push_back(1); - REQUIRE(map.verify_ok(verify_policy::chatty())); - - map[2] = 200; - key_v.push_back(2); - REQUIRE(map.verify_ok(verify_policy::chatty())); - - map[3] = 300; - key_v.push_back(3); - REQUIRE(map.verify_ok(verify_policy::chatty())); - - REQUIRE(map.size() == 3); - - // read back via operator[] - REQUIRE(map[1] == 100); - REQUIRE(map[2] == 200); - REQUIRE(map[3] == 300); - - // update via operator[] - map[2] = 250; - REQUIRE(map[2] == 250); - REQUIRE(map.size() == 3); // size unchanged - REQUIRE(map.verify_ok(verify_policy::chatty())); - - // verify via find - { - auto it = map.find(1); - REQUIRE(it != map.end()); - REQUIRE(it->second == 100); - } - { - auto it = map.find(2); - REQUIRE(it != map.end()); - REQUIRE(it->second == 250); - } - { - auto it = map.find(3); - REQUIRE(it != map.end()); - REQUIRE(it->second == 300); - } - { - auto it = map.find(4); - REQUIRE(it == map.end()); - } - - REQUIRE(map.verify_ok(verify_policy::chatty())); - - // operator[] on non-existent key creates default entry - int & val = map[999]; - key_v.push_back(999); - - for (uint64_t i_slot = 0, N = map._store()->n_slot_; i_slot < N; ++i_slot) { - auto key = map._store()->slots_[i_slot].first; - auto ctrl = map._store()->control_ - [i_slot + DArenaHashMapUtil::c_control_stub]; - auto isdata = DArenaHashMapUtil::is_data(ctrl); - auto [h1,h2] = map._hash(key); - - if ((key != 0) - || (h1 != 0) - || (h2 != 0) - || (ctrl != DArenaHashMapUtil::c_empty_slot) - || isdata - ) { - log && log(xtag("i", i_slot), - xtag("key[i]", key), - xtag("h1", h1), xtag("h2", h2), - xtag("ctrl[i]", (int)ctrl), - xtag("isdata", isdata)); - } - } - - REQUIRE(map.verify_ok(verify_policy::chatty())); - - REQUIRE(map.size() == 4); - REQUIRE(val == 0); // default-initialized - val = 999; - REQUIRE(map[999] == 999); - } - - TEST_CASE("DArenaHashMap-string_view-key", "[arena][DArenaHashMap]") - { - using HashMap = DArenaHashMap; - - HashMap map("utest", 1024); - - REQUIRE(map.verify_ok()); - - map["hello"] = 42; - REQUIRE(map.size() == 1); - REQUIRE(map.verify_ok()); - - map["world"] = 100; - REQUIRE(map.size() == 2); - REQUIRE(map.verify_ok()); - - REQUIRE(map["hello"] == 42); - REQUIRE(map["world"] == 100); - } - - // TODO: - // - let's try getting lcov to work in xo-umbrella2 - } -} - -/* end DArenaHashMap.test.cpp */ diff --git a/.xo-arena/utest/DArenaVector.test.cpp b/.xo-arena/utest/DArenaVector.test.cpp deleted file mode 100644 index 963c9513..00000000 --- a/.xo-arena/utest/DArenaVector.test.cpp +++ /dev/null @@ -1,539 +0,0 @@ -/** @file DArenaVector.test.cpp - * - * @author Roland Conybeare, Jan 2026 - **/ - -#include "DArenaVector.hpp" -#include - -namespace xo { - using xo::mm::DArenaVector; - using xo::mm::ArenaConfig; - using std::byte; - - namespace ut { - TEST_CASE("DArenaVector-tiny", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 1 }; - DArenaVector arenavec = DArenaVector::map(cfg); - - REQUIRE(arenavec.empty()); - } - - TEST_CASE("DArenaVector-push_back-rvalue", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - REQUIRE(vec.empty()); - REQUIRE(vec.size() == 0); - - vec.push_back(1.5); - - REQUIRE(!vec.empty()); - REQUIRE(vec.size() == 1); - REQUIRE(vec[0] == 1.5); - - vec.push_back(2.5); - vec.push_back(3.5); - - REQUIRE(vec.size() == 3); - REQUIRE(vec[0] == 1.5); - REQUIRE(vec[1] == 2.5); - REQUIRE(vec[2] == 3.5); - } - - TEST_CASE("DArenaVector-push_back-lvalue", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - double a = 10.0; - double b = 20.0; - double c = 30.0; - - vec.push_back(a); - - REQUIRE(vec.size() == 1); - REQUIRE(vec[0] == 10.0); - - vec.push_back(b); - vec.push_back(c); - - REQUIRE(vec.size() == 3); - REQUIRE(vec[0] == 10.0); - REQUIRE(vec[1] == 20.0); - REQUIRE(vec[2] == 30.0); - } - - TEST_CASE("DArenaVector-at-valid", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - vec.push_back(100.0); - vec.push_back(200.0); - vec.push_back(300.0); - - REQUIRE(vec.at(0) == 100.0); - REQUIRE(vec.at(1) == 200.0); - REQUIRE(vec.at(2) == 300.0); - - // test mutability via at() - vec.at(1) = 250.0; - REQUIRE(vec.at(1) == 250.0); - } - - TEST_CASE("DArenaVector-at-throws", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - // empty vector - any index is invalid - REQUIRE_THROWS_AS(vec.at(0), std::out_of_range); - - vec.push_back(1.0); - vec.push_back(2.0); - - // valid indices work - REQUIRE_NOTHROW(vec.at(0)); - REQUIRE_NOTHROW(vec.at(1)); - - // index == size is invalid - REQUIRE_THROWS_AS(vec.at(2), std::out_of_range); - - // index > size is invalid - REQUIRE_THROWS_AS(vec.at(100), std::out_of_range); - } - - TEST_CASE("DArenaVector-resize-expand", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - REQUIRE(vec.size() == 0); - - // resize from 0 to 5 - vec.resize(5); - REQUIRE(vec.size() == 5); - - // can write to all indices - for (size_t i = 0; i < 5; ++i) { - vec[i] = static_cast(i * 10); - } - - REQUIRE(vec[0] == 0.0); - REQUIRE(vec[1] == 10.0); - REQUIRE(vec[2] == 20.0); - REQUIRE(vec[3] == 30.0); - REQUIRE(vec[4] == 40.0); - - // resize to larger - vec.resize(8); - REQUIRE(vec.size() == 8); - - // original values preserved - REQUIRE(vec[0] == 0.0); - REQUIRE(vec[1] == 10.0); - REQUIRE(vec[4] == 40.0); - } - - TEST_CASE("DArenaVector-resize-shrink", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - vec.push_back(1.0); - vec.push_back(2.0); - vec.push_back(3.0); - vec.push_back(4.0); - vec.push_back(5.0); - - REQUIRE(vec.size() == 5); - - // shrink to 3 - vec.resize(3); - REQUIRE(vec.size() == 3); - - // first 3 elements preserved - REQUIRE(vec[0] == 1.0); - REQUIRE(vec[1] == 2.0); - REQUIRE(vec[2] == 3.0); - - // index 3 now out of bounds - REQUIRE_THROWS_AS(vec.at(3), std::out_of_range); - - // shrink to 0 - vec.resize(0); - REQUIRE(vec.size() == 0); - REQUIRE(vec.empty()); - } - - TEST_CASE("DArenaVector-resize-same", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - vec.push_back(10.0); - vec.push_back(20.0); - vec.push_back(30.0); - - REQUIRE(vec.size() == 3); - - // resize to same size - vec.resize(3); - REQUIRE(vec.size() == 3); - - // values unchanged - REQUIRE(vec[0] == 10.0); - REQUIRE(vec[1] == 20.0); - REQUIRE(vec[2] == 30.0); - } - - TEST_CASE("DArenaVector-clear", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - vec.push_back(1.0); - vec.push_back(2.0); - vec.push_back(3.0); - - REQUIRE(vec.size() == 3); - REQUIRE(!vec.empty()); - - vec.clear(); - - REQUIRE(vec.size() == 0); - REQUIRE(vec.empty()); - - // can still push after clear - vec.push_back(99.0); - REQUIRE(vec.size() == 1); - REQUIRE(vec[0] == 99.0); - } - - TEST_CASE("DArenaVector-iterators", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - vec.push_back(10.0); - vec.push_back(20.0); - vec.push_back(30.0); - - // begin/end - REQUIRE(vec.begin() != vec.end()); - REQUIRE(vec.end() - vec.begin() == 3); - - // iterate with pointer arithmetic - auto it = vec.begin(); - REQUIRE(*it == 10.0); - ++it; - REQUIRE(*it == 20.0); - ++it; - REQUIRE(*it == 30.0); - ++it; - REQUIRE(it == vec.end()); - - // modify through iterator - *vec.begin() = 15.0; - REQUIRE(vec[0] == 15.0); - } - - TEST_CASE("DArenaVector-const-iterators", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - vec.push_back(1.0); - vec.push_back(2.0); - vec.push_back(3.0); - - const DArenaVector & cvec = vec; - - REQUIRE(cvec.cbegin() != cvec.cend()); - REQUIRE(cvec.begin() == cvec.cbegin()); - REQUIRE(cvec.end() == cvec.cend()); - - auto it = cvec.cbegin(); - REQUIRE(*it == 1.0); - ++it; - REQUIRE(*it == 2.0); - } - - TEST_CASE("DArenaVector-range-for", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - vec.push_back(1.0); - vec.push_back(2.0); - vec.push_back(3.0); - - // read via range-for - double sum = 0.0; - for (double x : vec) { - sum += x; - } - REQUIRE(sum == 6.0); - - // modify via range-for - for (double & x : vec) { - x *= 2.0; - } - REQUIRE(vec[0] == 2.0); - REQUIRE(vec[1] == 4.0); - REQUIRE(vec[2] == 6.0); - } - - TEST_CASE("DArenaVector-reserve", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - REQUIRE(vec.size() == 0); - REQUIRE(vec.capacity() > 0); - - size_t initial_capacity = vec.capacity(); - - // reserve doesn't change size - vec.reserve(100); - REQUIRE(vec.size() == 0); - REQUIRE(vec.capacity() >= 100); - - // add some elements - vec.push_back(1.0); - vec.push_back(2.0); - vec.push_back(3.0); - - REQUIRE(vec.size() == 3); - size_t cap_after_push = vec.capacity(); - - // reserve more space - vec.reserve(200); - REQUIRE(vec.size() == 3); - REQUIRE(vec.capacity() >= 200); - - // values still intact - REQUIRE(vec[0] == 1.0); - REQUIRE(vec[1] == 2.0); - REQUIRE(vec[2] == 3.0); - } - - TEST_CASE("DArenaVector-swap", "[arena][DArenaVector]") - { - ArenaConfig cfg1 { .name_ = "testarena1", - .size_ = 4096 }; - ArenaConfig cfg2 { .name_ = "testarena2", - .size_ = 4096 }; - - DArenaVector vec1 = DArenaVector::map(cfg1); - DArenaVector vec2 = DArenaVector::map(cfg2); - - vec1.push_back(1.0); - vec1.push_back(2.0); - - vec2.push_back(10.0); - vec2.push_back(20.0); - vec2.push_back(30.0); - - REQUIRE(vec1.size() == 2); - REQUIRE(vec2.size() == 3); - - vec1.swap(vec2); - - // sizes swapped - REQUIRE(vec1.size() == 3); - REQUIRE(vec2.size() == 2); - - // contents swapped - REQUIRE(vec1[0] == 10.0); - REQUIRE(vec1[1] == 20.0); - REQUIRE(vec1[2] == 30.0); - - REQUIRE(vec2[0] == 1.0); - REQUIRE(vec2[1] == 2.0); - } - - TEST_CASE("DArenaVector-data", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - vec.push_back(1.0); - vec.push_back(2.0); - vec.push_back(3.0); - - double * ptr = vec.data(); - - // data() points to first element - REQUIRE(ptr == &vec[0]); - - // can read via pointer - REQUIRE(ptr[0] == 1.0); - REQUIRE(ptr[1] == 2.0); - REQUIRE(ptr[2] == 3.0); - - // can write via pointer - ptr[1] = 99.0; - REQUIRE(vec[1] == 99.0); - - // const version - const DArenaVector & cvec = vec; - const double * cptr = cvec.data(); - REQUIRE(cptr[0] == 1.0); - REQUIRE(cptr[1] == 99.0); - REQUIRE(cptr[2] == 3.0); - } - - TEST_CASE("DArenaVector-move-ctor", "[arena][DArenaVector]") - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec1 = DArenaVector::map(cfg); - - vec1.push_back(10.0); - vec1.push_back(20.0); - vec1.push_back(30.0); - - double * original_data = vec1.data(); - size_t original_size = vec1.size(); - - // move construct vec2 from vec1 - DArenaVector vec2(std::move(vec1)); - - // vec2 has the data - REQUIRE(vec2.size() == original_size); - REQUIRE(vec2.data() == original_data); - REQUIRE(vec2[0] == 10.0); - REQUIRE(vec2[1] == 20.0); - REQUIRE(vec2[2] == 30.0); - - // vec1 is in valid but moved-from state - REQUIRE(vec1.size() == 0); - REQUIRE(vec1.empty()); - } - - // Helper class to track ctor/dtor calls - struct LifetimeTracker { - static int ctor_count; - static int dtor_count; - - static void reset() { - ctor_count = 0; - dtor_count = 0; - } - - int value; - - LifetimeTracker() : value{0} { ++ctor_count; } - LifetimeTracker(int v) : value{v} { ++ctor_count; } - LifetimeTracker(const LifetimeTracker & other) : value{other.value} { ++ctor_count; } - LifetimeTracker(LifetimeTracker && other) : value{other.value} { ++ctor_count; other.value = 0; } - ~LifetimeTracker() { ++dtor_count; } - - LifetimeTracker & operator=(const LifetimeTracker &) = default; - LifetimeTracker & operator=(LifetimeTracker &&) = default; - }; - - int LifetimeTracker::ctor_count = 0; - int LifetimeTracker::dtor_count = 0; - - TEST_CASE("DArenaVector-nontrivial-dtor", "[arena][DArenaVector]") - { - LifetimeTracker::reset(); - - { - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - vec.push_back(LifetimeTracker{1}); - vec.push_back(LifetimeTracker{2}); - vec.push_back(LifetimeTracker{3}); - - // 3 temp objects created, 3 moved into vector - // temps destroyed after push_back - REQUIRE(vec.size() == 3); - REQUIRE(LifetimeTracker::ctor_count == 6); // 3 temps + 3 moves - REQUIRE(LifetimeTracker::dtor_count == 3); // 3 temps destroyed - - // verify values - REQUIRE(vec[0].value == 1); - REQUIRE(vec[1].value == 2); - REQUIRE(vec[2].value == 3); - } - // vec destroyed, should call dtor for all 3 elements - REQUIRE(LifetimeTracker::dtor_count == 6); - } - - TEST_CASE("DArenaVector-nontrivial-resize-shrink", "[arena][DArenaVector]") - { - LifetimeTracker::reset(); - - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - vec.push_back(LifetimeTracker{1}); - vec.push_back(LifetimeTracker{2}); - vec.push_back(LifetimeTracker{3}); - vec.push_back(LifetimeTracker{4}); - vec.push_back(LifetimeTracker{5}); - - REQUIRE(vec.size() == 5); - int dtors_before_shrink = LifetimeTracker::dtor_count; - - // shrink from 5 to 2 - vec.resize(2); - - REQUIRE(vec.size() == 2); - // should have called dtor for 3 elements (indices 2,3,4) - REQUIRE(LifetimeTracker::dtor_count == dtors_before_shrink + 3); - - // remaining elements intact - REQUIRE(vec[0].value == 1); - REQUIRE(vec[1].value == 2); - } - - TEST_CASE("DArenaVector-nontrivial-clear", "[arena][DArenaVector]") - { - LifetimeTracker::reset(); - - ArenaConfig cfg { .name_ = "testarena", - .size_ = 4096 }; - DArenaVector vec = DArenaVector::map(cfg); - - vec.push_back(LifetimeTracker{1}); - vec.push_back(LifetimeTracker{2}); - vec.push_back(LifetimeTracker{3}); - - REQUIRE(vec.size() == 3); - int dtors_before_clear = LifetimeTracker::dtor_count; - - vec.clear(); - - REQUIRE(vec.size() == 0); - REQUIRE(vec.empty()); - // should have called dtor for all 3 elements - REQUIRE(LifetimeTracker::dtor_count == dtors_before_clear + 3); - } - } -} - -/* end DArenaVector.test.cpp */ diff --git a/.xo-arena/utest/DCircularBuffer.test.cpp b/.xo-arena/utest/DCircularBuffer.test.cpp deleted file mode 100644 index ea5a01be..00000000 --- a/.xo-arena/utest/DCircularBuffer.test.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/** @file DCircularBuffer.test.cpp -* - * @author Roland Conybeare, Jan 2026 - **/ - -#include "DCircularBuffer.hpp" -#include "print.hpp" -#include -#include -#include // for getpagesize() on osx - -namespace xo { - using xo::mm::DCircularBuffer; - using xo::mm::CircularBufferConfig; - using xo::mm::span; - using std::byte; - - namespace ut { - TEST_CASE("DCircularBuffer-tiny", "[arena][DCircularBuffer]") - { - // buffer works with bytes, not chars - - CircularBufferConfig cfg { .name_ = "testcbuf", - .max_capacity_ = 1 }; - DCircularBuffer buf = DCircularBuffer::map(cfg); - - REQUIRE(buf.reserved_range().size() == getpagesize()); - REQUIRE(buf.mapped_range().size() == 0); - REQUIRE(buf.occupied_range().size() == 0); - REQUIRE(buf.input_range().size() == 0); - - REQUIRE(buf.verify_ok(verify_policy::log_only())); - REQUIRE(buf.get_append_span(1).size() == getpagesize()); - REQUIRE(buf.mapped_range().size() == getpagesize()); - REQUIRE(buf.occupied_range().size() == 0); - REQUIRE(buf.input_range().size() == 0); - - auto s0 = DCircularBuffer::const_span_type::from_cstr("abcdefghijk"); - /* return value is unaccepted suffix of input */ - REQUIRE(buf.append(s0).empty()); - REQUIRE(buf.verify_ok(verify_policy::log_only())); - REQUIRE(buf.mapped_range().size() == getpagesize()); - REQUIRE(buf.occupied_range().size() == s0.size()); - REQUIRE(buf.input_range().size() == s0.size()); - - auto s1 = DCircularBuffer::const_span_type::from_cstr("lmnopq"); - REQUIRE(buf.append(s1).empty()); - REQUIRE(buf.mapped_range().size() == getpagesize()); - REQUIRE(buf.occupied_range().size() == s0.size() + s1.size()); - - REQUIRE(buf.occupied_range().to_string_view() == std::string_view("abcdefghijklmnopq")); - - buf.consume(buf.occupied_range().prefix(3)); - - REQUIRE(buf.occupied_range().to_string_view() == std::string_view("defghijklmnopq")); - } - - // TODO: test pin_range() / unpin_range() - } /*namespace ut*/ -} /*namespace xo*/ - -/* end DCircularBuffer.test.cpp */ diff --git a/.xo-arena/utest/arena_utest_main.cpp b/.xo-arena/utest/arena_utest_main.cpp deleted file mode 100644 index e0fa95fa..00000000 --- a/.xo-arena/utest/arena_utest_main.cpp +++ /dev/null @@ -1,6 +0,0 @@ -/* file arena_utest_main.cpp */ - -#define CATCH_CONFIG_MAIN -#include "catch2/catch.hpp" - -/* end arena_utest_main.cpp */ diff --git a/.xo-arena/utest/random_hash_ops.hpp b/.xo-arena/utest/random_hash_ops.hpp deleted file mode 100644 index affc56d9..00000000 --- a/.xo-arena/utest/random_hash_ops.hpp +++ /dev/null @@ -1,714 +0,0 @@ -/* @file random_hash_ops.hpp **/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace utest { - struct Util { - /* generate vector with integers [0.. n-1] */ - static std::vector vector_upto(std::uint32_t n) { - std::vector u(n); - for (std::uint32_t i = 0; i < n; ++i) - u[i] = i; - - return u; - } /*vector_upto*/ - - static std::map - map_upto(std::uint32_t n) - { - std::map m; - for(std::uint32_t i=0; i - random_permutation(uint32_t n, xo::rng::xoshiro256ss *p_rgen) { - /* vector [0 .. n-1] */ - std::vector u = vector_upto(n); - - /* shuffle to get unpredictable permutation */ - std::shuffle(u.begin(), u.end(), *p_rgen); - - return u; - } /*random_permutation*/ - }; /*Util*/ - - // TODO: move REQUIRE_OR_CAPTURE(), REQUIRE_ORFAIL() to new subsystem xo-utestutil - -/* 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 - - /** UtestTools - **/ - struct UtestTools { - /** bimodal may run twice: - * - first mode is silent, only determines success or failure. - * - second mode skipped when first mode succeeds. - * when first mode fails, second mode runs noisily with debug logging enabled - * - * goal is to get detailed information from failing test; - * more detailed than feasible from catch2 INFO() - * - * test function should use REQUIRE_ORCAPTURE() / REQUIRE_ORFAIL(). - * It should *not* use REQUIRE() or CHECK(). - * - * @p test_name banner for initial log message (only printed on 2nd pass) - * @p test_fn function to invoke test pass. - * @p n test size/id (cosmetic - printed in log messages) - **/ - static inline bool bimodal_test(std::string test_name, - std::function test_fn, - std::uint32_t n) - { - bool ok_flag = false; - - for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) { - bool debug_flag = (attention == 1); - - xo::scope log(XO_DEBUG2(debug_flag, test_name)); - - ok_flag = test_fn(debug_flag, n); - } - - return ok_flag; - } - }; - - /* compare xo-ordinaltree/utest/random_tree_ops.hpp */ - template - struct HashMapUtil : public Util { -#ifdef NOT_YET - static bool - test_clear(bool catch_flag, - Tree * p_tree) - { - bool ok_flag = true; - - REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); - - p_tree->clear(); - - REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); - REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->empty()); - REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == 0); - - return ok_flag; - } /*test_clear*/ -#endif - - static bool - random_inserts(const std::vector & keys, - bool catch_flag, - xo::rng::xoshiro256ss * p_rgen, - HashMap * p_map) - { - using xo::xtag; - - bool ok_flag = true; - - xo::scope log(XO_DEBUG(catch_flag), xtag("n-keys", keys.size())); - - REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag)); - - /* n keys */ - std::size_t n = keys.size(); - /* permute keys, remembering original position */ - std::vector> permuted_keys(n); - { - uint32_t i = 0; - for (const auto & x : keys) { - permuted_keys[i] = std::make_pair(i, x); - } - } - /* shuffle to get unpredictable insert order */ - std::shuffle(keys.begin(), keys.end(), *p_rgen); - - size_t tree_z0 = p_map->size(); - - /* insert keys in permuted order */ - { - uint32_t i = 1; - for(const auto & pr_i : permuted_keys) { - log && log(xtag("i", i), xtag("ord", pr_i.first), xtag("n", n), xtag("key", pr_i.second)); - - /* .first: iterator @ insert position - * .second: true if insert occurred (ịẹ tree size incremented) - */ - auto insert_result = p_map->insert(typename HashMap::value_type(pr_i.second, 10.0 * i)); - - REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag)); - - REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.second); - - /* verify: iterator returned by Treẹinsert(), refers to inserted key,value pair */ - log && log(xtag("iter.node", insert_result.first.node())); - - REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->first == pr_i.second); - REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->second == 10.0 * i); - - ++i; - } - } - - REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->size() == tree_z0 + n); - - return ok_flag; - } - - /* do - * n = (hi - lo) / k - * random inserts (taken from *p_rgen) into *p_rbtreẹ - * inserted keys will comprise the distinct values - * {lo, lo+k, lo+2k, ..., lo+n.k} - */ - static bool - random_inserts(std::uint32_t lo, - std::uint32_t hi, - std::uint32_t k, - bool catch_flag, - xo::rng::xoshiro256ss * p_rgen, - HashMap * p_map) - { - // TODO: rewrite in terms of 'random_inserts with explicit vector'. - - using xo::xtag; - - bool ok_flag = true; - - xo::scope log(XO_DEBUG(catch_flag), xtag("lo", lo), xtag("hi", hi), xtag("k", k)); - - auto policy = xo::verify_policy::chatty(); - - REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(policy)); - - if ((hi <= lo) || (k == 0)) - return true; - - uint32_t n = (hi - lo) / k; - - /* n keys 0..n-1 */ - std::vector u(n); - for(std::uint32_t i=0; isize(); - - /* insert keys according to permutation u */ - uint32_t i = 1; - for(uint32_t x : u) { - log && log(xtag("i", i), xtag("n", n), xtag("key", x)); - /* .first: iterator @ insert position - * .second: true if insert occurred (ịẹ tree size incremented) - */ - auto insert_result = p_map->try_insert(typename HashMap::value_type(x, 10 * x)); - - REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(policy)); - - REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.second); - - /* verify: iterator returned by Treẹinsert(), refers to inserted key,value pair */ - //log && log(xtag("iter.node", insert_result.first.node())); - REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->first == x); - REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->second == 10 * x); - - ++i; - } - - REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->size() == tree_z0 + n); - - return ok_flag; - } /*random_inserts*/ - - static bool - random_inserts(std::uint32_t n, - bool catch_flag, - xo::rng::xoshiro256ss * p_rgen, - HashMap * p_map) - { - return random_inserts(0, n, 1, catch_flag, p_rgen, p_map); - } - -#ifdef NOT_YET - /* do n random removes (taken from *p_rgen) from *p_rbtree; - * assumes *p_rbtree has keys [0 .. n-1] where n=p_rbtreẹsize - */ - static bool - random_removes(bool catch_flag, // dbg_flag - xo::rng::xoshiro256ss * p_rgen, - Tree * p_map) - { - using xo::scope; - using xo::xtag; - - bool ok_flag = true; - - xo::scope log(XO_DEBUG(catch_flag)); - - REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag)); - - uint32_t n = p_map->size(); - - /* random permutation of keys in *p_map */ - std::vector u - = random_permutation(n, p_rgen); - - log && log(xtag("remove-order", u)); - - /* will keep track of which keys remain as we move them */ - std::map m = Util::map_upto(n); - - /* remove keys in permutation order */ - std::uint32_t i = 1; - for (std::uint32_t x : u) { - log && log("iter i: removing key from n-node tree", - xtag("i", i), xtag("key", x), xtag("n", n)); - - /* remove x from tracking map m also */ - m.erase(x); - - log && log("remove key :iter ", i, "/", n, xtag("key", x)); - - p_map->erase(x); - // rbtreẹdisplay(); - REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->size() == n-i); - /* amongst other things, this guarantees that keys in *p_map - * appear in increasing order - */ - REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->verify_ok(catch_flag)); - -#ifdef NOT_YET - /* 1. rbtree should now contain all the keys in [0..n-1], - * with u[0]..u[i-1] excluded; this is the same as the - * contents of m. - */ - auto m_ix = m.begin(); - auto m_end_ix = m.end(); - auto visitor_fn = - ([&m_ix, m_end_ix] - (std::pair const & contents) - { - REQUIRE(m_ix != m_end_ix); - REQUIRE(contents.first == m_ix->second); - ++m_ix; - }); - p_map->visit_inorder(visitor_fn); -#endif - ++i; - } - - REQUIRE_ORFAIL(ok_flag, catch_flag, m.empty()); - REQUIRE_ORFAIL(ok_flag, catch_flag, p_map->size() == 0); - - log.end_scope(); - - return ok_flag; - } /*random_removes*/ -#endif - - /* Require: - * - map has keys [0..n-1], where n=map.size() - * - for each key k, associated value is dvalue+10*k - */ - static bool - random_lookups(uint32_t dvalue, - bool catch_flag, - xo::rng::xoshiro256ss * p_rgen, - HashMap & map) - { - using xo::scope; - using xo::xtag; - - xo::scope log(XO_DEBUG(catch_flag)); - - /* -> false if/when verification fails */ - bool ok_flag = true; - - REQUIRE_ORFAIL(ok_flag, catch_flag, map.verify_ok()); - - size_t n = map.size(); - std::vector u - = random_permutation(n, p_rgen); - - /* lookup keys in permutation order */ - std::uint32_t i = 1; - for (std::uint32_t x : u) { - INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x))); - - auto find_ix = map.find(x); - - REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix != map.end()); - REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->first == x); - REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->second == dvalue + x*10); - REQUIRE_ORFAIL(ok_flag, catch_flag, map.verify_ok()); - REQUIRE_ORFAIL(ok_flag, catch_flag, map.size() == n); - - ++i; - } - - REQUIRE_ORFAIL(ok_flag, catch_flag, map.size() == n); - - log.end_scope(); - - return ok_flag; - } /*random_lookups*/ - - /* Require: - * - hash has keys [0..n-1] where n=map size - * - hash value at key k is dvalue+10*k - */ - static bool - check_forward_iterator(uint32_t dvalue, - bool catch_flag, - HashMap & map) - { - using xo::scope; - using xo::xtag; - - /* -> flase if/when verification fails */ - bool ok_flag = true; - - std::size_t const n = map.size(); - - scope log(XO_DEBUG(catch_flag)); - - log && log("map with size n", xtag("n", n)); - - std::unordered_set keys; - - { - auto end_ix = map.end(); - - //log && log(xtag("end_ix", end_ix)); - - auto begin_ix = map.begin(); - auto ix = begin_ix; - - int last_key = -1; - - while (ix != end_ix) { - log && log("forward loop top" - //xtag("ix", ix) - ); - - /* verify: keys in map are in [0 .. n) */ - REQUIRE_ORFAIL(ok_flag, catch_flag, 0 <= ix->first); - REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first < n); - - /* verify: keys in map are unique */ - REQUIRE_ORFAIL(ok_flag, catch_flag, !keys.contains(ix->first)); - keys.insert(ix->first); - - REQUIRE_ORFAIL(ok_flag, catch_flag, ix->second == dvalue + 10 * ix->first); - - last_key = ix->first; - ++ix; - - log && log("forward loop bottom", - xtag("last_key", last_key) - //xtag("next ix", ix) - ); - } - - /* should have visited exactly n locations */ - REQUIRE_ORFAIL(ok_flag, catch_flag, map.size() == keys.size()); - REQUIRE_ORFAIL(ok_flag, catch_flag, ix == end_ix); - - //log && log(xtag("ix", ix), xtag("begin_ix", begin_ix)); - } - - return ok_flag; - } - - /* Require: - * - hash has keys [0..n-1] where n=map size - * - hash value at key k is dvalue+10*k - */ - static bool - check_backward_iterator(uint32_t dvalue, - bool catch_flag, - HashMap & map) - { - using xo::scope; - using xo::xtag; - - /* -> flase if/when verification fails */ - bool ok_flag = true; - - std::size_t const n = map.size(); - - scope log(XO_DEBUG(catch_flag)); - - log && log("map with size n", xtag("n", n)); - - std::unordered_set keys; - - { - auto end_ix = map.end(); - - //log && log(xtag("end_ix", end_ix)); - - auto begin_ix = map.begin(); - auto ix = end_ix; - - if (ix == begin_ix) [[unlikely]] { - return ok_flag; - } - - while (ix != begin_ix) { - log && log("backward loop top", - xtag("n", n) - ); - - --ix; - - /* verify: keys in map are in [0 .. n) */ - REQUIRE_ORFAIL(ok_flag, catch_flag, 0 <= ix->first); - REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first < n); - - log && log(xtag("ix->first", ix->first)); - - /* verify: keys in map are unique */ - REQUIRE_ORFAIL(ok_flag, catch_flag, !keys.contains(ix->first)); - keys.insert(ix->first); - - REQUIRE_ORFAIL(ok_flag, catch_flag, ix->second == dvalue + 10 * ix->first); - } - - /* should have visited exactly n locations */ - REQUIRE_ORFAIL(ok_flag, catch_flag, map.size() == keys.size()); - REQUIRE_ORFAIL(ok_flag, catch_flag, ix == begin_ix); - - //log && log(xtag("ix", ix), xtag("begin_ix", begin_ix)); - } - - return ok_flag; - } - -#ifdef NOT_YET - /* Require: - * - tree has keys [0..n-1], where n=treẹsize() - * - tree values at key k is dvalue+10*k - * - * catch_flag. true -> log to console + interact with catch2 - * false -> verify iteration behavior for return code - */ - static bool - check_bidirectional_iterator(uint32_t dvalue, - bool catch_flag, - Tree const & tree) - { - using xo::scope; - using xo::xtag; - - /* -> false if/when verification fails */ - bool ok_flag = true; - - std::size_t const n = tree.size(); - - xo::scope log(XO_DEBUG(catch_flag)); - - log && log("tree with size n", xtag("n", n)); - - { - std::size_t i = 0; - - auto end_ix = tree.end(); - - log && log(xtag("end_ix", end_ix)); - - auto begin_ix = tree.begin(); - auto ix = begin_ix; - - int last_key = -1; - - while (ix != end_ix) { - log && log("forward loop top", - xtag("i", i), - xtag("ix", ix)); - - REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first == i); - REQUIRE_ORFAIL(ok_flag, catch_flag, ix->second == dvalue + 10*i); - if(i > 0) { - REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first > last_key); - } - last_key = ix->first; - ++i; - ++ix; - - log && log("forward loop bottom", - xtag("last_key", last_key), - xtag("next ix", ix)); - } - - /* should have visited exactly n locations */ - REQUIRE_ORFAIL(ok_flag, catch_flag, i == n); - REQUIRE_ORFAIL(ok_flag, catch_flag, ix == end_ix); - - log && log(xtag("ix", ix), xtag("begin_ix", begin_ix)); - - /* now run iterator backwards, - * starting from "one past the end" - */ - if(ix != begin_ix) { - do { - --i; - --ix; - - log && log("forward backup", - xtag("i", i), - xtag("ix", ix)); - - REQUIRE_ORFAIL(ok_flag, catch_flag, ix.is_dereferenceable()); - - log && log(xtag("ix.first", (*ix).first)); - - REQUIRE_ORFAIL(ok_flag, catch_flag, (*ix).first == i); - } while (ix != begin_ix); - } - - /* should have visited exactly n locations in reverse */ - REQUIRE_ORFAIL(ok_flag, catch_flag, i == 0); - } - - /* ----- reverse iterators ----- */ - - { - std::int64_t i = n - 1; - - auto rbegin_ix = tree.rbegin(); - auto rend_ix = tree.rend(); - - auto rix = rbegin_ix; - - int last_key = -1; - - while (rix != rend_ix) { - log && log("reverse loop top", - xtag("i", i), - xtag("rix", rix)); - - REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first == i); - REQUIRE_ORFAIL(ok_flag, catch_flag, rix->second == dvalue + 10*i); - if (i < n-1) { - REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first < last_key); - } - last_key = rix->first; - --i; - ++rix; - - log && log("reverse loop bottom", - xtag("last_key", last_key), - xtag("next ix", rix)); - } - - /* should have visited exactly n locations */ - REQUIRE_ORFAIL(ok_flag, catch_flag, i == -1); - - log && log(xtag("rbegin_ix", rbegin_ix)); - - /* now run reverse iterator backwrds, - * starting from "one before the beginning" - */ - if (rix != rbegin_ix) { - do { - ++i; - --rix; - - log && log("reverse backup", - xtag("i", i), - xtag("rix", rix), - xtag("rix.first", rix->first)); - - REQUIRE_ORFAIL(ok_flag, catch_flag, (*rix).first == i); - } while (rix != rbegin_ix); - } - - /* should have visited exactly n locations in reversê2 */ - REQUIRE_ORFAIL(ok_flag, catch_flag, i == n - 1); - } - - log.end_scope(); - - return ok_flag; - } /*check_bidirectional_iterator*/ -#endif - -#ifdef NOT_YET - /* Require: - * - *p_rbtree has keys [0..n-1], where n=rbtree.size() - * - for each key k, associated value is 10*k - * - * Promise: - * - for each key k, associated value is dvalue + 10*k - */ - static bool - random_updates(uint32_t dvalue, - bool catch_flag, - Tree * p_rbtree, - xo::rng::xoshiro256ss * p_rgen) - { - using xo::scope; - using xo::xtag; - - scope log(XO_DEBUG(catch_flag)); - - /* -> false if/when check fails */ - bool ok_flag = true; - - REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->verify_ok()); - - std::size_t n = p_rbtree->size(); - std::vector u - = Util::random_permutation(n, p_rgen); - - /* update key/value pairs in permutation order */ - uint32_t i = 1; - for (uint32_t x : u) { - REQUIRE_ORFAIL(ok_flag, catch_flag, (*p_rbtree)[x] == x*10); - - (*p_rbtree)[x] = dvalue + 10*x; - - REQUIRE_ORFAIL(ok_flag, catch_flag, (*p_rbtree)[x] == dvalue + 10*x); - REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->verify_ok()); - /* assignment to existing key does not change tree size */ - REQUIRE_ORFAIL(ok_flag, catch_flag, p_rbtree->size() == n); - ++i; - } - - REQUIRE(p_rbtree->size() == n); - - return ok_flag; - } /*random_updates*/ -#endif - }; /*TreeUtil*/ -} /*namespace utest*/ - -/* end random_tree_ops.hpp */