Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allocator pmr conformance, tests, comments, style. #1497

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading