diff --git a/include/realtime_memory/memory_resources.h b/include/realtime_memory/memory_resources.h index 854e2a0..ecf31e4 100644 --- a/include/realtime_memory/memory_resources.h +++ b/include/realtime_memory/memory_resources.h @@ -4,8 +4,6 @@ #pragma once #include "pmr_includes.h" -PMR_DIAGNOSTIC_PUSH - //============================================================================== namespace cradle::pmr { @@ -626,5 +624,3 @@ inline bool unsynchronized_pool_resource::is_oversized (std::size_t bytes, std:: } } // namespace cradle::pmr - -PMR_DIAGNOSTIC_POP diff --git a/include/realtime_memory/pmr_includes.h b/include/realtime_memory/pmr_includes.h index cdcf4db..cac3184 100644 --- a/include/realtime_memory/pmr_includes.h +++ b/include/realtime_memory/pmr_includes.h @@ -6,28 +6,17 @@ #include #if defined (__apple_build_version__) - #define USE_EXPERIMENTAL_PMR __apple_build_version__ < 15000000 || __MAC_OS_X_VERSION_MIN_REQUIRED < 140000 + #define USE_PMR_SHIM 1 #elif defined (__clang__) - #define USE_EXPERIMENTAL_PMR _LIBCPP_VERSION < 1600 + #define USE_PMR_SHIM _LIBCPP_VERSION < 1600 #else - #define USE_EXPERIMENTAL_PMR 0 + #define USE_PMR_SHIM 0 #endif -#if USE_EXPERIMENTAL_PMR - #include - namespace std_pmr = std::experimental::pmr; +#if USE_PMR_SHIM + #include "pmr_shim.h" + namespace std_pmr = rtm_pmr; #else #include namespace std_pmr = std::pmr; #endif - -#if defined (__apple_build_version__) && USE_EXPERIMENTAL_PMR - #define PMR_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") - #define PMR_DIAGNOSTIC_POP \ - _Pragma("clang diagnostic pop") -#else - #define PMR_DIAGNOSTIC_PUSH - #define PMR_DIAGNOSTIC_POP -#endif diff --git a/include/realtime_memory/pmr_shim.h b/include/realtime_memory/pmr_shim.h new file mode 100644 index 0000000..6b243f6 --- /dev/null +++ b/include/realtime_memory/pmr_shim.h @@ -0,0 +1,266 @@ +//============================================================================== +// Copyright (c) 2019-2026 CradleApps, LLC - All Rights Reserved +//============================================================================== +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +/** On macOS std::pmr is available but requires the libc++ from macOS 14. + * + * This implementation ensures that memory_resource & polymorphic_allocator + * are available on macOS 10.14 - 13 as well. + * + * Care is taken to ensure that everything is header-only to ensure all + * symbols including the vtables are present in consuming object + * files rather than linking on load. + */ +namespace rtm_pmr +{ +class memory_resource; +} + +namespace cradle::pmr +{ +/** Defined in memory_resources.h. Declared here so that a default-constructed + * polymorphic_allocator picks up the same default resource as the rest of the + * library, rather than keeping a second, independent one. + */ +rtm_pmr::memory_resource* get_default_resource() noexcept; +} // namespace cradle::pmr + +namespace rtm_pmr +{ +//============================================================================== +/** An abstract interface for classes that encapsulate memory resources. */ +class memory_resource +{ +public: + memory_resource() = default; + memory_resource (const memory_resource&) = default; + memory_resource& operator= (const memory_resource&) = default; + + /** Defined inline, so this class has no Itanium ABI "key function". + * The vtable is then created in any consuming object file and deduplicated + * at link time. + */ + virtual ~memory_resource() = default; + + void* allocate (std::size_t bytes, std::size_t alignment = alignof (std::max_align_t)) + { + return do_allocate (bytes, alignment); + } + + void deallocate (void* p, std::size_t bytes, std::size_t alignment = alignof (std::max_align_t)) + { + do_deallocate (p, bytes, alignment); + } + + bool is_equal (const memory_resource& other) const noexcept + { + return do_is_equal (other); + } + +private: + virtual void* do_allocate (std::size_t bytes, std::size_t alignment) = 0; + virtual void do_deallocate (void* p, std::size_t bytes, std::size_t alignment) = 0; + virtual bool do_is_equal (const memory_resource& other) const noexcept = 0; +}; + +inline bool operator== (const memory_resource& a, const memory_resource& b) noexcept +{ + return &a == &b || a.is_equal (b); +} + +inline bool operator!= (const memory_resource& a, const memory_resource& b) noexcept +{ + return ! (a == b); +} + +//============================================================================== +/** An allocator that routes its allocations through a memory_resource, and + * that performs uses-allocator construction so that allocator-aware elements + * inherit the resource. + */ +template +class polymorphic_allocator +{ +public: + using value_type = T; + + polymorphic_allocator() noexcept + : m_resource (cradle::pmr::get_default_resource()) + { + } + + /** Intentionally implicit, matching std::pmr::polymorphic_allocator. + */ + polymorphic_allocator (memory_resource* r) noexcept + : m_resource (r) + { + } + + polymorphic_allocator (const polymorphic_allocator&) = default; + + template + polymorphic_allocator (const polymorphic_allocator& other) noexcept + : m_resource (other.resource()) + { + } + + /** Deleted, as in the standard so an allocator's resource is fixed for its lifetime. + */ + polymorphic_allocator& operator= (const polymorphic_allocator&) = delete; + + T* allocate (std::size_t n) + { + if (n > max_size()) + throw std::length_error ("polymorphic_allocator::allocate(n) exceeds max_size()"); + + return static_cast (m_resource->allocate (n * sizeof (T), alignof (T))); + } + + void deallocate (T* p, std::size_t n) + { + m_resource->deallocate (p, n * sizeof (T), alignof (T)); + } + + //============================================================================== + template + void construct (U* p, Args&&... args) + { + construct_using_allocator (p, std::forward (args)...); + } + + template + void construct (std::pair* p, + std::piecewise_construct_t, + std::tuple x, + std::tuple y) + { + ::new (static_cast (p)) + std::pair (std::piecewise_construct, + allocator_args (std::move (x)), + allocator_args (std::move (y))); + } + + template + void construct (std::pair* p) + { + construct (p, std::piecewise_construct, std::tuple<>(), std::tuple<>()); + } + + template + void construct (std::pair* p, U&& x, V&& y) + { + construct (p, + std::piecewise_construct, + std::forward_as_tuple (std::forward (x)), + std::forward_as_tuple (std::forward (y))); + } + + template + void construct (std::pair* p, const std::pair& pr) + { + construct (p, + std::piecewise_construct, + std::forward_as_tuple (pr.first), + std::forward_as_tuple (pr.second)); + } + + template + void construct (std::pair* p, std::pair&& pr) + { + construct (p, + std::piecewise_construct, + std::forward_as_tuple (std::forward (pr.first)), + std::forward_as_tuple (std::forward (pr.second))); + } + + template + void destroy (U* p) + { + p->~U(); + } + + polymorphic_allocator select_on_container_copy_construction() const + { + return polymorphic_allocator(); + } + + memory_resource* resource() const noexcept { return m_resource; } + +private: + static constexpr std::size_t max_size() noexcept + { + return std::numeric_limits::max() / sizeof (T); + } + + template + void construct_using_allocator (U* p, Args&&... args) + { + if constexpr (! std::uses_allocator::value) + { + static_assert (std::is_constructible::value, + "U must be constructible from Args"); + + ::new (static_cast (p)) U (std::forward (args)...); + } + else if constexpr (std::is_constructible::value) + { + ::new (static_cast (p)) U (std::allocator_arg, *this, std::forward (args)...); + } + else + { + static_assert (std::is_constructible::value, + "U uses an allocator but is not constructible from Args plus this allocator"); + + ::new (static_cast (p)) U (std::forward (args)..., *this); + } + } + + /** Returns the tuple of arguments to build a U with, with this allocator + * spliced in wherever U expects it (leading, trailing, or not at all). + */ + template + auto allocator_args (std::tuple&& t) + { + if constexpr (! std::uses_allocator::value) + { + return std::move (t); + } + else if constexpr (std::is_constructible::value) + { + return std::tuple_cat (std::tuple (std::allocator_arg, *this), + std::move (t)); + } + else + { + static_assert (std::is_constructible::value, + "U uses an allocator but is not constructible from Args plus this allocator"); + + return std::tuple_cat (std::move (t), std::tuple (*this)); + } + } + + memory_resource* m_resource; +}; + +template +bool operator== (const polymorphic_allocator& a, const polymorphic_allocator& b) noexcept +{ + return *a.resource() == *b.resource(); +} + +template +bool operator!= (const polymorphic_allocator& a, const polymorphic_allocator& b) noexcept +{ + return ! (a == b); +} + +} // namespace rtm_pmr diff --git a/include/realtime_memory/polymorphic_allocators.h b/include/realtime_memory/polymorphic_allocators.h index d5250dc..3e239e1 100644 --- a/include/realtime_memory/polymorphic_allocators.h +++ b/include/realtime_memory/polymorphic_allocators.h @@ -6,8 +6,6 @@ #include "pmr_includes.h" #include "memory_resources.h" -PMR_DIAGNOSTIC_PUSH - namespace cradle::pmr { /** A simple alias would trigger 'undefined symbols for get_default_resource()' on ARM. */ @@ -117,5 +115,3 @@ bool operator!= (const propagating_allocator& alloc1, } } // cradle::pmr - -PMR_DIAGNOSTIC_POP diff --git a/include/realtime_memory/realtime_memory.h b/include/realtime_memory/realtime_memory.h index fafd670..7791135 100644 --- a/include/realtime_memory/realtime_memory.h +++ b/include/realtime_memory/realtime_memory.h @@ -4,7 +4,7 @@ /** A fill-in implementation of C++17 , to get * monotonic_buffer_resource and unsynchronized_pool_resource. * - * Sadly these haven't made it to libc++ (clang) yet, though MSVC + * These are not available in macOS libc++ pre macOS 14, though MSVC * and newer GCCs have got implementations. This file is intended * to be a drop-in replacement, and it is inspired by the MSVC * and Bloomberg implementations (permissive licenses). diff --git a/tests/resource_tests.cpp b/tests/resource_tests.cpp index a223ce0..eb46e4d 100644 --- a/tests/resource_tests.cpp +++ b/tests/resource_tests.cpp @@ -11,8 +11,6 @@ #include "realtime_memory/free_list_resource.h" #include "realtime_memory/containers.h" -PMR_DIAGNOSTIC_PUSH - #include #include #include @@ -259,8 +257,8 @@ TEST_CASE ("unsynchronized_pool_resource", "[memory_resource]") { constexpr auto max_u64 = std::numeric_limits::max(); constexpr auto max_u32 = (std::size_t) std::numeric_limits::max(); - const auto maxBlocks = GENERATE (as(), 32ul, 1 << 15, max_u64 - 1); - const auto largestBlock = GENERATE (as(), 32ul, 1 << 15, max_u64 - 1); + const auto maxBlocks = GENERATE_COPY (as(), 32ul, 1 << 15, max_u64 - 1); + const auto largestBlock = GENERATE_COPY (as(), 32ul, 1 << 15, max_u64 - 1); CAPTURE (maxBlocks, largestBlock); pmr::pool_options options {maxBlocks, largestBlock}; @@ -908,5 +906,3 @@ TEST_CASE ("unsynchronized_pool_resource overhead limits", "[memory_resource]") resource.deallocate (ptr, numBytes, alignment); } } - -PMR_DIAGNOSTIC_POP