//===-- Shared memory RPC client / server utilities -------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef LLVM_LIBC_SHARED_RPC_UTIL_H #define LLVM_LIBC_SHARED_RPC_UTIL_H #include #include #if (defined(__NVPTX__) || defined(__AMDGPU__) || defined(__SPIRV__)) && \ !((defined(__CUDA__) && !defined(__CUDA_ARCH__)) || \ (defined(__HIP__) && !defined(__HIP_DEVICE_COMPILE__))) #include #define RPC_TARGET_IS_GPU #endif // Workaround for missing __has_builtin in < GCC 10. #ifndef __has_builtin #define __has_builtin(x) 0 #endif #ifndef RPC_ATTRS #if defined(__CUDA__) || defined(__HIP__) #define RPC_ATTRS __attribute__((host, device)) inline #else #define RPC_ATTRS inline #endif #endif namespace rpc { template struct type_identity { using type = T; }; template struct type_constant { static inline constexpr T value = v; }; /// Freestanding type trait helpers. template struct remove_cv : type_identity {}; template struct remove_cv : type_identity {}; template using remove_cv_t = typename remove_cv::type; template struct remove_pointer : type_identity {}; template struct remove_pointer : type_identity {}; template using remove_pointer_t = typename remove_pointer::type; template struct remove_const : type_identity {}; template struct remove_const : type_identity {}; template using remove_const_t = typename remove_const::type; template struct remove_reference : type_identity {}; template struct remove_reference : type_identity {}; template struct remove_reference : type_identity {}; template using remove_reference_t = typename remove_reference::type; template struct is_const : type_constant {}; template struct is_const : type_constant {}; template inline constexpr bool is_const_v = is_const::value; template struct is_pointer : type_constant {}; template struct is_pointer : type_constant {}; template struct is_pointer : type_constant {}; template inline constexpr bool is_pointer_v = is_pointer::value; template struct is_same : type_constant {}; template struct is_same : type_constant {}; template inline constexpr bool is_same_v = is_same::value; template struct is_void : type_constant {}; template <> struct is_void : type_constant {}; template inline constexpr bool is_void_v = is_void::value; // Scary trait that can change within a TU, use with caution. template using void_t = void; template struct is_complete : type_constant {}; template struct is_complete> : type_constant { }; template inline constexpr bool is_complete_v = is_complete::value; template struct is_trivially_copyable : public type_constant {}; template inline constexpr bool is_trivially_copyable_v = is_trivially_copyable::value; template struct is_trivially_constructible : type_constant {}; template inline constexpr bool is_trivially_constructible_v = is_trivially_constructible::value; /// Tag type to indicate an array of elements being passed through RPC. template struct span { T *data; uint64_t size; RPC_ATTRS operator T *() const { return data; } }; template struct is_span : type_constant {}; template struct is_span> : type_constant {}; template inline constexpr bool is_span_v = is_span::value; template struct remove_span : type_identity {}; template struct remove_span> : type_identity {}; template using remove_span_t = typename remove_span::type; template struct conditional : type_identity {}; template struct conditional : type_identity {}; template using conditional_t = typename conditional::type; /// Freestanding implementation of std::move. template RPC_ATTRS constexpr typename remove_reference::type &&move(T &&t) { return static_cast::type &&>(t); } /// Freestanding integer sequence. template struct integer_sequence { template using append = integer_sequence; }; namespace detail { template struct make_integer_sequence { using type = typename make_integer_sequence::type::template append; }; template struct make_integer_sequence { using type = integer_sequence; }; } // namespace detail template using index_sequence = integer_sequence; template using make_index_sequence = typename detail::make_integer_sequence::type; template using index_sequence_for = make_index_sequence; /// Freestanding implementation of std::forward. template RPC_ATTRS constexpr T &&forward(typename remove_reference::type &value) { return static_cast(value); } template RPC_ATTRS constexpr T &&forward(typename remove_reference::type &&value) { return static_cast(value); } struct in_place_t { RPC_ATTRS explicit in_place_t() = default; }; struct nullopt_t { RPC_ATTRS constexpr explicit nullopt_t() = default; }; constexpr inline in_place_t in_place{}; constexpr inline nullopt_t nullopt{}; /// Freestanding and minimal implementation of std::optional. template struct optional { template struct OptionalStorage { union { char empty; U stored_value; }; bool in_use = false; RPC_ATTRS ~OptionalStorage() { reset(); } RPC_ATTRS constexpr OptionalStorage() : empty() {} template RPC_ATTRS constexpr explicit OptionalStorage(in_place_t, Args &&...args) : stored_value(forward(args)...) { in_use = true; } RPC_ATTRS constexpr void reset() { if (in_use) stored_value.~U(); in_use = false; } }; OptionalStorage storage; public: RPC_ATTRS constexpr optional() = default; RPC_ATTRS constexpr optional(nullopt_t) {} RPC_ATTRS constexpr optional(const T &t) : storage(in_place, t) {} RPC_ATTRS constexpr optional(const optional &) = default; RPC_ATTRS constexpr optional(T &&t) : storage(in_place, move(t)) {} RPC_ATTRS constexpr optional(optional &&O) = default; template RPC_ATTRS constexpr optional(in_place_t, Args &&...args) : storage(in_place, forward(args)...) {} RPC_ATTRS constexpr optional &operator=(T &&t) { storage = move(t); return *this; } RPC_ATTRS constexpr optional &operator=(optional &&) = default; RPC_ATTRS constexpr optional &operator=(const T &t) { storage = t; return *this; } RPC_ATTRS constexpr optional &operator=(const optional &) = default; RPC_ATTRS constexpr void reset() { storage.reset(); } RPC_ATTRS constexpr const T &value() const & { return storage.stored_value; } RPC_ATTRS constexpr T &value() & { return storage.stored_value; } RPC_ATTRS constexpr explicit operator bool() const { return storage.in_use; } RPC_ATTRS constexpr bool has_value() const { return storage.in_use; } RPC_ATTRS constexpr const T *operator->() const { return &storage.stored_value; } RPC_ATTRS constexpr T *operator->() { return &storage.stored_value; } RPC_ATTRS constexpr const T &operator*() const & { return storage.stored_value; } RPC_ATTRS constexpr T &operator*() & { return storage.stored_value; } RPC_ATTRS constexpr T &&value() && { return move(storage.stored_value); } RPC_ATTRS constexpr T &&operator*() && { return move(storage.stored_value); } }; /// Minimal array type. template struct array { T elems[N]; RPC_ATTRS constexpr T *data() { return elems; } RPC_ATTRS constexpr const T *data() const { return elems; } RPC_ATTRS static constexpr uint64_t size() { return N; } RPC_ATTRS constexpr T &operator[](uint64_t i) { return elems[i]; } RPC_ATTRS constexpr const T &operator[](uint64_t i) const { return elems[i]; } }; /// Minimal tuple type. template struct tuple; template <> struct tuple<> {}; template struct tuple : tuple { Head head; RPC_ATTRS constexpr tuple() = default; template RPC_ATTRS constexpr tuple &operator=(const tuple &other) { head = other.get_head(); this->get_tail() = other.get_tail(); return *this; } RPC_ATTRS constexpr tuple(const Head &h, const Tail &...t) : tuple(t...), head(h) {} RPC_ATTRS constexpr Head &get_head() { return head; } RPC_ATTRS constexpr const Head &get_head() const { return head; } RPC_ATTRS constexpr tuple &get_tail() { return *this; } RPC_ATTRS constexpr const tuple &get_tail() const { return *this; } }; template struct tuple_element; template struct tuple_element> : tuple_element> {}; template struct tuple_element<0, tuple> { using type = remove_cv_t>; }; template using tuple_element_t = typename tuple_element::type; template RPC_ATTRS constexpr auto &get(tuple &t) { if constexpr (Idx == 0) return t.get_head(); else return get(t.get_tail()); } template RPC_ATTRS constexpr const auto &get(const tuple &t) { if constexpr (Idx == 0) return t.get_head(); else return get(t.get_tail()); } namespace detail { template RPC_ATTRS auto apply(F &&f, Tuple &&t, index_sequence) { return f(get(static_cast(t))...); } } // namespace detail template RPC_ATTRS auto apply(F &&f, tuple &t) { return detail::apply(static_cast(f), t, make_index_sequence{}); } /// Suspend the thread briefly to assist the thread scheduler during busy loops. RPC_ATTRS void sleep_briefly() { #if __has_builtin(__nvvm_reflect) if (__nvvm_reflect("__CUDA_ARCH") >= 700) asm("nanosleep.u32 64;" ::: "memory"); #elif __has_builtin(__builtin_amdgcn_s_sleep) __builtin_amdgcn_s_sleep(2); #elif __has_builtin(__builtin_ia32_pause) __builtin_ia32_pause(); #elif __has_builtin(__builtin_arm_isb) __builtin_arm_isb(0xf); #else // Simply do nothing if sleeping isn't supported on this platform. #endif } /// Conditional to indicate if this process is running on the GPU. RPC_ATTRS constexpr bool is_process_gpu() { #ifdef RPC_TARGET_IS_GPU return true; #else return false; #endif } /// Wait for all lanes in the group to complete. RPC_ATTRS void sync_lane([[maybe_unused]] uint64_t lane_mask) { #ifdef RPC_TARGET_IS_GPU return __gpu_sync_lane(lane_mask); #endif } /// Copies the value from the first active thread to the rest. RPC_ATTRS uint32_t broadcast_value([[maybe_unused]] uint64_t lane_mask, uint32_t x) { #ifdef RPC_TARGET_IS_GPU return __gpu_read_first_lane_u32(lane_mask, x); #else return x; #endif } /// Returns the number lanes that participate in the RPC interface. RPC_ATTRS uint32_t get_num_lanes() { #ifdef RPC_TARGET_IS_GPU return __gpu_num_lanes(); #else return 1; #endif } /// Returns a bitmask of the currently active lanes. RPC_ATTRS uint64_t get_lane_mask() { #ifdef RPC_TARGET_IS_GPU return __gpu_lane_mask(); #else return 1; #endif } /// Returns the id of the thread inside of an AMD wavefront executing together. RPC_ATTRS uint32_t get_lane_id() { #ifdef RPC_TARGET_IS_GPU return __gpu_lane_id(); #else return 0; #endif } /// Conditional that is only true for a single thread in a lane. RPC_ATTRS bool is_first_lane([[maybe_unused]] uint64_t lane_mask) { #ifdef RPC_TARGET_IS_GPU return __gpu_is_first_in_lane(lane_mask); #else return true; #endif } /// Returns a bitmask of threads in the current lane for which \p x is true. RPC_ATTRS uint64_t ballot([[maybe_unused]] uint64_t lane_mask, bool x) { #ifdef RPC_TARGET_IS_GPU return __gpu_ballot(lane_mask, x); #else return x; #endif } /// Signal an interrupt from the device to wake the server. Only supported for /// AMDGPU targets currently. RPC_ATTRS void signal_interrupt([[maybe_unused]] uint32_t event_id) { #ifdef __AMDGPU__ constexpr uint32_t MSG_INTERRUPT = 1; __builtin_amdgcn_s_sendmsg(MSG_INTERRUPT, event_id); #endif } /// Return \p val aligned "upwards" according to \p align. template RPC_ATTRS constexpr V align_up(V val, A align) { return ((val + V(align) - 1) / V(align)) * V(align); } /// Utility to provide a unified interface between the CPU and GPU's memory /// model. On the GPU stack variables are always private to a lane so we can /// simply use the variable passed in. On the CPU we need to allocate enough /// space for the whole lane and index into it. template RPC_ATTRS V &lane_value(V *val, uint32_t id) { if constexpr (is_process_gpu()) return *val; return val[id]; } /// Advance the \p p by \p bytes. template RPC_ATTRS T *advance(T *ptr, U bytes) { if constexpr (is_const::value) return reinterpret_cast(reinterpret_cast(ptr) + bytes); else return reinterpret_cast(reinterpret_cast(ptr) + bytes); } /// Wrapper around the optimal memory copy implementation for the target. RPC_ATTRS void rpc_memcpy(void *dst, const void *src, uint64_t count) { #if __has_builtin(__builtin_memcpy) if (count) __builtin_memcpy(dst, src, count); #else for (uint64_t i = 0; i < count; ++i) static_cast(dst)[i] = static_cast(src)[i]; #endif } /// Minimal string length function. RPC_ATTRS constexpr uint64_t string_length(const char *s) { const char *end = s; for (; *end != '\0'; ++end) ; return static_cast(end - s + 1); } /// Helper for dealing with function pointers and lambda types. template struct function_traits; template struct function_traits { using return_type = R; using arg_types = rpc::tuple; static constexpr uint64_t ARITY = sizeof...(Args); }; template struct function_traits { using return_type = R; using arg_types = rpc::tuple; static constexpr uint64_t ARITY = sizeof...(Args); }; template T &&declval(); template struct function_traits : function_traits>())> {}; template RPC_ATTRS constexpr T max(const T &a, const U &b) { return (a < b) ? b : a; } template RPC_ATTRS constexpr T min(const T &a, const U &b) { return (a < b) ? a : b; } } // namespace rpc #endif // LLVM_LIBC_SHARED_RPC_UTIL_H