xo-alloc + xo-allocutil: refactor to shrink dep surface area
This commit is contained in:
parent
949e2ca375
commit
7a2dc433bc
7 changed files with 466 additions and 0 deletions
43
CMakeLists.txt
Normal file
43
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# xo-allocutil/CMakeLists.txt
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(xo_allocutil VERSION 0.1)
|
||||
enable_language(CXX)
|
||||
|
||||
# common XO macros (see github:Rconybea/xo-cmake)
|
||||
include(GNUInstallDirs)
|
||||
include(cmake/xo-bootstrap-macros.cmake)
|
||||
|
||||
xo_cxx_toplevel_options3()
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# c++ settings
|
||||
|
||||
set(PROJECT_CXX_FLAGS "")
|
||||
add_definitions(${PROJECT_CXX_FLAGS})
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# output targets
|
||||
|
||||
#add_subdirectory(utest)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# header-only library
|
||||
|
||||
set(SELF_LIB xo_allocutil)
|
||||
xo_add_headeronly_library(${SELF_LIB})
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
#
|
||||
xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets)
|
||||
# (note: ..Targets from xo_install_library2())
|
||||
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# input dependencies
|
||||
|
||||
# NOTE: dependency set here must be kept consistent with allocutil/cmake/xo_allocutilConfig.cmake.in
|
||||
|
||||
# xo-allocutil is also header-only
|
||||
#xo_headeronly_dependency(${SELF_LIB} randomgen)
|
||||
14
cmake/xo-bootstrap-macros.cmake
Normal file
14
cmake/xo-bootstrap-macros.cmake
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix"))
|
||||
# default to typical install location for xo-project-macros
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake)
|
||||
endif()
|
||||
|
||||
if (NOT XO_SUBMODULE_BUILD)
|
||||
message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
|
||||
message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
|
||||
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)
|
||||
6
cmake/xo_allocutilConfig.cmake.in
Normal file
6
cmake/xo_allocutilConfig.cmake.in
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@PACKAGE_INIT@
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
#find_dependency(randomgen)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
||||
154
include/xo/allocutil/IAlloc.hpp
Normal file
154
include/xo/allocutil/IAlloc.hpp
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/** @file IAlloc.hpp
|
||||
*
|
||||
* @author: Roland Conybeare, Jul 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
template <typename T>
|
||||
using up = std::unique_ptr<T>;
|
||||
|
||||
class IObject;
|
||||
|
||||
namespace gc {
|
||||
/** @class IAllocator
|
||||
* @brief Abstract API for allocation interface
|
||||
*
|
||||
* Garbage collector support methods:
|
||||
* - checkpoint()
|
||||
* - assign_member()
|
||||
* - alloc_gc_copy()
|
||||
*
|
||||
* See class GC for copying incremental collector.
|
||||
* See class ArenaAlloc for arena allocator
|
||||
**/
|
||||
class IAlloc {
|
||||
public:
|
||||
virtual ~IAlloc() {}
|
||||
|
||||
static inline std::uint32_t alloc_padding(std::size_t z) {
|
||||
/* word size for alignment */
|
||||
constexpr uint32_t c_bpw = sizeof(std::uintptr_t);
|
||||
|
||||
/* 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::uint32_t dz = (c_bpw - (z % c_bpw)) % c_bpw;
|
||||
z += dz;
|
||||
|
||||
return dz;
|
||||
}
|
||||
|
||||
static inline std::size_t with_padding(std::size_t z) {
|
||||
return z + alloc_padding(z);
|
||||
}
|
||||
|
||||
/** optional name for this allocator; labelling for diagnostics **/
|
||||
virtual const std::string & name() const = 0;
|
||||
/** allocator size in bytes (up to reserved limit)
|
||||
* Includes unallocated mmeory
|
||||
**/
|
||||
virtual std::size_t size() const = 0;
|
||||
/** committed size in bytes **/
|
||||
virtual std::size_t committed() 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 pointer x comes from this allocator **/
|
||||
virtual bool contains(const void * x) const = 0;
|
||||
/** true iff object at address @p x was allocated by this allocator,
|
||||
* and before checkpoint
|
||||
**/
|
||||
virtual bool is_before_checkpoint(const void * 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;
|
||||
/** @return true iff debug logging enabled **/
|
||||
virtual bool debug_flag() const = 0;
|
||||
|
||||
/** remember allocator state. All currently-allocated addresses xo
|
||||
* 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::byte * alloc(std::size_t z) = 0;
|
||||
/** reset allocator to empty state. **/
|
||||
virtual void clear() = 0;
|
||||
|
||||
// ----- GC-specific methods -----
|
||||
|
||||
/** true iff this allocator owns object at address @p src.
|
||||
* Use to assist Object::_shallow_move
|
||||
**/
|
||||
virtual bool check_owned(IObject * /*src*/) const { return false; }
|
||||
/** true iff object at address @p src must move as part of
|
||||
* in-progress collection phase
|
||||
**/
|
||||
virtual bool check_move(IObject * /*src*/) const { return false; }
|
||||
/** write barrier for collector. perform assignment
|
||||
* @code
|
||||
* *lhs = rhs
|
||||
* @endcode
|
||||
* plus additional book keeping if needed (e.g. in @ref GC)
|
||||
* Default implementation just does the assignment.
|
||||
**/
|
||||
virtual void assign_member(IObject * /*parent*/,
|
||||
IObject ** lhs,
|
||||
IObject * rhs) { *lhs = rhs; }
|
||||
/** allocate @p z bytes for copy of object at @p src.
|
||||
* Only used in @ref GC. Default implementation asserts and returns nullptr
|
||||
**/
|
||||
virtual std::byte * alloc_gc_copy(std::size_t /*z*/, const void * /*src*/) {
|
||||
// LCOV_EXCL_START
|
||||
//assert(false);
|
||||
return nullptr;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
};
|
||||
} /*namespace gc*/
|
||||
|
||||
class MMPtr {
|
||||
public:
|
||||
explicit MMPtr(gc::IAlloc * mm) : mm_{mm} {}
|
||||
|
||||
gc::IAlloc * mm_ = nullptr;
|
||||
};
|
||||
} /*namespace xo*/
|
||||
|
||||
inline void * operator new (std::size_t z, const xo::MMPtr & mmp) {
|
||||
return mmp.mm_->alloc(z);
|
||||
}
|
||||
|
||||
//inline void operator delete (void * p, const MMPtr & mmp) {
|
||||
// mmp.mm_->free(reinterpret_cast<std::byte *>(p));
|
||||
//}
|
||||
|
||||
inline void * operator new[] (std::size_t z, const xo::MMPtr & mmp) {
|
||||
return mmp.mm_->alloc(z);
|
||||
}
|
||||
|
||||
//inline void operator delete[] (void * p, const MMPtr & mmp) {
|
||||
// mmp.mm_->free(reinterpret_cast<std::byte *>(p));
|
||||
//}
|
||||
|
||||
|
||||
/* end IAlloc.hpp */
|
||||
97
include/xo/allocutil/IObject.hpp
Normal file
97
include/xo/allocutil/IObject.hpp
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/** @file IObject.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Nov 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
namespace gc { class IAlloc; }
|
||||
|
||||
/** @class IObject
|
||||
* @brief Base interface for GC interaction
|
||||
*
|
||||
* Classes that can be collected inherit this api
|
||||
* indirectly via xo::Object (see xo/alloc/Object.hpp)
|
||||
**/
|
||||
class IObject {
|
||||
public:
|
||||
/** true iff this object represents a forwarding pointer.
|
||||
* Forwarding pointers are exclusively created by the garbage collector;
|
||||
* forwarding pointers (and only forwarding pointers) return true here.
|
||||
**/
|
||||
virtual bool _is_forwarded() const { return false; }
|
||||
|
||||
/** offset for uncommon situation where pointer address is offset from object
|
||||
* base address
|
||||
**/
|
||||
virtual IObject * _offset_destination(IObject * src) const { return src; };
|
||||
|
||||
/** replace this object with a forwarding pointer referring to @p dest.
|
||||
**/
|
||||
virtual void _forward_to(IObject * dest) = 0;
|
||||
|
||||
/** if this object represents a forwarding pointer, return its new location.
|
||||
* forwarding pointers belong to the garbage collector implementation.
|
||||
* (if you have to ask -- no, your class is not a forwarding pointer)
|
||||
* all other objects return nullptr here.
|
||||
**/
|
||||
virtual IObject * _destination() { return nullptr; }
|
||||
|
||||
/** return amount of storage (including padding) consumed by this object,
|
||||
* excluding immediate Object-pointer children
|
||||
**/
|
||||
virtual std::size_t _shallow_size() const = 0;
|
||||
|
||||
/** if subject is allocated by GC:
|
||||
* - create copy C in to-space
|
||||
* - destination C will be nursery|tenured depending on location of this.
|
||||
* else
|
||||
* - return this to disengage from GC
|
||||
*
|
||||
* Require: @ref mm is an instance of @ref gc::GC
|
||||
**/
|
||||
virtual IObject * _shallow_copy(gc::IAlloc * gc) const = 0;
|
||||
|
||||
/** update child pointers that refer to forwarding pointers,
|
||||
* replacing them with the correct destination.
|
||||
* See @ref Object::deep_move
|
||||
*
|
||||
* this gray object, located in to-space.
|
||||
* fwd1 forwarding objects.
|
||||
* Located in from-space. Invalid at end of GC cycle.
|
||||
* p1,p2 source pointers.
|
||||
* D1,D2 already-forwarded objects. located in to-space.
|
||||
*
|
||||
* before:
|
||||
* this fwd1
|
||||
* +----+ +-+
|
||||
* | p1 ----->|x|-------> D1
|
||||
* | | +-+
|
||||
* | |
|
||||
* | p2 ----------------> D2
|
||||
* +----+
|
||||
*
|
||||
* after:
|
||||
* this
|
||||
* +----+
|
||||
* | p1 ----------------> D1
|
||||
* | |
|
||||
* | |
|
||||
* | p2 ----------------> D2
|
||||
* +----+
|
||||
*
|
||||
* this is now white
|
||||
*
|
||||
* @return shallow size of *this. Must exactly match the amount of memory in to-space
|
||||
* allocated by @ref _shallow_move
|
||||
*
|
||||
**/
|
||||
virtual std::size_t _forward_children(gc::IAlloc * gc) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
/* end IObject.hpp */
|
||||
75
include/xo/allocutil/gc_allocator_traits.hpp
Normal file
75
include/xo/allocutil/gc_allocator_traits.hpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/** @file gc_allocator_traits.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Nov 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
|
||||
namespace xo {
|
||||
namespace gc {
|
||||
/** Extended version of
|
||||
* std::allocator_traits<Allocator>
|
||||
* Introduces additional i/face methods
|
||||
* for garbage-collector-enabled allocators
|
||||
*
|
||||
* allocator A can identify itself as a copying collector:
|
||||
* 1. provide A::object_interface
|
||||
* A::object_interface = xo::Object
|
||||
* 2. provide A::is_incremental_collector
|
||||
* A::is_incremental_collector = std::true_type
|
||||
* Collectible objects must:
|
||||
* 2a. inherit A::object_interface
|
||||
* 2b. implement A::object_interface::_shallow_size()
|
||||
* 2c. implement A::object_interface::_shallow_copy(alloc)
|
||||
* 2d. implement A::object_interface::_forward_children(alloc)
|
||||
**/
|
||||
template <typename Allocator>
|
||||
struct gc_allocator_traits : std::allocator_traits<Allocator> {
|
||||
using super = std::allocator_traits<Allocator>;
|
||||
using pointer = typename super::pointer;
|
||||
using value_type = typename super::value_type;
|
||||
|
||||
// default: allocator A fallback to standard non-gc allocator behavior
|
||||
template <typename A, typename = void>
|
||||
struct is_incremental_collector : std::false_type {};
|
||||
|
||||
// opt-in: A provides nested type 'is_incremental_collector':
|
||||
// struct A {
|
||||
// using is_incremental_collector = std::true_type;
|
||||
// };
|
||||
template <typename A>
|
||||
struct is_incremental_collector<A, std::void_t<typename A::is_incremental_collector>> :
|
||||
A::is_incremental_collector {};
|
||||
|
||||
// default: empty object interface.
|
||||
// classes that want to conditionally support GC
|
||||
// (e.g. see xo::tree::RedBlackTree, xo::tree::Node
|
||||
// in xo-ordinal-tree)
|
||||
// can inherit
|
||||
// gc_allocator_traits<Allocator>::template object_interface<Allocator>
|
||||
//
|
||||
template <typename A, typename = void>
|
||||
struct object_interface {};
|
||||
|
||||
// specialization when A provides gc_object_interface
|
||||
template <typename A>
|
||||
struct object_interface<A, std::void_t<typename A::gc_object_interface>>
|
||||
: A::gc_object_interface {};
|
||||
|
||||
/** true iff this allocator advertises itself as an incremental collector
|
||||
* allocator will include:
|
||||
*
|
||||
* struct GC {
|
||||
* using is_incremental_collector = std::true_type;
|
||||
* };
|
||||
**/
|
||||
static inline constexpr bool is_incremental_collector_v = is_incremental_collector<Allocator>::value;
|
||||
|
||||
};
|
||||
} /*namespace gc*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end gc_allocator_traits.hpp */
|
||||
77
include/xo/allocutil/gc_ptr.hpp
Normal file
77
include/xo/allocutil/gc_ptr.hpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/** @file gc_ptr.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Nov 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
template <typename T>
|
||||
class gc_ptr;
|
||||
|
||||
template <typename T>
|
||||
using gp = gc_ptr<T>;
|
||||
|
||||
/** wrapper for a pointer to garbage-collector-eligible T.
|
||||
* Application code will usually use the alias template gp<T>
|
||||
**/
|
||||
template <typename T>
|
||||
class gc_ptr {
|
||||
public:
|
||||
using element_type = T;
|
||||
|
||||
public:
|
||||
gc_ptr() = default;
|
||||
gc_ptr(T * p) : ptr_{p} {}
|
||||
gc_ptr(const gc_ptr & x) : ptr_{x.ptr_} {}
|
||||
|
||||
/** create from gc_ptr to some related type @tparam S **/
|
||||
template <typename S>
|
||||
gc_ptr(const gc_ptr<S> & x) : ptr_{x.ptr()} {}
|
||||
|
||||
/** runtime downcast. shorthand for dynamic_cast<T*> **/
|
||||
template <typename S>
|
||||
static gc_ptr<T> from(const gc_ptr<S> & x) { return gc_ptr<T>{dynamic_cast<T*>(x.ptr())}; }
|
||||
|
||||
/** convenience for static asserts **/
|
||||
static constexpr bool is_gc_ptr = true;
|
||||
/** see also: xo/refcnt/Refcounted.hpp **/
|
||||
static constexpr bool is_rc_ptr = false;
|
||||
|
||||
static bool is_eq(gc_ptr x1, gc_ptr x2) {
|
||||
std::uintptr_t u1 = reinterpret_cast<std::uintptr_t>(x1.ptr());
|
||||
std::uintptr_t u2 = reinterpret_cast<std::uintptr_t>(x2.ptr());
|
||||
|
||||
// multiple inheritance shenanigans.
|
||||
// (allow interface pointers separated by one pointer)
|
||||
|
||||
if (u1 >= u2)
|
||||
return (u1 <= u2 + sizeof(std::uintptr_t));
|
||||
else
|
||||
return (u2 <= u1 + sizeof(std::uintptr_t));
|
||||
}
|
||||
|
||||
/** (for consistency's sake) **/
|
||||
T * get() const { return ptr_; }
|
||||
|
||||
T * ptr() const { return ptr_; }
|
||||
T ** ptr_address() { return &ptr_; }
|
||||
|
||||
bool is_null() const { return ptr_ == nullptr; }
|
||||
void make_null() { ptr_ = nullptr; }
|
||||
|
||||
void assign_ptr(T * x) { ptr_ = x; }
|
||||
|
||||
gc_ptr & operator=(const gc_ptr & x) { ptr_ = x.ptr(); return *this; }
|
||||
|
||||
T * operator->() const { return ptr_; }
|
||||
T & operator*() const { return *ptr_; }
|
||||
|
||||
private:
|
||||
T * ptr_ = nullptr;
|
||||
};
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end gc_ptr.hpp */
|
||||
Loading…
Add table
Add a link
Reference in a new issue