diff --git a/CMakeLists.txt b/CMakeLists.txt index de956198..1cfaa5b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,7 +75,7 @@ set(DOX_EXCLUDE_PATTERNS [=[ # in reverse topological order i.e. dependencies first add_subdirectory(xo-cmake) -add_subdirectory(xo-facet) +add_subdirectory(xo-facet) # sep iface,data add_subdirectory(xo-indentlog) add_subdirectory(xo-allocutil) add_subdirectory(xo-refcnt) diff --git a/conf.py b/conf.py index 1cb86062..d6535873 100644 --- a/conf.py +++ b/conf.py @@ -17,7 +17,7 @@ author = 'Roland Conybeare' extensions = [ "breathe", "sphinx.ext.mathjax", # inline math "sphinx.ext.autodoc", # generate info from docstrings - "sphinxcontrib.ditaa", # diagrams-through-ascii-art + # "sphinxcontrib.ditaa", # diagrams-through-ascii-art "sphinxcontrib.plantuml", # text -> uml diagrams ] diff --git a/xo-alloc/src/alloc/ArenaAlloc.cpp b/xo-alloc/src/alloc/ArenaAlloc.cpp index 886b6a70..d7f794f3 100644 --- a/xo-alloc/src/alloc/ArenaAlloc.cpp +++ b/xo-alloc/src/alloc/ArenaAlloc.cpp @@ -374,7 +374,7 @@ namespace xo { ArenaAlloc::clear() { this->set_free_ptr(lo_); - this->limit_ = hi_; + //this->limit_ = hi_; } void diff --git a/xo-alloc2/CMakeLists.txt b/xo-alloc2/CMakeLists.txt index 8e4c7a30..9a607607 100644 --- a/xo-alloc2/CMakeLists.txt +++ b/xo-alloc2/CMakeLists.txt @@ -19,7 +19,7 @@ add_definitions(${PROJECT_CXX_FLAGS}) # ---------------------------------------------------------------- # must complete definition of expression lib before configuring examples -#add_subdirectory(src/alloc) +add_subdirectory(src/alloc2) add_subdirectory(utest) #xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/xo-alloc2/cmake/xo_alloc2Config.cmake.in b/xo-alloc2/cmake/xo_alloc2Config.cmake.in index c25a5c24..65ab5b1c 100644 --- a/xo-alloc2/cmake/xo_alloc2Config.cmake.in +++ b/xo-alloc2/cmake/xo_alloc2Config.cmake.in @@ -2,6 +2,6 @@ include(CMakeFindDependencyMacro) #find_dependency(indentlog) -#find_dependency(xo_flatstring) +find_dependency(xo_facet) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/xo-alloc2/include/xo/alloc2/AAllocator.hpp b/xo-alloc2/include/xo/alloc2/AAllocator.hpp new file mode 100644 index 00000000..5b814737 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/AAllocator.hpp @@ -0,0 +1,91 @@ +/** @file AAllocator.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "xo/facet/facet_implementation.hpp" +#include "xo/facet/typeseq.hpp" +#include + +namespace xo { + namespace mm { + using Copaque = const void *; + using Opaque = void *; + + + /** Abstract facet for allocation + * + * <----------------------------size--------------------------> + * <------------committed-----------><-------uncommitted------> + * <--allocated--> + * + * XXXXXXXXXXXXXXX___________________.......................... + * + * allocated: in use + * committed: physical memory obtained + * uncommitted: mapped in virtual memory, not backed by memory + **/ + struct AAllocator { + /** RTTI: unique id# for actual runtime data repr **/ + virtual int32_t _typeseq() = 0; + /** optional name for this allocator. + * Labeling, for diagnostics. + **/ + virtual const std::string & name(Copaque d) = 0; + /** allocator size in bytes (up to reserved limit) + * includes allocated and uncomitted memory + **/ + virtual std::size_t size(Copaque d) = 0; + /** committed size (physical addresses obtained) + **/ + virtual std::size_t committed(Copaque d) = 0; + /** true iff pointer @p in range of this allocator + **/ + virtual bool contains(Copaque d, const void * p) = 0; + + /** allocate @p z bytes of memory. **/ + virtual std::byte * alloc(Opaque d, std::size_t z) = 0; + /** reset allocator to empty state **/ + virtual void clear(Opaque d) = 0; + /** **/ + virtual void destruct_data(Opaque d) = 0; + }; + + template + struct IAllocator_Impl; + + template + struct IAllocator_Xfer : public AAllocator { + // parallel interface to AAllocator, with specific data type + using Impl = IAllocator_Impl; + + static const DRepr & _dcast(Copaque d) { return *(const DRepr *)d; } + + // from AAllocator + int32_t _typeseq() override { return s_typeseq; } + const std::string & name(Copaque d) override { return Impl::name(_dcast(d)); } + std::size_t size(Copaque d) override { return Impl::size(*(DRepr*)d); } + std::size_t committed(Copaque d) override { return Impl::committed(*(DRepr*)d); } + bool contains(Copaque d, const void * p) override { return Impl::contains(*(DRepr*)d, p); } + + std::byte * alloc(Opaque d, std::size_t z) override { return Impl::alloc(*(DRepr*)d, z); } + void clear(Opaque d) override { return Impl::clear(*(DRepr*)d); } + void destruct_data(Opaque d) override { return Impl::destruct_data(*(DRepr*)d); } + + static int32_t s_typeseq; + static bool _valid; + }; + + template + int32_t + IAllocator_Xfer::s_typeseq = facet::typeseq::id(); + + template + bool + IAllocator_Xfer::_valid = facet::valid_facet_implementation(); + } /*namespace mm*/ +} /*namespace xo*/ + +/* end AAllocator.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/DArena.hpp b/xo-alloc2/include/xo/alloc2/DArena.hpp new file mode 100644 index 00000000..77bcf04d --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/DArena.hpp @@ -0,0 +1,82 @@ +/** @file DArena.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include + +namespace xo { + namespace mm { + + /** ArenaConfig + **/ + struct ArenaConfig { + /** optional name, for diagnostics **/ + std::string name_; + /** arena size -- hard max = reserved virtual memory **/ + std::size_t size_; + /** hugepage size -- using huge pages relieves some TLB pressure + * (provided you use their full extent :) + **/ + std::size_t hugepage_z_ = 2 * 1024 * 1024; + /** true to enable debug logging **/ + bool debug_flag_ = false; + }; + + /** Arena allocator state + * + * <----------------------------size--------------------------> + * <------------committed-----------><-------uncommitted------> + * <--allocated--> + * + * XXXXXXXXXXXXXXX___________________.......................... + * + * allocated: in use + * committed: physical memory obtained + * uncommitted: mapped in virtual memory, not backed by memory + **/ + struct DArena { + /** [lo, hi) already-mapped address range **/ + DArena(const ArenaConfig & cfg, + std::byte * lo, + std::byte * hi); + + ~DArena(); + + ArenaConfig config_; + + /** size of a VM page (via getpagesize()). Likely 4k **/ + std::size_t page_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. + **/ + std::size_t committed_z_ = 0; + + /** 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; + }; + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end DArena.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/IAllocator_DArena.hpp b/xo-alloc2/include/xo/alloc2/IAllocator_DArena.hpp new file mode 100644 index 00000000..96191749 --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/IAllocator_DArena.hpp @@ -0,0 +1,30 @@ +/** @file IAllocator_DArena.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "AAllocator.hpp" +#include "DArena.hpp" + +namespace xo { + namespace mm { + + template <> + struct IAllocator_Impl { + static const std::string & name(const DArena & s) { + return s.name_; + } + + static std::size_t size(const DArena & s); + static std::size_t committed(const DArena & s); + static bool contains(const DArena & s, const void * p); + static std::byte * alloc(const DArena & s, std::size_t z); + static void clear(DArena & s); + static void destruct_data(DArena & s); + }; + + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocator_DArena.hpp */ diff --git a/xo-alloc2/include/xo/alloc2/padding.hpp b/xo-alloc2/include/xo/alloc2/padding.hpp new file mode 100644 index 00000000..c58908cd --- /dev/null +++ b/xo-alloc2/include/xo/alloc2/padding.hpp @@ -0,0 +1,57 @@ +/** @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); + + /** 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; + + z += dz; + + 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) + { + return z + alloc_padding(z, align); + } + }; + + } /*namespace mm*/ +} /*namespace xo*/ + +/* end padding.hpp */ diff --git a/xo-alloc2/src/DArena.cpp b/xo-alloc2/src/DArena.cpp new file mode 100644 index 00000000..e69de29b diff --git a/xo-alloc2/src/alloc2/CMakeLists.txt b/xo-alloc2/src/alloc2/CMakeLists.txt new file mode 100644 index 00000000..53f75425 --- /dev/null +++ b/xo-alloc2/src/alloc2/CMakeLists.txt @@ -0,0 +1,11 @@ +# alloc2/CMakeLists.txt + +set(SELF_LIB xo_alloc2) +set(SELF_SRCS + DArena.cpp + IAllocator_DArena.cpp + ) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +# note: deps here must also appear in cmake/xo_alloc2Config.cmake.in +xo_dependency(${SELF_LIB} xo_facet) diff --git a/xo-alloc2/src/alloc2/DArena.cpp b/xo-alloc2/src/alloc2/DArena.cpp new file mode 100644 index 00000000..860735f2 --- /dev/null +++ b/xo-alloc2/src/alloc2/DArena.cpp @@ -0,0 +1,149 @@ +/** @file DArena.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "xo/alloc2/AAllocator.hpp" +#include "xo/alloc2/DArena.hpp" +#include "xo/alloc2/padding.hpp" +#include +#include // for ::munmap() +#include // for ::getpagesize() + +namespace xo { + using std::byte; + + namespace mm { + + DArena::DArena(const ArenaConfig & cfg, + std::byte * lo, + std::byte * hi + ) + { + //scope log(XO_DEBUG(debug_flag), xtag("name", name)); + + this->page_z_ = getpagesize(); + + // 1. need k pagetable entries where k is lub {k | k * .page_z >= z} + // 2. base will be aligned with .page_z but likely not with .hugepage_z + // 3. bad to have misalignment, because misaligned {prefix, suffix} of [base, base+z) + // will use 4k pages instead of 2mb pages + // + // strategy: + // 4. round up z to multiple of hugepage_z_ + // 5. over-request so reserved range contains an aligned subrange of size z + // 6. unmap misaligned prefix + // 7. unmap misaligned suffix. + // 8. enable huge pages for now-aligned remainder of reserved range + // + // Z. note: rejecting inferior MAP_HUGETLB|MAP_HUGE_2MB flags on ::mmap here: + // Za. requires previously-reserved memory in /proc/sys/vm/nr_hugepages + // Zb. reserved pages permenently resident in RAM, never swapped + // Zc. memory cost incurred even if no application is using said pages + + std::size_t z = cfg.size_; + + z = padding::with_padding(z, config_.hugepage_z_); // 4. + + // 5. + byte * base = reinterpret_cast( + ::mmap(nullptr, + z + config_.hugepage_z_, + PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0)); + +#ifdef NOT_YET + log && log("acquired memory [lo,hi) using mmap", + xtag("lo", base), + xtag("z", z), + xtag("hi", reinterpret_cast(base) + z)); +#endif + + if (base == MAP_FAILED) { + assert(false); +#ifdef NOPE + throw std::runtime_error(tostr("ArenaAlloc: uncommitted allocation failed", + xtag("size", z))); +#endif + } + + byte * aligned_base = reinterpret_cast + (padding::with_padding(reinterpret_cast(base), + config_.hugepage_z_)); + + assert(reinterpret_cast(aligned_base) % config_.hugepage_z_ == 0); + assert(aligned_base >= base); + assert(aligned_base < base + config_.hugepage_z_); + + if (base < aligned_base) { + size_t prefix = aligned_base - base; + + ::munmap(base, prefix); // 6. + } + + byte * aligned_hi = aligned_base + z; + byte * hi = base + z + config_.hugepage_z_; + + if (aligned_hi < hi) { + size_t suffix = hi - aligned_hi; + + ::munmap(aligned_hi, suffix); // 7. + } + +#ifdef __linux__ + /** opt-in to huge pages, provided they're available. + * otherwise fallback gracefully + **/ + ::madvise(aligned_base, z, MADV_HUGEPAGE); // 8. +#endif + // TODO: for OSX -> need something else here. + // MAP_ALIGNED_SUPER with mmap() and/or + // use mach_vm_allocate() + // + + this->lo_ = aligned_base; + this->committed_z_ = 0; + //this->checkpoint_ = lo_; + this->free_ = lo_; + this->limit_ = lo_; + this->hi_ = lo_ + z; + + if (!lo_) { + assert(false); +#ifdef NOPE + throw std::runtime_error(tostr("ArenaAlloc: allocation failed", + xtag("size", z))); +#endif + } + +#ifdef NOPE + log && log(xtag("lo", (void*)lo_), + xtag("page_z", page_z_), + xtag("hugepage_z", hugepage_z_)); +#endif + } + + DArena::~DArena() + { + if (lo_) { + //log && log("unmap [lo,hi)", + // xtag("lo", lo_), + // xtag("z", hi_ - lo_), + // xtag("hi", hi_)); + + ::munmap(lo_, hi_ - lo_); + } + + // hygiene + lo_ = nullptr; + committed_z_ = 0; + // checkpoint_ = nullptr; + free_ = nullptr; + limit_ = nullptr; + hi_ = nullptr; + } + } +} /*namespace xo*/ + +/* end DArena.cpp */ diff --git a/xo-alloc2/src/alloc2/IAllocator_DArena.cpp b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp new file mode 100644 index 00000000..26c8901d --- /dev/null +++ b/xo-alloc2/src/alloc2/IAllocator_DArena.cpp @@ -0,0 +1,56 @@ +/** @file IAllocator_DArena.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "IAllocator_DArena.hpp" +#include + +namespace xo { + namespace mm { + + std::size_t + IAllocator_Impl::size(const DArena & s) { + return s.limit_ - s.lo_; + } + + std::size_t + IAllocator_Impl::committed(const DArena & s) { + return s.committed_z_; + } + + bool + IAllocator_Impl::contains(const DArena & s, + const void * p) + { + return (s.lo_ <= p) && (p < s.hi_); + } + + std::byte * + IAllocator_Impl::alloc(const DArena & s, + std::size_t z) + { + (void)s; + (void)z; + + // scope log(XO_DEBUG(config_.debug_flag_)); + + assert(false); + } + + void + IAllocator_Impl::clear(DArena & s) + { + s.free_ = s.lo_; + //s.checkpoint_ = s.lo_; + } + + void + IAllocator_Impl::destruct_data(DArena & s) + { + s.~DArena(); + } + } /*namespace mm*/ +} /*namespace xo*/ + +/* end IAllocator_DArena.cpp */ diff --git a/xo-alloc2/utest/CMakeLists.txt b/xo-alloc2/utest/CMakeLists.txt index 0983d6e1..1065ae81 100644 --- a/xo-alloc2/utest/CMakeLists.txt +++ b/xo-alloc2/utest/CMakeLists.txt @@ -4,11 +4,14 @@ set(UTEST_EXE utest.alloc2) set(UTEST_SRCS alloc2_utest_main.cpp + arena.test.cpp objectmodel.test.cpp) if (ENABLE_TESTING) xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) + xo_headeronly_dependency(${UTEST_EXE} xo_facet) xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) endif() + # end CMakeLists.txt diff --git a/xo-alloc2/utest/arena.test.cpp b/xo-alloc2/utest/arena.test.cpp new file mode 100644 index 00000000..ea9187f0 --- /dev/null +++ b/xo-alloc2/utest/arena.test.cpp @@ -0,0 +1,26 @@ +/** @file arena.test.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "xo/alloc2/AAllocator.hpp" +#include "xo/alloc2/DArena.hpp" +#include "xo/alloc2/IAllocator_DArena.hpp" +#include "xo/alloc2/padding.hpp" +#include + +namespace xo { + using xo::mm::IAllocator_Xfer; + using xo::mm::DArena; + + namespace ut { + TEST_CASE("IAllocator_Xfer_DArena", "[alloc2]") + { + IAllocator_Xfer xfer; + + REQUIRE(IAllocator_Xfer::_valid); + } + } /*namespace ut*/ +} /*namespace xo*/ + +/* end arena.test.cpp */ diff --git a/xo-alloc2/utest/objectmodel.test.cpp b/xo-alloc2/utest/objectmodel.test.cpp index 8aed7ad4..1fcc9a81 100644 --- a/xo-alloc2/utest/objectmodel.test.cpp +++ b/xo-alloc2/utest/objectmodel.test.cpp @@ -77,12 +77,6 @@ * self-sufficent object with convenient interface * * Application code will deal with ubox - * - * - * - * - * - * **/ #include diff --git a/xo-facet/CMakeLists.txt b/xo-facet/CMakeLists.txt index c0c0747c..60a67b5c 100644 --- a/xo-facet/CMakeLists.txt +++ b/xo-facet/CMakeLists.txt @@ -23,7 +23,13 @@ add_definitions(${PROJECT_CXX_FLAGS}) add_subdirectory(utest) #xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) +set(SELF_LIB xo_facet) +xo_add_headeronly_library(${SELF_LIB}) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + # ---------------------------------------------------------------- + # docs targets depend on other library/utest/exec targets above, # --> must come after them. # diff --git a/xo-facet/include/xo/facet/facet.hpp b/xo-facet/include/xo/facet/facet.hpp index b4cd1f09..47abae46 100644 --- a/xo-facet/include/xo/facet/facet.hpp +++ b/xo-facet/include/xo/facet/facet.hpp @@ -69,7 +69,7 @@ namespace xo { (std::is_trivially_destructible_v, "Abstract facet expected to have trivial dtor since no state"); static_assert - (requires(const T & facet) { + (requires(T & facet) { { facet._typeseq() } -> std::convertible_to; }, "Abstract facet must provide a _typeseq() method for safe downcasting"); return true; diff --git a/xo-facet/include/xo/facet/facet_implementation.hpp b/xo-facet/include/xo/facet/facet_implementation.hpp index 8565ab41..0fe68c33 100644 --- a/xo-facet/include/xo/facet/facet_implementation.hpp +++ b/xo-facet/include/xo/facet/facet_implementation.hpp @@ -5,8 +5,7 @@ #pragma once -//#include "xo/facet/facet.hpp" - +#include "xo/facet/facet.hpp" #include #include diff --git a/xo-facet/include/xo/facet/obj.hpp b/xo-facet/include/xo/facet/obj.hpp index f23ae798..82d4cdab 100644 --- a/xo-facet/include/xo/facet/obj.hpp +++ b/xo-facet/include/xo/facet/obj.hpp @@ -52,16 +52,15 @@ namespace xo { /** copy constructor **/ template - obj(const obj && other) : Super() + obj(const obj && other) + requires (std::is_convertible_v + || std::is_same_v) + : Super() { if constexpr (std::is_convertible_v) { this->data_ = other.data_; - } else if constexpr (std::is_same_v) { - this->from_data(other.data_); } else { - /* still need something for downcasting */ - - static_assert(false, "expect DOther compatible with DRepr"); + this->from_data(other.data_); } } diff --git a/xo-facet/utest/objectmodel.test.cpp b/xo-facet/utest/objectmodel.test.cpp index 2156e619..0468f6b8 100644 --- a/xo-facet/utest/objectmodel.test.cpp +++ b/xo-facet/utest/objectmodel.test.cpp @@ -59,12 +59,10 @@ namespace xo { // parallel interface to AComplex, but with specific data type using Impl = IComplex_Impl; - // from FacetRttiShim - - virtual int32_t _typeseq() const { return s_typeseq; } - // from AComplex + virtual int32_t _typeseq() const final override { return s_typeseq; } + virtual double xcoord(void * data) const final override { return Impl::xcoord(*(DRepr*)data); } virtual double ycoord(void * data) const final override { return Impl::ycoord(*(DRepr*)data); } virtual double argument(void * data) const final override { return Impl::argument(*(DRepr*)data); } @@ -98,7 +96,7 @@ namespace xo { * such as IComplex_RectCoords or IComplex_PolarCoords. **/ struct IComplex_Any : public AComplex { - virtual int32_t _typeseq() const { return s_typeseq; } + virtual int32_t _typeseq() const final override { return s_typeseq; } virtual double xcoord(void *) const final override { assert(false); return 0.0; } virtual double ycoord(void *) const final override { assert(false); return 0.0; }