From a47e3a24d5bf1ede276737096c9ebca87e19a7eb Mon Sep 17 00:00:00 2001 From: R43Qi8krC Date: Sun, 25 May 2025 21:33:08 +0800 Subject: [PATCH] feat: Add support for Clang compiling CUDA code (#1577) --- ci/install-cuda | 5 +- src/ccache/ccache.cpp | 84 +++++++++- src/ccache/util/CMakeLists.txt | 1 + src/ccache/util/clang.cpp | 47 ++++++ src/ccache/util/clang.hpp | 11 ++ test/CMakeLists.txt | 4 + test/suites/clang_cu.bash | 259 +++++++++++++++++++++++++++++++ test/suites/clang_cu_direct.bash | 195 +++++++++++++++++++++++ test/suites/clang_cu_nocpp2.bash | 13 ++ unittest/CMakeLists.txt | 1 + unittest/test_util_clang.cpp | 125 +++++++++++++++ 11 files changed, 739 insertions(+), 6 deletions(-) create mode 100644 src/ccache/util/clang.cpp create mode 100644 src/ccache/util/clang.hpp create mode 100644 test/suites/clang_cu.bash create mode 100644 test/suites/clang_cu_direct.bash create mode 100644 test/suites/clang_cu_nocpp2.bash create mode 100644 unittest/test_util_clang.cpp diff --git a/ci/install-cuda b/ci/install-cuda index daba29cba..4518316de 100755 --- a/ci/install-cuda +++ b/ci/install-cuda @@ -23,7 +23,10 @@ retry sudo apt-get update -qq cuda_prefix=${CUDA:0:4} cuda_prefix=${cuda_prefix/./-} -retry sudo apt-get install --allow-unauthenticated -y cuda-command-line-tools-${cuda_prefix} +retry sudo apt-get install --allow-unauthenticated -y \ + cuda-command-line-tools-${cuda_prefix} \ + libcurand-dev-${cuda_prefix} + retry sudo apt-get clean cuda_home=/usr/local/cuda-${CUDA:0:4} diff --git a/src/ccache/ccache.cpp b/src/ccache/ccache.cpp index 00712987a..bbc952671 100644 --- a/src/ccache/ccache.cpp +++ b/src/ccache/ccache.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -1327,6 +1328,52 @@ to_cache(Context& ctx, return *result_key; } +// Process a CUDA preprocessed chunk for a specific architecture +static tl::expected +process_cuda_chunk(Context& ctx, + Hash& hash, + const std::string& chunk, + size_t index) +{ + // 1. Create a temp file for this CUDA chunk + auto tmp_result = util::TemporaryFile::create( + FMT("{}/cuda_tmp_{}.i", ctx.config.temporary_dir(), index), + FMT(".{}", ctx.config.cpp_extension())); + if (!tmp_result) { + return tl::unexpected(Statistic::internal_error); + } + + const auto& chunk_path = tmp_result->path; + tmp_result->fd.close(); // we only need the path, not the open fd + + // 2. Write the chunk contents into the temp file + if (!util::write_file(chunk_path, chunk)) { + return tl::unexpected(Statistic::internal_error); + } + // 3. Register the file so it gets cleaned up later + ctx.register_pending_tmp_file(chunk_path); + + // 4. Add a unique hash delimiter for this chunk + hash.hash_delimiter(FMT("cu_{}", index)); + + // 5. Process the chunk just like a normal preprocessed file + TRY(process_preprocessed_file(ctx, hash, chunk_path)); + + return {}; +} + +static bool +get_clang_cu_enable_verbose_mode(Args& args) +{ + for (size_t i = 1; i < args.size(); i++) { + if (args[i] == "-v") { + return true; + } + } + + return false; +} + // Find the result key by running the compiler in preprocessor mode and // hashing the result. static tl::expected @@ -1334,6 +1381,17 @@ get_result_key_from_cpp(Context& ctx, Args& args, Hash& hash) { fs::path preprocessed_path; util::Bytes cpp_stderr_data; + util::Bytes cpp_stdout_data; + + // When Clang runs in verbose mode, it outputs command details to stdout, + // which can corrupt the output of precompiled CUDA files. + // Therefore, caching is disabled in this scenario. + // (Is there a better approach to handle this?) + const bool is_clang_cu = ctx.config.is_compiler_group_clang() + && ctx.args_info.actual_language == "cu" + && !get_clang_cu_enable_verbose_mode(args); + + const bool capture_stdout = is_clang_cu; if (ctx.args_info.direct_i_file) { // We are compiling a .i or .ii file - that means we can skip the cpp stage @@ -1366,8 +1424,10 @@ get_result_key_from_cpp(Context& ctx, Args& args, Hash& hash) args.push_back(FMT("-Fi{}", preprocessed_path)); } else { args.push_back("-E"); - args.push_back("-o"); - args.push_back(preprocessed_path); + if (!is_clang_cu) { + args.push_back("-o"); + args.push_back(preprocessed_path); + } } args.push_back( @@ -1375,7 +1435,7 @@ get_result_key_from_cpp(Context& ctx, Args& args, Hash& hash) add_prefix(ctx, args, ctx.config.prefix_command_cpp()); LOG_RAW("Running preprocessor"); - const auto result = do_execute(ctx, args, false); + const auto result = do_execute(ctx, args, capture_stdout); args.pop_back(args.size() - orig_args_size); if (!result) { @@ -1386,10 +1446,24 @@ get_result_key_from_cpp(Context& ctx, Args& args, Hash& hash) } cpp_stderr_data = result->stderr_data; + cpp_stdout_data = result->stdout_data; } - hash.hash_delimiter("cpp"); - TRY(process_preprocessed_file(ctx, hash, preprocessed_path)); + if (is_clang_cu) { + util::write_file(preprocessed_path, cpp_stdout_data); + + auto chunks = + util::split_preprocess_file_in_clang_cuda(preprocessed_path.string()); + + for (size_t i = 0; i < chunks.size(); ++i) { + TRY(process_cuda_chunk(ctx, hash, chunks[i], i)); + } + + } else { + hash.hash_delimiter("cpp"); + + TRY(process_preprocessed_file(ctx, hash, preprocessed_path)); + } hash.hash_delimiter("cppstderr"); hash.hash(util::to_string_view(cpp_stderr_data)); diff --git a/src/ccache/util/CMakeLists.txt b/src/ccache/util/CMakeLists.txt index 5d5a68ced..e6d191952 100644 --- a/src/ccache/util/CMakeLists.txt +++ b/src/ccache/util/CMakeLists.txt @@ -24,6 +24,7 @@ set( tokenizer.cpp umaskscope.cpp zstd.cpp + clang.cpp ) file(GLOB headers *.hpp) diff --git a/src/ccache/util/clang.cpp b/src/ccache/util/clang.cpp new file mode 100644 index 000000000..9a2e6656f --- /dev/null +++ b/src/ccache/util/clang.cpp @@ -0,0 +1,47 @@ +#include "clang.hpp" + +#include + +#include +#include +#include +#include + +namespace util { + +std::vector +split_preprocess_file_in_clang_cuda(const std::string& mixed_preprocessed_path) +{ + std::ifstream infile(mixed_preprocessed_path); + std::vector split_preprocess_file_list; + + if (!infile) { + LOG("Can't open file {}", mixed_preprocessed_path); + return split_preprocess_file_list; + } + + std::string delimiter; + if (!std::getline(infile, delimiter)) { + return split_preprocess_file_list; + } + + std::string currentPart = delimiter + "\n"; + std::string line; + + while (std::getline(infile, line)) { + if (line == delimiter) { + split_preprocess_file_list.push_back(currentPart); + currentPart = delimiter + "\n"; + } else { + currentPart += line + "\n"; + } + } + + if (!currentPart.empty()) { + split_preprocess_file_list.push_back(currentPart); + } + + return split_preprocess_file_list; +} + +} // namespace util \ No newline at end of file diff --git a/src/ccache/util/clang.hpp b/src/ccache/util/clang.hpp new file mode 100644 index 000000000..211d625c9 --- /dev/null +++ b/src/ccache/util/clang.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace util { + +std::vector +split_preprocess_file_in_clang_cuda(const std::string& mixed_preprocessed_path); + +} // namespace util diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bcdddfecd..e5c06dfff 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -23,6 +23,9 @@ set_property( addtest(base) addtest(basedir) addtest(cache_levels) +addtest(clang_cu) +addtest(clang_cu_direct) +addtest(clang_cu_nocpp2) addtest(cleanup) addtest(color_diagnostics) addtest(config) @@ -69,3 +72,4 @@ addtest(split_dwarf) addtest(stats_log) addtest(trim_dir) addtest(upgrade) + diff --git a/test/suites/clang_cu.bash b/test/suites/clang_cu.bash new file mode 100644 index 000000000..cf297ea53 --- /dev/null +++ b/test/suites/clang_cu.bash @@ -0,0 +1,259 @@ +setup_clang() { + local CUDA_PATH="--cuda-path=/usr/local/cuda" + if [ ! -z "$CUDA_HOME" ]; then + local CUDA_PATH="--cuda-path=$CUDA_HOME" + fi + + export REAL_CLANG="clang $CUDA_PATH" +} + +clang_cu_PROBE() { + if [ -z "$REAL_NVCC" ]; then + echo "nvcc is not available" + return + elif ! command -v cuobjdump >/dev/null; then + echo "cuobjdump is not available" + return + elif ! command -v clang >/dev/null; then + echo "clang is not available" + return + fi + + setup_clang + + touch test.cu + if ! $REAL_CLANG -c -x cu test.cu >/dev/null 2>&1; then + echo "Clang's CUDA support is not compatible." + fi + +} + +clang_cu_SETUP() { + # Test code using only c++ (option -x c++). Faster than compiling cuda. + cat < test_cpp.cu +#ifndef NUM +#define NUM 10000 +#endif + +void caller() { + for (int i = 0; i < NUM; ++i); +} +EOF + + + # Test code using cuda. + cat <test_cuda.cu +#ifndef NUM +#define NUM 10000 +#endif + +__global__ +void add(int *a, int *b) { + int i = blockIdx.x; + if (i < NUM) { + b[i] = 2 * a[i]; + } +} + +void caller() { + add<<>>(NULL,NULL); +} +EOF +} + +clang_cu_tests() { + setup_clang + + clang_opts_cpp="-c -x c++" + clang_opts_cuda="-c -x cu" + clang_opts_gpu1="--cuda-gpu-arch=sm_50" + clang_opts_gpu2="--cuda-gpu-arch=sm_75" + ccache_clang_cpp="$CCACHE $REAL_CLANG $clang_opts_cpp" + ccache_clang_cuda="$CCACHE $REAL_CLANG $clang_opts_cuda" + cuobjdump="cuobjdump -all -elf -symbols -ptx -sass" + + # ------------------------------------------------------------------------- + TEST "Simple mode" + + $REAL_CLANG $clang_opts_cpp -o reference_test1.o test_cpp.cu + + # First compile. + $ccache_clang_cpp test_cpp.cu + expect_stat preprocessed_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 1 + expect_equal_content reference_test1.o test_cpp.o + + $ccache_clang_cpp test_cpp.cu + expect_stat preprocessed_cache_hit 1 + expect_stat cache_miss 1 + expect_stat files_in_cache 1 + expect_equal_content reference_test1.o test_cpp.o + + # ------------------------------------------------------------------------- + TEST "Different GPU architectures" + + $REAL_CLANG $clang_opts_cuda -o reference_test1.o test_cuda.cu + $REAL_CLANG $clang_opts_cuda $clang_opts_gpu1 -o reference_test2.o test_cuda.cu + $REAL_CLANG $clang_opts_cuda $clang_opts_gpu2 -o reference_test3.o test_cuda.cu + $REAL_CLANG $clang_opts_cuda $clang_opts_gpu1 $clang_opts_gpu2 -o reference_test4.o test_cuda.cu + + $cuobjdump reference_test1.o > reference_test1.dump + $cuobjdump reference_test2.o > reference_test2.dump + $cuobjdump reference_test3.o > reference_test3.dump + $cuobjdump reference_test4.o > reference_test4.dump + expect_different_content reference_test1.dump reference_test2.dump + expect_different_content reference_test1.dump reference_test3.dump + expect_different_content reference_test2.dump reference_test3.dump + expect_different_content reference_test4.dump reference_test3.dump + expect_different_content reference_test4.dump reference_test2.dump + + $ccache_clang_cuda test_cuda.cu + expect_stat preprocessed_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 1 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test1.dump test1.dump + + # Other GPU. + $ccache_clang_cuda $clang_opts_gpu1 test_cuda.cu + expect_stat preprocessed_cache_hit 0 + expect_stat cache_miss 2 + expect_stat files_in_cache 2 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test2.dump test1.dump + + $ccache_clang_cuda $clang_opts_gpu1 test_cuda.cu + expect_stat preprocessed_cache_hit 1 + expect_stat cache_miss 2 + expect_stat files_in_cache 2 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test2.dump test1.dump + + # Another GPU. + $ccache_clang_cuda $clang_opts_gpu2 test_cuda.cu + expect_stat preprocessed_cache_hit 1 + expect_stat cache_miss 3 + expect_stat files_in_cache 3 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test3.dump test1.dump + + $ccache_clang_cuda $clang_opts_gpu2 test_cuda.cu + expect_stat preprocessed_cache_hit 2 + expect_stat cache_miss 3 + expect_stat files_in_cache 3 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test3.dump test1.dump + + # Multi GPU + $ccache_clang_cuda $clang_opts_gpu1 $clang_opts_gpu2 test_cuda.cu + expect_stat preprocessed_cache_hit 2 + expect_stat cache_miss 4 + expect_stat files_in_cache 4 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test4.dump test1.dump + + $ccache_clang_cuda $clang_opts_gpu1 $clang_opts_gpu2 test_cuda.cu + expect_stat preprocessed_cache_hit 3 + expect_stat cache_miss 4 + expect_stat files_in_cache 4 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test4.dump test1.dump + + # ------------------------------------------------------------------------- + TEST "Option -fgpu-rdc" + + $REAL_CLANG $clang_opts_cuda -fgpu-rdc -o reference_test4.o test_cuda.cu + $cuobjdump reference_test4.o > reference_test4.dump + + $ccache_clang_cuda -fgpu-rdc -o test_cuda.o test_cuda.cu + expect_stat preprocessed_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 1 + $cuobjdump test_cuda.o > test4.dump + expect_equal_content test4.dump reference_test4.dump + + $ccache_clang_cuda -fgpu-rdc -o test_cuda.o test_cuda.cu + expect_stat preprocessed_cache_hit 1 + expect_stat cache_miss 1 + expect_stat files_in_cache 1 + $cuobjdump test_cuda.o > test4.dump + expect_equal_content test4.dump reference_test4.dump + + # ------------------------------------------------------------------------- + TEST "Different defines" + + $REAL_CLANG $clang_opts_cpp -o reference_test1.o test_cpp.cu + $REAL_CLANG $clang_opts_cpp -DNUM=10 -o reference_test2.o test_cpp.cu + expect_different_content reference_test1.o reference_test2.o + + $ccache_clang_cpp test_cpp.cu + expect_stat preprocessed_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 1 + expect_equal_content reference_test1.o test_cpp.o + + # Specified define, but unused. Can only be found by preprocessed mode. + $ccache_clang_cpp -DDUMMYENV=1 test_cpp.cu + expect_stat preprocessed_cache_hit 1 + expect_stat cache_miss 1 + expect_stat files_in_cache 1 + expect_equal_content reference_test1.o test_cpp.o + + # Specified used define. + $ccache_clang_cpp -DNUM=10 test_cpp.cu + expect_stat preprocessed_cache_hit 1 + expect_stat cache_miss 2 + expect_stat files_in_cache 2 + expect_equal_content reference_test2.o test_cpp.o + + $ccache_clang_cpp -DNUM=10 test_cpp.cu + expect_stat preprocessed_cache_hit 2 + expect_stat cache_miss 2 + expect_stat files_in_cache 2 + expect_equal_content reference_test2.o test_cpp.o + + TEST "No cache(preprocess failed)" + + $ccache_clang_cuda -DNUM=i test_cuda.cu + expect_stat preprocessed_cache_hit 0 + expect_stat cache_miss 0 + expect_stat files_in_cache 0 + + $ccache_clang_cuda -DNUM=i test_cuda.cu + expect_stat preprocessed_cache_hit 0 + expect_stat cache_miss 0 + expect_stat files_in_cache 0 + + TEST "verbose mode" + + $REAL_CLANG $clang_opts_cuda -o reference_test5.o test_cuda.cu + $cuobjdump reference_test5.o > reference_test5.dump + + # First compile. + $ccache_clang_cuda -v test_cuda.cu + expect_stat preprocessed_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 1 + $cuobjdump test_cuda.o > test5_1.dump + expect_equal_content test5_1.dump reference_test5.dump + + $ccache_clang_cuda -v test_cuda.cu + expect_stat preprocessed_cache_hit 1 + expect_stat cache_miss 1 + expect_stat files_in_cache 1 + $cuobjdump test_cuda.o > test5_2.dump + expect_equal_content test5_2.dump reference_test5.dump +} + +SUITE_clang_cu_PROBE() { + clang_cu_PROBE +} + +SUITE_clang_cu_SETUP() { + clang_cu_SETUP +} + +SUITE_clang_cu() { + clang_cu_tests +} diff --git a/test/suites/clang_cu_direct.bash b/test/suites/clang_cu_direct.bash new file mode 100644 index 000000000..7d750f1f5 --- /dev/null +++ b/test/suites/clang_cu_direct.bash @@ -0,0 +1,195 @@ +SUITE_clang_cu_direct_PROBE() { + clang_cu_PROBE +} + +SUITE_clang_cu_direct_SETUP() { + unset CCACHE_NODIRECT + + clang_cu_SETUP +} + +SUITE_clang_cu_direct() { + setup_clang + + clang_opts_cpp="-c -x c++" + clang_opts_cuda="-c -x cu" + clang_opts_gpu1="--cuda-gpu-arch=sm_50" + clang_opts_gpu2="--cuda-gpu-arch=sm_75" + ccache_clang_cpp="$CCACHE $REAL_CLANG $clang_opts_cpp" + ccache_clang_cuda="$CCACHE $REAL_CLANG $clang_opts_cuda" + cuobjdump="cuobjdump -all -elf -symbols -ptx -sass" + + # ------------------------------------------------------------------------- + TEST "Simple mode" + + $REAL_CLANG $clang_opts_cpp -o reference_test1.o test_cpp.cu + + # First compile. + $ccache_clang_cpp test_cpp.cu + expect_stat direct_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 2 + expect_equal_content reference_test1.o test_cpp.o + + $ccache_clang_cpp test_cpp.cu + expect_stat direct_cache_hit 1 + expect_stat cache_miss 1 + expect_stat files_in_cache 2 + expect_equal_content reference_test1.o test_cpp.o + + # ------------------------------------------------------------------------- + TEST "Different GPU architectures" + + $REAL_CLANG $clang_opts_cuda -o reference_test1.o test_cuda.cu + $REAL_CLANG $clang_opts_cuda $clang_opts_gpu1 -o reference_test2.o test_cuda.cu + $REAL_CLANG $clang_opts_cuda $clang_opts_gpu2 -o reference_test3.o test_cuda.cu + $REAL_CLANG $clang_opts_cuda $clang_opts_gpu1 $clang_opts_gpu2 -o reference_test4.o test_cuda.cu + + $cuobjdump reference_test1.o > reference_test1.dump + $cuobjdump reference_test2.o > reference_test2.dump + $cuobjdump reference_test3.o > reference_test3.dump + $cuobjdump reference_test4.o > reference_test4.dump + expect_different_content reference_test1.dump reference_test2.dump + expect_different_content reference_test1.dump reference_test3.dump + expect_different_content reference_test2.dump reference_test3.dump + expect_different_content reference_test4.dump reference_test3.dump + expect_different_content reference_test4.dump reference_test2.dump + + $ccache_clang_cuda test_cuda.cu + expect_stat direct_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 2 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test1.dump test1.dump + + # Other GPU. + $ccache_clang_cuda $clang_opts_gpu1 test_cuda.cu + expect_stat direct_cache_hit 0 + expect_stat cache_miss 2 + expect_stat files_in_cache 4 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test2.dump test1.dump + + $ccache_clang_cuda $clang_opts_gpu1 test_cuda.cu + expect_stat direct_cache_hit 1 + expect_stat cache_miss 2 + expect_stat files_in_cache 4 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test2.dump test1.dump + + # Another GPU. + $ccache_clang_cuda $clang_opts_gpu2 test_cuda.cu + expect_stat direct_cache_hit 1 + expect_stat cache_miss 3 + expect_stat files_in_cache 6 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test3.dump test1.dump + + $ccache_clang_cuda $clang_opts_gpu2 test_cuda.cu + expect_stat direct_cache_hit 2 + expect_stat cache_miss 3 + expect_stat files_in_cache 6 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test3.dump test1.dump + + # Multi GPU + $ccache_clang_cuda $clang_opts_gpu1 $clang_opts_gpu2 test_cuda.cu + expect_stat direct_cache_hit 2 + expect_stat cache_miss 4 + expect_stat files_in_cache 8 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test4.dump test1.dump + + $ccache_clang_cuda $clang_opts_gpu1 $clang_opts_gpu2 test_cuda.cu + expect_stat direct_cache_hit 3 + expect_stat cache_miss 4 + expect_stat files_in_cache 8 + $cuobjdump test_cuda.o > test1.dump + expect_equal_content reference_test4.dump test1.dump + + # ------------------------------------------------------------------------- + TEST "Option -fgpu-rdc" + + $REAL_CLANG $clang_opts_cuda -fgpu-rdc -o reference_test4.o test_cuda.cu + $cuobjdump reference_test4.o > reference_test4.dump + + $ccache_clang_cuda -fgpu-rdc -o test_cuda.o test_cuda.cu + expect_stat direct_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 2 + $cuobjdump test_cuda.o > test4.dump + expect_equal_content test4.dump reference_test4.dump + + $ccache_clang_cuda -fgpu-rdc -o test_cuda.o test_cuda.cu + expect_stat direct_cache_hit 1 + expect_stat cache_miss 1 + expect_stat files_in_cache 2 + $cuobjdump test_cuda.o > test4.dump + expect_equal_content test4.dump reference_test4.dump + + # ------------------------------------------------------------------------- + TEST "Different defines" + + $REAL_CLANG $clang_opts_cpp -o reference_test1.o test_cpp.cu + $REAL_CLANG $clang_opts_cpp -DNUM=10 -o reference_test2.o test_cpp.cu + expect_different_content reference_test1.o reference_test2.o + + $ccache_clang_cpp test_cpp.cu + expect_stat direct_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 2 + expect_equal_content reference_test1.o test_cpp.o + + # Specified define, but unused. Can only be found by preprocessed mode. + $ccache_clang_cpp -DDUMMYENV=1 test_cpp.cu + expect_stat preprocessed_cache_hit 1 + expect_stat direct_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 3 + expect_equal_content reference_test1.o test_cpp.o + + # Specified used define. + $ccache_clang_cpp -DNUM=10 test_cpp.cu + expect_stat direct_cache_hit 0 + expect_stat cache_miss 2 + expect_stat files_in_cache 5 + expect_equal_content reference_test2.o test_cpp.o + + $ccache_clang_cpp -DNUM=10 test_cpp.cu + expect_stat direct_cache_hit 1 + expect_stat cache_miss 2 + expect_stat files_in_cache 5 + expect_equal_content reference_test2.o test_cpp.o + + TEST "No cache(preprocess failed)" + + $ccache_clang_cuda -DNUM=i test_cuda.cu + expect_stat direct_cache_hit 0 + expect_stat cache_miss 0 + expect_stat files_in_cache 0 + + $ccache_clang_cuda -DNUM=i test_cuda.cu + expect_stat direct_cache_hit 0 + expect_stat cache_miss 0 + expect_stat files_in_cache 0 + + TEST "verbose mode" + + $REAL_CLANG $clang_opts_cuda -v -o reference_test5.o test_cuda.cu + $cuobjdump reference_test5.o > reference_test5.dump + + # First compile. + $ccache_clang_cuda -v test_cuda.cu + expect_stat direct_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 2 + $cuobjdump test_cuda.o > test5_1.dump + expect_equal_content test5_1.dump reference_test5.dump + + $ccache_clang_cuda -v test_cuda.cu + expect_stat direct_cache_hit 1 + expect_stat cache_miss 1 + expect_stat files_in_cache 2 + $cuobjdump test_cuda.o > test5_2.dump + expect_equal_content test5_2.dump reference_test5.dump +} diff --git a/test/suites/clang_cu_nocpp2.bash b/test/suites/clang_cu_nocpp2.bash new file mode 100644 index 000000000..84672eb42 --- /dev/null +++ b/test/suites/clang_cu_nocpp2.bash @@ -0,0 +1,13 @@ +SUITE_clang_cu_nocpp2_PROBE() { + clang_cu_PROBE +} + +SUITE_clang_cu_nocpp2_SETUP() { + export CCACHE_NOCPP2=1 + + clang_cu_SETUP +} + +SUITE_clang_cu_nocpp2() { + clang_cu_tests +} diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 9540f86df..ac14cbb76 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -20,6 +20,7 @@ set( test_storage_local_util.cpp test_util_bitset.cpp test_util_bytes.cpp + test_util_clang.cpp test_util_conversion.cpp test_util_direntry.cpp test_util_duration.cpp diff --git a/unittest/test_util_clang.cpp b/unittest/test_util_clang.cpp new file mode 100644 index 000000000..783f84e97 --- /dev/null +++ b/unittest/test_util_clang.cpp @@ -0,0 +1,125 @@ +// Copyright (C) 2021-2024 Joel Rosdahl and other contributors +// +// See doc/AUTHORS.adoc for a complete list of contributors. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the Free Software Foundation, Inc., 51 +// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include + +#include + +#include +#include +#include +#include // https://github.com/doctest/doctest/issues/618 +#include +#include + +TEST_SUITE_BEGIN("util"); + +// RAII helper class for test file management +class TestFileGuard +{ +private: + std::string filename_; + +public: + explicit TestFileGuard(const std::string& filename) : filename_(filename) + { + } + + ~TestFileGuard() + { + try { + if (std::filesystem::exists(filename_)) { + std::filesystem::remove(filename_); + } + } catch (...) { + // Ignore cleanup errors in destructor + } + } + + void + create_file(const std::string& content) + { + std::ofstream outfile; + outfile.open(filename_); + if (outfile.is_open()) { + outfile << content; + outfile.close(); + } + } + + const std::string& + filename() const + { + return filename_; + } +}; + +TEST_CASE("util::split_preprocess_file_in_clang_cuda") +{ + SUBCASE("normal") + { + TestFileGuard guard("test_normal.txt"); + std::string content = R"(# 1 "test_cuda.cu" +# 1 "" 1 +# 1 "" 3 +void caller() { + add<<<10000, 1>>>(__null,__null); +} +# 1 "test_cuda.cu" +# 1 "" 1 +# 1 "" 3 +)"; + guard.create_file(content); + + auto result = util::split_preprocess_file_in_clang_cuda(guard.filename()); + + REQUIRE(result.size() == 2); + CHECK(result[0] == R"(# 1 "test_cuda.cu" +# 1 "" 1 +# 1 "" 3 +void caller() { + add<<<10000, 1>>>(__null,__null); +} +)"); + CHECK(result[1] == R"(# 1 "test_cuda.cu" +# 1 "" 1 +# 1 "" 3 +)"); + } + + SUBCASE("non-existent file") + { + std::string nonexistent_file = "nonexistent_file_12345.txt"; + + auto result = util::split_preprocess_file_in_clang_cuda(nonexistent_file); + + CHECK(result.empty()); + } + + SUBCASE("empty file") + { + TestFileGuard guard("test_empty.txt"); + guard.create_file(""); + + auto result = util::split_preprocess_file_in_clang_cuda(guard.filename()); + + CHECK(result.empty()); + } +} + +TEST_SUITE_END(); -- 2.47.2