/// @file dpf/aligned_allocator.hpp /// @brief defines an allocator that aligns memory allocations to a specified alignment /// @details The `dpf::aligned_allocator` class template is used to allocate /// uninitialized memory with a specified alignment for all `libdpf++` /// buffers and memoizers, if no user-specified allocator is /// provided. It is stateless, so all instances of the allocator are /// interchangeable. The alignment is specified by the Alignment /// parameter, which must be a power of two (default: /// `dpf::utils::max_align_v`). /// /// The allocator supports the `dpf::aligned_allocator::allocate()` function for allocating /// aligned, yet uninitialized memory and the `dpf::aligned_allocator::deallocate()` function /// for freeing the same. It also includes a convenient /// `dpf::aligned_allocator::allocate_unique_ptr()` function that returns a `std::unique_ptr` /// to the output of a call to `dpf::aligned_allocator::allocate()`. /// @author Ryan Henry /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref license) for details. #ifndef LIBDPF_INCLUDE_DPF_ALIGNED_ALLOCATOR_HPP__ #define LIBDPF_INCLUDE_DPF_ALIGNED_ALLOCATOR_HPP__ #include #include #include #include #include #include #include "hedley/hedley.h" #include "simde/simde/x86/avx2.h" namespace dpf { /// @brief an allocator that allocates aligned memory /// @details The `dpf::aligned_allocator` class template is the default memory /// allocator used by all `libdpf++` buffers and memoizers, if no /// user-specified allocator is provided. It allocates uninitialized /// storage whose alignment is specified by `Alignment` and whose /// size is an integral multiple of `sizeof(T)`. The allocator is /// stateless; that is, all instances of the given allocator are /// interchangeable and can deallocate memory allocated by any other /// instance of the same allocator type. /// @tparam T the type to allocate /// @tparam Alignment specifies the alignment (default: `dpf::utils::max_align`).' /// The program is ill-formed if `Alignment` is not a power of 2. template class aligned_allocator { private: /// @brief a `deleter` functor for use by `std::unique_ptr` to free /// memory allocated when the `std::unique_ptr` was /// constructed /// @tparam Pointer pointer type stored in the deleter template struct deleter { HEDLEY_NO_THROW constexpr void operator()(Pointer p) const noexcept { free(p); } }; public: using value_type = T; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using pointer = value_type *; using unique_ptr = std::unique_ptr>; using const_pointer = const value_type *; using reference = value_type &; using const_reference = const value_type &; static constexpr size_type alignment = Alignment; /// @brief class whose member `other` is a typedef of /// `dpf::aligned_allocator` for some type `U` with alignment `A`. /// @tparam U the type to rebind to /// @tparam A the alignment of the rebound allocator template struct rebind { using other = aligned_allocator; }; /// @name Constructors /// @brief Constructs the default allocator. Since the default allocator /// is stateless, the constructors have no visible effect. /// @{ /// @brief Default constructor /// @details Constructs an instance of `dpf::aligned_allocator`. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr aligned_allocator() noexcept = default; /// @brief Copy constructor /// @details Constructs an instance of `dpf::aligned_allocator` from another /// using copy semantics. /// @param other another `dpf::aligned_allocator` to construct with HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr aligned_allocator(const aligned_allocator & other) noexcept = default; /// @brief Move constructor /// @details Constructs an instance of `dpf::aligned_allocator` from another /// using move semantics. /// @param other another `dpf::aligned_allocator` to construct with HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr aligned_allocator(aligned_allocator && other) noexcept = default; /// @} /// @{ aligned_allocator & operator=(const aligned_allocator &) noexcept = default; aligned_allocator & operator=(aligned_allocator &&) noexcept = default; /// @} /// @brief D'tor /// @details Destroys an instance of `dpf::aligned_allocator`. ~aligned_allocator() = default; /// @brief returns the largest supported allocation size /// @details Returns the maximum theoretically possible value of `num`, /// for which the call `allocate(num)` could succeed. /// @note This function returns the maximum number of elements that can /// be allocated, not the maximum allocation size in bytes /// @return The maximum supported allocation size. HEDLEY_NO_THROW constexpr size_type max_size() const noexcept { return std::numeric_limits::max() / sizeof(value_type); } /// @brief allocates aligned, yet uninitialized storage /// @details Allocates `num * sizeof(T)` bytes of uninitialized /// storage by invoking /// `std::aligned_alloc(alignment, num * sizeof(T))`. /// @param num the number of instances of `T` to allocate storage for /// @return Pointer to the first element of an array of `num` instaces /// of type `T` whose elements have not been constructed yet. /// @throws std::bad_array_new_length if `max_size() < num` /// @throws std::bad_alloc if allocation fails. HEDLEY_WARN_UNUSED_RESULT HEDLEY_MALLOC HEDLEY_RETURNS_NON_NULL constexpr pointer allocate(size_type num, const void * /*hint*/ = nullptr) const { if (max_size() < num) { throw std::bad_array_new_length(); } // C11 `aligned_alloc` requires the size to be a multiple of the // alignment. Round up so odd element counts (or odd sizeof(T)) // do not pass a non-conforming size and corrupt the heap. const size_type bytes = num * sizeof(T); const size_type aligned_bytes = (bytes + (alignment - 1)) & ~(alignment - 1); void * ptr = std::aligned_alloc(alignment, aligned_bytes); if (ptr == nullptr) { throw std::bad_alloc(); } return assume_aligned(static_cast(ptr)); } /// @brief allocates and constructs a `std::unqiue_ptr` to aligned, yet /// uninitialized storage /// @details Allocates `num * sizeof(T)` bytes of uninitialized /// storage by invoking `allocate(size_type, const void *)` and /// returns a `std::unique_ptr` that owns it. /// @param num the number of instances of `T` to allocate storage for /// @return An `std::unique_ptr` owning the pointer to the first /// element of an array of `num` instaces of type `T` whose elements /// have not been constructed yet. /// @throws std::bad_array_new_length if `max_size() < num` /// @throws std::bad_alloc if allocation fails. HEDLEY_ALWAYS_INLINE constexpr auto allocate_unique_ptr(size_type num) const { return unique_ptr{allocate(num)}; } /// @brief deallocates storage /// @details Deallocates the storage referenced by the pointer `p`, /// which must be a pointer obtained by an earlier call to /// `allocate()`. /// @param p pointer obtained from allocate() HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr void deallocate(pointer p, size_type /*num*/ = 0) const noexcept { free(p); } /// @brief informs the compiler that a pointer is aligned /// @details Informs the implementation that the object ptr points to is /// aligned to at least `alignment`. The implementation may use /// this information to generate more efficient code, but it might /// only make this assumption if the object is accessed via the /// return value of `assume_aligned`. /// /// The behavior is undefined if `ptr` does not point to an object /// of type `T` (ignoring cv-qualification at every level), or if the /// object's alignment is not at least `Alignment`. /// @note It is up to the program to ensure that the alignment assumption /// actually holds. A call to `assume_aligned` does not cause the /// compiler to verify or enforce this. /// @param ptr the pointer /// @return `ptr` HEDLEY_WARN_UNUSED_RESULT HEDLEY_ALWAYS_INLINE HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_NON_NULL(1) constexpr static auto assume_aligned(pointer ptr) noexcept { return static_cast( __builtin_assume_aligned(ptr, alignment)); } }; } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_ALIGNED_ALLOCATOR_HPP__