//===----------------------------------------------------------------------===// // // Part of the libcu++ 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 // //===----------------------------------------------------------------------===// // Modifications Copyright (c) 2024-2025 Advanced Micro Devices, Inc. // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #ifndef HETEROGENEOUS_HELPERS_H #define HETEROGENEOUS_HELPERS_H #include "../cuda_runtime.h" #include #include #include #include #include #include "meta.h" #define DEFINE_ASYNC_TRAIT(...) \ template \ struct async##__VA_ARGS__##_trait_impl \ { \ using type = cuda::std::false_type; \ }; \ \ template \ struct async##__VA_ARGS__##_trait_impl \ { \ using type = cuda::std::true_type; \ }; \ \ template \ using async##__VA_ARGS__##_trait = typename async##__VA_ARGS__##_trait_impl::type; DEFINE_ASYNC_TRAIT() DEFINE_ASYNC_TRAIT(_initialize) DEFINE_ASYNC_TRAIT(_validate) #undef DEFINE_ASYNC_TRAIT template struct has_threadcount : std::false_type {}; template struct has_threadcount : std::true_type {}; template ::value> struct threadcount_trait_impl { static constexpr size_t value = 1; }; template struct threadcount_trait_impl { static constexpr size_t value = T::threadcount; }; template using threadcount_trait = threadcount_trait_impl; #define HETEROGENEOUS_SAFE_CALL(...) \ do \ { \ cudaError_t err = __VA_ARGS__; \ if (err != cudaSuccess) \ { \ printf("CUDA ERROR: %s: %s\n", cudaGetErrorName(err), cudaGetErrorString(err)); \ abort(); \ } \ } while (false) __host__ inline std::vector& host_threads() { static std::vector threads; return threads; } __host__ inline void sync_host_threads() { for (auto&& thread : host_threads()) { thread.join(); } host_threads().clear(); } __host__ inline std::vector& device_threads() { static std::vector threads; return threads; } __host__ inline void sync_device_threads() { for (auto&& thread : device_threads()) { thread.join(); } device_threads().clear(); } __host__ void sync_all() { sync_host_threads(); sync_device_threads(); } struct async_tester_fence { template __host__ __device__ static void initialize(T&) {} template __host__ __device__ static void validate(T&) {} template __host__ __device__ static void perform(T&) {} }; template using tester_list = type_list; template __host__ __device__ void initialize(T& object) { Tester::initialize(object); } template __host__ __device__ auto validate_impl(T& object) -> decltype(Tester::validate(object), void()) { Tester::validate(object); } template __host__ __device__ void validate_impl(Ts&&...) {} template __host__ __device__ void validate(T& object) { validate_impl(object); } template __global__ void construct_kernel(void* address, Args... args) { new (address) T(args...); } template __global__ void destroy_kernel(T* object) { object->~T(); } template __global__ void initialization_kernel(T& object) { initialize(object); } template __global__ void validation_kernel(T& object) { validate(object); } template T* device_construct(void* address, Args... args) { construct_kernel<<<1, 1>>>(address, args...); HETEROGENEOUS_SAFE_CALL(cudaGetLastError()); HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize()); return reinterpret_cast(address); } template void device_destroy(T* object) { destroy_kernel<<<1, 1>>>(object); HETEROGENEOUS_SAFE_CALL(cudaGetLastError()); HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize()); } template void device_launch_async(Fn& launcher) { auto streamManager = [launcher]() { cudaStream_t stream; HETEROGENEOUS_SAFE_CALL(cudaStreamCreate(&stream)); launcher(stream); HETEROGENEOUS_SAFE_CALL(cudaGetLastError()); HETEROGENEOUS_SAFE_CALL(cudaStreamSynchronize(stream)); HETEROGENEOUS_SAFE_CALL(cudaStreamDestroy(stream)); }; device_threads().push_back(std::thread(streamManager)); } template void device_initialize(T& object) { #ifdef DEBUG_TESTERS printf(" %s\n", __PRETTY_FUNCTION__); fflush(stdout); #endif auto kernel_launcher = [&object](cudaStream_t stream) { constexpr auto tc = threadcount_trait::value; #ifdef DEBUG_TESTERS printf(" %i device init threads launched\r\n", (int) tc); fflush(stdout); #endif initialization_kernel<<<1, tc, 0, stream>>>(object); }; device_launch_async(kernel_launcher); if (!async_initialize_trait::value) { HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize()); sync_all(); } } template void device_validate(T& object) { #ifdef DEBUG_TESTERS printf(" %s\n", __PRETTY_FUNCTION__); fflush(stdout); #endif auto kernel_launcher = [&object](cudaStream_t stream) { constexpr auto tc = threadcount_trait::value; #ifdef DEBUG_TESTERS printf(" %i device validate threads launched\r\n", (int) tc); fflush(stdout); #endif validation_kernel<<<1, tc, 0, stream>>>(object); }; device_launch_async(kernel_launcher); if (!async_validate_trait::value) { HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize()); sync_all(); } } template void host_initialize(T& object) { #ifdef DEBUG_TESTERS printf(" %s\n", __PRETTY_FUNCTION__); fflush(stdout); #endif constexpr auto tc = threadcount_trait::value; #ifdef DEBUG_TESTERS printf(" %i host init threads launched\r\n", (int) tc); fflush(stdout); #endif for (size_t i = 0; i < tc; i++) { host_threads().emplace_back([&] { initialize(object); }); } if (!async_initialize_trait::value) { HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize()); sync_all(); } } template void host_validate(T& object) { #ifdef DEBUG_TESTERS printf(" %s\n", __PRETTY_FUNCTION__); fflush(stdout); #endif constexpr auto tc = threadcount_trait::value; #ifdef DEBUG_TESTERS printf(" %i host validate threads launched\r\n", (int) tc); fflush(stdout); #endif for (size_t i = 0; i < tc; i++) { host_threads().emplace_back([&] { validate(object); }); } if (!async_initialize_trait::value) { HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize()); sync_all(); } } template using performer = void (*)(T&); template struct initializer_validator { performer initializer; performer validator; }; template struct host_launcher { template static initializer_validator get_exec() { return initializer_validator{host_initialize, host_validate}; } }; template struct device_launcher { template static initializer_validator get_exec() { return initializer_validator{device_initialize, device_validate}; } }; template void do_heterogeneous_test(T* test_input, type_list, type_list) { initializer_validator performers[] = {{Launchers::template get_exec()}...}; for (auto&& performer : performers) { performer.initializer(*test_input); performer.validator(*test_input); } HETEROGENEOUS_SAFE_CALL(cudaGetLastError()); HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize()); sync_all(); } template using enable_if_permutations_remain = typename std::enable_if::type; template using enable_if_no_permutations_remain = typename std::enable_if::type; template = 0> void permute_tests(const Fn& fn, Launchers launchers) { #ifdef DEBUG_TESTERS printf(" Testing permutation %zd (%s)\r\n", Idx, __PRETTY_FUNCTION__); fflush(stdout); #endif fn(launchers); permute_tests(fn, rotl{}); } template = 0> void permute_tests(const Fn&, Launchers) {} template void permute_tests(const Fn& fn, type_list launchers) { permute_tests(fn, launchers); } template struct test_wrapper { InputCreator creator; InputDestructor destructor; template void operator()(Launchers) const { auto input = creator(); do_heterogeneous_test(input, Testers{}, Launchers{}); destructor(input); } }; template void validate_device_dynamic(tester_list testers, Args... args) { auto test_input_creator = [args...]() -> T* { void* pointer = nullptr; HETEROGENEOUS_SAFE_CALL(cudaMallocHost(&pointer, sizeof(T))); return device_construct(pointer, args...); }; auto test_input_destructor = [](T* test_input) { device_destroy(test_input); HETEROGENEOUS_SAFE_CALL(cudaFreeHost(test_input)); }; test_wrapper, decltype(test_input_creator), decltype(test_input_destructor)> test_harness{ test_input_creator, test_input_destructor}; // ex: type_list using initial_launcher_list = append_n>, host_launcher>; #ifdef DEBUG_TESTERS printf("Launching %zd permutations\r\n", sizeof...(Testers)); fflush(stdout); #endif permute_tests(test_harness, initial_launcher_list{}); } #if _CCCL_STD_VER >= 2014 template struct manual_object { template void construct(Args... args) { new (static_cast(&data.object)) T(args...); } template void device_construct(Args... args) { ::device_construct(&data.object, args...); } void destroy() { data.object.~T(); } void device_destroy() { ::device_destroy(&data.object); } T& get() { return data.object; } union data { __host__ __device__ constexpr data() noexcept : dummy(){}; char dummy = {}; T object; } data; constexpr manual_object() noexcept {} }; template __managed__ manual_object managed_variable{}; #endif template void validate_in_managed_memory_helper(const Creator& creator, const Destroyer& destroyer, Validator (&performers)[N]) { auto object = creator(); for (auto&& performer : performers) { performer.initializer(*object); performer.validator(*object); } HETEROGENEOUS_SAFE_CALL(cudaGetLastError()); HETEROGENEOUS_SAFE_CALL(cudaDeviceSynchronize()); sync_all(); destroyer(object); } template void validate_managed(tester_list, Args... args) { initializer_validator host_init_device_check[] = {{host_initialize, device_validate}...}; initializer_validator device_init_host_check[] = {{device_initialize, host_validate}...}; auto host_constructor = [args...]() -> T* { void* pointer; HETEROGENEOUS_SAFE_CALL(cudaMallocManaged(&pointer, sizeof(T))); return new (pointer) T(args...); }; auto device_constructor = [args...]() -> T* { void* pointer; HETEROGENEOUS_SAFE_CALL(cudaMallocManaged(&pointer, sizeof(T))); return device_construct(pointer, args...); }; auto host_destructor = [](T* object) { object->~T(); HETEROGENEOUS_SAFE_CALL(cudaFree(object)); }; auto device_destructor = [](T* object) { device_destroy(object); HETEROGENEOUS_SAFE_CALL(cudaFree(object)); }; validate_in_managed_memory_helper(host_constructor, host_destructor, host_init_device_check); validate_in_managed_memory_helper(host_constructor, host_destructor, device_init_host_check); validate_in_managed_memory_helper(host_constructor, device_destructor, host_init_device_check); validate_in_managed_memory_helper(host_constructor, device_destructor, device_init_host_check); validate_in_managed_memory_helper(device_constructor, host_destructor, host_init_device_check); validate_in_managed_memory_helper(device_constructor, host_destructor, device_init_host_check); validate_in_managed_memory_helper(device_constructor, device_destructor, host_init_device_check); validate_in_managed_memory_helper(device_constructor, device_destructor, device_init_host_check); #if _CCCL_STD_VER >= 2014 && !defined(__clang__) // The managed variable template part of this test is disabled under clang, pending nvbug 2790305 being fixed. auto host_variable_constructor = [args...]() -> T* { managed_variable.construct(args...); return &managed_variable.get(); }; auto device_variable_constructor = [args...]() -> T* { managed_variable.device_construct(args...); return &managed_variable.get(); }; auto host_variable_destructor = [](T*) { managed_variable.destroy(); }; auto device_variable_destructor = [](T*) { managed_variable.device_destroy(); }; validate_in_managed_memory_helper(host_variable_constructor, host_variable_destructor, host_init_device_check); validate_in_managed_memory_helper(host_variable_constructor, host_variable_destructor, device_init_host_check); validate_in_managed_memory_helper(host_variable_constructor, device_variable_destructor, host_init_device_check); validate_in_managed_memory_helper(host_variable_constructor, device_variable_destructor, device_init_host_check); validate_in_managed_memory_helper(device_variable_constructor, host_variable_destructor, host_init_device_check); validate_in_managed_memory_helper(device_variable_constructor, host_variable_destructor, device_init_host_check); validate_in_managed_memory_helper(device_variable_constructor, device_variable_destructor, host_init_device_check); validate_in_managed_memory_helper(device_variable_constructor, device_variable_destructor, device_init_host_check); #endif } bool check_managed_memory_support(bool is_async) { int current_device, property_value; HETEROGENEOUS_SAFE_CALL(cudaGetDevice(¤t_device)); HETEROGENEOUS_SAFE_CALL(cudaDeviceGetAttribute( &property_value, is_async ? cudaDevAttrConcurrentManagedAccess : cudaDevAttrManagedMemory, current_device)); return property_value == 1; } struct dummy_tester { template __host__ __device__ static void initialize(Ts&&...) {} template __host__ __device__ static void validate(Ts&&...) {} }; template struct validate_list { using type = List; }; template struct validate_list> { using type = tester_list; }; template struct any_of; template struct any_of : cuda::std::true_type {}; template struct any_of : any_of {}; template <> struct any_of<> : cuda::std::false_type {}; template struct is_tester_list_async; template struct is_tester_list_async> : any_of::value..., async_validate_trait::value...> {}; template void validate_pinned(Args... args) { using list_t = typename validate_list::type; list_t list0; #ifdef DEBUG_TESTERS printf("%s\n", "Launching permuted H/D tests"); fflush(stdout); #endif validate_device_dynamic(list0, args...); if (check_managed_memory_support(is_tester_list_async::value)) { #ifdef DEBUG_TESTERS printf("%s\n", "Launching mixed H/D tests"); fflush(stdout); #endif typename validate_list::type list1; validate_managed(list1, args...); } } enum class performer_side { initialize, validate }; template struct performer_adapter; template struct performer_adapter { using async_initialize = async_trait; using async_validate = async_trait; static constexpr auto threadcount = threadcount_trait::value; template __host__ __device__ static void initialize(T& t) { Performer::perform(t); } }; template struct performer_adapter { using async_initialize = async_trait; using async_validate = async_trait; static constexpr auto threadcount = threadcount_trait::value; template __host__ __device__ static void initialize(T&) {} template __host__ __device__ static void validate(T& t) { Performer::perform(t); } }; template struct performer_list {}; template struct generate_variants_t; template struct generate_variants_t> { using type = tester_list; }; template struct generate_variants_t, First, Performers...> { using type = append>, Performers...>::type, typename generate_variants_t>, Performers...>::type>; }; template using generate_variants = typename generate_variants_t::type; template struct validate_list> { using type = generate_variants< // Lock the first performer to initialize only. // Otherwise, here's what would get generated (for 2 performers): // // ii, iv, vi, vv (i - initialize, v - validate) // // but because the testing process itself is symmetrical, that means that // testing for `ii` also covers `vv`, and testing for `iv` also covers `vi`. // Therefore, only the first half of the generated sequence is actually // meaningfully useful. tester_list>, Rest...>; }; template struct validate_list> { using type = tester_list...>; }; #endif