diff --git a/doc/container.qbk b/doc/container.qbk index ada8306..b1dfa1f 100644 --- a/doc/container.qbk +++ b/doc/container.qbk @@ -1267,7 +1267,8 @@ use [*Boost.Container]? There are several reasons for that: [section:release_notes Release Notes] [section:release_notes_boost_1_71_00 Boost 1.71 Release] - * [@https://github.com/boostorg/container/issues/88 GitHub #88: ['"Implement C++17 MoveAssignable requirements for self-move assignments"]]. + * [@https://github.com/boostorg/container/issues/88 GitHub #88: ['"Implement C++17 MoveAssignable requirements for self-move assignments"]]. + * [@https://github.com/boostorg/container/issues/107 GitHub #107: ['"Alignment ignored in resource_adaptor"]]. * [@https://github.com/boostorg/container/pull/109 GitHub #109: ['"Get rid of integer overflow in copy_move_algo.hpp (-fsanitize=integer)"]]. * [@https://github.com/boostorg/container/pull/110 GitHub #110: ['"Avoid gcc 9 deprecated copy warnings in new_allocator.hpp"]]. * [@https://github.com/boostorg/container/issues/112 GitHub #112: ['"vector::resize() compilation error with msvc-10..12: data is not a member of boost::detail::aligned_storage"]]. diff --git a/include/boost/container/pmr/resource_adaptor.hpp b/include/boost/container/pmr/resource_adaptor.hpp index 3340626..3216350 100644 --- a/include/boost/container/pmr/resource_adaptor.hpp +++ b/include/boost/container/pmr/resource_adaptor.hpp @@ -15,14 +15,44 @@ # pragma once #endif -#include +#include +#include +#include + #include #include #include #include +#include +#include + +#include namespace boost { namespace container { + +namespace pmr_dtl { + +template +struct max_allocator_alignment +{ + static const std::size_t value = 1; +}; + +template +struct max_allocator_alignment< ::boost::container::new_allocator > +{ + static const std::size_t value = boost::move_detail::alignment_of::value; +}; + +template +struct max_allocator_alignment< std::allocator > +{ + static const std::size_t value = boost::move_detail::alignment_of::value; +}; + +} //namespace pmr_dtl + namespace pmr { //! An instance of resource_adaptor is an adaptor that wraps a memory_resource interface @@ -57,7 +87,7 @@ class resource_adaptor_imp void static_assert_if_not_char_allocator() const { //This class can only be used with allocators type char - BOOST_STATIC_ASSERT((dtl::is_same::value)); + BOOST_STATIC_ASSERT((boost::container::dtl::is_same::value)); } #endif @@ -114,15 +144,25 @@ class resource_adaptor_imp protected: //! Returns: Allocated memory obtained by calling m_alloc.allocate. The size and alignment //! of the allocated memory shall meet the requirements for a class derived from memory_resource. - virtual void* do_allocate(size_t bytes, size_t alignment) - { (void)alignment; return this->ebo_alloc_t::get().allocate(bytes); } + virtual void* do_allocate(std::size_t bytes, std::size_t alignment) + { + if (alignment <= priv_guaranteed_allocator_alignment()) + return this->ebo_alloc_t::get().allocate(bytes); + else + return this->priv_aligned_alloc(bytes, alignment); + } //! Requires: p was previously allocated using A.allocate, where A == m_alloc, and not //! subsequently deallocated. //! //! Effects: Returns memory to the allocator using m_alloc.deallocate(). - virtual void do_deallocate(void* p, size_t bytes, size_t alignment) - { (void)alignment; this->ebo_alloc_t::get().deallocate((char*)p, bytes); } + virtual void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) + { + if (alignment <= priv_guaranteed_allocator_alignment()) + this->ebo_alloc_t::get().deallocate((char*)p, bytes); + else + this->priv_aligned_dealloc(p, bytes, alignment); + } //! Let p be dynamic_cast(&other). //! @@ -132,6 +172,48 @@ class resource_adaptor_imp const resource_adaptor_imp* p = dynamic_cast(&other); return p && p->ebo_alloc_t::get() == this->ebo_alloc_t::get(); } + + private: + void * priv_aligned_alloc(std::size_t bytes, std::size_t alignment) + { + //Allocate space for requested bytes, plus alignment, plus bookeeping data + void *const p = this->ebo_alloc_t::get().allocate(bytes + priv_extra_bytes_for_overalignment(alignment)); + + if (0 != p) { + //Obtain the aligned address after the bookeeping data + void *const aligned_ptr = (void*)(((std::size_t)p + priv_extra_bytes_for_overalignment(alignment)) & ~(alignment - 1)); + + //Store bookeeping data. Use memcpy as the underlying memory might be unaligned for + //a pointer (e.g. 2 byte alignment in 32 bit, 4 byte alignment in 64 bit) + std::memcpy(priv_bookeeping_addr_from_aligned_ptr(aligned_ptr), &p, sizeof(p)); + return aligned_ptr; + } + return 0; + } + + void priv_aligned_dealloc(void *aligned_ptr, std::size_t bytes, std::size_t alignment) + { + //Obtain bookeeping data + void *p; + std::memcpy(&p, priv_bookeeping_addr_from_aligned_ptr(aligned_ptr), sizeof(p)); + std::size_t s = bytes + priv_extra_bytes_for_overalignment(alignment); + this->ebo_alloc_t::get().deallocate((char*)p, s); + } + + static BOOST_CONTAINER_FORCEINLINE void *priv_bookeeping_addr_from_aligned_ptr(void *aligned_ptr) + { + return reinterpret_cast(reinterpret_cast(aligned_ptr) - sizeof(void*)); + } + + BOOST_CONTAINER_FORCEINLINE static std::size_t priv_extra_bytes_for_overalignment(std::size_t alignment) + { + return alignment - 1 + sizeof(void*); + } + + BOOST_CONTAINER_FORCEINLINE static std::size_t priv_guaranteed_allocator_alignment() + { + return pmr_dtl::max_allocator_alignment::value; + } }; #if !defined(BOOST_NO_CXX11_TEMPLATE_ALIASES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) @@ -190,4 +272,6 @@ class resource_adaptor } //namespace container { } //namespace boost { +#include + #endif //BOOST_CONTAINER_PMR_RESOURCE_ADAPTOR_HPP diff --git a/test/resource_adaptor_test.cpp b/test/resource_adaptor_test.cpp index 412d471..5f8ece2 100644 --- a/test/resource_adaptor_test.cpp +++ b/test/resource_adaptor_test.cpp @@ -11,9 +11,13 @@ #include #include "propagation_test_allocator.hpp" #include "derived_from_memory_resource.hpp" +#include +#include using namespace boost::container::pmr; +static const std::size_t max_alignment_value = boost::move_detail::alignment_of::value; + void test_default_constructor() { typedef propagation_test_allocator alloc_t; @@ -138,23 +142,64 @@ struct derived_from_resource_adaptor_stateful using base_t::do_is_equal; }; -void test_do_allocate() +void test_do_allocate_deallocate() { - derived_from_resource_adaptor_stateful dra; - char dummy = 0; - dra.get_allocator().allocate_return = &dummy; - void *allocate_ret = dra.do_allocate(998, 1234); - BOOST_TEST(allocate_ret == &dummy); - BOOST_TEST(dra.get_allocator().allocate_size == 998); -} + { + derived_from_resource_adaptor_stateful dra; + char dummy = 0; + dra.get_allocator().allocate_return = &dummy; + void *allocate_ret = dra.do_allocate(998, 1); + BOOST_TEST(allocate_ret == &dummy); + BOOST_TEST(dra.get_allocator().allocate_size == 998); + } + { + derived_from_resource_adaptor_stateful dra; + char dummy = 0; + dra.do_deallocate(&dummy, 1234, 1); + BOOST_TEST(dra.get_allocator().deallocate_p == &dummy); + BOOST_TEST(dra.get_allocator().deallocate_size == 1234); + } + { + //Overaligned allocation + derived_from_resource_adaptor_stateful dra; + const std::size_t alignment = max_alignment_value*2u; + const std::size_t bytes = alignment/2; + char dummy[alignment*2u+sizeof(void*)]; + dra.get_allocator().allocate_return = dummy; -void test_do_deallocate() -{ - derived_from_resource_adaptor_stateful dra; - char dummy = 0; - dra.do_deallocate(&dummy, 1234, 753); - BOOST_TEST(dra.get_allocator().deallocate_p == &dummy); - BOOST_TEST(dra.get_allocator().deallocate_size == 1234); + //First allocate + void *allocate_ret = dra.do_allocate(bytes, alignment); + BOOST_TEST( (char*)allocate_ret >= (dummy+sizeof(void*)) && (char*)allocate_ret < (dummy + sizeof(dummy)) ); + BOOST_TEST( (std::size_t(allocate_ret) & (alignment - 1u)) == 0 ); + BOOST_TEST( dra.get_allocator().allocate_size >= (alignment/2+sizeof(void*)) ); + + //Then allocate + dra.do_deallocate(allocate_ret, bytes, alignment); + BOOST_TEST(dra.get_allocator().deallocate_p == dummy); + BOOST_TEST(dra.get_allocator().deallocate_size == dra.get_allocator().allocate_size); + } + { + typedef resource_adaptor< boost::container::new_allocator > new_resource_alloc_t; + new_resource_alloc_t ra; + boost::container::pmr::memory_resource &mr = ra; + + //new_allocator, low alignment + mr.deallocate(mr.allocate(16, 1), 16, 1); + + //new_allocator, high alignment + mr.deallocate(mr.allocate(16, max_alignment_value*4u), 16, max_alignment_value*4u); + } + { + typedef resource_adaptor > new_resource_alloc_t; + new_resource_alloc_t ra; + boost::container::pmr::memory_resource &mr = ra; + + //std::allocator, low alignment + mr.deallocate(mr.allocate(16, 1), 16, 1); + + //std::allocator, high alignment + mr.deallocate(mr.allocate(16, max_alignment_value*4u), 16, max_alignment_value*4u); + } } void test_do_is_equal() @@ -183,8 +228,7 @@ int main() test_copy_assign(); test_move_assign(); test_get_allocator(); - test_do_allocate(); - test_do_deallocate(); + test_do_allocate_deallocate(); test_do_is_equal(); return ::boost::report_errors(); }