Skip to content

Commit

Permalink
Reuse any_sender storage if moving to a unique_any_sender
Browse files Browse the repository at this point in the history
  • Loading branch information
msimberg committed Nov 3, 2023
1 parent 96a7691 commit 105f1aa
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,18 @@ namespace pika::detail {
return ∅
}
#endif
template <typename Base, std::size_t EmbeddedStorageSize,
std::size_t AlignmentSize = sizeof(void*)>
class copyable_sbo_storage;

template <typename Base, std::size_t EmbeddedStorageSize,
std::size_t AlignmentSize = sizeof(void*)>
class movable_sbo_storage
{
protected:
using base_type = Base;
#if defined(PIKA_DETAIL_ENABLE_ANY_SENDER_SBO)
static constexpr std::size_t embedded_storage_size = EmbeddedStorageSize;
static constexpr std::size_t alignment_size = AlignmentSize;
#endif

// The union has two members:
// - embedded_storage: Embedded storage size array used for types that
Expand Down Expand Up @@ -180,6 +181,35 @@ namespace pika::detail {
}
}

template <typename T>
void move_assign(copyable_sbo_storage<T, embedded_storage_size, alignment_size>&& other)
{
static_assert(std::is_base_of_v<base_type, T>);

PIKA_ASSERT(static_cast<void*>(&other) != static_cast<void*>(this));
PIKA_ASSERT(empty());

if (!other.empty())
{
#if defined(PIKA_DETAIL_ENABLE_ANY_SENDER_SBO)
if (other.using_embedded_storage())
{
auto p = reinterpret_cast<base_type*>(&embedded_storage);
other.get().move_into(p);
object = p;
}
else
#endif
{
heap_storage = other.heap_storage;
other.heap_storage = nullptr;
object = heap_storage;
}

other.reset_vtable();
}
}

public:
movable_sbo_storage() = default;

Expand All @@ -190,6 +220,15 @@ namespace pika::detail {

movable_sbo_storage(movable_sbo_storage&& other) { move_assign(PIKA_MOVE(other)); }

template <typename T>
explicit movable_sbo_storage(
copyable_sbo_storage<T, embedded_storage_size, alignment_size>&& other)
{
static_assert(std::is_base_of_v<base_type, T>);

move_assign(PIKA_MOVE(other));
}

movable_sbo_storage& operator=(movable_sbo_storage&& other)
{
if (&other != this)
Expand All @@ -201,6 +240,21 @@ namespace pika::detail {
return *this;
}

template <typename T>
movable_sbo_storage&
operator=(copyable_sbo_storage<T, embedded_storage_size, alignment_size>&& other)
{
static_assert(std::is_base_of_v<base_type, T>);

if (static_cast<void*>(&other) != static_cast<void*>(this))
{
if (!empty()) { release(); }

move_assign(PIKA_MOVE(other));
}
return *this;
}

movable_sbo_storage(movable_sbo_storage const&) = delete;
movable_sbo_storage& operator=(movable_sbo_storage const&) = delete;

Expand Down Expand Up @@ -236,11 +290,13 @@ namespace pika::detail {
}
};

template <typename Base, std::size_t EmbeddedStorageSize,
std::size_t AlignmentSize = sizeof(void*)>
template <typename Base, std::size_t EmbeddedStorageSize, std::size_t AlignmentSize>
class copyable_sbo_storage
: public movable_sbo_storage<Base, EmbeddedStorageSize, AlignmentSize>
{
template <typename Base2, std::size_t EmbeddedStorageSize2, std::size_t AlignmentSize2>
friend class movable_sbo_storage;

using storage_base_type = movable_sbo_storage<Base, EmbeddedStorageSize, AlignmentSize>;

using typename storage_base_type::base_type;
Expand Down Expand Up @@ -658,6 +714,9 @@ namespace pika::execution::experimental {
} // namespace detail
#endif

template <typename... Ts>
class any_sender;

template <typename... Ts>
class unique_any_sender
#if !defined(PIKA_HAVE_CXX20_TRIVIAL_VIRTUAL_DESTRUCTOR)
Expand Down Expand Up @@ -698,6 +757,20 @@ namespace pika::execution::experimental {
unique_any_sender& operator=(unique_any_sender&&) = default;
unique_any_sender& operator=(unique_any_sender const&) = delete;

// cppcheck-suppress noExplicitConstructor
unique_any_sender(any_sender<Ts...>&& other)
: storage(PIKA_MOVE(other.storage))
{
other.reset();
}

unique_any_sender& operator=(any_sender<Ts...>&& other)
{
storage = PIKA_MOVE(other.storage);
other.reset();
return *this;
};

template <template <typename...> class Tuple, template <typename...> class Variant>
using value_types = Variant<Tuple<Ts...>>;

Expand Down Expand Up @@ -767,6 +840,8 @@ namespace pika::execution::experimental {

storage_type storage{};

friend unique_any_sender<Ts...>;

public:
using is_sender = void;
any_sender() = default;
Expand Down
98 changes: 98 additions & 0 deletions libs/pika/execution_base/tests/unit/any_sender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,85 @@ void test_const_reference()
PIKA_TEST(true);
}

template <template <typename...> typename Sender, typename... Ts, typename F>
void test_any_sender_to_unique_any_sender(F&& f, Ts&&... ts)
{
static_assert(std::is_copy_constructible_v<Sender<Ts...>>,
"This test requires the sender to be copy constructible.");

Sender<std::decay_t<Ts>...> s{std::forward<Ts>(ts)...};

ex::any_sender<std::decay_t<Ts>...> as1{s};
ex::any_sender<std::decay_t<Ts>...> as2{s};

ex::unique_any_sender<std::decay_t<Ts>...> as3{as1};
ex::unique_any_sender<std::decay_t<Ts>...> as4;
as4 = as2;

ex::unique_any_sender<std::decay_t<Ts>...> as5{std::move(as1)};
ex::unique_any_sender<std::decay_t<Ts>...> as6;
as6 = std::move(as2);

PIKA_TEST(as1.empty());
PIKA_TEST(as2.empty());
PIKA_TEST(as3);
PIKA_TEST(as4);
PIKA_TEST(as5);
PIKA_TEST(as6);

// We expect set_value to be called here for as3 to as6
auto check_set_value = [&](auto&& s) {
std::atomic<bool> set_value_called = false;
auto os =
ex::connect(std::forward<decltype(s)>(s), callback_receiver<F>{f, set_value_called});
ex::start(os);
PIKA_TEST(set_value_called);
// NOLINTNEXTLINE(bugprone-use-after-move)
PIKA_TEST(s.empty());
PIKA_TEST(!s);
};

check_set_value(std::move(as3));
check_set_value(std::move(as4));
check_set_value(std::move(as5));
check_set_value(std::move(as6));

// All senders have been moved now so we expect exceptions here
auto check_exception = [&](auto&& s) {
std::atomic<bool> set_value_called{false};
try
{
auto os = ex::connect(
// NOLINTNEXTLINE(bugprone-use-after-move)
std::forward<decltype(s)>(s), callback_receiver<F>{f, set_value_called});
PIKA_TEST(false);
ex::start(os);
}
catch (pika::exception const& e)
{
PIKA_TEST_EQ(e.get_error(), pika::error::bad_function_call);
}
catch (...)
{
PIKA_TEST(false);
}
PIKA_TEST(!set_value_called);
};

// NOLINTNEXTLINE(bugprone-use-after-move)
check_exception(std::move(as1));
// NOLINTNEXTLINE(bugprone-use-after-move)
check_exception(std::move(as2));
// NOLINTNEXTLINE(bugprone-use-after-move)
check_exception(std::move(as3));
// NOLINTNEXTLINE(bugprone-use-after-move)
check_exception(std::move(as4));
// NOLINTNEXTLINE(bugprone-use-after-move)
check_exception(std::move(as5));
// NOLINTNEXTLINE(bugprone-use-after-move)
check_exception(std::move(as6));
}

int main()
{
// We can only wrap copyable senders in any_sender
Expand Down Expand Up @@ -812,5 +891,24 @@ int main()
// Test using {unique_,}any_sender with a just sender of a const reference
test_const_reference();

// Test construction of unique_any_sender from any_sender
test_any_sender_to_unique_any_sender<sender>([] {});
test_any_sender_to_unique_any_sender<sender, int>([](int x) { PIKA_TEST_EQ(x, 42); }, 42);
test_any_sender_to_unique_any_sender<sender, int, double>(
[](int x, double y) {
PIKA_TEST_EQ(x, 42);
PIKA_TEST_EQ(y, 3.14);
},
42, 3.14);

test_any_sender_to_unique_any_sender<large_sender>([] {});
test_any_sender_to_unique_any_sender<large_sender, int>([](int x) { PIKA_TEST_EQ(x, 42); }, 42);
test_any_sender_to_unique_any_sender<large_sender, int, double>(
[](int x, double y) {
PIKA_TEST_EQ(x, 42);
PIKA_TEST_EQ(y, 3.14);
},
42, 3.14);

return 0;
}

0 comments on commit 105f1aa

Please sign in to comment.