Skip to content
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
4 changes: 0 additions & 4 deletions include/realtime_memory/memory_resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#pragma once
#include "pmr_includes.h"

PMR_DIAGNOSTIC_PUSH

//==============================================================================
namespace cradle::pmr
{
Expand Down Expand Up @@ -626,5 +624,3 @@ inline bool unsynchronized_pool_resource::is_oversized (std::size_t bytes, std::
}

} // namespace cradle::pmr

PMR_DIAGNOSTIC_POP
23 changes: 6 additions & 17 deletions include/realtime_memory/pmr_includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,17 @@
#include <vector>

#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 <experimental/memory_resource>
namespace std_pmr = std::experimental::pmr;
#if USE_PMR_SHIM
#include "pmr_shim.h"
namespace std_pmr = rtm_pmr;
#else
#include <memory_resource>
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
266 changes: 266 additions & 0 deletions include/realtime_memory/pmr_shim.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
//==============================================================================
// Copyright (c) 2019-2026 CradleApps, LLC - All Rights Reserved
//==============================================================================
#pragma once
#include <cstddef>
#include <limits>
#include <memory>
#include <new>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>

/** 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 T>
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 <class U>
polymorphic_allocator (const polymorphic_allocator<U>& 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<T>::allocate(n) exceeds max_size()");

return static_cast<T*> (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 <class U, class... Args>
void construct (U* p, Args&&... args)
{
construct_using_allocator (p, std::forward<Args> (args)...);
}

template <class T1, class T2, class... Args1, class... Args2>
void construct (std::pair<T1, T2>* p,
std::piecewise_construct_t,
std::tuple<Args1...> x,
std::tuple<Args2...> y)
{
::new (static_cast<void*> (p))
std::pair<T1, T2> (std::piecewise_construct,
allocator_args<T1> (std::move (x)),
allocator_args<T2> (std::move (y)));
}

template <class T1, class T2>
void construct (std::pair<T1, T2>* p)
{
construct (p, std::piecewise_construct, std::tuple<>(), std::tuple<>());
}

template <class T1, class T2, class U, class V>
void construct (std::pair<T1, T2>* p, U&& x, V&& y)
{
construct (p,
std::piecewise_construct,
std::forward_as_tuple (std::forward<U> (x)),
std::forward_as_tuple (std::forward<V> (y)));
}

template <class T1, class T2, class U, class V>
void construct (std::pair<T1, T2>* p, const std::pair<U, V>& pr)
{
construct (p,
std::piecewise_construct,
std::forward_as_tuple (pr.first),
std::forward_as_tuple (pr.second));
}

template <class T1, class T2, class U, class V>
void construct (std::pair<T1, T2>* p, std::pair<U, V>&& pr)
{
construct (p,
std::piecewise_construct,
std::forward_as_tuple (std::forward<U> (pr.first)),
std::forward_as_tuple (std::forward<V> (pr.second)));
}

template <class U>
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<std::size_t>::max() / sizeof (T);
}

template <class U, class... Args>
void construct_using_allocator (U* p, Args&&... args)
{
if constexpr (! std::uses_allocator<U, polymorphic_allocator>::value)
{
static_assert (std::is_constructible<U, Args...>::value,
"U must be constructible from Args");

::new (static_cast<void*> (p)) U (std::forward<Args> (args)...);
}
else if constexpr (std::is_constructible<U, std::allocator_arg_t, polymorphic_allocator&, Args...>::value)
{
::new (static_cast<void*> (p)) U (std::allocator_arg, *this, std::forward<Args> (args)...);
}
else
{
static_assert (std::is_constructible<U, Args..., polymorphic_allocator&>::value,
"U uses an allocator but is not constructible from Args plus this allocator");

::new (static_cast<void*> (p)) U (std::forward<Args> (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 <class U, class... Args>
auto allocator_args (std::tuple<Args...>&& t)
{
if constexpr (! std::uses_allocator<U, polymorphic_allocator>::value)
{
return std::move (t);
}
else if constexpr (std::is_constructible<U, std::allocator_arg_t, polymorphic_allocator&, Args...>::value)
{
return std::tuple_cat (std::tuple<std::allocator_arg_t, polymorphic_allocator&> (std::allocator_arg, *this),
std::move (t));
}
else
{
static_assert (std::is_constructible<U, Args..., polymorphic_allocator&>::value,
"U uses an allocator but is not constructible from Args plus this allocator");

return std::tuple_cat (std::move (t), std::tuple<polymorphic_allocator&> (*this));
}
}

memory_resource* m_resource;
};

template <class T1, class T2>
bool operator== (const polymorphic_allocator<T1>& a, const polymorphic_allocator<T2>& b) noexcept
{
return *a.resource() == *b.resource();
}

template <class T1, class T2>
bool operator!= (const polymorphic_allocator<T1>& a, const polymorphic_allocator<T2>& b) noexcept
{
return ! (a == b);
}

} // namespace rtm_pmr
4 changes: 0 additions & 4 deletions include/realtime_memory/polymorphic_allocators.h
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -117,5 +115,3 @@ bool operator!= (const propagating_allocator<ValueType1>& alloc1,
}

} // cradle::pmr

PMR_DIAGNOSTIC_POP
2 changes: 1 addition & 1 deletion include/realtime_memory/realtime_memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/** A fill-in implementation of C++17 <memory_resource>, 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).
Expand Down
8 changes: 2 additions & 6 deletions tests/resource_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
#include "realtime_memory/free_list_resource.h"
#include "realtime_memory/containers.h"

PMR_DIAGNOSTIC_PUSH

#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
Expand Down Expand Up @@ -259,8 +257,8 @@ TEST_CASE ("unsynchronized_pool_resource", "[memory_resource]")
{
constexpr auto max_u64 = std::numeric_limits<std::size_t>::max();
constexpr auto max_u32 = (std::size_t) std::numeric_limits<std::uint32_t>::max();
const auto maxBlocks = GENERATE (as<size_t>(), 32ul, 1 << 15, max_u64 - 1);
const auto largestBlock = GENERATE (as<size_t>(), 32ul, 1 << 15, max_u64 - 1);
const auto maxBlocks = GENERATE_COPY (as<size_t>(), 32ul, 1 << 15, max_u64 - 1);
const auto largestBlock = GENERATE_COPY (as<size_t>(), 32ul, 1 << 15, max_u64 - 1);
CAPTURE (maxBlocks, largestBlock);

pmr::pool_options options {maxBlocks, largestBlock};
Expand Down Expand Up @@ -908,5 +906,3 @@ TEST_CASE ("unsynchronized_pool_resource overhead limits", "[memory_resource]")
resource.deallocate (ptr, numBytes, alignment);
}
}

PMR_DIAGNOSTIC_POP
Loading