Skip to content

Commit

Permalink
Allocator pmr conformance, tests, comments, style.
Browse files Browse the repository at this point in the history
  • Loading branch information
evoskuil committed Jul 10, 2024
1 parent 766b63b commit 99480f5
Show file tree
Hide file tree
Showing 8 changed files with 715 additions and 152 deletions.
169 changes: 99 additions & 70 deletions include/bitcoin/system/allocator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,20 @@
namespace libbitcoin {

/// No-default-fill polymorphic allocator.
/// Strictly conforms to std::pmr::polymorphic_allocator.
/// Does not default to std::pmr::get_default_resource() but
/// default_arena::get() exposes the same underlying global default allocators.
template <class Value = uint8_t>
class allocator
{
public:
template <class>
friend class allocator;

using value_type = Value;

/// construct/assign
/// -----------------------------------------------------------------------

template <class Type>
allocator(const allocator<Type>& other) NOEXCEPT
: allocator{ other.arena_ }
Expand All @@ -51,27 +56,20 @@ class allocator
{
}

allocator(arena* const value) NOEXCEPT
allocator(arena* value) NOEXCEPT
: arena_{ value }
{
}

allocator(const allocator&) = default;
allocator& operator=(const allocator&) = delete;

NODISCARD ALLOCATOR Value* allocate(size_t count)
{
const auto bytes = get_byte_size<sizeof(Value)>(count);
return static_cast<Value*>(arena_->allocate(bytes, alignof(Value)));
}

void deallocate(Value* ptr, size_t count) NOEXCEPT
{
// No need to verify multiplication overflow.
arena_->deallocate(ptr, count * sizeof(Value), alignof(Value));
}
/// allocate_bytes/deallocate_bytes
/// -----------------------------------------------------------------------
/// These allocate/deallocate bytes, without consideration of other types.

NODISCARD ALLOCATOR void* allocate_bytes(size_t bytes,
size_t align=alignof(max_align_t))
size_t align=alignof(max_align_t)) THROWS
{
return arena_->allocate(bytes, align);
}
Expand All @@ -82,8 +80,12 @@ class allocator
arena_->deallocate(ptr, bytes, align);
}

/// allocate_object/deallocate_object
/// -----------------------------------------------------------------------
/// These allocate/deallocate bytes of Type size, for count of Type.

template <class Type>
NODISCARD ALLOCATOR Type* allocate_object(size_t count=1)
NODISCARD ALLOCATOR Type* allocate_object(size_t count=1) THROWS
{
const auto bytes = get_byte_size<sizeof(Type)>(count);
return static_cast<Type*>(allocate_bytes(bytes, alignof(Type)));
Expand All @@ -95,54 +97,105 @@ class allocator
deallocate_bytes(ptr, count * sizeof(Type), alignof(Type));
}

////template <class Type>
////NODISCARD ALLOCATOR Type* new_object()
////{
//// // Default construction fill is bypassed here.
//// return allocate_object<Type>();
////}
/// allocate/deallocate
/// -----------------------------------------------------------------------
/// These allocate/deallocate bytes of Value size, for count of Value.

NODISCARD ALLOCATOR Value* allocate(size_t count) THROWS
{
return allocate_object<Value>(count);
}

void deallocate(Value* ptr, size_t count) NOEXCEPT
{
return deallocate_object<Value>(ptr, count);
}

/// new_object/delete_object
/// -----------------------------------------------------------------------
/// These allocate & construct / destruct & deallocate.

template <class Type, class... Args>
NODISCARD ALLOCATOR Type* new_object(Args&&... args)
template <class Type, class ...Args>
NODISCARD ALLOCATOR Type* new_object(Args&&... args) THROWS
{
// construct_guard ensures deallocation if construct exception.
auto ptr = allocate_object<Type>();
construct_guard<Type> guard{ arena_, ptr };
construct(ptr, std::forward<Args>(args)...);
construct<Type>(ptr, std::forward<Args>(args)...);
guard.arena_ = nullptr;
return ptr;
}

template <class Type>
void delete_object(Type* ptr) NOEXCEPT
{
destroy_in_place(*ptr);
destroy<Type>(ptr);
deallocate_object(ptr);
}

////template <class Type>
////void construct(Type*) NOEXCEPT
////{
//// // Default construction fill is bypassed here.
////}
/// construct/destroy
/// -----------------------------------------------------------------------
/// These neither allocate nor deallocate.

template <class Type, class... Args>
void construct(Type* ptr, Args&&... arguments)
// Clang is not yet C++20 compliant in terms of aggregate initialization.
// See [reviews.llvm.org/D140327] for details, resolved in future releases.
template <class Type, class ...Args>
void construct(Type* ptr, Args&&... arguments) THROWS
{
BC_PUSH_WARNING(NO_IMPLICIT_CONVERTABLE_CAST)
auto at = const_cast<void*>(static_cast<const volatile void*>(ptr));
BC_POP_WARNING()

std::apply
(
[ptr](auto&&... args)
// std::apply forwards second argument (tuple) as args to lambda.
[at](auto&&... args)
{
return ::new(
const_cast<void*>(static_cast<const volatile void*>(ptr)))
Type(std::forward<decltype(args)>(args)...);
BC_PUSH_WARNING(NO_ARRAY_TO_POINTER_DECAY)
BC_PUSH_WARNING(NO_RETURN_MOVEABLE_HEAP_OBJECT)

// Construct Type(...) in previously-allocated address 'at'.
return ::new(at) Type(std::forward<decltype(args)>(args)...);

BC_POP_WARNING()
BC_POP_WARNING()
},

// std::uses_allocator_construction_args merges *this as last arg
// if exists Type(..., const Alloc& alloc), otherwise forwards args.
std::uses_allocator_construction_args<Type>(*this,
std::forward<Args>(arguments)...)
);
}

// Container copy results in default arena!!!
// www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2875r0.pdf
// To become undeprecated in C++26 (which basically means it is not now).
template <class Type>
void destroy(Type* ptr) NOEXCEPT
{
if constexpr (std::is_array_v<Type>)
{
using element = std::iter_value_t<Type>;
if constexpr (!std::is_trivially_destructible_v<element>)
{
const auto last = *ptr + std::extent_v<Type>;
for (auto first = *ptr; first != last; ++first)
{
// Recurse until non-array or trivially destructible.
destroy(first);
}
}
}
else
{
ptr->~Type();
}
}

/// other
/// -----------------------------------------------------------------------

// polymorphic allocators do not propagate on container copy construction!
allocator select_on_container_copy_construction() const NOEXCEPT
{
return {};
Expand All @@ -153,14 +206,6 @@ class allocator
return arena_;
}

// To become undeprecated in C++26.
// www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2875r0.pdf
template <class Type>
DEPRECATED void destroy(Type* ptr) NOEXCEPT
{
destroy_in_place(*ptr);
}

friend bool operator==(const allocator& left,
const allocator& right) NOEXCEPT
{
Expand Down Expand Up @@ -190,40 +235,24 @@ class allocator
if constexpr (Size > 1u)
{
if (count > (std::numeric_limits<size_t>::max() / Size))
throw overflow_exception("allocation overflow");
throw bad_array_new_length();
}

return count * Size;
}

template <class Type>
constexpr void destroy_in_place(Type& object_) NOEXCEPT
{
if constexpr (std::is_array_v<Type>)
{
destroy_range(object_, object_ + std::extent_v<Type>);
}
else
{
object_.~Type();
}
}

template <class First, class Last>
constexpr void destroy_range(First first, const Last last) NOEXCEPT
{
using element = std::iter_value_t<First>;

// Optimization for debug mode, in release mode this is removed.
if constexpr (!std::is_trivially_destructible_v<element>)
for (; first != last; ++first)
destroy_in_place(*first);
}

private:
arena* arena_;
};

/// Same as friend equality but allows conversion to allocator.
template<class Left, class Right>
inline bool operator==(const allocator<Left>& left,
const allocator<Right>& right) NOEXCEPT
{
return left == right;
}

} // namespace libbitcoin

#endif
23 changes: 10 additions & 13 deletions include/bitcoin/system/arena.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,29 @@

namespace libbitcoin {

/// Memory arena interface, for use with our (polymorphic) allocator.
/// Memory resource interface, for use with our (polymorphic) allocator.
/// Strictly conforms to std::pmr::memory_resource.
class arena
{
public:
static constexpr auto max_align = alignof(max_align_t);
virtual ~arena() NOEXCEPT = default;

/// Allocate bytes with alignment (align must be power of 2).
/// Throws if the requested size and alignment cannot be obtained.
NODISCARD ALLOCATOR void* allocate(size_t bytes,
size_t align=max_align) THROWS
size_t align=alignof(max_align_t)) THROWS
{
// actual allocation.
auto ptr = do_allocate(bytes, align);

// non-allocating placement.
// "The standard library implementation performs no action and returns
// ptr unmodified. The behavior is undefined if this function is called
// through a placement new expression and ptr is a null pointer."
return ::operator new(bytes, ptr);
return do_allocate(bytes, align);
}

/// Deallocate allocated bytes with alignment (align must be power of 2).
void deallocate(void* ptr, const size_t bytes,
size_t align=max_align) NOEXCEPT
size_t align=alignof(max_align_t)) NOEXCEPT
{
return do_deallocate(ptr, bytes, align);
}

/// Other can deallocate memory allocated by this.
/// Other can deallocate memory allocated by this and vice versa.
NODISCARD bool is_equal(const arena& other) const NOEXCEPT
{
return do_is_equal(other);
Expand All @@ -63,6 +57,9 @@ class arena
virtual bool do_is_equal(const arena& other) const NOEXCEPT = 0;
};

/// Left can deallocate memory allocated by right and vice versa.
bool operator==(const arena& left, const arena& right) NOEXCEPT;

/// ***************************************************************************
/// BE AWARE of the risks of memory relocation. Generally speaking a custom
/// resource [arena] must be used in strict isolation (avoiding relocation).
Expand Down
3 changes: 2 additions & 1 deletion include/bitcoin/system/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ using istream_exception = boost::program_options::invalid_option_value;
using ifstream_exception = boost::program_options::reading_file;

/// Allocation.
using allocation_exception = std::bad_alloc;
////using allocation_exception = std::bad_alloc;
using bad_array_new_length = std::bad_array_new_length;


} // namespace libbitcoin
Expand Down
3 changes: 3 additions & 0 deletions include/bitcoin/system/warnings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
#define NO_READ_OVERRUN 6385
#define NO_WRITE_OVERRUN 6386
#define NO_DELETE_RAW_POINTER 26401
#define NO_RETURN_MOVEABLE_HEAP_OBJECT 26402
#define NO_UNCLEARED_OWNER_POINTER 26403
#define NO_MALLOC_OR_FREE 26408
#define NO_NEW_OR_DELETE 26409
#define NO_UNUSED_LOCAL_SMART_PTR 26414
Expand All @@ -74,6 +76,7 @@
#define NO_STATIC_CAST 26467
#define NO_CASTS_FOR_ARITHMETIC_CONVERSION 26472
#define NO_IDENTITY_CAST 26473
#define NO_IMPLICIT_CONVERTABLE_CAST 26474
#define NO_POINTER_ARITHMETIC 26481
#define NO_DYNAMIC_ARRAY_INDEXING 26482
#define NO_ARRAY_TO_POINTER_DECAY 26485
Expand Down
5 changes: 5 additions & 0 deletions src/arena.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ namespace libbitcoin {

BC_PUSH_WARNING(NO_NEW_OR_DELETE)

bool operator==(const arena& left, const arena& right) NOEXCEPT
{
return &left == &right || left.is_equal(right);
}

void* default_arena::do_allocate(size_t bytes, size_t) THROWS
{
////if (align > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
Expand Down
Loading

0 comments on commit 99480f5

Please sign in to comment.