DDC 0.9.0
Loading...
Searching...
No Matches
discrete_space.hpp
1// Copyright (C) The DDC development team, see COPYRIGHT.md file
2//
3// SPDX-License-Identifier: MIT
4
5#pragma once
6
7#include <cassert>
8#include <cstddef>
9#include <functional>
10#include <map>
11#include <optional>
12#include <ostream>
13#include <stdexcept>
14#include <string>
15#include <tuple>
16#include <type_traits>
17#include <utility>
18
19#include <Kokkos_Core.hpp>
20
21#include "detail/dual_discretization.hpp"
22#include "detail/macros.hpp"
23
24#if defined(KOKKOS_ENABLE_CUDA)
25# include <sstream>
26
27# include <cuda.h>
28#elif defined(KOKKOS_ENABLE_HIP)
29# include <sstream>
30
31# include <hip/hip_runtime.h>
32#endif
33
34#if defined(KOKKOS_ENABLE_CUDA) || defined(KOKKOS_ENABLE_HIP)
35# define DDC_DETAIL_DEVICE_THROW_ON_ERROR(val)
36 ddc::detail::device_throw_on_error((val), #val, __FILE__, __LINE__)
37#endif
38
39namespace ddc {
40
41namespace detail {
42
43#if defined(KOKKOS_ENABLE_CUDA)
44inline void device_throw_on_error(
45 cudaError_t const err,
46 const char* const func,
47 const char* const file,
48 const int line)
49{
50 if (err != cudaSuccess) {
51 std::stringstream ss;
52 ss << "CUDA Runtime Error at: " << file << ":" << line << "\n";
53 ss << cudaGetErrorString(err) << " " << func << "\n";
54 throw std::runtime_error(ss.str());
55 }
56}
57#elif defined(KOKKOS_ENABLE_HIP)
58inline void device_throw_on_error(
59 hipError_t const err,
60 const char* const func,
61 const char* const file,
62 const int line)
63{
64 if (err != hipSuccess) {
65 std::stringstream ss;
66 ss << "HIP Runtime Error at: " << file << ":" << line << "\n";
67 ss << hipGetErrorString(err) << " " << func << "\n";
68 throw std::runtime_error(ss.str());
69 }
70}
71#endif
72
73template <class DDim, class MemorySpace>
74using ddim_impl_t = typename DDim::template Impl<DDim, MemorySpace>;
75
76template <class T>
77class gpu_proxy
78{
79 // Here are some reasonable concepts that T should satisfy to avoid undefined behaviors:
80 // - copy-constructible: objects may be memcopied to the device,
81 // - standard layout: objects will be ensured to have the same, standard, representation on the host and the device,
82 // - trivially destructible: the destructor of objects located on a device may not be called.
83 // static_assert(std::is_standard_layout_v<T>, "Not standard layout");
84 // static_assert(std::is_trivially_destructible_v<T>, "Not trivially destructible");
85 // static_assert(std::is_trivially_copy_constructible_v<T>, "Not trivially copy-constructible");
86 // Currently not trivially destructible because for example of the Kokkos::View (mostly reference-counting)
87 // Currently not trivially copy-constructible because of discrete spaces that have deleted copy-constructors and Kokkos::View (mostly reference-counting)
88
89private:
90 alignas(T) Kokkos::Array<std::byte, sizeof(T)> m_data;
91
92public:
93 KOKKOS_FUNCTION
94 T* operator->()
95 {
96 return reinterpret_cast<T*>(m_data.data());
97 }
98
99 KOKKOS_FUNCTION
100 T& operator*()
101 {
102 return *reinterpret_cast<T*>(m_data.data());
103 }
104
105 KOKKOS_FUNCTION
106 T* data()
107 {
108 return reinterpret_cast<T*>(m_data.data());
109 }
110};
111
112// Global CPU variable storing resetters. Required to correctly free data.
113inline std::optional<std::map<std::string, std::function<void()>>> g_discretization_store;
114
115// Global CPU variable owning discrete spaces data for CPU and GPU
116template <class DDim>
117inline std::optional<DualDiscretization<DDim>> g_discrete_space_dual;
118
119#if defined(KOKKOS_ENABLE_CUDA)
120// Global GPU variable viewing data owned by the CPU
121template <class DDim>
122__constant__ gpu_proxy<ddim_impl_t<DDim, GlobalVariableDeviceSpace>> g_discrete_space_device;
123#elif defined(KOKKOS_ENABLE_HIP)
124// Global GPU variable viewing data owned by the CPU
125// WARNING: do not put the `inline` keyword, seems to fail on MI100 rocm/4.5.0
126template <class DDim>
127__constant__ gpu_proxy<ddim_impl_t<DDim, GlobalVariableDeviceSpace>> g_discrete_space_device;
128#elif defined(KOKKOS_ENABLE_SYCL)
129// Global GPU variable viewing data owned by the CPU
130template <class DDim>
131SYCL_EXTERNAL inline sycl::ext::oneapi::experimental::device_global<
132 gpu_proxy<ddim_impl_t<DDim, GlobalVariableDeviceSpace>>>
133 g_discrete_space_device;
134#endif
135
136inline void display_discretization_store(std::ostream& os)
137{
138 if (g_discretization_store) {
139 os << "The host discretization store is initialized:\n";
140 for (auto const& [key, value] : *g_discretization_store) {
141 os << " - " << key << "\n";
142 }
143 } else {
144 os << "The host discretization store is not initialized:\n";
145 }
146}
147
148template <class Tuple, std::size_t... Ids>
149auto extract_after(Tuple&& t, std::index_sequence<Ids...>)
150{
151 return std::make_tuple(std::move(std::get<Ids + 1>(t))...);
152}
153
154} // namespace detail
155
156/** Initialize (emplace) a global singleton discrete space
157 *
158 * @param args the constructor arguments
159 */
160template <class DDim, class... Args>
161void init_discrete_space(Args&&... args)
162{
163 static_assert(
164 !std::is_same_v<DDim, typename DDim::discrete_dimension_type>,
165 "Discrete dimensions should inherit from the discretization, not use an alias");
166 if (detail::g_discrete_space_dual<DDim>) {
167 throw std::runtime_error("Discrete space function already initialized.");
168 }
169 detail::g_discrete_space_dual<DDim>.emplace(std::forward<Args>(args)...);
170 detail::g_discretization_store->emplace(typeid(DDim).name(), []() {
171 detail::g_discrete_space_dual<DDim>.reset();
172 });
173#if defined(KOKKOS_ENABLE_CUDA)
174 DDC_DETAIL_DEVICE_THROW_ON_ERROR(cudaMemcpyToSymbol(
175 detail::g_discrete_space_device<DDim>,
176 &detail::g_discrete_space_dual<DDim>->get_device(),
177 sizeof(detail::g_discrete_space_dual<DDim>->get_device())));
178#elif defined(KOKKOS_ENABLE_HIP)
179 DDC_DETAIL_DEVICE_THROW_ON_ERROR(hipMemcpyToSymbol(
180 detail::g_discrete_space_device<DDim>,
181 &detail::g_discrete_space_dual<DDim>->get_device(),
182 sizeof(detail::g_discrete_space_dual<DDim>->get_device())));
183#elif defined(KOKKOS_ENABLE_SYCL)
184 Kokkos::DefaultExecutionSpace exec;
185 sycl::queue q = exec.sycl_queue();
186 q.memcpy(detail::g_discrete_space_device<DDim>,
187 &detail::g_discrete_space_dual<DDim>->get_device())
188 .wait();
189#endif
190}
191
192/** Move construct a global singleton discrete space and pass through the other argument
193 *
194 * @param a - the discrete space to move at index 0
195 * - the arguments to pass through at index 1
196 */
197template <class DDim, class DDimImpl, class Arg0>
198Arg0 init_discrete_space(std::tuple<DDimImpl, Arg0>&& a)
199{
200 init_discrete_space<DDim>(std::move(std::get<0>(a)));
201 return std::get<1>(a);
202}
203
204/** Move construct a global singleton discrete space and pass through remaining arguments
205 *
206 * @param a - the discrete space to move at index 0
207 * - the (2+) arguments to pass through in other indices
208 */
209template <class DDim, class DDimImpl, class Arg0, class Arg1, class... Args>
210std::tuple<Arg0, Arg1, Args...> init_discrete_space(std::tuple<DDimImpl, Arg0, Arg1, Args...>&& a)
211{
212 init_discrete_space<DDim>(std::move(std::get<0>(a)));
213 return detail::extract_after(std::move(a), std::index_sequence_for<Arg0, Arg1, Args...>());
214}
215
216/**
217 * @tparam DDim a discrete dimension
218 * @return a boolean indicating whether DDim is initialized.
219 * This function indicates whether a dimension is initialized.
220 */
221template <class DDim>
222bool is_discrete_space_initialized() noexcept
223{
224 return detail::g_discrete_space_dual<DDim>.has_value();
225}
226
227/**
228 * @tparam DDim a discrete dimension
229 * @return the discrete space instance associated with `DDim`.
230 * This function must be called from a `KOKKOS_FUNCTION`.
231 * Call `ddc::host_discrete_space` for a host-only function instead.
232 */
233template <class DDim, class MemorySpace = DDC_CURRENT_KOKKOS_SPACE>
234KOKKOS_FUNCTION detail::ddim_impl_t<DDim, MemorySpace> const& discrete_space()
235{
236 // This function requires that `ddc::init_discrete_space<DDim>(...);` be called first
237 if constexpr (std::is_same_v<MemorySpace, Kokkos::HostSpace>) {
238 KOKKOS_ASSERT((is_discrete_space_initialized<DDim>()))
239 return detail::g_discrete_space_dual<DDim>->get_host();
240 }
241#if defined(KOKKOS_ENABLE_CUDA) || defined(KOKKOS_ENABLE_HIP)
242 else if constexpr (std::is_same_v<MemorySpace, detail::GlobalVariableDeviceSpace>) {
243 return *detail::g_discrete_space_device<DDim>;
244 }
245#elif defined(KOKKOS_ENABLE_SYCL)
246 else if constexpr (std::is_same_v<MemorySpace, detail::GlobalVariableDeviceSpace>) {
247 return *detail::g_discrete_space_device<DDim>.get();
248 }
249#endif
250 else {
251 static_assert(std::is_same_v<MemorySpace, MemorySpace>, "Memory space not handled");
252 }
253}
254
255template <class DDim>
256detail::ddim_impl_t<DDim, Kokkos::HostSpace> const& host_discrete_space()
257{
258 // This function requires that `ddc::init_discrete_space<DDim>(...);` be called first
259 assert(is_discrete_space_initialized<DDim>());
260 return detail::g_discrete_space_dual<DDim>->get_host();
261}
262
263} // namespace ddc
264
265#if defined(KOKKOS_ENABLE_CUDA) || defined(KOKKOS_ENABLE_HIP)
266# undef DDC_DETAIL_DEVICE_THROW_ON_ERROR
267#endif
The top-level namespace of DDC.
bool is_discrete_space_initialized() noexcept
void init_discrete_space(Args &&... args)
Initialize (emplace) a global singleton discrete space.
detail::ddim_impl_t< DDim, Kokkos::HostSpace > const & host_discrete_space()
KOKKOS_FUNCTION detail::ddim_impl_t< DDim, MemorySpace > const & discrete_space()
Arg0 init_discrete_space(std::tuple< DDimImpl, Arg0 > &&a)
Move construct a global singleton discrete space and pass through the other argument.
std::tuple< Arg0, Arg1, Args... > init_discrete_space(std::tuple< DDimImpl, Arg0, Arg1, Args... > &&a)
Move construct a global singleton discrete space and pass through remaining arguments.