Skip to content

Commit

Permalink
Cleaning up polyfill namespace for memory.hpp (#49)
Browse files Browse the repository at this point in the history
* cleaning up array resource design
* added true array resource and renamed existing to buffer resource
* refactor of array memory resources
* fixing MemoryResourceDeleter design
* fixing formatting errors
* fixing MISRA warnings
* fixing coverage and a bug in reallocate
* fixing examples build
* fixing up some MISRA code smells
  • Loading branch information
thirtytwobits authored Jul 18, 2023
1 parent 8b0a3b9 commit 64209d5
Show file tree
Hide file tree
Showing 28 changed files with 1,265 additions and 536 deletions.
14 changes: 13 additions & 1 deletion cetlvast/include/cetlvast/helpers_gtest_memory_resource.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/// Copyright Amazon.com Inc. or its affiliates.
/// SPDX-License-Identifier: MIT
///
// cSpell: words pocca pocma soccc
// cSpell: words soccc

#ifndef CETLVAST_HELPERS_GTEST_MEMORY_RESOURCE_H_INCLUDED
#define CETLVAST_HELPERS_GTEST_MEMORY_RESOURCE_H_INCLUDED
Expand All @@ -32,6 +32,9 @@ class MockPf17MemoryResource : public cetl::pf17::pmr::memory_resource
MOCK_METHOD(void*, do_allocate, (std::size_t size_bytes, std::size_t alignment));
MOCK_METHOD(void, do_deallocate, (void* p, std::size_t size_bytes, std::size_t alignment));
MOCK_METHOD(bool, do_is_equal, (const cetl::pf17::pmr::memory_resource& rhs), (const, noexcept));
MOCK_METHOD(void*,
do_reallocate,
(void* p, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t new_alignment));

static constexpr bool ReturnsNullWhenFNoExceptions = true;

Expand All @@ -41,6 +44,15 @@ class MockPf17MemoryResource : public cetl::pf17::pmr::memory_resource
}
};

/// No-type memory resource that looks like std::pmr::memory_resource.
class MockMemoryResource
{
public:
MOCK_METHOD(void*, allocate, (std::size_t size_bytes, std::size_t alignment));
MOCK_METHOD(void, deallocate, (void* p, std::size_t size_bytes, std::size_t alignment));
MOCK_METHOD(bool, is_equal, (const MockMemoryResource& rhs), (const, noexcept));
};

#if (__cplusplus >= CETL_CPP_STANDARD_17)
/// Std Mock of memory_resource.
class MockStdMemoryResource : public std::pmr::memory_resource
Expand Down
60 changes: 19 additions & 41 deletions cetlvast/suites/docs/examples/example_03_memory_resource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@
/// SPDX-License-Identifier: MIT
///
#include "cetl/cetl.hpp"
#include "cetl/pmr/memory.hpp"
#include "cetl/pf17/sys/memory_resource.hpp"

#if (__cplusplus >= CETL_CPP_STANDARD_17 && !defined(CETL_DOXYGEN))
#include <memory_resource>
#include "cetl/pf17/byte.hpp"

#include <iostream>
#include <memory>
#include <algorithm>
Expand All @@ -23,15 +22,14 @@

#include <gtest/gtest.h>


namespace cetl
{
namespace pf17
{

/// Implements a memory resource that over-allocates memory from an upstream memory resource to support
/// over-aligning allocations without C++17 or platform-specific system calls.
class OverAlignedMemoryResource : public pmr::memory_resource
class OverAlignedMemoryResource : public cetl::pf17::pmr::memory_resource
{
/// A control-block, of sorts.
/// The MemoryBlock provides a map between the system aligned memory returned by the
Expand Down Expand Up @@ -113,7 +111,7 @@ class OverAlignedMemoryResource : public pmr::memory_resource
// std::pmr::monotonic_buffer_resource.
if (nullptr == upstream_)
{
#if __cpp_exceptions
#if defined(__cpp_exceptions)
throw std::bad_alloc();
#endif
return nullptr;
Expand Down Expand Up @@ -165,7 +163,7 @@ class OverAlignedMemoryResource : public pmr::memory_resource
// is just example code we haven't rigorously proven that it will always be correct. UMMV.
if (nullptr == aligned_memory || max_aligned_memory_size < size_bytes)
{
#if __cpp_exceptions
#if defined(__cpp_exceptions)
throw std::bad_alloc();
#endif
return nullptr;
Expand Down Expand Up @@ -321,37 +319,19 @@ class FakeDmaTransfer
};

template <typename T>
struct MemoryResourceDeleter
{
MemoryResourceDeleter(pmr::memory_resource* resource, std::size_t allocation_size, std::size_t buffer_alignment)
: resource_(resource)
, allocation_size_(allocation_size)
, buffer_alignment_(buffer_alignment)
{
}

void operator()(T* p)
{
CETL_DEBUG_ASSERT(nullptr != resource_, "null memory_resource stored in MemoryResourceDeleter!");
resource_->deallocate(p, allocation_size_, buffer_alignment_);
};

private:
pmr::memory_resource* resource_;
std::size_t allocation_size_;
std::size_t buffer_alignment_;
};

template <typename T>
std::unique_ptr<T, MemoryResourceDeleter<T>> allocate_buffer(pmr::memory_resource& allocator,
std::size_t buffer_size,
std::size_t buffer_alignment)
std::unique_ptr<T, cetl::pmr::MemoryResourceDeleter<cetl::pf17::pmr::memory_resource>> allocate_buffer(
pmr::memory_resource& allocator,
std::size_t buffer_size,
std::size_t buffer_alignment)
{
return std::unique_ptr<T, MemoryResourceDeleter<T>>{static_cast<T*>(
allocator.allocate(buffer_size, buffer_alignment)),
MemoryResourceDeleter<T>{&allocator,
buffer_size,
buffer_alignment}};
return std::unique_ptr<
T,
cetl::pmr::MemoryResourceDeleter<
cetl::pf17::pmr::memory_resource>>{static_cast<T*>(allocator.allocate(buffer_size, buffer_alignment)),
cetl::pmr::MemoryResourceDeleter<
cetl::pf17::pmr::memory_resource>{&allocator,
buffer_size,
buffer_alignment}};
}

// +--------------------------------------------------------------------------+
Expand All @@ -360,7 +340,7 @@ std::unique_ptr<T, MemoryResourceDeleter<T>> allocate_buffer(pmr::memory_resourc

TEST(example_03_memory_resource, main)
{
//! [main]
//! [main]
cetl::pf17::OverAlignedMemoryResource over_aligned_new_delete_resource{};

// let's pretend we have dma that must be aligned to a 128-byte (1024-bit) boundary:
Expand All @@ -386,7 +366,5 @@ TEST(example_03_memory_resource, main)
// Do remember that memory_resource does not construct and delete objects. That is the job of allocators like
// cetl::pf17::pmr::polymorphic_allocator. Because this example only used trivially constructable, copyable, and
// destructible types we didn't have to build that machinery.
//! [main]
//! [main]
}

#endif
Original file line number Diff line number Diff line change
@@ -1,49 +1,43 @@
/// @file
/// Example of using the cetl::pmr::UnsynchronizedArrayMemoryResource in cetl/pmr/array_memory_resource.hpp.
/// Example of using the cetl::pf17::pmr::UnsynchronizedArrayMemoryResource in cetl/pf17/array_memory_resource.hpp.
///
/// @copyright
/// Copyright (C) OpenCyphal Development Team <opencyphal.org>
/// Copyright Amazon.com Inc. or its affiliates.
/// SPDX-License-Identifier: MIT
///

//![example_include]
#include "cetl/pmr/array_memory_resource.hpp"
#include "cetl/pf17/cetlpf.hpp"
//![example_include]
#include "cetl/pf17/sys/memory_resource.hpp"
#include "cetl/pf17/buffer_memory_resource.hpp"

#include <gtest/gtest.h>
#include <gmock/gmock.h>

#include <vector>
#include <iostream>

//![example_setup]
struct Message
{
explicit Message(const cetl::pmr::polymorphic_allocator<std::uint64_t>& allocator)
explicit Message(const cetl::pf17::pmr::polymorphic_allocator<std::uint64_t>& allocator)
: data{allocator}
{
}
std::vector<std::uint64_t, cetl::pmr::polymorphic_allocator<std::uint64_t>> data;
std::vector<std::uint64_t, cetl::pf17::pmr::polymorphic_allocator<std::uint64_t>> data;
};

// Let's say we have a data structure that contains a Message with variable-length data in it.
// We can use UnsynchronizedArrayMemoryResource to allocate a buffer large enough to hold all of this data at once
// but if there is less data the std::vector in the message will return the size() of that data (i.e. where an
// std::array would not).
static constexpr std::size_t SmallMessageSizeBytes = 64 * 8;
static cetl::byte small_message_buffer_[SmallMessageSizeBytes];
static cetl::pf17::byte small_message_buffer_[SmallMessageSizeBytes];

//![example_setup]

TEST(example_04_array_memory_resource_array, example_a)
TEST(example_04_buffer_memory_resource, example_a)
{
//![example_a]
cetl::pmr::UnsynchronizedArrayMemoryResource<cetl::pmr::memory_resource>
aResource{small_message_buffer_, SmallMessageSizeBytes, cetl::pmr::null_memory_resource(), 0};
cetl::pmr::polymorphic_allocator<std::uint64_t> aAlloc{&aResource};
Message a{aAlloc};
cetl::pf17::pmr::UnsynchronizedBufferMemoryResource aResource{small_message_buffer_, SmallMessageSizeBytes};
cetl::pf17::pmr::polymorphic_allocator<std::uint64_t> aAlloc{&aResource};
Message a{aAlloc};

// The big "gotcha" when using UnsynchronizedArrayMemoryResource with STL containers is that you must reserve
// the size needed before you insert data into them. This is because UnsynchronizedArrayMemoryResource only
Expand All @@ -63,21 +57,20 @@ TEST(example_04_array_memory_resource_array, example_a)
//![example_a]
}

TEST(example_04_array_memory_resource_array, example_b)
TEST(example_04_buffer_memory_resource, example_b)
{
//![example_b]
// BUT WAIT! THERE'S MORE! The UnsynchronizedArrayMemoryResource both slices and dices! That is, you can provide
// an upstream allocator to turn this into a "small buffer optimization" resource where the internal allocation
// is the small buffer and the upstream allocator becomes the larger allocator.

cetl::pmr::UnsynchronizedArrayMemoryResource<cetl::pmr::memory_resource>
bResource{small_message_buffer_,
SmallMessageSizeBytes,
cetl::pmr::new_delete_resource(),
std::numeric_limits<std::size_t>::max()};
cetl::pf17::pmr::UnsynchronizedBufferMemoryResource bResource{small_message_buffer_,
SmallMessageSizeBytes,
cetl::pf17::pmr::new_delete_resource(),
std::numeric_limits<std::size_t>::max()};

cetl::pmr::polymorphic_allocator<std::uint64_t> bAlloc{&bResource};
Message b{bAlloc};
cetl::pf17::pmr::polymorphic_allocator<std::uint64_t> bAlloc{&bResource};
Message b{bAlloc};

// This time we won't reserve which should cause vector to do multiple allocations. We'll also insert
// a bunch more items than there is space in the small message buffer.
Expand All @@ -93,22 +86,20 @@ TEST(example_04_array_memory_resource_array, example_b)
//![example_b]
}

TEST(example_04_array_memory_resource_array, example_c)
TEST(example_04_buffer_memory_resource, example_c)
{
//![example_c]
// One more example: by using another UnsynchronizedArrayMemoryResource as an upstream for another
// UnsynchronizedArrayMemoryResource with the same-sized buffer you can use vector push_back without reserve up
// to the size of these buffers.
static cetl::byte upstream_buffer[SmallMessageSizeBytes];
cetl::pmr::UnsynchronizedArrayMemoryResource<cetl::pmr::memory_resource>
cUpstreamResource{&upstream_buffer, SmallMessageSizeBytes, cetl::pmr::null_memory_resource(), 0};
cetl::pmr::UnsynchronizedArrayMemoryResource<cetl::pmr::memory_resource>
cResource{small_message_buffer_,
SmallMessageSizeBytes,
&cUpstreamResource,
std::numeric_limits<std::size_t>::max()};
cetl::pmr::polymorphic_allocator<std::uint64_t> cAlloc{&cResource};
Message c{cAlloc};
static cetl::pf17::byte upstream_buffer[SmallMessageSizeBytes];
cetl::pf17::pmr::UnsynchronizedBufferMemoryResource cUpstreamResource{&upstream_buffer, SmallMessageSizeBytes};
cetl::pf17::pmr::UnsynchronizedBufferMemoryResource cResource{small_message_buffer_,
SmallMessageSizeBytes,
&cUpstreamResource,
std::numeric_limits<std::size_t>::max()};
cetl::pf17::pmr::polymorphic_allocator<std::uint64_t> cAlloc{&cResource};
Message c{cAlloc};

// We also won't reserve in this example which should cause vector to do multiple allocations. We'll insert
// exactly the number of items that will fit in the small message buffer. Because containers like vector use a
Expand Down
58 changes: 58 additions & 0 deletions cetlvast/suites/docs/examples/example_05_array_memory_resource.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/// @file
/// Demonstration of memory alignment when using the cetl::pf17::pmr::UnsynchronizedArrayMemoryResource in
/// cetl/pf17/array_memory_resource.hpp.
///
/// @copyright
/// Copyright (C) OpenCyphal Development Team <opencyphal.org>
/// Copyright Amazon.com Inc. or its affiliates.
/// SPDX-License-Identifier: MIT
///

#include <iostream>

#include <gtest/gtest.h>

#include "cetl/pf17/array_memory_resource.hpp"
#include "cetl/pf17/byte.hpp"

TEST(example_05_array_memory_resource, example_0)
{
static_assert(alignof(std::max_align_t) < 128, "Wow, what hardware are you running on?");
//![example_0]
constexpr std::size_t BufferSizeBytes = 64;
cetl::pf17::pmr::UnsynchronizedArrayMemoryResource<BufferSizeBytes> resource{};

// let's say we have a buffer that must be aligned to a 128-byte (1024-bit) boundary. If we tried to use
// UnsynchronizedArrayMemoryResource with a 64-byte internal array, on a typical system, the allocation would fail.

void* r = nullptr;
#if defined(__cpp_exceptions)
try
{
#endif
r = resource.allocate(64, 128);
#if defined(__cpp_exceptions)
} catch (const std::bad_alloc&)
{
// This is expected.
}
#endif
std::cout << "Over-aligned attempt failed: " << r << std::endl;

//![example_0]
}

TEST(example_05_array_memory_resource, example_1)
{
//![example_1]
// By over-provisioning the buffer you can now get the alignment you want:
constexpr std::size_t BufferSizeBytes = 64 + 128;
cetl::pf17::pmr::UnsynchronizedArrayMemoryResource<BufferSizeBytes> resource{};

void* r = resource.allocate(64, 128);

std::cout << "Over-aligned address at: " << r << std::endl;

resource.deallocate(r, 64, 128);
//![example_1]
}
Loading

0 comments on commit 64209d5

Please sign in to comment.