libdpf/include/dpf/aligned_allocator.hpp

229 lines
9.4 KiB
C++

/// @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 <ryan.henry@ucalgary.ca>
/// @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 <cstddef>
#include <cstdlib>
#include <type_traits>
#include <memory>
#include <limits>
#include <new>
#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 <typename T,
std::size_t Alignment = alignof(T)>
class aligned_allocator
{
private:
/// @brief a `deleter` functor for use by `std::unique_ptr<T[]>` to free
/// memory allocated when the `std::unique_ptr<T[]>` was
/// constructed
template <typename Pointer>
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<value_type[], deleter<pointer>>;
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 <typename U, size_type A = alignment> struct rebind
{
using other = aligned_allocator<U, A>;
};
/// @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<size_type>::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<pointer>(ptr));
}
/// @brief allocates and constructs a `std::unqiue_ptr<T[]>` 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<T[]>` that owns it.
/// @param num the number of instances of `T` to allocate storage for
/// @return An `std::unique_ptr<T[]>` 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<pointer>(
__builtin_assume_aligned(ptr, alignment));
}
};
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_ALIGNED_ALLOCATOR_HPP__