Skip to content

Commit

Permalink
Refactor allocator handling of contiguous_storage
Browse files Browse the repository at this point in the history
I was hunting a potential bug where the allocator was seemingly swapped twice in some cases, only to find out later that the code is actually correct. This PR proposes the simplifications I made on the way.
  • Loading branch information
bernhardmgruber committed Dec 4, 2024
1 parent 10da5db commit 0328e81
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 199 deletions.
123 changes: 88 additions & 35 deletions thrust/thrust/detail/contiguous_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#include <thrust/detail/execution_policy.h>
#include <thrust/iterator/detail/normal_iterator.h>

#include <cuda/std/utility>

THRUST_NAMESPACE_BEGIN

namespace detail
Expand All @@ -38,6 +40,13 @@ namespace detail
struct copy_allocator_t
{};

struct allocator_mismatch_on_swap : std::runtime_error
{
allocator_mismatch_on_swap()
: std::runtime_error("swap called on containers with allocators that propagate on swap, but compare non-equal")
{}
};

// XXX parameter T is redundant with parameter Alloc
template <typename T, typename Alloc>
class contiguous_storage
Expand Down Expand Up @@ -70,6 +79,8 @@ class contiguous_storage
_CCCL_EXEC_CHECK_DISABLE
_CCCL_HOST_DEVICE explicit contiguous_storage(copy_allocator_t, const contiguous_storage& other, size_type n);

contiguous_storage& operator=(const contiguous_storage& x) = delete;

_CCCL_EXEC_CHECK_DISABLE
_CCCL_HOST_DEVICE ~contiguous_storage();

Expand Down Expand Up @@ -100,7 +111,33 @@ class contiguous_storage

_CCCL_HOST_DEVICE void deallocate() noexcept;

_CCCL_HOST_DEVICE void swap(contiguous_storage& x);
_CCCL_EXEC_CHECK_DISABLE
_CCCL_HOST_DEVICE void swap(contiguous_storage& x) noexcept(
allocator_traits<Alloc>::propagate_on_container_swap::value || allocator_traits<Alloc>::is_always_equal::value)
{
using ::cuda::std::swap;
swap(m_begin, x.m_begin);
swap(m_size, x.m_size);

// From C++ standard [container.reqmts]
// If allocator_traits<allocator_type>::propagate_on_container_swap::value is true, then allocator_type
// shall meet the Cpp17Swappable requirements and the allocators of a and b shall also be exchanged by calling
// swap as described in [swappable.requirements]. Otherwise, the allocators shall not be swapped, and the behavior
// is undefined unless a.get_allocator() == b.get_allocator().
_CCCL_IF_CONSTEXPR (allocator_traits<Alloc>::propagate_on_container_swap::value)
{
swap(m_allocator, x.m_allocator);
}
else
{
_CCCL_IF_CONSTEXPR (!allocator_traits<Alloc>::is_always_equal::value)
{
NV_IF_TARGET(NV_IS_DEVICE, (assert(m_allocator == other);), (if (m_allocator != x.m_allocator) {
throw allocator_mismatch_on_swap();
}));
}
}
}

_CCCL_HOST_DEVICE void value_initialize_n(iterator first, size_type n);

Expand All @@ -122,23 +159,42 @@ class contiguous_storage

_CCCL_HOST_DEVICE void destroy(iterator first, iterator last) noexcept;

_CCCL_HOST_DEVICE void deallocate_on_allocator_mismatch(const contiguous_storage& other) noexcept;
_CCCL_HOST_DEVICE void deallocate_on_allocator_mismatch(const contiguous_storage& other) noexcept
{
// TODO(bgruber): replace dispatch by if constexpr in C++17
integral_constant<bool, allocator_traits<Alloc>::propagate_on_container_copy_assignment::value> c;
deallocate_on_allocator_mismatch_dispatch(c, other);
}

_CCCL_HOST_DEVICE void
destroy_on_allocator_mismatch(const contiguous_storage& other, iterator first, iterator last) noexcept;
destroy_on_allocator_mismatch(const contiguous_storage& other, iterator first, iterator last) noexcept
{
// TODO(bgruber): replace dispatch by if constexpr in C++17
integral_constant<bool, allocator_traits<Alloc>::propagate_on_container_copy_assignment::value> c;
destroy_on_allocator_mismatch_dispatch(c, other, first, last);
}

_CCCL_HOST_DEVICE void set_allocator(const allocator_type& alloc);

_CCCL_HOST_DEVICE bool is_allocator_not_equal(const allocator_type& alloc) const;

_CCCL_HOST_DEVICE bool is_allocator_not_equal(const contiguous_storage& other) const;

_CCCL_HOST_DEVICE void propagate_allocator(const contiguous_storage& other);
_CCCL_EXEC_CHECK_DISABLE
_CCCL_HOST_DEVICE void propagate_allocator(const contiguous_storage& other)
{
_CCCL_IF_CONSTEXPR (allocator_traits<Alloc>::propagate_on_container_copy_assignment::value)
{
m_allocator = other.m_allocator;
}
}

_CCCL_HOST_DEVICE void propagate_allocator(contiguous_storage& other);
_CCCL_EXEC_CHECK_DISABLE
_CCCL_HOST_DEVICE void propagate_allocator(contiguous_storage& other)
{
_CCCL_IF_CONSTEXPR (allocator_traits<Alloc>::propagate_on_container_move_assignment::value)
{
m_allocator = ::cuda::std::move(other.m_allocator);
}
}

// allow move assignment for a sane implementation of allocator propagation
// on move assignment
_CCCL_HOST_DEVICE contiguous_storage& operator=(contiguous_storage&& other);

_CCCL_SYNTHESIZE_SEQUENCE_ACCESS(contiguous_storage, const_iterator);
Expand All @@ -151,34 +207,31 @@ class contiguous_storage

size_type m_size;

// disallow assignment
contiguous_storage& operator=(const contiguous_storage& x);

_CCCL_HOST_DEVICE void swap_allocators(true_type, const allocator_type&);

_CCCL_HOST_DEVICE void swap_allocators(false_type, allocator_type&);

_CCCL_HOST_DEVICE bool is_allocator_not_equal_dispatch(true_type, const allocator_type&) const;

_CCCL_HOST_DEVICE bool is_allocator_not_equal_dispatch(false_type, const allocator_type&) const;

_CCCL_HOST_DEVICE void deallocate_on_allocator_mismatch_dispatch(true_type, const contiguous_storage& other) noexcept;

_CCCL_HOST_DEVICE void deallocate_on_allocator_mismatch_dispatch(false_type, const contiguous_storage& other) noexcept;
_CCCL_EXEC_CHECK_DISABLE
_CCCL_HOST_DEVICE void deallocate_on_allocator_mismatch_dispatch(true_type, const contiguous_storage& other) noexcept
{
if (m_allocator != other.m_allocator)
{
deallocate();
}
}

_CCCL_HOST_DEVICE void destroy_on_allocator_mismatch_dispatch(
true_type, const contiguous_storage& other, iterator first, iterator last) noexcept;
_CCCL_HOST_DEVICE static void deallocate_on_allocator_mismatch_dispatch(false_type, const contiguous_storage&) noexcept
{}

_CCCL_EXEC_CHECK_DISABLE
_CCCL_HOST_DEVICE void destroy_on_allocator_mismatch_dispatch(
false_type, const contiguous_storage& other, iterator first, iterator last) noexcept;

_CCCL_HOST_DEVICE void propagate_allocator_dispatch(true_type, const contiguous_storage& other);

_CCCL_HOST_DEVICE void propagate_allocator_dispatch(false_type, const contiguous_storage& other);

_CCCL_HOST_DEVICE void propagate_allocator_dispatch(true_type, contiguous_storage& other);

_CCCL_HOST_DEVICE void propagate_allocator_dispatch(false_type, contiguous_storage& other);
true_type, const contiguous_storage& other, iterator first, iterator last) noexcept
{
if (m_allocator != other.m_allocator)
{
destroy(first, last);
}
} // end contiguous_storage::destroy_on_allocator_mismatch()

_CCCL_HOST_DEVICE static void destroy_on_allocator_mismatch_dispatch(
false_type, const contiguous_storage& other, iterator first, iterator last) noexcept
{}

friend _CCCL_HOST_DEVICE void swap(contiguous_storage& lhs, contiguous_storage& rhs) noexcept(noexcept(lhs.swap(rhs)))
{
Expand Down
Loading

0 comments on commit 0328e81

Please sign in to comment.