diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e4c7a3..9a60760 100644 --- a/CMakeLists.txt +++ b/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/cmake/xo_alloc2Config.cmake.in b/cmake/xo_alloc2Config.cmake.in index c25a5c2..65ab5b1 100644 --- a/cmake/xo_alloc2Config.cmake.in +++ b/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/include/xo/alloc2/AAllocator.hpp b/include/xo/alloc2/AAllocator.hpp new file mode 100644 index 0000000..5b81473 --- /dev/null +++ b/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/include/xo/alloc2/DArena.hpp b/include/xo/alloc2/DArena.hpp new file mode 100644 index 0000000..77bcf04 --- /dev/null +++ b/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/include/xo/alloc2/IAllocator_DArena.hpp b/include/xo/alloc2/IAllocator_DArena.hpp new file mode 100644 index 0000000..9619174 --- /dev/null +++ b/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/include/xo/alloc2/padding.hpp b/include/xo/alloc2/padding.hpp new file mode 100644 index 0000000..c58908c --- /dev/null +++ b/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/src/DArena.cpp b/src/DArena.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/alloc2/CMakeLists.txt b/src/alloc2/CMakeLists.txt new file mode 100644 index 0000000..53f7542 --- /dev/null +++ b/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/src/alloc2/DArena.cpp b/src/alloc2/DArena.cpp new file mode 100644 index 0000000..860735f --- /dev/null +++ b/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/src/alloc2/IAllocator_DArena.cpp b/src/alloc2/IAllocator_DArena.cpp new file mode 100644 index 0000000..26c8901 --- /dev/null +++ b/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/utest/CMakeLists.txt b/utest/CMakeLists.txt index 0983d6e..1065ae8 100644 --- a/utest/CMakeLists.txt +++ b/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/utest/arena.test.cpp b/utest/arena.test.cpp new file mode 100644 index 0000000..ea9187f --- /dev/null +++ b/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/utest/objectmodel.test.cpp b/utest/objectmodel.test.cpp index 8aed7ad..1fcc9a8 100644 --- a/utest/objectmodel.test.cpp +++ b/utest/objectmodel.test.cpp @@ -77,12 +77,6 @@ * self-sufficent object with convenient interface * * Application code will deal with ubox - * - * - * - * - * - * **/ #include