diff --git a/Help/release/dev/findcudatoolkit-sanitizer-api.rst b/Help/release/dev/findcudatoolkit-sanitizer-api.rst new file mode 100644 index 0000000000..94bb55bdb7 --- /dev/null +++ b/Help/release/dev/findcudatoolkit-sanitizer-api.rst @@ -0,0 +1,6 @@ +findcudatoolkit-sanitizer-api +----------------------------- + +* The :module:`FindCUDAToolkit` module now creates a ``CUDA::sanitizer`` + target for the :ref:`compute-sanitizer ` + library. diff --git a/Modules/FindCUDAToolkit.cmake b/Modules/FindCUDAToolkit.cmake index 12037a7338..8a6e1fbe7b 100644 --- a/Modules/FindCUDAToolkit.cmake +++ b/Modules/FindCUDAToolkit.cmake @@ -537,6 +537,22 @@ Target Created: - ``CUDA::bin2c`` +.. _`FindCUDAToolkit_sanitizer`: + +compute-sanitizer +""""""""""""""""" + +.. versionadded:: 4.4 + +The `NVIDIA Compute Sanitizer`_ library, which allows the tracing of CUDA +runtime and driver calls. + +Target Created: + +- ``CUDA::sanitizer`` + +.. _`NVIDIA Compute Sanitizer`: https://docs.nvidia.com/compute-sanitizer + Result Variables ^^^^^^^^^^^^^^^^ @@ -1223,7 +1239,11 @@ endif() if(CUDAToolkit_FOUND) function(_CUDAToolkit_find_and_add_import_lib lib_name) - cmake_parse_arguments(arg "" "" "ALT;DEPS;EXTRA_PATH_SUFFIXES;EXTRA_INCLUDE_DIRS;ONLY_SEARCH_FOR" ${ARGN}) + cmake_parse_arguments(arg "" "" "ALT;DEPS;EXTRA_PATH_SUFFIXES;EXTRA_INCLUDE_DIRS;ONLY_SEARCH_FOR;LIBRARY_SEARCH_DIRS" ${ARGN}) + + if(NOT arg_LIBRARY_SEARCH_DIRS) + set(arg_LIBRARY_SEARCH_DIRS "${CUDAToolkit_LIBRARY_SEARCH_DIRS}") + endif() if(arg_ONLY_SEARCH_FOR) set(search_names ${arg_ONLY_SEARCH_FOR}) @@ -1233,7 +1253,7 @@ if(CUDAToolkit_FOUND) find_library(CUDA_${lib_name}_LIBRARY NAMES ${search_names} - HINTS ${CUDAToolkit_LIBRARY_SEARCH_DIRS} + HINTS ${arg_LIBRARY_SEARCH_DIRS} ENV CUDA_PATH PATH_SUFFIXES nvidia/current lib64 ${_CUDAToolkit_win_search_dirs} lib # Support NVHPC splayed math library layout @@ -1248,7 +1268,7 @@ if(CUDAToolkit_FOUND) if(NOT CUDA_${lib_name}_LIBRARY) find_library(CUDA_${lib_name}_LIBRARY NAMES ${search_names} - HINTS ${CUDAToolkit_LIBRARY_SEARCH_DIRS} + HINTS ${arg_LIBRARY_SEARCH_DIRS} ENV CUDA_PATH PATH_SUFFIXES lib64/stubs ${_CUDAToolkit_win_stub_search_dirs} lib/stubs stubs ) @@ -1515,6 +1535,21 @@ if(CUDAToolkit_FOUND) add_executable(CUDA::bin2c IMPORTED) set_property(TARGET CUDA::bin2c PROPERTY IMPORTED_LOCATION "${CUDA_bin2c_EXECUTABLE}") endif() + + _CUDAToolkit_find_and_add_import_lib( + sanitizer + ONLY_SEARCH_FOR sanitizer-public + LIBRARY_SEARCH_DIRS + "${CUDAToolkit_LIBRARY_ROOT}/compute-sanitizer" + "${CUDAToolkit_LIBRARY_ROOT}/Sanitizer" + "${CUDAToolkit_LIBRARY_ROOT}/extras/Sanitizer" + EXTRA_INCLUDE_DIRS "${CUDAToolkit_CUPTI_INCLUDE_DIR}" + ) + if(TARGET CUDA::sanitizer) + get_property(loc TARGET CUDA::sanitizer PROPERTY IMPORTED_LOCATION) + get_filename_component(sanitizer_dir "${loc}" DIRECTORY) + target_include_directories(CUDA::sanitizer INTERFACE "${sanitizer_dir}/include") + endif() endif() if(_CUDAToolkit_Pop_ROOT_PATH) diff --git a/Tests/Cuda/CMakeLists.txt b/Tests/Cuda/CMakeLists.txt index a355a4dea3..cfe47daa0d 100644 --- a/Tests/Cuda/CMakeLists.txt +++ b/Tests/Cuda/CMakeLists.txt @@ -32,3 +32,4 @@ endif() add_cuda_test_macro(Cuda.WithC CudaWithC) add_cuda_test_macro(Cuda.Bin2C CudaBin2C) +add_cuda_test_macro(Cuda.Sanitizer ${CMAKE_CTEST_COMMAND} --output-on-failure) diff --git a/Tests/Cuda/Sanitizer/CMakeLists.txt b/Tests/Cuda/Sanitizer/CMakeLists.txt new file mode 100644 index 0000000000..df3b4f0cb7 --- /dev/null +++ b/Tests/Cuda/Sanitizer/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.3) +project(Sanitizer CXX) + +enable_testing() + +# Goal for this example: +# Validate that we can use CUDAToolkit sanitizer +find_package(CUDAToolkit REQUIRED) + +if(CUDAToolkit_VERSION VERSION_GREATER_EQUAL 10.1) + add_executable(CudaSanitizer test_sanitizer.cpp) + target_link_libraries(CudaSanitizer PRIVATE CUDA::sanitizer CUDA::cudart_static) + + add_test(NAME CudaSanitizer COMMAND CudaSanitizer) + if(WIN32) + get_property(sanitizer_path TARGET CUDA::sanitizer PROPERTY IMPORTED_LOCATION) + get_filename_component(sanitizer_dir "${sanitizer_path}" DIRECTORY) + set_property(TEST CudaSanitizer PROPERTY ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:${sanitizer_dir}") + endif() +endif() diff --git a/Tests/Cuda/Sanitizer/test_sanitizer.cpp b/Tests/Cuda/Sanitizer/test_sanitizer.cpp new file mode 100644 index 0000000000..dc3f70dda1 --- /dev/null +++ b/Tests/Cuda/Sanitizer/test_sanitizer.cpp @@ -0,0 +1,74 @@ +#include + +#include +#include +#include + +namespace { + +bool malloc_called = false; +bool free_called = false; + +void sanitizer_callback(void* userdata, Sanitizer_CallbackDomain domain, + Sanitizer_CallbackId cbid, void const* cbdata) +{ + if (domain == SANITIZER_CB_DOMAIN_RESOURCE) { + switch (cbid) { + case SANITIZER_CBID_RESOURCE_DEVICE_MEMORY_ALLOC: + malloc_called = true; + break; + case SANITIZER_CBID_RESOURCE_DEVICE_MEMORY_FREE: + free_called = true; + break; + default: + break; + } + } +} + +} + +#define SANITIZER_TRY(call) \ + do { \ + SanitizerResult result; \ + if ((result = (call)) != SANITIZER_SUCCESS) { \ + const char* result_str; \ + sanitizerGetResultString(result, &result_str); \ + std::cerr << __FILE__ << ":" << __LINE__ << ": " << result_str << "\n"; \ + return 1; \ + } \ + } while (0) + +#define CUDA_TRY(call) \ + do { \ + cudaError_t result; \ + if ((result = (call)) != cudaSuccess) { \ + std::cerr << __FILE__ << ":" << __LINE__ << ": " \ + << cudaGetErrorName(result) << "\n"; \ + return 1; \ + } \ + } while (0) + +int main() +{ + Sanitizer_SubscriberHandle handle; + SANITIZER_TRY(sanitizerSubscribe(&handle, sanitizer_callback, NULL)); + SANITIZER_TRY( + sanitizerEnableDomain(1, handle, SANITIZER_CB_DOMAIN_RESOURCE)); + + void* dev_ptr; + CUDA_TRY(cudaMalloc(&dev_ptr, 10)); + if (!malloc_called) { + std::cerr << "cudaMalloc() did not invoke callback\n"; + return 1; + } + + CUDA_TRY(cudaFree(dev_ptr)); + if (!free_called) { + std::cerr << "cudaFree() did not invoke callback\n"; + return 1; + } + + SANITIZER_TRY(sanitizerUnsubscribe(handle)); + return 0; +} diff --git a/Tests/CudaOnly/CMakeLists.txt b/Tests/CudaOnly/CMakeLists.txt index 2ec6ee85f6..396f6d38ab 100644 --- a/Tests/CudaOnly/CMakeLists.txt +++ b/Tests/CudaOnly/CMakeLists.txt @@ -41,6 +41,7 @@ if(CMake_TEST_CUDA AND NOT CMake_TEST_CUDA STREQUAL "Clang") endif() add_cuda_test_macro(CudaOnly.DeviceLTO CudaOnlyDeviceLTO) +add_cuda_test_macro(CudaOnly.Sanitizer ${CMAKE_CTEST_COMMAND} --output-on-failure) if(MSVC) # Tests for features that only work with MSVC diff --git a/Tests/CudaOnly/Sanitizer/CMakeLists.txt b/Tests/CudaOnly/Sanitizer/CMakeLists.txt new file mode 100644 index 0000000000..10cf163769 --- /dev/null +++ b/Tests/CudaOnly/Sanitizer/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.3) +project(Sanitizer CUDA) + +enable_testing() + +# Goal for this example: +# Validate that we can use CUDAToolkit sanitizer +find_package(CUDAToolkit REQUIRED) + +if(CUDAToolkit_VERSION VERSION_GREATER_EQUAL 10.1) + add_executable(CudaOnlySanitizer test_sanitizer.cu) + target_link_libraries(CudaOnlySanitizer PRIVATE CUDA::sanitizer) + + add_test(NAME CudaOnlySanitizer COMMAND CudaOnlySanitizer) + if(WIN32) + get_property(sanitizer_path TARGET CUDA::sanitizer PROPERTY IMPORTED_LOCATION) + get_filename_component(sanitizer_dir "${sanitizer_path}" DIRECTORY) + set_property(TEST CudaOnlySanitizer PROPERTY ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:${sanitizer_dir}") + endif() +endif() diff --git a/Tests/CudaOnly/Sanitizer/test_sanitizer.cu b/Tests/CudaOnly/Sanitizer/test_sanitizer.cu new file mode 100644 index 0000000000..4fa9c25c2f --- /dev/null +++ b/Tests/CudaOnly/Sanitizer/test_sanitizer.cu @@ -0,0 +1 @@ +#include "../../Cuda/Sanitizer/test_sanitizer.cpp"