From b5e7d9188361f04ee397ee750e814c8126a35242 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 29 Jul 2025 07:15:43 -0500 Subject: [PATCH 1/2] + README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..eeef6ca5 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# xo-alloc -- arena allocator with rudimentary GC support + +Xo-alloc is a lightweight arena allocator From 8970f51dbd36f8ed4d6c0670f3bf1b7d64fc8cba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 29 Jul 2025 07:17:35 -0500 Subject: [PATCH 2/2] + LinearAlloc + utest --- CMakeLists.txt | 28 +++++++ cmake/xo-bootstrap-macros.cmake | 35 ++++++++ cmake/xo_allocConfig.cmake.in | 7 ++ include/xo/alloc/GCAlloc.hpp | 20 +++++ include/xo/alloc/IAlloc.hpp | 57 +++++++++++++ include/xo/alloc/LinearAlloc.hpp | 90 +++++++++++++++++++++ include/xo/alloc/ListAlloc.hpp | 47 +++++++++++ src/alloc/CMakeLists.txt | 11 +++ src/alloc/LinearAlloc.cpp | 135 +++++++++++++++++++++++++++++++ utest/CMakeLists.txt | 10 +++ utest/LinearAlloc.test.cpp | 49 +++++++++++ utest/alloc_utest_main.cpp | 6 ++ 12 files changed, 495 insertions(+) create mode 100644 CMakeLists.txt create mode 100755 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_allocConfig.cmake.in create mode 100644 include/xo/alloc/GCAlloc.hpp create mode 100644 include/xo/alloc/IAlloc.hpp create mode 100644 include/xo/alloc/LinearAlloc.hpp create mode 100644 include/xo/alloc/ListAlloc.hpp create mode 100644 src/alloc/CMakeLists.txt create mode 100644 src/alloc/LinearAlloc.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/LinearAlloc.test.cpp create mode 100644 utest/alloc_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..f93e8b0c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,28 @@ +# alloc/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_alloc VERSION 0.1) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") # gcc-only! +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +# must complete definition of expression lib before configuring examples +add_subdirectory(src/alloc) + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- + +add_subdirectory(utest) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100755 index 00000000..aba31169 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# 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 (NOT XO_SUBMODULE_BUILD) + 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/cmake/xo_allocConfig.cmake.in b/cmake/xo_allocConfig.cmake.in new file mode 100644 index 00000000..f5afd837 --- /dev/null +++ b/cmake/xo_allocConfig.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(indentlog) +#find_dependency(xo_flatstring) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/alloc/GCAlloc.hpp b/include/xo/alloc/GCAlloc.hpp new file mode 100644 index 00000000..e0c6ab7a --- /dev/null +++ b/include/xo/alloc/GCAlloc.hpp @@ -0,0 +1,20 @@ +/* file GCAlloc.hpp + * + * author: Roland Conybeare, Jul 2025 + */ + +#pragma once + +namespace xo { + namespace gc { + class GC : public IAlloc { + enum class Space { A, B, N_Space }; + enum class Gen { Nursery, Tenured }; + + }; + + } /*namespace mem */ +} /*namespace xo*/ + + +/* end GCAlloc.hpp */ diff --git a/include/xo/alloc/IAlloc.hpp b/include/xo/alloc/IAlloc.hpp new file mode 100644 index 00000000..848f182c --- /dev/null +++ b/include/xo/alloc/IAlloc.hpp @@ -0,0 +1,57 @@ +/* file IAlloc.hpp + * + * author: Roland Conybeare, Jul 2025 + */ + +#pragma once + +#include +#include + +namespace xo { + template + using up = std::unique_ptr; + + namespace gc { + /** @class IAllocator + * @brief memory allocation interface with limited garbaga collector support + **/ + class IAlloc { + public: + virtual ~IAlloc() {} + + /** allocator size in bytes (up to soft limit). + * Includes unallocated mmeory + **/ + virtual std::size_t size() const = 0; + /** number of unallocated bytes available (up to soft limit) + * from this allocator + **/ + virtual std::size_t available() const = 0; + /** number of bytes allocated from this allocator **/ + virtual std::size_t allocated() const = 0; + /** true iff object at address @p x was allocated by this allocator, + * and before checkpoint + **/ + virtual bool is_before_checkpoint(const std::uint8_t * x) const = 0; + /** number of bytes allocated before @ref checkpoint **/ + virtual std::size_t before_checkpoint() const = 0; + /** number of bytes allocated since @ref checkpoint **/ + virtual std::size_t after_checkpoint() const = 0; + + /** reset allocator to empty state. **/ + virtual void clear() = 0; + /** remember allocator state. All currently-allocated addresses x + * will satisfy is_before_checkpoint(x). Subsequent allocations x + * will fail is_before_checkpoint(x), until checkpoint superseded + * by @ref clear or another call to @ref checkpoint + **/ + virtual void checkpoint() = 0; + /** allocate @p z bytes of memory. returns pointer to first address **/ + virtual std::uint8_t * alloc(std::size_t z) = 0; + }; + } /*namespace gc*/ +} /*namespace xo*/ + + +/* end IAlloc.hpp */ diff --git a/include/xo/alloc/LinearAlloc.hpp b/include/xo/alloc/LinearAlloc.hpp new file mode 100644 index 00000000..e1390dfc --- /dev/null +++ b/include/xo/alloc/LinearAlloc.hpp @@ -0,0 +1,90 @@ +/* file LinearAlloc.hpp + * + * author: Roland Conybeare, Jul 2025 + */ + +#pragma once + +#include "IAlloc.hpp" + +namespace xo { + namespace gc { + /** @class LinearAlloc + * @brief Bump allocator with fixed capacity + * + * @text + * + * before @ref release_redline_memory + * + * <-----allocated----> <-free-> <-reserved-> + * XXXXXXXXXXXXXXXXXXXX______________________ + * ^ ^ ^ ^ + * lo free redline hi + * limit + * + * after @ref release_redline_memory + * + * <-----allocated----> <--------free-------> + * XXXXXXXXXXXXXXXXXXXX______________________ + * ^ ^ ^ + * lo free hi + * limit + * @endtext + * + * TODO: rename to ArenaAlloc + **/ + class LinearAlloc : public IAlloc { + public: + ~LinearAlloc(); + + /** create allocator with capacity @p z, + * with reserved capacity @p redline_z. + **/ + static up make(std::size_t redline_z, std::size_t z); + + std::uint8_t * free_ptr() const { return free_ptr_; } + void set_free_ptr(std::uint8_t * x); + + // inherited from IAlloc... + + virtual std::size_t size() const override; + virtual std::size_t available() const override; + virtual std::size_t allocated() const override; + virtual bool is_before_checkpoint(const std::uint8_t * x) const override; + virtual std::size_t before_checkpoint() const override; + virtual std::size_t after_checkpoint() const override; + + virtual void clear() override; + virtual void checkpoint() override; + virtual std::uint8_t * alloc(std::size_t z) override; + + + private: + LinearAlloc(std::size_t rz, std::size_t z); + + private: + /** + * Invariants: + * - @ref free_ always a multiple of word size (assumed to be sizeof(void*)) + **/ + + /** allocator owns memory in range [@ref lo_, @ref hi_) **/ + std::uint8_t * lo_ = nullptr; + /** checkpoint (for GC support); divides objects into + * older (addresses below checkpoint) + * and younger (addresses above checkpoint) + **/ + std::uint8_t * checkpoint_; + /** free pointer. memory in range [@ref free_, @ref limit_) available **/ + std::uint8_t * free_ptr_ = nullptr; + /** soft limit: end of released memory **/ + std::uint8_t * limit_ = nullptr; + /** hard limit: end of allocated memory **/ + std::uint8_t * hi_ = nullptr; + }; + + } /*namespace gc*/ +} /*namespace xo*/ + + +/* end LinearAlloc.hpp */ diff --git a/include/xo/alloc/ListAlloc.hpp b/include/xo/alloc/ListAlloc.hpp new file mode 100644 index 00000000..780d2bd2 --- /dev/null +++ b/include/xo/alloc/ListAlloc.hpp @@ -0,0 +1,47 @@ +/* file ListAlloc.hpp + * + * author: Roland Conybeare, Jul 2025 + */ + +#pragma once + +#include "IAlloc.hpp" +#include +#include + +namespace xo { + namespace gc { + /** GC-compatible allocator using a linked list of buckets. + * + * GC Support: + * - reserved memory, released after call to @ref release_redline_memory. + * + * TODO: reserve address space using mmap, + * but don't commit until alloc requires it. + **/ + class ListAlloc : public IAlloc { + public: + ListAlloc(LinearAlloc* hd, + std::size_t cz, std::size_t nz; std::size_tz, + LinearAlloc* marked, bool use_redline, + bool redlined_flag, OnEmptyFn on_overflow); + ~ListAlloc(); + + static up make(std::size_t cz, std::size_t nz, + OnEmptyFn on_overflow); + + private: + std::size_t start_z_ = 0; + LinearAlloc* hd_ = nullptr; + std::size_t current_z_ = 0;; + std::size_t next_z_ = 0;; + std::size_t total_z_ = 0; + bool use_redline_ = false; + bool redlined_flag_ = false; + + }; + } /*namespace gc*/ +} /*namespace xo*/ + + +/* end ListAlloc.hpp */ diff --git a/src/alloc/CMakeLists.txt b/src/alloc/CMakeLists.txt new file mode 100644 index 00000000..cc5768d8 --- /dev/null +++ b/src/alloc/CMakeLists.txt @@ -0,0 +1,11 @@ +# alloc/CMakeLists.txt + +set(SELF_LIB xo_alloc) +set(SELF_SRCS + LinearAlloc.cpp +) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_dependency(${SELF_LIB} indentlog) + +#end CMakeLists.txt diff --git a/src/alloc/LinearAlloc.cpp b/src/alloc/LinearAlloc.cpp new file mode 100644 index 00000000..3ae57e70 --- /dev/null +++ b/src/alloc/LinearAlloc.cpp @@ -0,0 +1,135 @@ +/* file LinearAlloc.cpp + * + * author: Roland Conybeare + */ + +#include "LinearAlloc.hpp" +#include "xo/indentlog/print/tag.hpp" +#include + +namespace xo { + namespace gc { + LinearAlloc::LinearAlloc(std::size_t rz, std::size_t z) + { + this->lo_ = (new std::uint8_t [rz + z]); + this->checkpoint_ = lo_; + this->free_ptr_ = lo_; + this->limit_ = lo_ + z; + this->hi_ = limit_ + rz; + + if (!lo_) { + throw std::runtime_error(tostr("LinearAlloc: allocation failed", + xtag("size", rz + z))); + } + } + + LinearAlloc::~LinearAlloc() + { + delete [] this->lo_; + + // hygiene.. + + this->lo_ = nullptr; + this->checkpoint_ = nullptr; + this->free_ptr_ = nullptr; + this->limit_ = nullptr; + this->hi_ = nullptr; + } + + up + LinearAlloc::make(std::size_t rz, std::size_t z) + { + return up(new LinearAlloc(rz, z)); + } + + void + LinearAlloc::set_free_ptr(std::uint8_t * x) + { + assert(lo_ <= x); + assert(x < limit_); + + if (lo_ <= x && x < limit_) { + this->free_ptr_ = x; + } else { + throw std::runtime_error(tostr("LinearAllog::set_free_ptr(x): expected lo <= x < limit", + xtag("lo", lo_), xtag("x", x), xtag("limit", limit_))); + } + } + + std::size_t + LinearAlloc::size() const { + return limit_ - lo_; + } + + std::size_t + LinearAlloc::available() const { + return limit_ - free_ptr_; + } + + std::size_t + LinearAlloc::allocated() const { + return free_ptr_ - lo_; + } + + bool + LinearAlloc::is_before_checkpoint(const std::uint8_t * x) const { + return (lo_ <= x) && (x < checkpoint_); + } + + std::size_t + LinearAlloc::before_checkpoint() const + { + return checkpoint_ - lo_; + } + + std::size_t + LinearAlloc::after_checkpoint() const + { + return free_ptr_ - checkpoint_; + } + + void + LinearAlloc::clear() + { + this->checkpoint_ = lo_; + this->free_ptr_ = lo_; + this->limit_ = lo_; + } + + void + LinearAlloc::checkpoint() + { + this->checkpoint_ = this->free_ptr_; + } + + std::uint8_t * + LinearAlloc::alloc(std::size_t z) + { + /* word size for alignment */ + constexpr uint32_t c_bpw = sizeof(void*); + + std::uintptr_t free_u64 = reinterpret_cast(free_ptr_); + + assert(free_u64 % c_bpw == 0ul); + + /* round up to multiple of c_bpw */ + std::uint32_t dz = (c_bpw - (z % c_bpw)); + z += dz; + + assert(z % c_bpw == 0ul); + + std::uint8_t * retval = this->free_ptr_; + + this->free_ptr_ += z; + + if (free_ptr_ > limit_) { + return nullptr; + } + + return retval; + } + } /*namespace gc*/ +} /*namespace xo*/ + + +/* end LinearAlloc.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..e845f729 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,10 @@ +# build unittest alloc/utest + +set(SELF_EXE utest.alloc) +set(SELF_SRCS + alloc_utest_main.cpp + LinearAlloc.test.cpp) + +xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) +xo_self_dependency(${SELF_EXE} xo_alloc) +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) diff --git a/utest/LinearAlloc.test.cpp b/utest/LinearAlloc.test.cpp new file mode 100644 index 00000000..5d3a1fcd --- /dev/null +++ b/utest/LinearAlloc.test.cpp @@ -0,0 +1,49 @@ +/* @file LinearAlloc.test.cpp + * + * author: Roland Conybeare, Jul 2025 + */ + +#include "xo/alloc/LinearAlloc.hpp" +#include + +namespace xo { + using xo::gc::LinearAlloc; + + namespace ut { + + namespace { + struct testcase_alloc { + testcase_alloc(std::size_t rz, std::size_t z) + : redline_z_{rz}, arena_z_{z} {} + + std::size_t redline_z_; + std::size_t arena_z_; + + }; + + std::vector + s_testcase_v = { + testcase_alloc(0, 4096) + }; + } + + + TEST_CASE("linearalloc", "[alloc]") + { + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + const testcase_alloc & tc = s_testcase_v[i_tc]; + + auto alloc = LinearAlloc::make(tc.redline_z_, tc.arena_z_); + + REQUIRE(alloc.get()); + REQUIRE(alloc->size() == tc.arena_z_); + REQUIRE(alloc->available() == tc.arena_z_); + REQUIRE(alloc->allocated() == 0); + REQUIRE(alloc->is_before_checkpoint(alloc->free_ptr()) == false); + REQUIRE(alloc->before_checkpoint() == 0); + REQUIRE(alloc->after_checkpoint() == 0); + } + } + + } /*namespace ut */ +} /*namespace xo*/ diff --git a/utest/alloc_utest_main.cpp b/utest/alloc_utest_main.cpp new file mode 100644 index 00000000..fa384613 --- /dev/null +++ b/utest/alloc_utest_main.cpp @@ -0,0 +1,6 @@ +/* file alloc_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end alloc_utest_main.cpp */