]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
feat: Add support for Clang compiling CUDA code (#1577)
authorR43Qi8krC <w169q169@gmail.com>
Sun, 25 May 2025 13:33:08 +0000 (21:33 +0800)
committerGitHub <noreply@github.com>
Sun, 25 May 2025 13:33:08 +0000 (15:33 +0200)
ci/install-cuda
src/ccache/ccache.cpp
src/ccache/util/CMakeLists.txt
src/ccache/util/clang.cpp [new file with mode: 0644]
src/ccache/util/clang.hpp [new file with mode: 0644]
test/CMakeLists.txt
test/suites/clang_cu.bash [new file with mode: 0644]
test/suites/clang_cu_direct.bash [new file with mode: 0644]
test/suites/clang_cu_nocpp2.bash [new file with mode: 0644]
unittest/CMakeLists.txt
unittest/test_util_clang.cpp [new file with mode: 0644]

index daba29cba97f85a0834f92f68af8724b525efa41..4518316decf19afb08c903b02c5e73222e020c43 100755 (executable)
@@ -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}
index 00712987acabd9b0aa57b0e33b3bdee4b75cfd55..bbc9526719e29cbea40d5590f27fb793df4f6c03 100644 (file)
@@ -46,6 +46,7 @@
 #include <ccache/storage/storage.hpp>
 #include <ccache/util/assertions.hpp>
 #include <ccache/util/bytes.hpp>
+#include <ccache/util/clang.hpp>
 #include <ccache/util/conversion.hpp>
 #include <ccache/util/defer.hpp>
 #include <ccache/util/direntry.hpp>
@@ -1327,6 +1328,52 @@ to_cache(Context& ctx,
   return *result_key;
 }
 
+// Process a CUDA preprocessed chunk for a specific architecture
+static tl::expected<void, Failure>
+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<Hash::Digest, Failure>
@@ -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));
index 5d5a68ced2ef1059e691dbf00b21cb1e092ff867..e6d1919527384c353b309fcae000b3ba033638c6 100644 (file)
@@ -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 (file)
index 0000000..9a2e665
--- /dev/null
@@ -0,0 +1,47 @@
+#include "clang.hpp"
+
+#include <ccache/util/logging.hpp>
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace util {
+
+std::vector<std::string>
+split_preprocess_file_in_clang_cuda(const std::string& mixed_preprocessed_path)
+{
+  std::ifstream infile(mixed_preprocessed_path);
+  std::vector<std::string> 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 (file)
index 0000000..211d625
--- /dev/null
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace util {
+
+std::vector<std::string>
+split_preprocess_file_in_clang_cuda(const std::string& mixed_preprocessed_path);
+
+} // namespace util
index bcdddfecd81145566015f011036709502b7da1fb..e5c06dfffab2ad6eadc8aadafa1f13e063f9a85e 100644 (file)
@@ -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 (file)
index 0000000..cf297ea
--- /dev/null
@@ -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 <<EOF > test_cpp.cu
+#ifndef NUM
+#define NUM 10000
+#endif
+
+void caller() {
+  for (int i = 0; i < NUM; ++i);
+}
+EOF
+
+
+    # Test code using cuda.
+    cat <<EOF >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<<<NUM, 1>>>(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 (file)
index 0000000..7d750f1
--- /dev/null
@@ -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 (file)
index 0000000..84672eb
--- /dev/null
@@ -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
+}
index 9540f86dfa1faedab2bc72d882f05740ead43430..ac14cbb762be110390ddc94bb536e756278b96ae 100644 (file)
@@ -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 (file)
index 0000000..783f84e
--- /dev/null
@@ -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 <ccache/util/clang.hpp>
+
+#include <doctest/doctest.h>
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <ostream> // https://github.com/doctest/doctest/issues/618
+#include <string>
+#include <vector>
+
+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 "<built-in>" 1
+# 1 "<built-in>" 3
+void caller() {
+  add<<<10000, 1>>>(__null,__null);
+}
+# 1 "test_cuda.cu"
+# 1 "<built-in>" 1
+# 1 "<built-in>" 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 "<built-in>" 1
+# 1 "<built-in>" 3
+void caller() {
+  add<<<10000, 1>>>(__null,__null);
+}
+)");
+    CHECK(result[1] == R"(# 1 "test_cuda.cu"
+# 1 "<built-in>" 1
+# 1 "<built-in>" 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();