Files
TheRock/rocm-libraries/projects/rocthrust/thrust/system/hip/detail/future.inl
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1243 lines
35 KiB
C++

// Copyright (c) 2018 NVIDIA Corporation
// Modifications Copyright© 2020-2026 Advanced Micro Devices, Inc. All rights reserved.
// Author: Bryce Adelstein Lelbach <brycelelbach@gmail.com>
//
// Distributed under the Boost Software License v1.0 (boost.org/LICENSE_1_0.txt)
// TODO: Split into more granular headers (move unique_stream/unique_marker to
// another header, etc).
#pragma once
#include <thrust/detail/config.h>
#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header
#include <thrust/detail/cpp_version_check.h>
#if THRUST_CPP_DIALECT >= 2017
# include <thrust/allocate_unique.h>
# include <thrust/detail/event_error.h>
# include <thrust/detail/execute_with_dependencies.h>
# include <thrust/detail/memory_wrapper.h>
# include <thrust/detail/static_assert.h>
# include <thrust/detail/tuple_algorithms.h>
# include <thrust/detail/type_deduction.h>
# include <thrust/detail/type_traits.h>
# include <thrust/detail/type_traits/pointer_traits.h>
# include <thrust/optional.h>
# include <thrust/system/hip/detail/get_value.h>
# include <thrust/system/hip/detail/util.h>
# include <thrust/system/hip/future.h>
# include <thrust/system/hip/memory.h>
# include <thrust/type_traits/integer_sequence.h>
#if _THRUST_HAS_DEVICE_SYSTEM_STD
// clang-format off
# include _THRUST_STD_INCLUDE(__memory/unique_ptr.h)
// clang-format on
#else
# include <thrust/detail/memory_wrapper.h>
#endif
# include <type_traits>
THRUST_SUPPRESS_DEPRECATED_PUSH
THRUST_NAMESPACE_BEGIN
// Forward declaration.
struct THRUST_DEPRECATED new_stream_t;
namespace system
{
namespace hip
{
namespace detail
{
///////////////////////////////////////////////////////////////////////////////
struct nonowning_t final
{};
THRUST_INLINE_CONSTANT nonowning_t nonowning{};
///////////////////////////////////////////////////////////////////////////////
struct marker_deleter final
{
THRUST_HOST void operator()(hipEvent_t e) const
{
if (nullptr != e)
{
thrust::hip_rocprim::throw_on_error(hipEventDestroy(e));
}
}
};
///////////////////////////////////////////////////////////////////////////////
struct unique_marker final
{
using native_handle_type = hipEvent_t;
private:
std::unique_ptr<ihipEvent_t, marker_deleter> handle_;
public:
/// \brief Create a new stream and construct a handle to it. When the handle
/// is destroyed, the stream is destroyed.
THRUST_HOST unique_marker()
: handle_(nullptr, marker_deleter())
{
native_handle_type e;
thrust::hip_rocprim::throw_on_error(hipEventCreateWithFlags(&e, hipEventDisableTiming));
handle_.reset(e);
}
THRUST_EXEC_CHECK_DISABLE
unique_marker(unique_marker const&) = delete;
THRUST_EXEC_CHECK_DISABLE
unique_marker(unique_marker&&) = default;
THRUST_EXEC_CHECK_DISABLE
unique_marker& operator=(unique_marker const&) = delete;
THRUST_EXEC_CHECK_DISABLE
unique_marker& operator=(unique_marker&&) = default;
THRUST_EXEC_CHECK_DISABLE
~unique_marker() = default;
THRUST_HOST auto get() const THRUST_DECLTYPE_RETURNS(native_handle_type(handle_.get()));
THRUST_HOST auto native_handle() const THRUST_DECLTYPE_RETURNS(native_handle_type(handle_.get()));
THRUST_HOST bool valid() const noexcept
{
return bool(handle_);
}
THRUST_HOST bool ready() const
{
hipError_t const err = hipEventQuery(handle_.get());
if (hipErrorNotReady == err)
{
return false;
}
// Throw on any other error.
thrust::hip_rocprim::throw_on_error(err);
return true;
}
THRUST_HOST void wait() const
{
thrust::hip_rocprim::throw_on_error(hipEventSynchronize(handle_.get()));
}
THRUST_HOST bool operator==(unique_marker const& other) const
{
return other.handle_ == handle_;
}
THRUST_HOST bool operator!=(unique_marker const& other) const
{
return !(other == *this);
}
};
///////////////////////////////////////////////////////////////////////////////
struct stream_deleter final
{
THRUST_HOST void operator()(hipStream_t s) const
{
if (nullptr != s)
{
thrust::hip_rocprim::throw_on_error(hipStreamDestroy(s));
}
}
};
struct stream_conditional_deleter final
{
private:
bool cond_;
public:
THRUST_HOST constexpr stream_conditional_deleter() noexcept
: cond_(true)
{}
THRUST_HOST explicit constexpr stream_conditional_deleter(nonowning_t) noexcept
: cond_(false)
{}
THRUST_HOST void operator()(hipStream_t s) const
{
if (cond_ && nullptr != s)
{
thrust::hip_rocprim::throw_on_error(hipStreamDestroy(s));
}
}
};
///////////////////////////////////////////////////////////////////////////////
struct unique_stream final
{
using native_handle_type = hipStream_t;
private:
std::unique_ptr<ihipStream_t, stream_conditional_deleter> handle_;
public:
/// \brief Create a new stream and construct a handle to it. When the handle
/// is destroyed, the stream is destroyed.
THRUST_HOST unique_stream()
: handle_(nullptr, stream_conditional_deleter())
{
native_handle_type s;
thrust::hip_rocprim::throw_on_error(hipStreamCreateWithFlags(&s, hipStreamNonBlocking));
handle_.reset(s);
}
/// \brief Construct a non-owning handle to an existing stream. When the
/// handle is destroyed, the stream is not destroyed.
THRUST_HOST explicit unique_stream(nonowning_t, native_handle_type handle)
: handle_(handle, stream_conditional_deleter(nonowning))
{}
THRUST_EXEC_CHECK_DISABLE
unique_stream(unique_stream const&) = delete;
// GCC 10 complains if this is defaulted. See NVIDIA/thrust#1269.
THRUST_EXEC_CHECK_DISABLE
THRUST_HOST unique_stream(unique_stream&& o) noexcept
: handle_(std::move(o.handle_))
{}
THRUST_EXEC_CHECK_DISABLE
unique_stream& operator=(unique_stream const&) = delete;
THRUST_EXEC_CHECK_DISABLE
unique_stream& operator=(unique_stream&&) = default;
THRUST_EXEC_CHECK_DISABLE
~unique_stream() = default;
THRUST_HOST auto get() const THRUST_DECLTYPE_RETURNS(native_handle_type(handle_.get()));
THRUST_HOST auto native_handle() const THRUST_DECLTYPE_RETURNS(native_handle_type(handle_.get()));
THRUST_HOST bool valid() const noexcept
{
return bool(handle_);
}
THRUST_HOST bool ready() const
{
hipError_t const err = hipStreamQuery(handle_.get());
if (hipErrorNotReady == err)
{
return false;
}
// Throw on any other error.
thrust::hip_rocprim::throw_on_error(err);
return true;
}
THRUST_HOST void wait() const
{
thrust::hip_rocprim::throw_on_error(hipStreamSynchronize(handle_.get()));
}
THRUST_HOST void depend_on(unique_marker& e)
{
thrust::hip_rocprim::throw_on_error(hipStreamWaitEvent(handle_.get(), e.get(), 0));
}
THRUST_HOST void depend_on(unique_stream& s)
{
if (s != *this)
{
unique_marker e;
s.record(e);
depend_on(e);
}
}
THRUST_HOST void record(unique_marker& e)
{
thrust::hip_rocprim::throw_on_error(hipEventRecord(e.get(), handle_.get()));
}
THRUST_HOST bool operator==(unique_stream const& other) const
{
return other.handle_ == handle_;
}
THRUST_HOST bool operator!=(unique_stream const& other) const
{
return !(other == *this);
}
};
///////////////////////////////////////////////////////////////////////////////
// Inheritance hierarchy of future/event shared state types.
struct async_signal;
template <typename KeepAlives>
struct async_keep_alives /* : virtual async_signal */;
template <typename T>
struct async_value /* : virtual async_signal */;
template <typename T, typename Pointer, typename KeepAlives>
struct async_addressable_value_with_keep_alives
/* : async_value<T>, async_keep_alives<KeepAlives> */;
///////////////////////////////////////////////////////////////////////////////
template <typename T, typename Pointer>
struct weak_promise;
template <typename X, typename XPointer = pointer<X>>
struct unique_eager_future_promise_pair final
{
unique_eager_future<X> future;
weak_promise<X, XPointer> promise;
};
THRUST_SUPPRESS_DEPRECATED_PUSH // for thrust::optional
struct acquired_stream final
{
unique_stream stream;
optional<std::size_t> const acquired_from;
// `acquired_from` contains the index in the tuple of dependencies from which
// the stream was acquired. If `acquired_from` is empty, no stream could be
// acquired from a dependency, and then the stream was newly created.
};
// Precondition: `device` is the current HIP device.
template <typename X, typename Y, typename Deleter>
THRUST_HOST optional<unique_stream> try_acquire_stream(int device, std::unique_ptr<Y, Deleter>&) noexcept;
// Precondition: `device` is the current HIP device.
THRUST_HOST inline optional<unique_stream> try_acquire_stream(int, unique_stream& stream) noexcept;
// Precondition: `device` is the current HIP device.
THRUST_HOST inline optional<unique_stream> try_acquire_stream(int device, ready_event&) noexcept;
// Precondition: `device` is the current HIP device.
template <typename X>
THRUST_HOST inline optional<unique_stream> try_acquire_stream(int device, ready_future<X>&) noexcept;
// Precondition: `device` is the current HIP device.
THRUST_HOST inline optional<unique_stream> try_acquire_stream(int device, unique_eager_event& parent) noexcept;
// Precondition: `device` is the current HIP device.
template <typename X>
THRUST_HOST optional<unique_stream> try_acquire_stream(int device, unique_eager_future<X>& parent) noexcept;
THRUST_SUPPRESS_DEPRECATED_POP
template <typename... Dependencies>
THRUST_HOST acquired_stream acquire_stream(int device, Dependencies&... deps) noexcept;
template <typename... Dependencies>
THRUST_HOST unique_eager_event make_dependent_event(std::tuple<Dependencies...>&& deps);
template <typename X, typename XPointer, typename ComputeContent, typename... Dependencies>
THRUST_HOST unique_eager_future_promise_pair<X, XPointer>
make_dependent_future(ComputeContent&& cc, std::tuple<Dependencies...>&& deps);
///////////////////////////////////////////////////////////////////////////////
struct async_signal
{
protected:
unique_stream stream_;
public:
// Constructs an `async_signal` which uses `stream`.
THRUST_HOST explicit async_signal(unique_stream&& stream)
: stream_(std::move(stream))
{}
THRUST_HOST virtual ~async_signal() {}
unique_stream& stream() noexcept
{
return stream_;
}
unique_stream const& stream() const noexcept
{
return stream_;
}
};
template <typename... KeepAlives>
struct async_keep_alives<std::tuple<KeepAlives...>> : virtual async_signal
{
using keep_alives_type = std::tuple<KeepAlives...>;
protected:
keep_alives_type keep_alives_;
public:
// Constructs an `async_keep_alives` which uses `stream`, and keeps the
// objects in the tuple `keep_alives` alive until the asynchronous signal is
// destroyed.
THRUST_HOST explicit async_keep_alives(unique_stream&& stream, keep_alives_type&& keep_alives)
: async_signal(std::move(stream))
, keep_alives_(std::move(keep_alives))
{}
THRUST_HOST virtual ~async_keep_alives() {}
};
template <typename T>
struct async_value : virtual async_signal
{
using value_type = T;
using raw_const_pointer = value_type const*;
// Constructs an `async_value` which uses `stream` and has no content.
THRUST_HOST explicit async_value(unique_stream stream)
: async_signal(std::move(stream))
{}
THRUST_HOST virtual ~async_value() {}
THRUST_HOST virtual bool valid_content() const noexcept
{
return false;
}
THRUST_HOST virtual value_type get()
{
throw thrust::event_error(event_errc::no_state);
}
THRUST_HOST virtual value_type extract()
{
throw thrust::event_error(event_errc::no_state);
}
// For testing only.
# if defined(THRUST_ENABLE_FUTURE_RAW_DATA_MEMBER)
THRUST_HOST virtual raw_const_pointer raw_data() const
{
return nullptr;
}
# endif
};
template <typename T, typename Pointer, typename... KeepAlives>
struct async_addressable_value_with_keep_alives<T, Pointer, std::tuple<KeepAlives...>> final
: async_value<T>
, async_keep_alives<std::tuple<KeepAlives...>>
{
using value_type = typename async_value<T>::value_type;
using raw_const_pointer = typename async_value<T>::raw_const_pointer;
using keep_alives_type = typename async_keep_alives<std::tuple<KeepAlives...>>::keep_alives_type;
using pointer = typename thrust::detail::pointer_traits<Pointer>::template rebind<value_type>::other;
using const_pointer = typename thrust::detail::pointer_traits<Pointer>::template rebind<value_type const>::other;
private:
pointer content_;
public:
// Constructs an `async_addressable_value_with_keep_alives` which uses
// `stream`, keeps the objects in the tuple `keep_alives` alive until the
// asynchronous value is destroyed, and determines the location of its
// content by evaluating `compute_content(content_keep_alive)`.
// NOTE: The use of a callback idiom is necessary if the content is stored in
// place in the content keep alive object, in which case we need to get its
// address after its been moved into the new signal we're constructing.
// NOTE: NVCC has a bug that causes it to reorder our base class initializers
// in generated host code, which leads to -Wreorder warnings.
THRUST_DIAG_PUSH
THRUST_DIAG_SUPPRESS_CLANG("-Wreorder")
THRUST_DIAG_SUPPRESS_GCC("-Wreorder")
THRUST_DIAG_SUPPRESS_ICC(2407)
template <typename ComputeContent>
THRUST_HOST explicit async_addressable_value_with_keep_alives(
unique_stream&& stream, keep_alives_type&& keep_alives, ComputeContent&& compute_content)
: async_signal(std::move(stream))
, async_value<T>(std::move(stream))
, async_keep_alives<keep_alives_type>(std::move(stream), std::move(keep_alives))
{
content_ = THRUST_FWD(compute_content)(std::get<0>(this->keep_alives_));
}
THRUST_DIAG_POP
THRUST_HOST bool valid_content() const noexcept final override
{
return nullptr != content_;
}
// Precondition: `true == valid_content()`.
THRUST_HOST pointer data()
{
if (!valid_content())
{
throw thrust::event_error(event_errc::no_content);
}
return content_;
}
// Precondition: `true == valid_content()`.
THRUST_HOST const_pointer data() const
{
if (!valid_content())
{
throw thrust::event_error(event_errc::no_content);
}
return content_;
}
// Blocks.
// Precondition: `true == valid_content()`.
THRUST_HOST value_type get() final override
{
this->stream().wait();
return *data();
}
// Blocks.
// Precondition: `true == valid_content()`.
THRUST_HOST value_type extract() final override
{
this->stream().wait();
return std::move(*data());
}
// For testing only.
# if defined(THRUST_ENABLE_FUTURE_RAW_DATA_MEMBER)
THRUST_HOST raw_const_pointer raw_data() const final override
{
return raw_pointer_cast(content_);
}
# endif
};
///////////////////////////////////////////////////////////////////////////////
template <typename T, typename Pointer>
struct weak_promise final
{
using value_type = typename async_value<T>::value_type;
using pointer = typename thrust::detail::pointer_traits<Pointer>::template rebind<T>::other;
using const_pointer = typename thrust::detail::pointer_traits<Pointer>::template rebind<T const>::other;
private:
int device_ = 0;
pointer content_;
explicit weak_promise(int device_id, pointer content)
: device_(device_id)
, content_(std::move(content))
{}
public:
THRUST_HOST_DEVICE weak_promise()
: device_(0)
, content_{}
{}
THRUST_EXEC_CHECK_DISABLE
weak_promise(weak_promise const&) = default;
THRUST_EXEC_CHECK_DISABLE
weak_promise(weak_promise&&) = default;
THRUST_EXEC_CHECK_DISABLE
weak_promise& operator=(weak_promise const&) = default;
THRUST_EXEC_CHECK_DISABLE
weak_promise& operator=(weak_promise&&) = default;
template <typename U>
THRUST_HOST_DEVICE void set_value(U&& value) &&
{
*content_ = THRUST_FWD(value);
}
template <typename X, typename XPointer, typename ComputeContent, typename... Dependencies>
friend THRUST_HOST unique_eager_future_promise_pair<X, XPointer>
thrust::system::hip::detail::make_dependent_future(ComputeContent&& cc, std::tuple<Dependencies...>&& deps);
};
///////////////////////////////////////////////////////////////////////////////
} // namespace detail
struct THRUST_DEPRECATED ready_event final
{
ready_event() = default;
template <typename U>
THRUST_HOST_DEVICE explicit ready_event(ready_future<U>)
{}
THRUST_HOST_DEVICE static constexpr bool valid_content() noexcept
{
return true;
}
THRUST_HOST_DEVICE static constexpr bool ready() noexcept
{
return true;
}
};
template <typename T>
struct THRUST_DEPRECATED ready_future final
{
using value_type = T;
using raw_const_pointer = T const*;
private:
value_type value_;
public:
THRUST_HOST_DEVICE ready_future()
: value_{}
{}
ready_future(ready_future&&) = default;
ready_future(ready_future const&) = default;
ready_future& operator=(ready_future&&) = default;
ready_future& operator=(ready_future const&) = default;
template <typename U>
THRUST_HOST_DEVICE explicit ready_future(U&& u)
: value_(THRUST_FWD(u))
{}
THRUST_HOST_DEVICE static constexpr bool valid_content() noexcept
{
return true;
}
THRUST_HOST_DEVICE static constexpr bool ready() noexcept
{
return true;
}
THRUST_HOST_DEVICE value_type get() const
{
return value_;
}
THRUST_NODISCARD THRUST_HOST_DEVICE value_type extract()
{
return std::move(value_);
}
# if defined(THRUST_ENABLE_FUTURE_RAW_DATA_MEMBER)
// For testing only.
THRUST_HOST_DEVICE raw_const_pointer data() const
{
return addressof(value_);
}
# endif
};
struct THRUST_DEPRECATED unique_eager_event final
{
protected:
int device_ = 0;
std::unique_ptr<detail::async_signal> async_signal_;
THRUST_HOST explicit unique_eager_event(int device_id, std::unique_ptr<detail::async_signal> async_signal)
: device_(device_id)
, async_signal_(std::move(async_signal))
{}
public:
THRUST_HOST unique_eager_event()
: device_(0)
, async_signal_()
{}
unique_eager_event(unique_eager_event&&) = default;
unique_eager_event(unique_eager_event const&) = delete;
unique_eager_event& operator=(unique_eager_event&&) = default;
unique_eager_event& operator=(unique_eager_event const&) = delete;
// Any `unique_eager_future<T>` can be explicitly converted to a
// `unique_eager_event<void>`.
template <typename U>
THRUST_HOST explicit unique_eager_event(unique_eager_future<U>&& other)
// NOTE: We upcast to `unique_ptr<async_signal>` here.
: device_(other.where())
, async_signal_(other.async_signal_.release())
{}
THRUST_HOST
// NOTE: We take `new_stream_t` by `const&` because it is incomplete here.
explicit unique_eager_event(new_stream_t const&)
: device_(0)
, async_signal_(new detail::async_signal(detail::unique_stream{}))
{
thrust::hip_rocprim::throw_on_error(hipGetDevice(&device_));
}
THRUST_HOST ~unique_eager_event()
{
// FIXME: If we could asynchronously handle destruction of keep alives, we
// could avoid doing this.
if (valid_stream())
{
wait();
}
}
THRUST_HOST bool valid_stream() const noexcept
{
return bool(async_signal_);
}
THRUST_HOST bool ready() const noexcept
{
if (valid_stream())
{
return stream().ready();
}
else
{
return false;
}
}
// Precondition: `true == valid_stream()`.
THRUST_HOST detail::unique_stream& stream()
{
if (!valid_stream())
{
throw thrust::event_error(event_errc::no_state);
}
return async_signal_->stream();
}
detail::unique_stream const& stream() const
{
if (!valid_stream())
{
throw thrust::event_error(event_errc::no_state);
}
return async_signal_->stream();
}
THRUST_HOST int where() const noexcept
{
return device_;
}
// Precondition: `true == valid_stream()`.
THRUST_HOST void wait()
{
stream().wait();
}
THRUST_SUPPRESS_DEPRECATED_PUSH // for thrust::optional
friend THRUST_HOST optional<detail::unique_stream>
thrust::system::hip::detail::try_acquire_stream(int device_id, unique_eager_event& parent) noexcept;
THRUST_SUPPRESS_DEPRECATED_POP
template <typename... Dependencies>
friend THRUST_HOST unique_eager_event
thrust::system::hip::detail::make_dependent_event(std::tuple<Dependencies...>&& deps);
};
template <typename T>
struct THRUST_DEPRECATED unique_eager_future final
{
THRUST_STATIC_ASSERT_MSG((!std::is_same<T, ::internal::remove_cvref_t<void>>::value),
"`thrust::event` should be used to express valueless futures");
using value_type = typename detail::async_value<T>::value_type;
using raw_const_pointer = typename detail::async_value<T>::raw_const_pointer;
private:
int device_ = 0;
_THRUST_STD::unique_ptr<detail::async_value<value_type>> async_signal_;
THRUST_HOST explicit unique_eager_future(int device_id, std::unique_ptr<detail::async_value<value_type>> async_signal)
: device_(device_id)
, async_signal_(async_signal.release())
{}
public:
THRUST_HOST unique_eager_future()
: device_(0)
, async_signal_()
{}
unique_eager_future(unique_eager_future&&) = default;
unique_eager_future(unique_eager_future const&) = delete;
unique_eager_future& operator=(unique_eager_future&&) = default;
unique_eager_future& operator=(unique_eager_future const&) = delete;
THRUST_HOST
// NOTE: We take `new_stream_t` by `const&` because it is incomplete here.
explicit unique_eager_future(new_stream_t const&)
: device_(0)
, async_signal_(new detail::async_value<value_type>(detail::unique_stream{}))
{
thrust::hip_rocprim::throw_on_error(hipGetDevice(&device_));
}
THRUST_HOST ~unique_eager_future()
{
// FIXME: If we could asynchronously handle destruction of keep alives, we
// could avoid doing this.
if (valid_stream())
{
wait();
}
}
THRUST_HOST bool valid_stream() const noexcept
{
return bool(async_signal_);
}
THRUST_HOST bool valid_content() const noexcept
{
if (!valid_stream())
{
return false;
}
// We might have been constructed with `new_stream_t`, in which case we'd
// have an async_value, but it doesn't have content.
return async_signal_->valid_content();
}
// Precondition: `true == valid_stream()`.
THRUST_HOST bool ready() const noexcept
{
if (valid_stream())
{
return stream().ready();
}
else
{
return false;
}
}
// Precondition: `true == valid_stream()`.
THRUST_HOST detail::unique_stream& stream()
{
if (!valid_stream())
{
throw thrust::event_error(event_errc::no_state);
}
return async_signal_->stream();
}
THRUST_HOST detail::unique_stream const& stream() const
{
if (!valid_stream())
{
throw thrust::event_error(event_errc::no_state);
}
return async_signal_->stream();
}
THRUST_HOST int where() const noexcept
{
return device_;
}
// Blocks.
// Precondition: `true == valid_stream()`.
THRUST_HOST void wait()
{
stream().wait();
}
// Blocks.
// Precondition: `true == valid_content()`.
THRUST_HOST value_type get()
{
if (!valid_content())
{
throw thrust::event_error(event_errc::no_content);
}
return async_signal_->get();
}
// Blocks.
// Precondition: `true == valid_content()`.
THRUST_NODISCARD THRUST_HOST value_type extract()
{
if (!valid_content())
{
throw thrust::event_error(event_errc::no_content);
}
value_type tmp(async_signal_->extract());
async_signal_.reset();
return tmp;
}
// For testing only.
# if defined(THRUST_ENABLE_FUTURE_RAW_DATA_MEMBER)
// Precondition: `true == valid_stream()`.
THRUST_HOST raw_const_pointer raw_data() const
{
if (!valid_stream())
{
throw thrust::event_error(event_errc::no_state);
}
return async_signal_->raw_data();
}
# endif
THRUST_SUPPRESS_DEPRECATED_PUSH // for thrust::optional
template <typename X>
friend THRUST_HOST optional<detail::unique_stream>
thrust::system::hip::detail::try_acquire_stream(int device_id, unique_eager_future<X>& parent) noexcept;
THRUST_SUPPRESS_DEPRECATED_POP
template <typename X, typename XPointer, typename ComputeContent, typename... Dependencies>
friend THRUST_HOST detail::unique_eager_future_promise_pair<X, XPointer>
thrust::system::hip::detail::make_dependent_future(ComputeContent&& cc, std::tuple<Dependencies...>&& deps);
friend struct unique_eager_event;
};
///////////////////////////////////////////////////////////////////////////////
namespace detail
{
THRUST_SUPPRESS_DEPRECATED_PUSH // for thrust::optional
template <typename X, typename Deleter>
THRUST_HOST optional<unique_stream> try_acquire_stream(int, std::unique_ptr<X, Deleter>&) noexcept
{
// There's no stream to acquire!
return {};
}
THRUST_HOST inline optional<unique_stream> try_acquire_stream(int, unique_stream& stream) noexcept
{
return {std::move(stream)};
}
THRUST_HOST inline optional<unique_stream> try_acquire_stream(int, ready_event&) noexcept
{
// There's no stream to acquire!
return {};
}
template <typename X>
THRUST_HOST optional<unique_stream> try_acquire_stream(int, ready_future<X>&) noexcept
{
// There's no stream to acquire!
return {};
}
THRUST_HOST optional<unique_stream> try_acquire_stream(int device_id, unique_eager_event& parent) noexcept
{
// We have unique ownership, so we can always steal the stream if the future
// has one as long as they are on the same device as us.
if (parent.valid_stream())
{
if (device_id == parent.device_)
{
return std::move(parent.async_signal_->stream());
}
}
return {};
}
template <typename X>
THRUST_HOST optional<unique_stream> try_acquire_stream(int device_id, unique_eager_future<X>& parent) noexcept
{
// We have unique ownership, so we can always steal the stream if the future
// has one as long as they are on the same device as us.
if (parent.valid_stream())
{
if (device_id == parent.device_)
{
return std::move(parent.async_signal_->stream());
}
}
return {};
}
THRUST_SUPPRESS_DEPRECATED_POP
///////////////////////////////////////////////////////////////////////////////
template <typename... Dependencies>
THRUST_HOST acquired_stream acquire_stream_impl(int, std::tuple<Dependencies...>&, index_sequence<>) noexcept
{
// We tried to take a stream from all of our dependencies and failed every
// time, so we need to make a new stream.
return {unique_stream{}, {}};
}
template <typename... Dependencies, std::size_t I0, std::size_t... Is>
THRUST_HOST acquired_stream
acquire_stream_impl(int device_id, std::tuple<Dependencies...>& deps, index_sequence<I0, Is...>) noexcept
{
THRUST_SUPPRESS_DEPRECATED_PUSH // for thrust::optional (MSVC warnings here)
auto tr = try_acquire_stream(device_id, std::get<I0>(deps));
if (tr)
{
return {std::move(*tr), {I0}};
}
else
{
return acquire_stream_impl(device_id, deps, index_sequence<Is...>{});
}
THRUST_SUPPRESS_DEPRECATED_POP
}
template <typename... Dependencies>
THRUST_HOST acquired_stream acquire_stream(int device_id, std::tuple<Dependencies...>& deps) noexcept
{
return acquire_stream_impl(device_id, deps, make_index_sequence<sizeof...(Dependencies)>{});
}
///////////////////////////////////////////////////////////////////////////////
template <typename X, typename Deleter>
THRUST_HOST void create_dependency(unique_stream&, std::unique_ptr<X, Deleter>&) noexcept
{}
THRUST_HOST inline void create_dependency(unique_stream&, ready_event&) noexcept {}
template <typename T>
THRUST_HOST void create_dependency(unique_stream&, ready_future<T>&) noexcept
{}
THRUST_HOST inline void create_dependency(unique_stream& child, unique_stream& parent)
{
child.depend_on(parent);
}
THRUST_HOST inline void create_dependency(unique_stream& child, unique_eager_event& parent)
{
child.depend_on(parent.stream());
}
template <typename X>
THRUST_HOST void create_dependency(unique_stream& child, unique_eager_future<X>& parent)
{
child.depend_on(parent.stream());
}
template <typename... Dependencies>
THRUST_HOST void create_dependencies_impl(acquired_stream&, std::tuple<Dependencies...>&, index_sequence<>)
{}
template <typename... Dependencies, std::size_t I0, std::size_t... Is>
THRUST_HOST void
create_dependencies_impl(acquired_stream& as, std::tuple<Dependencies...>& deps, index_sequence<I0, Is...>)
{
// We only need to wait on the current dependency if we didn't steal our
// stream from it.
THRUST_SUPPRESS_DEPRECATED_PUSH
if (!as.acquired_from || *as.acquired_from != I0)
{
create_dependency(as.stream, std::get<I0>(deps));
}
THRUST_SUPPRESS_DEPRECATED_POP
create_dependencies_impl(as, deps, index_sequence<Is...>{});
}
template <typename... Dependencies>
THRUST_HOST void create_dependencies(acquired_stream& as, std::tuple<Dependencies...>& deps)
{
create_dependencies_impl(as, deps, make_index_sequence<sizeof...(Dependencies)>{});
}
///////////////////////////////////////////////////////////////////////////////
// Metafunction that determine which `Dependencies` need to be kept alive.
// Returns the result as an `index_sequence` of indices into the parameter
// pack.
template <typename Tuple, typename Indices>
struct find_keep_alives_impl;
template <typename Tuple>
using find_keep_alives =
typename find_keep_alives_impl<Tuple, make_index_sequence<std::tuple_size<Tuple>::value>>::type;
template <>
struct find_keep_alives_impl<std::tuple<>, index_sequence<>>
{
using type = index_sequence<>;
};
// User-provided stream.
template <typename... Dependencies, std::size_t I0, std::size_t... Is>
struct find_keep_alives_impl<std::tuple<unique_stream, Dependencies...>, index_sequence<I0, Is...>>
{
// Nothing to keep alive, skip this index.
using type = typename find_keep_alives_impl<std::tuple<Dependencies...>, index_sequence<Is...>>::type;
};
template <typename... Dependencies, std::size_t I0, std::size_t... Is>
struct find_keep_alives_impl<std::tuple<ready_event, Dependencies...>, index_sequence<I0, Is...>>
{
// Nothing to keep alive, skip this index.
using type = typename find_keep_alives_impl<std::tuple<Dependencies...>, index_sequence<Is...>>::type;
};
template <typename T, typename... Dependencies, std::size_t I0, std::size_t... Is>
struct find_keep_alives_impl<std::tuple<ready_future<T>, Dependencies...>, index_sequence<I0, Is...>>
{
// Add this index to the list.
using type = integer_sequence_push_front<
std::size_t,
I0,
typename find_keep_alives_impl<std::tuple<Dependencies...>, index_sequence<Is...>>::type>;
};
template <typename... Dependencies, std::size_t I0, std::size_t... Is>
struct find_keep_alives_impl<std::tuple<unique_eager_event, Dependencies...>, index_sequence<I0, Is...>>
{
// Add this index to the list.
using type = integer_sequence_push_front<
std::size_t,
I0,
typename find_keep_alives_impl<std::tuple<Dependencies...>, index_sequence<Is...>>::type>;
};
template <typename X, typename... Dependencies, std::size_t I0, std::size_t... Is>
struct find_keep_alives_impl<std::tuple<unique_eager_future<X>, Dependencies...>, index_sequence<I0, Is...>>
{
// Add this index to the list.
using type = integer_sequence_push_front<
std::size_t,
I0,
typename find_keep_alives_impl<std::tuple<Dependencies...>, index_sequence<Is...>>::type>;
};
// Content storage.
template <typename T, typename Deleter, typename... Dependencies, std::size_t I0, std::size_t... Is>
struct find_keep_alives_impl<std::tuple<std::unique_ptr<T, Deleter>, Dependencies...>, index_sequence<I0, Is...>>
{
// Add this index to the list.
using type = integer_sequence_push_front<
std::size_t,
I0,
typename find_keep_alives_impl<std::tuple<Dependencies...>, index_sequence<Is...>>::type>;
};
///////////////////////////////////////////////////////////////////////////////
template <typename... Dependencies>
THRUST_HOST unique_eager_event make_dependent_event(std::tuple<Dependencies...>&& deps)
{
int device_id = 0;
thrust::hip_rocprim::throw_on_error(hipGetDevice(&device_id));
// First, either steal a stream from one of our children or make a new one.
auto as = acquire_stream(device_id, deps);
// Then, make the stream we've acquired asynchronously wait on all of our
// dependencies, except the one we stole the stream from.
create_dependencies(as, deps);
// Then, we determine which subset of dependencies need to be kept alive.
auto ka = tuple_subset(std::move(deps), find_keep_alives<std::tuple<Dependencies...>>{});
// Next, we create the asynchronous signal.
using async_signal_type = async_keep_alives<decltype(ka)>;
std::unique_ptr<async_signal_type> sig(new async_signal_type(std::move(as.stream), std::move(ka)));
// Finally, we create the event object.
return unique_eager_event(device_id, std::move(sig));
}
///////////////////////////////////////////////////////////////////////////////
template <typename X, typename XPointer, typename ComputeContent, typename... Dependencies>
THRUST_HOST unique_eager_future_promise_pair<X, XPointer>
make_dependent_future(ComputeContent&& cc, std::tuple<Dependencies...>&& deps)
{
int device_id = 0;
thrust::hip_rocprim::throw_on_error(hipGetDevice(&device_id));
// First, either steal a stream from one of our children or make a new one.
auto as = acquire_stream(device_id, deps);
// Then, make the stream we've acquired asynchronously wait on all of our
// dependencies, except the one we stole the stream from.
create_dependencies(as, deps);
// Then, we determine which subset of dependencies need to be kept alive.
auto ka = tuple_subset(std::move(deps), find_keep_alives<std::tuple<Dependencies...>>{});
// Next, we create the asynchronous value.
using async_signal_type = async_addressable_value_with_keep_alives<X, XPointer, decltype(ka)>;
std::unique_ptr<async_signal_type> sig(new async_signal_type(std::move(as.stream), std::move(ka), std::move(cc)));
// Finally, we create the promise and future objects.
weak_promise<X, XPointer> child_prom(device_id, sig->data());
unique_eager_future<X> child_fut(device_id, std::move(sig));
return unique_eager_future_promise_pair<X, XPointer>{std::move(child_fut), std::move(child_prom)};
}
} // namespace detail
///////////////////////////////////////////////////////////////////////////////
template <typename... Events>
THRUST_DEPRECATED THRUST_HOST unique_eager_event when_all(Events&&... evs)
// TODO: Constrain to events, futures, and maybe streams (currently allows keep
// alives).
{
return detail::make_dependent_event(std::make_tuple(std::move(evs)...));
}
// ADL hook for transparent `.after` move support.
THRUST_DEPRECATED THRUST_HOST inline auto capture_as_dependency(unique_eager_event& dependency)
THRUST_DECLTYPE_RETURNS(std::move(dependency))
// ADL hook for transparent `.after` move support.
template <typename X>
THRUST_DEPRECATED THRUST_HOST auto capture_as_dependency(unique_eager_future<X>& dependency)
THRUST_DECLTYPE_RETURNS(std::move(dependency))
} // namespace hip
} // namespace system
THRUST_SUPPRESS_DEPRECATED_POP
THRUST_NAMESPACE_END
#endif // C++17