From 6ebe37709b23f9898f1fed12bddc4591cf9fbbd7 Mon Sep 17 00:00:00 2001 From: Ambrose Leeb Date: Wed, 24 Jun 2026 17:52:33 +0000 Subject: [PATCH 01/10] [Hostjit] Move Clang/LLD hostjit into a separate library --- c/parallel.v2/CMakeLists.txt | 7 + c/parallel.v2/src/hostjit/CMakeLists.txt | 233 +-- c/parallel.v2/src/hostjit/codegen/bitcode.cpp | 30 +- c/parallel.v2/src/hostjit/config.cpp | 122 +- .../src/hostjit/include/hostjit/compiler.hpp | 79 +- .../src/hostjit/include/hostjit/config.hpp | 9 +- .../hostjit/include/hostjit/jit_compiler.hpp | 1 - c/parallel.v2/src/hostjit/jit_compiler.cpp | 179 ++- .../src/hostjit/libnvcc/CMakeLists.txt | 260 ++++ .../src/hostjit/{ => libnvcc}/compiler.cpp | 1368 +++++++++++++---- .../hostjit/libnvcc/include/libnvcc/libnvcc.h | 203 +++ python/cuda_cccl/CMakeLists.txt | 10 +- 12 files changed, 1961 insertions(+), 540 deletions(-) create mode 100644 c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt rename c/parallel.v2/src/hostjit/{ => libnvcc}/compiler.cpp (59%) create mode 100644 c/parallel.v2/src/hostjit/libnvcc/include/libnvcc/libnvcc.h diff --git a/c/parallel.v2/CMakeLists.txt b/c/parallel.v2/CMakeLists.txt index b82f51e1204..7e7c0348ebd 100644 --- a/c/parallel.v2/CMakeLists.txt +++ b/c/parallel.v2/CMakeLists.txt @@ -65,6 +65,13 @@ if (CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY) ) endif() +if (UNIX AND NOT APPLE) + set_target_properties( + cccl.c.parallel.v2 + PROPERTIES BUILD_RPATH_USE_ORIGIN ON INSTALL_RPATH "$ORIGIN" + ) +endif() + cccl_get_cub() cccl_get_cudatoolkit() cccl_get_thrust() diff --git a/c/parallel.v2/src/hostjit/CMakeLists.txt b/c/parallel.v2/src/hostjit/CMakeLists.txt index 5a83ae803d5..fb9666d989a 100644 --- a/c/parallel.v2/src/hostjit/CMakeLists.txt +++ b/c/parallel.v2/src/hostjit/CMakeLists.txt @@ -1,77 +1,32 @@ cmake_minimum_required(VERSION 3.30) -# -------------------------------------------------------------------------- -# LLVM/Clang/LLD — fetched via CPM as static libraries -# -------------------------------------------------------------------------- -# CPM.cmake is at the cccl repo root: cccl/cmake/CPM.cmake -# From c/parallel.v2/src/hostjit/ that's ../../../../cmake/CPM.cmake -set(_cccl_cmake_dir "${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake") -if (EXISTS "${_cccl_cmake_dir}/CPM.cmake") - include("${_cccl_cmake_dir}/CPM.cmake") -else() - message(FATAL_ERROR "CPM.cmake not found at ${_cccl_cmake_dir}/CPM.cmake") -endif() - -if (MSVC AND CMAKE_BUILD_TYPE STREQUAL "Debug") - message( - FATAL_ERROR - "hostjit does not support Debug builds on Windows. " - "The statically-linked LLVM Debug build is too large and causes stack " - "overflows at runtime. Use MinSizeRel, Release, or RelWithDebInfo instead." - ) -endif() - -set(HOSTJIT_LLVM_VERSION "llvmorg-22.1.1" CACHE STRING "LLVM git tag to fetch") - -# List options must be set before CPMAddPackage -set(LLVM_ENABLE_PROJECTS "clang;lld" CACHE STRING "" FORCE) -set(LLVM_TARGETS_TO_BUILD "X86;NVPTX" CACHE STRING "" FORCE) - -CPMAddPackage( - NAME llvm_project - GIT_REPOSITORY https://github.com/llvm/llvm-project.git - GIT_TAG ${HOSTJIT_LLVM_VERSION} - GIT_SHALLOW ON - SOURCE_SUBDIR llvm - EXCLUDE_FROM_ALL YES - OPTIONS - "LLVM_BUILD_LLVM_C_DYLIB OFF" - "LLVM_BUILD_TOOLS OFF" - "LLVM_BUILD_UTILS OFF" - "LLVM_BUILD_RUNTIME OFF" - "LLVM_BUILD_RUNTIMES OFF" - "LLVM_INCLUDE_BENCHMARKS OFF" - "LLVM_INCLUDE_DOCS OFF" - "LLVM_INCLUDE_EXAMPLES OFF" - "LLVM_INCLUDE_RUNTIMES OFF" - "LLVM_INCLUDE_TESTS OFF" - "LLVM_INCLUDE_TOOLS ON" - "LLVM_INCLUDE_UTILS OFF" - "LLVM_ENABLE_ZLIB OFF" - "LLVM_ENABLE_ZSTD OFF" - "LLVM_ENABLE_TERMINFO OFF" - "LLVM_ENABLE_BINDINGS OFF" - "CLANG_BUILD_TOOLS OFF" - "CLANG_ENABLE_ARCMT OFF" - "CLANG_ENABLE_STATIC_ANALYZER OFF" -) - -# Ensure the clang resource directory exists -file( - MAKE_DIRECTORY "${llvm_project_BINARY_DIR}/lib/clang/${LLVM_VERSION_MAJOR}" -) - # Find CUDA toolkit (may already be found by parent) if (NOT CUDAToolkit_FOUND) find_package(CUDAToolkit) endif() +# CCCL_SOURCE_DIR points to the cccl repo root +# From c/parallel.v2/src/hostjit -> c/parallel.v2/src -> c/parallel.v2 -> c -> cccl +cmake_path(GET CMAKE_CURRENT_SOURCE_DIR PARENT_PATH _src_dir) # c/parallel.v2/src +cmake_path(GET _src_dir PARENT_PATH _c_parallel_dir) # c/parallel.v2 +cmake_path(GET _c_parallel_dir PARENT_PATH _c_dir) # c +cmake_path(GET _c_dir PARENT_PATH _cccl_root) # cccl +set(_hostjit_include_dir "${CMAKE_CURRENT_SOURCE_DIR}/include") + +set(LIBNVCC_CPM_CMAKE_PATH "${_cccl_root}/cmake/CPM.cmake") +set(LIBNVCC_HEADER_INSTALL_DESTINATION "cuda/cccl/headers/libnvcc") +set(LIBNVCC_CLANG_HEADER_INSTALL_DESTINATION "cuda/cccl/headers/clang") +if (CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY) + set(LIBNVCC_LIBRARY_OUTPUT_DIRECTORY "${CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY}") +endif() + +add_subdirectory(libnvcc) + # -------------------------------------------------------------------------- # hostjit library # -------------------------------------------------------------------------- add_library( cccl.c.parallel.v2.hostjit_lib - compiler.cpp config.cpp loader.cpp jit_compiler.cpp @@ -82,24 +37,11 @@ add_library( codegen/cub_call.cpp ) -# CCCL_SOURCE_DIR points to the cccl repo root -# From c/parallel.v2/src/hostjit -> c/parallel.v2/src -> c/parallel.v2 -> c -> cccl -cmake_path(GET CMAKE_CURRENT_SOURCE_DIR PARENT_PATH _src_dir) # c/parallel.v2/src -cmake_path(GET _src_dir PARENT_PATH _c_parallel_dir) # c/parallel.v2 -cmake_path(GET _c_parallel_dir PARENT_PATH _c_dir) # c -cmake_path(GET _c_dir PARENT_PATH _cccl_root) # cccl - target_include_directories( cccl.c.parallel.v2.hostjit_lib PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_hostjit_include_dir} ${_c_parallel_dir}/include - ${llvm_project_SOURCE_DIR}/llvm/include - ${llvm_project_BINARY_DIR}/include - ${llvm_project_SOURCE_DIR}/clang/include - ${llvm_project_BINARY_DIR}/tools/clang/include - ${llvm_project_SOURCE_DIR}/lld/include - ${llvm_project_BINARY_DIR}/tools/lld/include ) target_compile_definitions( @@ -107,104 +49,35 @@ target_compile_definitions( PRIVATE CCCL_C_EXPERIMENTAL=1 CCCL_SOURCE_DIR="${_cccl_root}" - CLANG_RESOURCE_DIR="${llvm_project_BINARY_DIR}/lib/clang/${LLVM_VERSION_MAJOR}" - CLANG_HEADERS_DIR="${llvm_project_SOURCE_DIR}/clang/lib/Headers" - HOSTJIT_INCLUDE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/include" + HOSTJIT_INCLUDE_DIR="${_hostjit_include_dir}" ) -if (CUDAToolkit_FOUND) - target_include_directories( +if (DEFINED CLANG_HEADERS_DIR) + target_compile_definitions( cccl.c.parallel.v2.hostjit_lib - PUBLIC ${CUDAToolkit_INCLUDE_DIRS} + PRIVATE CLANG_HEADERS_DIR="${CLANG_HEADERS_DIR}" ) +endif() + +if (CUDAToolkit_FOUND) cmake_path(GET CUDAToolkit_BIN_DIR PARENT_PATH CUDA_TOOLKIT_ROOT_FROM_CMAKE) target_compile_definitions( cccl.c.parallel.v2.hostjit_lib - PRIVATE - CUDA_TOOLKIT_PATH="${CUDA_TOOLKIT_ROOT_FROM_CMAKE}" - CUDA_SDK_VERSION="${CUDAToolkit_VERSION_MAJOR}.0" + PRIVATE CUDA_TOOLKIT_PATH="${CUDA_TOOLKIT_ROOT_FROM_CMAKE}" ) endif() -# Link against LLVM/Clang/LLD -target_link_libraries( - cccl.c.parallel.v2.hostjit_lib - PUBLIC - # LLVM - LLVMCore - LLVMSupport - LLVMIRReader - LLVMMC - LLVMObject - LLVMX86CodeGen - LLVMX86AsmParser - LLVMX86Desc - LLVMX86Info - LLVMNVPTXCodeGen - LLVMNVPTXDesc - LLVMNVPTXInfo - LLVMLinker - LLVMPasses - # Clang - clangAST - clangBasic - clangCodeGen - clangDriver - clangFrontend - clangFrontendTool - clangLex - clangParse - clangSema - clangEdit - clangAnalysis - clangRewrite - clangSerialization - # LLD - $,lldCOFF,lldELF> - lldCommon -) - if (NOT WIN32) target_link_libraries(cccl.c.parallel.v2.hostjit_lib PUBLIC dl) endif() +target_link_libraries( + cccl.c.parallel.v2.hostjit_lib + PUBLIC libnvcc +) + if (CUDAToolkit_FOUND) - target_link_libraries( - cccl.c.parallel.v2.hostjit_lib - PUBLIC CUDA::cuda_driver CUDA::cudart - ) - if (WIN32) - # On Windows, static CUDA libs are built with /MT which conflicts with - # the project's dynamic CRT (/MD). Use dynamic variants instead. - target_link_libraries( - cccl.c.parallel.v2.hostjit_lib - PUBLIC CUDA::nvJitLink CUDA::nvfatbin - ) - else() - # Prefer static CUDA libs on Linux for self-contained binaries. If the - # toolchain (e.g. lite/pip CUDA installs or some Docker images) only ships - # the dynamic variants, fall back to those rather than failing configure. - foreach (_cudalib nvJitLink nvptxcompiler nvfatbin) - if (TARGET "CUDA::${_cudalib}_static") - target_link_libraries( - cccl.c.parallel.v2.hostjit_lib - PUBLIC "CUDA::${_cudalib}_static" - ) - elseif (TARGET "CUDA::${_cudalib}") - target_link_libraries( - cccl.c.parallel.v2.hostjit_lib - PUBLIC "CUDA::${_cudalib}" - ) - else() - message( - FATAL_ERROR - "hostjit needs CUDA::${_cudalib}[_static] but neither variant was " - "found by FindCUDAToolkit. Install the full CUDA toolkit " - "(libnvjitlink-dev / libnvfatbin-dev or equivalent)." - ) - endif() - endforeach() - endif() + target_link_libraries(cccl.c.parallel.v2.hostjit_lib PUBLIC CUDA::cudart) endif() if (NOT MSVC) @@ -216,48 +89,10 @@ set_target_properties( PROPERTIES CXX_STANDARD 20 POSITION_INDEPENDENT_CODE ON ) -# -------------------------------------------------------------------------- -# Install clang headers into wheel (for self-sufficient packaging) -# -------------------------------------------------------------------------- -# Clang CUDA headers we still use from the LLVM source tree. -# We DON'T install device_functions, math, or libdevice_declares — our local -# copies in cuda_minimal/ replace them. -set( - _clang_cuda_headers_needed - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_math_forward_declares.h" - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_builtin_vars.h" - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_cmath.h" - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_intrinsics.h" - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_complex_builtins.h" - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_texture_intrinsics.h" -) -install( - FILES ${_clang_cuda_headers_needed} - DESTINATION "cuda/cccl/headers/clang" -) - -# Clang builtin C headers needed by our stubs and CUDA toolkit headers. -file( - GLOB _clang_stddef_headers - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__stddef_*.h" -) -set( - _clang_c_headers - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/limits.h" - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/stddef.h" - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/stdint.h" - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__stddef_header_macro.h" - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/float.h" - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__float_header_macro.h" - "${llvm_project_SOURCE_DIR}/clang/lib/Headers/inttypes.h" - ${_clang_stddef_headers} -) -install(FILES ${_clang_c_headers} DESTINATION "cuda/cccl/headers/clang") - # Hostjit's minimal CUDA runtime headers (replacements for upstream clang headers) set( _hostjit_cuda_minimal_dir - "${CMAKE_CURRENT_SOURCE_DIR}/include/hostjit/cuda_minimal" + "${_hostjit_include_dir}/hostjit/cuda_minimal" ) file(GLOB _hostjit_cuda_minimal_headers "${_hostjit_cuda_minimal_dir}/*.h") install( @@ -274,7 +109,7 @@ install( ) # On Windows with multi-config generators (Visual Studio), exclude hostjit -# targets from Debug builds — the LLVM Debug build causes stack overflows. +# targets that depend on libnvcc from Debug builds. if (MSVC) set_target_properties( cccl.c.parallel.v2.hostjit_lib diff --git a/c/parallel.v2/src/hostjit/codegen/bitcode.cpp b/c/parallel.v2/src/hostjit/codegen/bitcode.cpp index 316aa7b6ec4..438a6b0844d 100644 --- a/c/parallel.v2/src/hostjit/codegen/bitcode.cpp +++ b/c/parallel.v2/src/hostjit/codegen/bitcode.cpp @@ -102,16 +102,34 @@ bool BitcodeCollector::compile_and_add(const char* source, size_t source_size, c return true; } - hostjit::CUDACompiler compiler; std::string src(source, source_size); - auto result = compiler.compileToDeviceBitcode(src, config_); - if (!result.success) + auto path = make_temp_path("cccl_" + name + "_", unique_id_, ".bc"); + + std::vector options; + config_.appendCommandLineArguments(options); + auto option_ptrs = hostjit::detail::make_libnvcc_option_ptrs(options); + + hostjit::detail::LibnvccProgramGuard program; + auto create_result = libnvccCreateProgram(&program.program, src.c_str(), "input.cu"); + if (create_result != LIBNVCC_SUCCESS) { - fprintf(stderr, "\nERROR compiling %s to bitcode: %s\n", name.c_str(), result.diagnostics.c_str()); + fprintf(stderr, "\nERROR creating libnvcc program for %s: %s\n", name.c_str(), libnvccGetErrorString(create_result)); return false; } - auto path = make_temp_path("cccl_" + name + "_", unique_id_, ".bc"); - if (write_file(result.bitcode.data(), result.bitcode.size(), path)) + + auto result = libnvccCompileProgramToDeviceBitcode( + program.program, + path.c_str(), + static_cast(option_ptrs.size()), + option_ptrs.empty() ? nullptr : option_ptrs.data()); + if (result != LIBNVCC_SUCCESS) + { + auto log = hostjit::detail::get_libnvcc_program_log(program.program); + fprintf(stderr, "\nERROR compiling %s to bitcode: %s\n", name.c_str(), log.c_str()); + return false; + } + + if (std::filesystem::exists(path)) { config_.device_bitcode_files.push_back(path); temp_paths_.push_back(path); diff --git a/c/parallel.v2/src/hostjit/config.cpp b/c/parallel.v2/src/hostjit/config.cpp index dcb3819e173..4b9f50c858b 100644 --- a/c/parallel.v2/src/hostjit/config.cpp +++ b/c/parallel.v2/src/hostjit/config.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include @@ -8,6 +8,126 @@ namespace hostjit { +namespace +{ +void append_system_include_path(std::vector& args, const std::string& include_path) +{ + if (!include_path.empty()) + { + args.push_back("--system-include-path=" + include_path); + } +} + +void append_macro_definition( + std::vector& args, const std::string& macro_name, const std::string& macro_value) +{ + if (macro_value.empty()) + { + args.push_back("-D" + macro_name); + } + else + { + args.push_back("-D" + macro_name + "=" + macro_value); + } +} + +void append_cccl_include_paths(std::vector& args, const CompilerConfig& config) +{ + if (!config.cccl_include_path.empty()) + { + append_system_include_path(args, config.cccl_include_path); + return; + } + +#ifdef CCCL_SOURCE_DIR + append_system_include_path(args, std::string(CCCL_SOURCE_DIR) + "/libcudacxx/include"); + append_system_include_path(args, std::string(CCCL_SOURCE_DIR) + "/cub"); + append_system_include_path(args, std::string(CCCL_SOURCE_DIR) + "/thrust"); +#endif +} + +void append_cccl_macro_definitions(std::vector& args) +{ + append_macro_definition(args, "CCCL_DISABLE_CTK_COMPATIBILITY_CHECK", ""); + append_macro_definition(args, "_CCCL_ENABLE_FREESTANDING", "1"); + append_macro_definition(args, "CCCL_DISABLE_NVTX", "1"); + append_macro_definition(args, "CCCL_DISABLE_EXCEPTIONS", "1"); +} +} // namespace + +void CompilerConfig::appendCommandLineArguments(std::vector& args) const +{ + if (!cuda_toolkit_path.empty()) + { + args.push_back("--cuda-path=" + cuda_toolkit_path); + } + if (!hostjit_include_path.empty()) + { + args.push_back("--hostjit-include-path=" + hostjit_include_path); + } + if (!clang_headers_path.empty()) + { + args.push_back("--clang-headers-path=" + clang_headers_path); + } + append_cccl_include_paths(args, *this); + for (const auto& include_path : include_paths) + { + args.push_back("-I" + include_path); + } + for (const auto& library_path : library_paths) + { + args.push_back("-L" + library_path); + } + for (const auto& bitcode_file : device_bitcode_files) + { + args.push_back("--device-bitcode=" + bitcode_file); + } + for (const auto& ltoir_file : device_ltoir_files) + { + args.push_back("--device-ltoir=" + ltoir_file); + } + append_cccl_macro_definitions(args); + for (const auto& [macro_name, macro_value] : macro_definitions) + { + append_macro_definition(args, macro_name, macro_value); + } + for (const auto& clang_arg : extra_clang_args) + { + args.push_back("-XClang"); + args.push_back(clang_arg); + } + if (!device_pch_path.empty()) + { + args.push_back("--device-pch=" + device_pch_path); + } + if (!host_pch_path.empty()) + { + args.push_back("--host-pch=" + host_pch_path); + } + args.push_back("--gpu-architecture=sm_" + std::to_string(sm_version)); + args.push_back("-O" + std::to_string(optimization_level)); + if (debug) + { + args.push_back("--debug"); + } + if (verbose) + { + args.push_back("--verbose"); + } + if (trace_includes) + { + args.push_back("--trace-includes"); + } + if (keep_artifacts) + { + args.push_back("--keep-artifacts"); + } + if (!entry_point_name.empty()) + { + args.push_back("--entry-point=" + entry_point_name); + } +} + CompilerConfig detectDefaultConfig() { CompilerConfig config; diff --git a/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp b/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp index 69f7e140fcd..95576044518 100644 --- a/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp +++ b/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp @@ -3,52 +3,47 @@ #include #include -namespace hostjit +#include + +namespace hostjit::detail { -struct CompilationResult +struct LibnvccProgramGuard { - bool success; - std::string object_file_path; // Path to generated .o file - std::string diagnostics; // Compiler messages - std::vector cubin; // Device cubin extracted during compilation -}; + libnvccProgram program = nullptr; -struct BitcodeResult -{ - bool success; - std::string bitcode; // LLVM bitcode bytes - std::string diagnostics; + ~LibnvccProgramGuard() + { + libnvccDestroyProgram(&program); + } }; -struct LinkResult +inline std::vector make_libnvcc_option_ptrs(const std::vector& options) { - bool success; - std::string library_path; // Path to .so file - std::string diagnostics; -}; - -// Forward declaration to avoid including heavy Clang headers -struct CompilerConfig; - -class CUDACompiler + std::vector ptrs; + ptrs.reserve(options.size()); + for (const auto& option : options) + { + ptrs.push_back(option.c_str()); + } + return ptrs; +} + +inline std::string get_libnvcc_program_log(libnvccProgram program) { -public: - CUDACompiler(); - ~CUDACompiler(); - - // Compile CUDA device source to LLVM bitcode - BitcodeResult compileToDeviceBitcode(const std::string& source_code, const CompilerConfig& config); - - // Compile CUDA source code to object file - CompilationResult - compileToObject(const std::string& source_code, const std::string& output_path, const CompilerConfig& config); - - // Link object files to shared library - LinkResult linkToSharedLibrary( - const std::vector& object_files, const std::string& output_path, const CompilerConfig& config); - -private: - class Impl; - Impl* impl_; -}; -} // namespace hostjit + size_t log_size = 0; + if (libnvccGetProgramLogSize(program, &log_size) != LIBNVCC_SUCCESS || log_size == 0) + { + return {}; + } + std::string log(log_size, '\0'); + if (libnvccGetProgramLog(program, log.data()) != LIBNVCC_SUCCESS) + { + return {}; + } + if (!log.empty() && log.back() == '\0') + { + log.pop_back(); + } + return log; +} +} // namespace hostjit::detail diff --git a/c/parallel.v2/src/hostjit/include/hostjit/config.hpp b/c/parallel.v2/src/hostjit/include/hostjit/config.hpp index 020ed085e56..f02ce738819 100644 --- a/c/parallel.v2/src/hostjit/include/hostjit/config.hpp +++ b/c/parallel.v2/src/hostjit/include/hostjit/config.hpp @@ -18,14 +18,19 @@ struct CompilerConfig std::vector device_ltoir_files; // NVRTC LTOIR; linked at the nvJitLink stage with -lto std::unordered_map macro_definitions; // key=macro name, value=macro value (empty for flag // macros) - int sm_version = 70; + std::vector extra_clang_args; // Arguments passed directly to Clang via libnvcc's -XClang option + std::string device_pch_path; // Existing device PCH file to load during device compilation + std::string host_pch_path; // Existing host PCH file to load during host compilation + int sm_version = 75; int optimization_level = 2; bool debug = false; bool verbose = false; bool trace_includes = false; // Show all included headers during compilation (for debugging header search) bool keep_artifacts = false; // Keep compiled artifacts for inspection (PTX, object files, etc.) std::string entry_point_name; // Name of the exported entry point function (used for post-link optimization) - bool enable_pch = false; // Cache precompiled headers on disk to speed up repeated builds + bool enable_pch = false; // Let CCCL create/load cached PCH files before invoking libnvcc + + void appendCommandLineArguments(std::vector& args) const; }; // Auto-detect CUDA toolkit and create default configuration diff --git a/c/parallel.v2/src/hostjit/include/hostjit/jit_compiler.hpp b/c/parallel.v2/src/hostjit/include/hostjit/jit_compiler.hpp index 9667ee26f78..16253c8d561 100644 --- a/c/parallel.v2/src/hostjit/include/hostjit/jit_compiler.hpp +++ b/c/parallel.v2/src/hostjit/include/hostjit/jit_compiler.hpp @@ -86,7 +86,6 @@ class JITCompiler void removeTempDirectory(); CompilerConfig config_; - CUDACompiler compiler_; DynamicLibrary library_; std::string temp_dir_; std::string last_error_; diff --git a/c/parallel.v2/src/hostjit/jit_compiler.cpp b/c/parallel.v2/src/hostjit/jit_compiler.cpp index 83490468aad..c600117649b 100644 --- a/c/parallel.v2/src/hostjit/jit_compiler.cpp +++ b/c/parallel.v2/src/hostjit/jit_compiler.cpp @@ -1,8 +1,10 @@ #include #include +#include #include #include #include +#include #include @@ -12,6 +14,121 @@ # include #endif +namespace +{ +static constexpr const char* pch_preamble_source = + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n"; + +std::filesystem::path get_pch_cache_dir() +{ + auto dir = std::filesystem::temp_directory_path() / "hostjit_pch"; + std::filesystem::create_directories(dir); + return dir; +} + +std::string get_pch_path(const std::string& kind, int sm_version) +{ + return (get_pch_cache_dir() / (kind + "_sm" + std::to_string(sm_version) + ".pch")).string(); +} + +std::string get_pch_source_path(const std::string& kind, int sm_version) +{ + return (get_pch_cache_dir() / (kind + "_sm" + std::to_string(sm_version) + "_preamble.cu")).string(); +} + +bool create_pch_if_needed( + hostjit::CompilerConfig config, + libnvccPCHKind kind, + const std::string& kind_name, + std::string& diagnostics, + std::string& pch_path) +{ + pch_path = get_pch_path(kind_name, config.sm_version); + if (std::filesystem::exists(pch_path)) + { + return true; + } + + config.enable_pch = false; + config.device_pch_path.clear(); + config.host_pch_path.clear(); + + std::vector options; + config.appendCommandLineArguments(options); + auto option_ptrs = hostjit::detail::make_libnvcc_option_ptrs(options); + + hostjit::detail::LibnvccProgramGuard program; + auto create_result = libnvccCreateProgram(&program.program, pch_preamble_source, "hostjit_preamble.cu"); + if (create_result != LIBNVCC_SUCCESS) + { + diagnostics += "Failed to create libnvcc PCH program: "; + diagnostics += libnvccGetErrorString(create_result); + diagnostics += "\n"; + pch_path.clear(); + return false; + } + + auto source_path = get_pch_source_path(kind_name, config.sm_version); + auto pch_result = libnvccCreatePCH( + program.program, + kind, + source_path.c_str(), + pch_path.c_str(), + static_cast(option_ptrs.size()), + option_ptrs.empty() ? nullptr : option_ptrs.data()); + if (pch_result != LIBNVCC_SUCCESS) + { + diagnostics += kind_name + " PCH generation failed: " + hostjit::detail::get_libnvcc_program_log(program.program); + diagnostics += "\n"; + pch_path.clear(); + return false; + } + return true; +} + +hostjit::CompilerConfig prepare_pch_config(const hostjit::CompilerConfig& config, std::string& diagnostics) +{ + hostjit::CompilerConfig prepared = config; + prepared.device_pch_path.clear(); + prepared.host_pch_path.clear(); + + if (!prepared.enable_pch) + { + return prepared; + } + + std::string device_pch_path; + if (create_pch_if_needed(prepared, LIBNVCC_PCH_DEVICE, "device", diagnostics, device_pch_path)) + { + prepared.device_pch_path = std::move(device_pch_path); + } + + std::string host_pch_path; + if (create_pch_if_needed(prepared, LIBNVCC_PCH_HOST, "host", diagnostics, host_pch_path)) + { + prepared.host_pch_path = std::move(host_pch_path); + } + + return prepared; +} + +bool read_file(const std::string& path, std::vector& out) +{ + std::ifstream f(path, std::ios::binary); + if (!f) + { + return false; + } + out.assign(std::istreambuf_iterator(f), std::istreambuf_iterator()); + return true; +} +} // anonymous namespace + namespace hostjit { JITCompiler::JITCompiler() @@ -45,22 +162,54 @@ bool JITCompiler::compile(const std::string& source_code) return false; } - std::string obj_path = temp_dir_ + "/cuda_code.o"; - auto compile_result = compiler_.compileToObject(source_code, obj_path, config_); + std::string pch_diagnostics; + CompilerConfig libnvcc_config = prepare_pch_config(config_, pch_diagnostics); + if (config_.verbose && !pch_diagnostics.empty()) + { + std::cout << pch_diagnostics; + } + + std::vector options; + libnvcc_config.appendCommandLineArguments(options); + auto option_ptrs = hostjit::detail::make_libnvcc_option_ptrs(options); - if (!compile_result.success) + hostjit::detail::LibnvccProgramGuard program; + auto create_result = libnvccCreateProgram(&program.program, source_code.c_str(), "input.cu"); + if (create_result != LIBNVCC_SUCCESS) { - last_error_ = "Compilation failed:\n" + compile_result.diagnostics; + last_error_ = std::string("Failed to create libnvcc program: ") + libnvccGetErrorString(create_result); removeTempDirectory(); return false; } - // Store the cubin for later inspection - cubin_ = std::move(compile_result.cubin); + std::string obj_path = temp_dir_ + "/cuda_code.o"; + std::string cubin_path = temp_dir_ + "/device.cubin"; + auto compile_result = libnvccCompileProgramToObject( + program.program, + obj_path.c_str(), + cubin_path.c_str(), + static_cast(option_ptrs.size()), + option_ptrs.empty() ? nullptr : option_ptrs.data()); + auto compile_log = hostjit::detail::get_libnvcc_program_log(program.program); + + if (compile_result != LIBNVCC_SUCCESS) + { + last_error_ = "Compilation failed:\n" + compile_log; + removeTempDirectory(); + return false; + } + + cubin_.clear(); + if (!read_file(cubin_path, cubin_)) + { + last_error_ = "Compilation failed: generated cubin could not be read"; + removeTempDirectory(); + return false; + } if (config_.verbose) { - std::cout << "Compilation diagnostics:\n" << compile_result.diagnostics << "\n"; + std::cout << "Compilation diagnostics:\n" << compile_log << "\n"; } #ifdef _WIN32 @@ -68,18 +217,26 @@ bool JITCompiler::compile(const std::string& source_code) #else std::string lib_path = temp_dir_ + "/libcuda_code.so"; #endif - auto link_result = compiler_.linkToSharedLibrary({obj_path}, lib_path, config_); + const char* object_files[] = {obj_path.c_str()}; + auto link_result = libnvccLinkToSharedLibrary( + program.program, + 1, + object_files, + lib_path.c_str(), + static_cast(option_ptrs.size()), + option_ptrs.empty() ? nullptr : option_ptrs.data()); + auto link_log = hostjit::detail::get_libnvcc_program_log(program.program); - if (!link_result.success) + if (link_result != LIBNVCC_SUCCESS) { - last_error_ = "Linking failed:\n" + link_result.diagnostics; + last_error_ = "Linking failed:\n" + link_log; removeTempDirectory(); return false; } if (config_.verbose) { - std::cout << "Linking diagnostics:\n" << link_result.diagnostics << "\n"; + std::cout << "Linking diagnostics:\n" << link_log << "\n"; } if (!library_.load(lib_path)) diff --git a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt new file mode 100644 index 00000000000..f0038a731b9 --- /dev/null +++ b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt @@ -0,0 +1,260 @@ +if (DEFINED LIBNVCC_CPM_CMAKE_PATH AND EXISTS "${LIBNVCC_CPM_CMAKE_PATH}") + include("${LIBNVCC_CPM_CMAKE_PATH}") +else() + find_file(LIBNVCC_CPM_CMAKE_PATH NAMES CPM.cmake PATHS ${CMAKE_MODULE_PATH} NO_DEFAULT_PATH) + if (LIBNVCC_CPM_CMAKE_PATH) + include("${LIBNVCC_CPM_CMAKE_PATH}") + else() + message(FATAL_ERROR "CPM.cmake not found. Set LIBNVCC_CPM_CMAKE_PATH or add it to CMAKE_MODULE_PATH.") + endif() +endif() + +if (MSVC AND CMAKE_BUILD_TYPE STREQUAL "Debug") + message( + FATAL_ERROR + "libnvcc does not support Debug builds on Windows. " + "The statically-linked LLVM Debug build is too large and causes stack " + "overflows at runtime. Use MinSizeRel, Release, or RelWithDebInfo instead." + ) +endif() + +if (DEFINED HOSTJIT_LLVM_VERSION AND NOT DEFINED LIBNVCC_LLVM_VERSION) + set(_libnvcc_llvm_version_default "${HOSTJIT_LLVM_VERSION}") +else() + set(_libnvcc_llvm_version_default "llvmorg-22.1.1") +endif() +set(LIBNVCC_LLVM_VERSION "${_libnvcc_llvm_version_default}" CACHE STRING "LLVM git tag to fetch") + +# List options must be set before CPMAddPackage +set(LLVM_ENABLE_PROJECTS "clang;lld" CACHE STRING "" FORCE) +set(LLVM_TARGETS_TO_BUILD "X86;NVPTX" CACHE STRING "" FORCE) + +CPMAddPackage( + NAME llvm_project + GIT_REPOSITORY https://github.com/llvm/llvm-project.git + GIT_TAG ${LIBNVCC_LLVM_VERSION} + GIT_SHALLOW ON + SOURCE_SUBDIR llvm + EXCLUDE_FROM_ALL YES + OPTIONS + "LLVM_BUILD_LLVM_C_DYLIB OFF" + "LLVM_BUILD_TOOLS OFF" + "LLVM_BUILD_UTILS OFF" + "LLVM_BUILD_RUNTIME OFF" + "LLVM_BUILD_RUNTIMES OFF" + "LLVM_INCLUDE_BENCHMARKS OFF" + "LLVM_INCLUDE_DOCS OFF" + "LLVM_INCLUDE_EXAMPLES OFF" + "LLVM_INCLUDE_RUNTIMES OFF" + "LLVM_INCLUDE_TESTS OFF" + "LLVM_INCLUDE_TOOLS ON" + "LLVM_INCLUDE_UTILS OFF" + "LLVM_ENABLE_ZLIB OFF" + "LLVM_ENABLE_ZSTD OFF" + "LLVM_ENABLE_TERMINFO OFF" + "LLVM_ENABLE_BINDINGS OFF" + "CLANG_BUILD_TOOLS OFF" + "CLANG_ENABLE_ARCMT OFF" + "CLANG_ENABLE_STATIC_ANALYZER OFF" +) + +# Ensure the clang resource directory exists +file( + MAKE_DIRECTORY "${llvm_project_BINARY_DIR}/lib/clang/${LLVM_VERSION_MAJOR}" +) + +add_library( + libnvcc + SHARED + compiler.cpp +) + +target_include_directories( + libnvcc + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + PRIVATE + ${llvm_project_SOURCE_DIR}/llvm/include + ${llvm_project_BINARY_DIR}/include + ${llvm_project_SOURCE_DIR}/clang/include + ${llvm_project_BINARY_DIR}/tools/clang/include + ${llvm_project_SOURCE_DIR}/lld/include + ${llvm_project_BINARY_DIR}/tools/lld/include +) + +target_compile_definitions( + libnvcc + PRIVATE + CLANG_RESOURCE_DIR="${llvm_project_BINARY_DIR}/lib/clang/${LLVM_VERSION_MAJOR}" + CLANG_HEADERS_DIR="${llvm_project_SOURCE_DIR}/clang/lib/Headers" + HOSTJIT_INCLUDE_DIR="${_hostjit_include_dir}" +) + +if (CUDAToolkit_FOUND) + target_include_directories( + libnvcc + PRIVATE ${CUDAToolkit_INCLUDE_DIRS} + ) + cmake_path(GET CUDAToolkit_BIN_DIR PARENT_PATH CUDA_TOOLKIT_ROOT_FROM_CMAKE) + target_compile_definitions( + libnvcc + PRIVATE + CUDA_TOOLKIT_PATH="${CUDA_TOOLKIT_ROOT_FROM_CMAKE}" + CUDA_SDK_VERSION="${CUDAToolkit_VERSION_MAJOR}.0" + ) +endif() + +# Link against LLVM/Clang/LLD +target_link_libraries( + libnvcc + PRIVATE + # LLVM + LLVMCore + LLVMSupport + LLVMIRReader + LLVMMC + LLVMObject + LLVMX86CodeGen + LLVMX86AsmParser + LLVMX86Desc + LLVMX86Info + LLVMNVPTXCodeGen + LLVMNVPTXDesc + LLVMNVPTXInfo + LLVMLinker + LLVMPasses + # Clang + clangAST + clangBasic + clangCodeGen + clangDriver + clangFrontend + clangFrontendTool + clangLex + clangParse + clangSema + clangEdit + clangAnalysis + clangRewrite + clangSerialization + # LLD + $,lldCOFF,lldELF> + lldCommon +) + +if (CUDAToolkit_FOUND) + target_link_libraries( + libnvcc + PRIVATE CUDA::cuda_driver CUDA::cudart + ) + if (WIN32) + # On Windows, static CUDA libs are built with /MT which conflicts with + # the project's dynamic CRT (/MD). Use dynamic variants instead. + target_link_libraries( + libnvcc + PRIVATE CUDA::nvJitLink CUDA::nvfatbin + ) + else() + # Prefer static CUDA libs on Linux for self-contained binaries. If the + # toolchain (e.g. lite/pip CUDA installs or some Docker images) only ships + # the dynamic variants, fall back to those rather than failing configure. + foreach (_cudalib nvJitLink nvptxcompiler nvfatbin) + if (TARGET "CUDA::${_cudalib}_static") + target_link_libraries( + libnvcc + PRIVATE "CUDA::${_cudalib}_static" + ) + elseif (TARGET "CUDA::${_cudalib}") + target_link_libraries( + libnvcc + PRIVATE "CUDA::${_cudalib}" + ) + else() + message( + FATAL_ERROR + "libnvcc needs CUDA::${_cudalib}[_static] but neither variant was " + "found by FindCUDAToolkit. Install the full CUDA toolkit " + "(libnvjitlink-dev / libnvfatbin-dev or equivalent)." + ) + endif() + endforeach() + endif() +endif() + +if (NOT MSVC) + target_compile_options(libnvcc PRIVATE -fno-rtti) +endif() + +set_target_properties( + libnvcc + PROPERTIES + CXX_STANDARD 20 + PREFIX "" + POSITION_INDEPENDENT_CODE ON + WINDOWS_EXPORT_ALL_SYMBOLS ON +) + +if (LIBNVCC_LIBRARY_OUTPUT_DIRECTORY) + set_target_properties( + libnvcc + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${LIBNVCC_LIBRARY_OUTPUT_DIRECTORY}" + ARCHIVE_OUTPUT_DIRECTORY "${LIBNVCC_LIBRARY_OUTPUT_DIRECTORY}" + RUNTIME_OUTPUT_DIRECTORY "${LIBNVCC_LIBRARY_OUTPUT_DIRECTORY}" + ) +endif() + +# On Windows with multi-config generators (Visual Studio), exclude libnvcc from +# Debug builds — the LLVM Debug build causes stack overflows. +if (MSVC) + set_target_properties( + libnvcc + PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_DEBUG TRUE + ) +endif() + +if (NOT LIBNVCC_HEADER_INSTALL_DESTINATION) + set(LIBNVCC_HEADER_INSTALL_DESTINATION "include/libnvcc") +endif() +if (NOT LIBNVCC_CLANG_HEADER_INSTALL_DESTINATION) + set(LIBNVCC_CLANG_HEADER_INSTALL_DESTINATION "include/libnvcc/clang") +endif() + +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/libnvcc/libnvcc.h" DESTINATION "${LIBNVCC_HEADER_INSTALL_DESTINATION}") + +# -------------------------------------------------------------------------- +# Install clang headers into wheel (for self-sufficient packaging) +# -------------------------------------------------------------------------- +# Clang CUDA headers we still use from the LLVM source tree. +# We DON'T install device_functions, math, or libdevice_declares — our local +# copies in cuda_minimal/ replace them. +set( + _clang_cuda_headers_needed + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_math_forward_declares.h" + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_builtin_vars.h" + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_cmath.h" + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_intrinsics.h" + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_complex_builtins.h" + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_texture_intrinsics.h" +) +install( + FILES ${_clang_cuda_headers_needed} + DESTINATION "${LIBNVCC_CLANG_HEADER_INSTALL_DESTINATION}" +) + +# Clang builtin C headers needed by our stubs and CUDA toolkit headers. +file( + GLOB _clang_stddef_headers + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__stddef_*.h" +) +set( + _clang_c_headers + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/limits.h" + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/stddef.h" + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/stdint.h" + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__stddef_header_macro.h" + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/float.h" + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/__float_header_macro.h" + "${llvm_project_SOURCE_DIR}/clang/lib/Headers/inttypes.h" + ${_clang_stddef_headers} +) +install(FILES ${_clang_c_headers} DESTINATION "${LIBNVCC_CLANG_HEADER_INSTALL_DESTINATION}") diff --git a/c/parallel.v2/src/hostjit/compiler.cpp b/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp similarity index 59% rename from c/parallel.v2/src/hostjit/compiler.cpp rename to c/parallel.v2/src/hostjit/libnvcc/compiler.cpp index f5697672f8b..529d1e8cb2b 100644 --- a/c/parallel.v2/src/hostjit/compiler.cpp +++ b/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp @@ -7,8 +7,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -51,17 +50,23 @@ LLD_HAS_DRIVER(elf) # include #endif +#include #include +#include #include #include #include #include +#include +#include +#include +#include #include #include #include -namespace hostjit +namespace libnvcc { static bool llvm_initialized = false; @@ -85,6 +90,589 @@ static void initialize_llvm() llvm_initialized = true; } +struct CompilerOptions +{ + std::string cuda_toolkit_path; + std::string hostjit_include_path; + std::string clang_headers_path; + std::vector system_include_paths; + std::vector include_paths; + std::vector library_paths; + std::vector device_bitcode_files; + std::vector device_ltoir_files; + std::unordered_map macro_definitions; + std::vector extra_clang_args; + std::string device_pch_path; + std::string host_pch_path; + int sm_version = 75; + int optimization_level = 2; + bool debug = false; + bool verbose = false; + bool trace_includes = false; + bool keep_artifacts = false; + std::string entry_point_name; +}; + +struct CompilationResult +{ + bool success = false; + std::string object_file_path; + std::string diagnostics; +}; + +struct BitcodeResult +{ + bool success = false; + std::string diagnostics; +}; + +struct LinkResult +{ + bool success = false; + std::string library_path; + std::string diagnostics; +}; + +static bool pathExists(const std::filesystem::path& path); + +static void addDefaultCudaLibraryPath(CompilerOptions& options) +{ + if (!options.cuda_toolkit_path.empty()) + { + std::filesystem::path lib64_path = std::filesystem::path(options.cuda_toolkit_path) / "lib64"; + std::filesystem::path lib_path = std::filesystem::path(options.cuda_toolkit_path) / "lib"; + + if (pathExists(lib64_path)) + { + options.library_paths.push_back(lib64_path.string()); + } + else if (pathExists(lib_path)) + { + options.library_paths.push_back(lib_path.string()); + } + } +} + +static void setDefaultOptions(CompilerOptions& options) +{ + if (const char* env = std::getenv("CUDA_PATH")) + { + options.cuda_toolkit_path = env; + } + else if (const char* env = std::getenv("CUDA_HOME")) + { + options.cuda_toolkit_path = env; + } +#ifdef CUDA_TOOLKIT_PATH + else + { + options.cuda_toolkit_path = CUDA_TOOLKIT_PATH; + } +#endif + + if (const char* env = std::getenv("HOSTJIT_INCLUDE_PATH")) + { + options.hostjit_include_path = env; + } +#ifdef HOSTJIT_INCLUDE_DIR + else + { + options.hostjit_include_path = HOSTJIT_INCLUDE_DIR; + } +#endif + + if (const char* env = std::getenv("HOSTJIT_CLANG_PATH")) + { + options.clang_headers_path = env; + } +#ifdef CLANG_HEADERS_DIR + else + { + options.clang_headers_path = CLANG_HEADERS_DIR; + } +#endif +} + +static bool pathExists(const std::filesystem::path& path) +{ + std::error_code ec; + return std::filesystem::exists(path, ec); +} + +static std::filesystem::path tempDirectoryPath() +{ + std::error_code ec; + auto path = std::filesystem::temp_directory_path(ec); + if (!ec) + { + return path; + } +#ifdef _WIN32 + if (const char* env = std::getenv("TEMP")) + { + return env; + } + if (const char* env = std::getenv("TMP")) + { + return env; + } +#endif + if (const char* env = std::getenv("TMPDIR")) + { + return env; + } + return "."; +} + +static bool createDirectories(const std::filesystem::path& path, std::string& diagnostics) +{ + std::error_code ec; + std::filesystem::create_directories(path, ec); + if (ec) + { + diagnostics += "Failed to create directory " + path.string() + ": " + ec.message() + "\n"; + return false; + } + return true; +} + +static void removeAll(const std::filesystem::path& path) +{ + std::error_code ec; + std::filesystem::remove_all(path, ec); +} + +template +static void forEachDirectoryEntry(const std::filesystem::path& dir, Fn&& fn) +{ + std::error_code ec; + for (std::filesystem::directory_iterator it(dir, ec), end; !ec && it != end; it.increment(ec)) + { + fn(*it); + } +} + +static bool parseInt(const std::string& value, int& out) +{ + if (value.empty()) + { + return false; + } + + int parsed = 0; + const char* begin = value.data(); + const char* end = begin + value.size(); + auto [ptr, ec] = std::from_chars(begin, end, parsed); + if (ec != std::errc{} || ptr != end) + { + return false; + } + out = parsed; + return true; +} + +static bool parseGpuArchitecture(const std::string& value, int& sm) +{ + std::string arch = value; + if (arch.starts_with("sm_")) + { + arch.erase(0, 3); + } + return parseInt(arch, sm); +} + +static bool parseMacroDefinition(const std::string& value, CompilerOptions& options) +{ + if (value.empty()) + { + return false; + } + auto eq = value.find('='); + if (eq == std::string::npos) + { + options.macro_definitions[value] = ""; + } + else if (eq == 0) + { + return false; + } + else + { + options.macro_definitions[value.substr(0, eq)] = value.substr(eq + 1); + } + return true; +} + +static bool parseOptions(int num_options, const char* const* raw_options, CompilerOptions& options, std::string& error) +{ + if (num_options < 0) + { + error = "Option count must be non-negative"; + return false; + } + if (num_options > 0 && raw_options == nullptr) + { + error = "Options array is null"; + return false; + } + + setDefaultOptions(options); + + auto value_after_equals = [](std::string_view option, std::string_view prefix) -> std::string { + return std::string(option.substr(prefix.size())); + }; + + for (int i = 0; i < num_options; ++i) + { + if (raw_options[i] == nullptr) + { + error = "Option string is null"; + return false; + } + + std::string_view option(raw_options[i]); + if (option.starts_with("--cuda-path=")) + { + options.cuda_toolkit_path = value_after_equals(option, "--cuda-path="); + } + else if (option.starts_with("--hostjit-include-path=")) + { + options.hostjit_include_path = value_after_equals(option, "--hostjit-include-path="); + } + else if (option.starts_with("--clang-headers-path=")) + { + options.clang_headers_path = value_after_equals(option, "--clang-headers-path="); + } + else if (option.starts_with("--system-include-path=")) + { + options.system_include_paths.push_back(value_after_equals(option, "--system-include-path=")); + } + else if (option.starts_with("-isystem") && option.size() > 8) + { + options.system_include_paths.emplace_back(option.substr(8)); + } + else if (option == "-isystem") + { + if (++i >= num_options || raw_options[i] == nullptr) + { + error = "-isystem requires an argument"; + return false; + } + options.system_include_paths.emplace_back(raw_options[i]); + } + else if (option.starts_with("--include-path=")) + { + options.include_paths.push_back(value_after_equals(option, "--include-path=")); + } + else if (option.starts_with("-I") && option.size() > 2) + { + options.include_paths.emplace_back(option.substr(2)); + } + else if (option == "-I") + { + if (++i >= num_options || raw_options[i] == nullptr) + { + error = "-I requires an argument"; + return false; + } + options.include_paths.emplace_back(raw_options[i]); + } + else if (option.starts_with("--library-path=")) + { + options.library_paths.push_back(value_after_equals(option, "--library-path=")); + } + else if (option.starts_with("-L") && option.size() > 2) + { + options.library_paths.emplace_back(option.substr(2)); + } + else if (option == "-L") + { + if (++i >= num_options || raw_options[i] == nullptr) + { + error = "-L requires an argument"; + return false; + } + options.library_paths.emplace_back(raw_options[i]); + } + else if (option.starts_with("--device-bitcode=")) + { + options.device_bitcode_files.push_back(value_after_equals(option, "--device-bitcode=")); + } + else if (option.starts_with("--device-ltoir=")) + { + options.device_ltoir_files.push_back(value_after_equals(option, "--device-ltoir=")); + } + else if (option.starts_with("--define-macro=")) + { + if (!parseMacroDefinition(value_after_equals(option, "--define-macro="), options)) + { + error = "Invalid macro definition: " + std::string(option); + return false; + } + } + else if (option.starts_with("-D") && option.size() > 2) + { + if (!parseMacroDefinition(std::string(option.substr(2)), options)) + { + error = "Invalid macro definition: " + std::string(option); + return false; + } + } + else if (option == "-D") + { + if (++i >= num_options || raw_options[i] == nullptr || !parseMacroDefinition(raw_options[i], options)) + { + error = "-D requires a macro definition"; + return false; + } + } + else if (option.starts_with("--gpu-architecture=")) + { + if (!parseGpuArchitecture(value_after_equals(option, "--gpu-architecture="), options.sm_version)) + { + error = "Invalid GPU architecture: " + std::string(option); + return false; + } + } + else if (option.starts_with("--optimization-level=")) + { + if (!parseInt(value_after_equals(option, "--optimization-level="), options.optimization_level)) + { + error = "Invalid optimization level: " + std::string(option); + return false; + } + } + else if (option.starts_with("-O") && option.size() > 2) + { + if (!parseInt(std::string(option.substr(2)), options.optimization_level)) + { + error = "Invalid optimization level: " + std::string(option); + return false; + } + } + else if (option == "--debug") + { + options.debug = true; + } + else if (option == "--verbose") + { + options.verbose = true; + } + else if (option == "--trace-includes") + { + options.trace_includes = true; + } + else if (option == "--keep-artifacts") + { + options.keep_artifacts = true; + } + else if (option.starts_with("--entry-point=")) + { + options.entry_point_name = value_after_equals(option, "--entry-point="); + } + else if (option.starts_with("--device-pch=")) + { + options.device_pch_path = value_after_equals(option, "--device-pch="); + } + else if (option.starts_with("--host-pch=")) + { + options.host_pch_path = value_after_equals(option, "--host-pch="); + } + else if (option.starts_with("-XClang=")) + { + options.extra_clang_args.emplace_back(option.substr(8)); + } + else if (option == "-XClang") + { + if (++i >= num_options || raw_options[i] == nullptr) + { + error = "-XClang requires an argument"; + return false; + } + options.extra_clang_args.emplace_back(raw_options[i]); + } + else + { + error = "Unknown option: " + std::string(option); + return false; + } + } + + if (options.library_paths.empty()) + { + addDefaultCudaLibraryPath(options); + } + + return true; +} + +static bool validateOptions(const CompilerOptions& options, std::string* error_message) +{ + if (options.cuda_toolkit_path.empty()) + { + if (error_message) + { + *error_message = "CUDA toolkit path not found. Please pass --cuda-path or set CUDA_PATH/CUDA_HOME."; + } + return false; + } + + if (!pathExists(options.cuda_toolkit_path)) + { + if (error_message) + { + *error_message = "CUDA toolkit path does not exist: " + options.cuda_toolkit_path; + } + return false; + } + + std::filesystem::path cuda_h = std::filesystem::path(options.cuda_toolkit_path) / "include" / "cuda.h"; + if (!pathExists(cuda_h)) + { + if (error_message) + { + *error_message = "CUDA headers not found at: " + cuda_h.string(); + } + return false; + } + + for (const auto& include_path : options.include_paths) + { + if (!pathExists(include_path)) + { + if (error_message) + { + *error_message = "Include path does not exist: " + include_path; + } + return false; + } + } + + for (const auto& include_path : options.system_include_paths) + { + if (!pathExists(include_path)) + { + if (error_message) + { + *error_message = "System include path does not exist: " + include_path; + } + return false; + } + } + + for (const auto& library_path : options.library_paths) + { + if (!pathExists(library_path)) + { + if (error_message) + { + *error_message = "Library path does not exist: " + library_path; + } + return false; + } + } + + for (const auto& bitcode_path : options.device_bitcode_files) + { + if (!pathExists(bitcode_path)) + { + if (error_message) + { + *error_message = "Device bitcode path does not exist: " + bitcode_path; + } + return false; + } + } + + for (const auto& ltoir_path : options.device_ltoir_files) + { + if (!pathExists(ltoir_path)) + { + if (error_message) + { + *error_message = "Device LTOIR path does not exist: " + ltoir_path; + } + return false; + } + } + + if (!options.device_pch_path.empty() && !pathExists(options.device_pch_path)) + { + if (error_message) + { + *error_message = "Device PCH path does not exist: " + options.device_pch_path; + } + return false; + } + + if (!options.host_pch_path.empty() && !pathExists(options.host_pch_path)) + { + if (error_message) + { + *error_message = "Host PCH path does not exist: " + options.host_pch_path; + } + return false; + } + + if (options.sm_version < 30 || options.sm_version > 150) + { + if (error_message) + { + *error_message = "Invalid SM version: " + std::to_string(options.sm_version) + " (must be between 30 and 150)"; + } + return false; + } + + if (options.optimization_level < 0 || options.optimization_level > 3) + { + if (error_message) + { + *error_message = + "Invalid optimization level: " + std::to_string(options.optimization_level) + " (must be between 0 and 3)"; + } + return false; + } + + return true; +} + +static void appendExtraClangArgs(std::vector& args, const CompilerOptions& options) +{ + args.insert(args.end(), options.extra_clang_args.begin(), options.extra_clang_args.end()); +} + +static void appendSystemIncludePaths(std::vector& args, const CompilerOptions& options) +{ + for (const auto& include_path : options.system_include_paths) + { + args.push_back("-internal-isystem"); + args.push_back(include_path); + } +} + +static void appendIncludePaths(std::vector& args, const CompilerOptions& options) +{ + for (const auto& include_path : options.include_paths) + { + args.push_back("-I" + include_path); + } +} + +static void appendMacroDefinitions(std::vector& args, const CompilerOptions& options) +{ + for (const auto& [macro_name, macro_value] : options.macro_definitions) + { + if (macro_value.empty()) + { + args.push_back("-D" + macro_name); + } + else + { + args.push_back("-D" + macro_name + "=" + macro_value); + } + } +} + #ifdef _WIN32 // Generate a minimal COFF import library for a given DLL. // This allows linking without requiring the Windows SDK or MSVC .lib files. @@ -135,59 +723,31 @@ static std::string findCudartDllName(const std::string& cuda_toolkit_path) for (const auto& subdir : {"bin/x64", "bin"}) { fs::path dir = fs::path(cuda_toolkit_path) / subdir; - if (!fs::exists(dir)) + if (!pathExists(dir)) { continue; } - for (const auto& entry : fs::directory_iterator(dir)) - { + std::string cudart_name; + forEachDirectoryEntry(dir, [&](const std::filesystem::directory_entry& entry) { auto name = entry.path().filename().string(); - if (name.starts_with("cudart64_") && name.ends_with(".dll")) + if (cudart_name.empty() && name.starts_with("cudart64_") && name.ends_with(".dll")) { - return name; + cudart_name = name; } + }); + if (!cudart_name.empty()) + { + return cudart_name; } } return "cudart64_12.dll"; // fallback } #endif -// Headers precompiled into the PCH cache. Covers the algorithms exposed -// by the C parallel library so that a single pair of PCH files (device + -// host) is reused across reduce, adjacent-difference, etc. -static constexpr const char* pch_preamble_source = - "#include \n" - "#include \n" - "#include \n" - "#include \n" - "#include \n" - "#include \n"; - -class CUDACompiler::Impl +class CompilerImpl { public: - Impl() {} - - // Get the persistent PCH cache directory. - static std::filesystem::path getPCHCacheDir() - { - auto dir = std::filesystem::temp_directory_path() / "hostjit_pch"; - std::filesystem::create_directories(dir); - return dir; - } - - // Get a persistent cache path for a PCH file. - static std::string getPCHPath(const std::string& kind, int sm_version) - { - return (getPCHCacheDir() / (kind + "_sm" + std::to_string(sm_version) + ".pch")).string(); - } - - // Get the persistent path for the PCH preamble source file. - // The PCH stores a reference to this path, so it must be stable across runs. - static std::string getPCHSourcePath(const std::string& kind, int sm_version) - { - return (getPCHCacheDir() / (kind + "_sm" + std::to_string(sm_version) + "_preamble.cu")).string(); - } + CompilerImpl() {} // Write preamble to a persistent file and generate a PCH from it. // arg_strings[0] will be replaced with the persistent preamble path. @@ -263,7 +823,7 @@ class CUDACompiler::Impl const std::string& source_code, const std::string& input_file, const std::string& output_ptx, - const CompilerConfig& config, + const CompilerOptions& config, std::string& diagnostics) { std::string temp_dir = std::filesystem::path(output_ptx).parent_path().string(); @@ -271,9 +831,9 @@ class CUDACompiler::Impl std::string resource_dir = CLANG_RESOURCE_DIR; - // PTX version floor is 7.8 — CUB's instruction selection assumes - // features added in PTX 7.6 (e.g. `bmsk`), so anything older fails to - // assemble even on sm_75/sm_80. + // PTX version floor is 7.8. Some generated device code uses features + // added in PTX 7.6 (e.g. `bmsk`), so older versions can fail to assemble + // even on sm_75/sm_80. int ptx_version = 78; if (config.sm_version >= 120) { @@ -323,63 +883,20 @@ class CUDACompiler::Impl arg_strings.push_back("-internal-isystem"); arg_strings.push_back( config.clang_headers_path.empty() ? std::string(CLANG_HEADERS_DIR) : config.clang_headers_path); - arg_strings.push_back("-internal-isystem"); - if (config.cccl_include_path.empty()) - { - arg_strings.push_back(std::string(CCCL_SOURCE_DIR) + "/libcudacxx/include"); - } - else - { - arg_strings.push_back(config.cccl_include_path); - } - arg_strings.push_back("-internal-isystem"); - if (config.cccl_include_path.empty()) - { - arg_strings.push_back(std::string(CCCL_SOURCE_DIR) + "/cub"); - } - else - { - arg_strings.push_back(config.cccl_include_path); - } - arg_strings.push_back("-internal-isystem"); - if (config.cccl_include_path.empty()) - { - arg_strings.push_back(std::string(CCCL_SOURCE_DIR) + "/thrust"); - } - else - { - arg_strings.push_back(config.cccl_include_path); - } + appendSystemIncludePaths(arg_strings, config); arg_strings.push_back("-internal-isystem"); arg_strings.push_back(config.cuda_toolkit_path + "/include"); arg_strings.push_back("-include"); arg_strings.push_back(config.hostjit_include_path + "/hostjit/cuda_minimal/__clang_cuda_runtime_wrapper.h"); - for (const auto& include_path : config.include_paths) - { - arg_strings.push_back("-I" + include_path); - } + appendIncludePaths(arg_strings, config); arg_strings.push_back("-D__HOSTJIT_DEVICE_COMPILATION__=1"); - arg_strings.push_back("-DNDEBUG"); - arg_strings.push_back("-DCCCL_DISABLE_CTK_COMPATIBILITY_CHECK"); - arg_strings.push_back("-D_CCCL_ENABLE_FREESTANDING=1"); - arg_strings.push_back("-DCCCL_DISABLE_NVTX=1"); - arg_strings.push_back("-DCCCL_DISABLE_EXCEPTIONS=1"); - - std::vector bitcode_files_to_link = config.device_bitcode_files; - - for (const auto& [macro_name, macro_value] : config.macro_definitions) - { - if (macro_value.empty()) - { - arg_strings.push_back("-D" + macro_name); - } - else - { - arg_strings.push_back("-D" + macro_name + "=" + macro_value); - } - } + arg_strings.push_back("-DNDEBUG"); + + std::vector bitcode_files_to_link = config.device_bitcode_files; + + appendMacroDefinitions(arg_strings, config); arg_strings.push_back("-fdeprecated-macro"); arg_strings.push_back("--offload-new-driver"); @@ -394,29 +911,11 @@ class CUDACompiler::Impl arg_strings.push_back("-H"); } + appendExtraClangArgs(arg_strings, config); arg_strings.push_back("-x"); arg_strings.push_back("cuda"); - // --- PCH: ensure device PCH exists --- - std::string device_pch_path; - if (config.enable_pch) - { - device_pch_path = getPCHPath("device", config.sm_version); - if (!std::filesystem::exists(device_pch_path)) - { - auto pch_src_path = getPCHSourcePath("device", config.sm_version); - std::string pch_diag; - if (!generatePCH(pch_preamble_source, pch_src_path, device_pch_path, arg_strings, pch_diag)) - { - diagnostics += "Device PCH generation failed: " + pch_diag + "\n"; - device_pch_path.clear(); - } - else if (config.verbose) - { - diagnostics += "Generated device PCH: " + device_pch_path + "\n"; - } - } - } + std::string device_pch_path = config.device_pch_path; std::vector args; for (const auto& arg : arg_strings) @@ -455,7 +954,7 @@ class CUDACompiler::Impl } // --- PCH: load cached device PCH --- - if (!device_pch_path.empty() && std::filesystem::exists(device_pch_path)) + if (!device_pch_path.empty() && pathExists(device_pch_path)) { invocation.getPreprocessorOpts().ImplicitPCHInclude = device_pch_path; } @@ -656,10 +1155,10 @@ class CUDACompiler::Impl pass.run(*mod); dest.flush(); - // Debug: when CCCL_HOSTJIT_DUMP_DIR is set, dump the optimized IR + // Debug: when LIBNVCC_DUMP_DIR is set, dump the optimized IR // and the PTX fed to ptxas, keyed by entry point name. Lets us // inspect codegen (register pressure, launch bounds) post-inline. - if (const char* dump_dir = std::getenv("CCCL_HOSTJIT_DUMP_DIR")) + if (const char* dump_dir = std::getenv("LIBNVCC_DUMP_DIR")) { std::error_code dec; std::filesystem::create_directories(dump_dir, dec); @@ -704,13 +1203,17 @@ class CUDACompiler::Impl return success; } - BitcodeResult compileToDeviceBitcode(const std::string& source_code, const CompilerConfig& config) + BitcodeResult compileToDeviceBitcode( + const std::string& source_code, + const std::string& input_name, + const std::string& output_bitcode_path, + const CompilerOptions& config) { BitcodeResult result; result.success = false; std::string error_msg; - if (!validateConfig(config, &error_msg)) + if (!validateOptions(config, &error_msg)) { result.diagnostics = "Configuration error: " + error_msg; return result; @@ -719,17 +1222,20 @@ class CUDACompiler::Impl initialize_llvm(); std::string temp_dir = - (std::filesystem::temp_directory_path() / ("hostjit_bc_" + std::to_string(reinterpret_cast(this)))) + (tempDirectoryPath() / ("hostjit_bc_" + std::to_string(reinterpret_cast(this)))) .string(); - std::filesystem::create_directories(temp_dir); + if (!createDirectories(temp_dir, result.diagnostics)) + { + return result; + } - std::string input_file = "input.cu"; + std::string input_file = input_name.empty() ? std::string("input.cu") : input_name; std::string source_file = temp_dir + "/" + input_file; std::string resource_dir = CLANG_RESOURCE_DIR; - // PTX version floor is 7.8 — CUB's instruction selection assumes - // features added in PTX 7.6 (e.g. `bmsk`), so anything older fails to - // assemble even on sm_75/sm_80. + // PTX version floor is 7.8. Some generated device code uses features + // added in PTX 7.6 (e.g. `bmsk`), so older versions can fail to assemble + // even on sm_75/sm_80. int ptx_version = 78; if (config.sm_version >= 120) { @@ -779,49 +1285,26 @@ class CUDACompiler::Impl arg_strings.push_back("-internal-isystem"); arg_strings.push_back( config.clang_headers_path.empty() ? std::string(CLANG_HEADERS_DIR) : config.clang_headers_path); - arg_strings.push_back("-internal-isystem"); - if (config.cccl_include_path.empty()) - { - arg_strings.push_back(std::string(CCCL_SOURCE_DIR) + "/libcudacxx/include"); - } - else - { - arg_strings.push_back(config.cccl_include_path); - } - arg_strings.push_back("-internal-isystem"); - if (config.cccl_include_path.empty()) - { - arg_strings.push_back(std::string(CCCL_SOURCE_DIR) + "/cub"); - } - else - { - arg_strings.push_back(config.cccl_include_path); - } - arg_strings.push_back("-internal-isystem"); - if (config.cccl_include_path.empty()) - { - arg_strings.push_back(std::string(CCCL_SOURCE_DIR) + "/thrust"); - } - else - { - arg_strings.push_back(config.cccl_include_path); - } + appendSystemIncludePaths(arg_strings, config); arg_strings.push_back("-internal-isystem"); arg_strings.push_back(config.cuda_toolkit_path + "/include"); arg_strings.push_back("-include"); arg_strings.push_back(config.hostjit_include_path + "/hostjit/cuda_minimal/__clang_cuda_runtime_wrapper.h"); + + appendIncludePaths(arg_strings, config); + arg_strings.push_back("-D__HOSTJIT_DEVICE_COMPILATION__=1"); arg_strings.push_back("-DNDEBUG"); - arg_strings.push_back("-DCCCL_DISABLE_CTK_COMPATIBILITY_CHECK"); - arg_strings.push_back("-D_CCCL_ENABLE_FREESTANDING=1"); - arg_strings.push_back("-DCCCL_DISABLE_NVTX=1"); - arg_strings.push_back("-DCCCL_DISABLE_EXCEPTIONS=1"); + + appendMacroDefinitions(arg_strings, config); + arg_strings.push_back("-fdeprecated-macro"); arg_strings.push_back("-fcxx-exceptions"); arg_strings.push_back("-fexceptions"); arg_strings.push_back("-O" + std::to_string(config.optimization_level)); arg_strings.push_back("-Wno-c++11-narrowing"); arg_strings.push_back("-std=c++17"); + appendExtraClangArgs(arg_strings, config); arg_strings.push_back("-x"); arg_strings.push_back("cuda"); @@ -847,10 +1330,15 @@ class CUDACompiler::Impl { diag_stream.flush(); result.diagnostics = diag_output + "\nFailed to create compiler invocation"; - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } + if (!config.device_pch_path.empty()) + { + invocation.getPreprocessorOpts().ImplicitPCHInclude = config.device_pch_path; + } + auto vfs = createVFSWithSource(source_code, source_file); compiler.createDiagnostics(diag_engine.getClient(), false); compiler.setVirtualFileSystem(vfs); @@ -865,11 +1353,18 @@ class CUDACompiler::Impl std::unique_ptr mod = emit_llvm_action.takeModule(); if (mod) { - llvm::SmallVector buffer; - llvm::raw_svector_ostream os(buffer); - llvm::WriteBitcodeToFile(*mod, os); - result.bitcode = std::string(buffer.begin(), buffer.end()); - result.success = true; + std::error_code ec; + llvm::raw_fd_ostream os(output_bitcode_path, ec, llvm::sys::fs::OF_None); + if (ec) + { + result.diagnostics = "Failed to open bitcode output file: " + output_bitcode_path + "\n"; + } + else + { + llvm::WriteBitcodeToFile(*mod, os); + os.flush(); + result.success = true; + } } else { @@ -879,7 +1374,7 @@ class CUDACompiler::Impl diag_stream.flush(); result.diagnostics += diag_output; - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } @@ -888,7 +1383,7 @@ class CUDACompiler::Impl const std::string& input_file, const std::string& fatbin_path, const std::string& output_obj, - const CompilerConfig& config, + const CompilerOptions& config, std::string& diagnostics) { std::string temp_dir = std::filesystem::path(output_obj).parent_path().string(); @@ -931,60 +1426,17 @@ class CUDACompiler::Impl arg_strings.push_back("-internal-isystem"); arg_strings.push_back( config.clang_headers_path.empty() ? std::string(CLANG_HEADERS_DIR) : config.clang_headers_path); - arg_strings.push_back("-internal-isystem"); - if (config.cccl_include_path.empty()) - { - arg_strings.push_back(std::string(CCCL_SOURCE_DIR) + "/libcudacxx/include"); - } - else - { - arg_strings.push_back(config.cccl_include_path); - } - arg_strings.push_back("-internal-isystem"); - if (config.cccl_include_path.empty()) - { - arg_strings.push_back(std::string(CCCL_SOURCE_DIR) + "/cub"); - } - else - { - arg_strings.push_back(config.cccl_include_path); - } - arg_strings.push_back("-internal-isystem"); - if (config.cccl_include_path.empty()) - { - arg_strings.push_back(std::string(CCCL_SOURCE_DIR) + "/thrust"); - } - else - { - arg_strings.push_back(config.cccl_include_path); - } + appendSystemIncludePaths(arg_strings, config); arg_strings.push_back("-internal-isystem"); arg_strings.push_back(config.cuda_toolkit_path + "/include"); arg_strings.push_back("-include"); arg_strings.push_back(config.hostjit_include_path + "/hostjit/cuda_minimal/__clang_cuda_runtime_wrapper.h"); - for (const auto& include_path : config.include_paths) - { - arg_strings.push_back("-I" + include_path); - } + appendIncludePaths(arg_strings, config); arg_strings.push_back("-DNDEBUG"); - arg_strings.push_back("-DCCCL_DISABLE_CTK_COMPATIBILITY_CHECK"); - arg_strings.push_back("-D_CCCL_ENABLE_FREESTANDING=1"); - arg_strings.push_back("-DCCCL_DISABLE_NVTX=1"); - arg_strings.push_back("-DCCCL_DISABLE_EXCEPTIONS=1"); - for (const auto& [macro_name, macro_value] : config.macro_definitions) - { - if (macro_value.empty()) - { - arg_strings.push_back("-D" + macro_name); - } - else - { - arg_strings.push_back("-D" + macro_name + "=" + macro_value); - } - } + appendMacroDefinitions(arg_strings, config); arg_strings.push_back("-fdeprecated-macro"); arg_strings.push_back("--offload-new-driver"); @@ -997,29 +1449,11 @@ class CUDACompiler::Impl arg_strings.push_back("-H"); } + appendExtraClangArgs(arg_strings, config); arg_strings.push_back("-x"); arg_strings.push_back("cuda"); - // --- PCH: ensure host PCH exists (before adding fatbin-specific args) --- - std::string host_pch_path; - if (config.enable_pch) - { - host_pch_path = getPCHPath("host", config.sm_version); - if (!std::filesystem::exists(host_pch_path)) - { - auto pch_src_path = getPCHSourcePath("host", config.sm_version); - std::string pch_diag; - if (!generatePCH(pch_preamble_source, pch_src_path, host_pch_path, arg_strings, pch_diag)) - { - diagnostics += "Host PCH generation failed: " + pch_diag + "\n"; - host_pch_path.clear(); - } - else if (config.verbose) - { - diagnostics += "Generated host PCH: " + host_pch_path + "\n"; - } - } - } + std::string host_pch_path = config.host_pch_path; // Add fatbin embedding (per-build, not part of PCH) arg_strings.push_back("-fcuda-include-gpubinary"); @@ -1062,7 +1496,7 @@ class CUDACompiler::Impl } // --- PCH: load cached host PCH --- - if (!host_pch_path.empty() && std::filesystem::exists(host_pch_path)) + if (!host_pch_path.empty() && pathExists(host_pch_path)) { invocation.getPreprocessorOpts().ImplicitPCHInclude = host_pch_path; } @@ -1104,15 +1538,19 @@ class CUDACompiler::Impl return success; } - CompilationResult - compileToObject(const std::string& source_code, const std::string& output_path, const CompilerConfig& config) + CompilationResult compileToObject( + const std::string& source_code, + const std::string& input_name, + const std::string& output_path, + const std::string& output_cubin_path, + const CompilerOptions& config) { CompilationResult result; result.success = false; result.object_file_path = output_path; std::string error_msg; - if (!validateConfig(config, &error_msg)) + if (!validateOptions(config, &error_msg)) { result.diagnostics = "Configuration error: " + error_msg; return result; @@ -1121,11 +1559,14 @@ class CUDACompiler::Impl initialize_llvm(); std::string temp_dir = - (std::filesystem::temp_directory_path() / ("hostjit_" + std::to_string(reinterpret_cast(this)))) + (tempDirectoryPath() / ("hostjit_" + std::to_string(reinterpret_cast(this)))) .string(); - std::filesystem::create_directories(temp_dir); + if (!createDirectories(temp_dir, result.diagnostics)) + { + return result; + } - std::string input_file = "input.cu"; + std::string input_file = input_name.empty() ? std::string("input.cu") : input_name; std::string ptx_file = temp_dir + "/device.ptx"; std::string fatbin_file = temp_dir + "/device.fatbin"; @@ -1137,7 +1578,7 @@ class CUDACompiler::Impl if (!compileDeviceToPTX(source_code, input_file, ptx_file, config, result.diagnostics)) { result.diagnostics += "\nDevice compilation failed"; - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } @@ -1155,7 +1596,7 @@ class CUDACompiler::Impl if (ptx_data.empty()) { result.diagnostics += "\nFailed to read ptx file"; - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } if (ptx_data.back() != '\0') @@ -1186,7 +1627,12 @@ class CUDACompiler::Impl if (jlr != NVJITLINK_SUCCESS) { result.diagnostics += "\nnvJitLinkCreate failed (error " + std::to_string(static_cast(jlr)) + ")"; - std::filesystem::remove_all(temp_dir); + result.diagnostics += "\nnvJitLink options:"; + for (const auto& option : jitlink_option_strs) + { + result.diagnostics += " " + option; + } + removeAll(temp_dir); return result; } @@ -1203,7 +1649,7 @@ class CUDACompiler::Impl } result.diagnostics += "\nnvJitLinkAddData failed"; nvJitLinkDestroy(&jitlink_handle); - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } @@ -1233,7 +1679,7 @@ class CUDACompiler::Impl } result.diagnostics += "\nnvJitLinkAddData(LTOIR) failed for " + ltoir_path; nvJitLinkDestroy(&jitlink_handle); - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } } @@ -1251,7 +1697,7 @@ class CUDACompiler::Impl } result.diagnostics += "\nnvJitLinkComplete failed"; nvJitLinkDestroy(&jitlink_handle); - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } @@ -1261,8 +1707,17 @@ class CUDACompiler::Impl nvJitLinkGetLinkedCubin(jitlink_handle, cubin_data.data()); nvJitLinkDestroy(&jitlink_handle); - // Store cubin in the result for inspection - result.cubin = cubin_data; + if (!output_cubin_path.empty()) + { + std::ofstream cubin_out(output_cubin_path, std::ios::binary); + cubin_out.write(cubin_data.data(), static_cast(cubin_data.size())); + if (!cubin_out) + { + result.diagnostics += "\nFailed to write cubin file"; + removeAll(temp_dir); + return result; + } + } std::string arch = std::to_string(config.sm_version); const char* fatbin_options[] = {"-64", "-cuda"}; @@ -1271,7 +1726,7 @@ class CUDACompiler::Impl if (fbr != NVFATBIN_SUCCESS) { result.diagnostics += std::string("\nnvFatbinCreate failed: ") + nvFatbinGetErrorString(fbr); - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } @@ -1280,7 +1735,7 @@ class CUDACompiler::Impl { result.diagnostics += std::string("\nnvFatbinAddCubin failed: ") + nvFatbinGetErrorString(fbr); nvFatbinDestroy(&fatbin_handle); - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } @@ -1289,7 +1744,7 @@ class CUDACompiler::Impl { result.diagnostics += std::string("\nnvFatbinAddPTX failed: ") + nvFatbinGetErrorString(fbr); nvFatbinDestroy(&fatbin_handle); - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } @@ -1299,7 +1754,7 @@ class CUDACompiler::Impl { result.diagnostics += std::string("\nnvFatbinSize failed: ") + nvFatbinGetErrorString(fbr); nvFatbinDestroy(&fatbin_handle); - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } @@ -1309,7 +1764,7 @@ class CUDACompiler::Impl if (fbr != NVFATBIN_SUCCESS) { result.diagnostics += std::string("\nnvFatbinGet failed: ") + nvFatbinGetErrorString(fbr); - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } @@ -1318,7 +1773,7 @@ class CUDACompiler::Impl if (!out) { result.diagnostics += "\nFailed to write fatbin file"; - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } } @@ -1331,17 +1786,164 @@ class CUDACompiler::Impl if (!compileHostCode(source_code, input_file, fatbin_file, output_path, config, result.diagnostics)) { result.diagnostics += "\nHost compilation failed"; - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); return result; } - std::filesystem::remove_all(temp_dir); + removeAll(temp_dir); result.success = true; return result; } + bool createPCH( + const std::string& source_code, + libnvccPCHKind kind, + const std::string& pch_source_path, + const std::string& pch_output_path, + const CompilerOptions& config, + std::string& diagnostics) + { + std::string error_msg; + if (!validateOptions(config, &error_msg)) + { + diagnostics = "Configuration error: " + error_msg; + return false; + } + + initialize_llvm(); + + std::string resource_dir = CLANG_RESOURCE_DIR; + std::vector arg_strings; + arg_strings.push_back(pch_source_path); + + if (kind == LIBNVCC_PCH_DEVICE) + { + int ptx_version = 78; + if (config.sm_version >= 120) + { + ptx_version = 87; + } + else if (config.sm_version >= 100) + { + ptx_version = 85; + } + else if (config.sm_version >= 90) + { + ptx_version = 80; + } + + arg_strings.push_back("-triple"); + arg_strings.push_back("nvptx64-nvidia-cuda"); + arg_strings.push_back("-aux-triple"); +#ifdef _WIN32 + arg_strings.push_back("x86_64-pc-windows-msvc"); +#else + arg_strings.push_back("x86_64-pc-linux-gnu"); +#endif + arg_strings.push_back("-S"); + arg_strings.push_back("-aux-target-cpu"); + arg_strings.push_back("x86-64"); + arg_strings.push_back("-fcuda-is-device"); + arg_strings.push_back("-fcuda-allow-variadic-functions"); +#ifdef _WIN32 + arg_strings.push_back("-fms-compatibility"); + arg_strings.push_back("-fms-compatibility-version=19.40"); +#else + arg_strings.push_back("-fgnuc-version=4.2.1"); +#endif + arg_strings.push_back("-mlink-builtin-bitcode"); + arg_strings.push_back(config.cuda_toolkit_path + "/nvvm/libdevice/libdevice.10.bc"); + arg_strings.push_back("-target-sdk-version=" CUDA_SDK_VERSION); + arg_strings.push_back("-target-cpu"); + arg_strings.push_back("sm_" + std::to_string(config.sm_version)); + arg_strings.push_back("-target-feature"); + arg_strings.push_back("+ptx" + std::to_string(ptx_version)); + } + else if (kind == LIBNVCC_PCH_HOST) + { + arg_strings.push_back("-triple"); +#ifdef _WIN32 + arg_strings.push_back("x86_64-pc-windows-msvc"); +#else + arg_strings.push_back("x86_64-pc-linux-gnu"); +#endif + arg_strings.push_back("-aux-triple"); + arg_strings.push_back("nvptx64-nvidia-cuda"); + arg_strings.push_back("-target-sdk-version=" CUDA_SDK_VERSION); + arg_strings.push_back("-emit-obj"); + arg_strings.push_back("-target-cpu"); + arg_strings.push_back("x86-64"); + arg_strings.push_back("-fcuda-allow-variadic-functions"); +#ifdef _WIN32 + arg_strings.push_back("-fms-compatibility"); + arg_strings.push_back("-fms-compatibility-version=19.40"); +#else + arg_strings.push_back("-fgnuc-version=4.2.1"); +#endif + arg_strings.push_back("-mrelocation-model"); + arg_strings.push_back("pic"); + arg_strings.push_back("-pic-level"); + arg_strings.push_back("2"); + } + else + { + diagnostics = "Invalid PCH kind"; + return false; + } + + arg_strings.push_back("-resource-dir"); + arg_strings.push_back(resource_dir); + arg_strings.push_back("-internal-isystem"); + arg_strings.push_back(config.hostjit_include_path + "/hostjit/cuda_minimal/stubs"); + arg_strings.push_back("-internal-isystem"); + arg_strings.push_back( + config.clang_headers_path.empty() ? std::string(CLANG_HEADERS_DIR) : config.clang_headers_path); + appendSystemIncludePaths(arg_strings, config); + arg_strings.push_back("-internal-isystem"); + arg_strings.push_back(config.cuda_toolkit_path + "/include"); + arg_strings.push_back("-include"); + arg_strings.push_back(config.hostjit_include_path + "/hostjit/cuda_minimal/__clang_cuda_runtime_wrapper.h"); + + appendIncludePaths(arg_strings, config); + + if (kind == LIBNVCC_PCH_DEVICE) + { + arg_strings.push_back("-D__HOSTJIT_DEVICE_COMPILATION__=1"); + } + arg_strings.push_back("-DNDEBUG"); + + appendMacroDefinitions(arg_strings, config); + + arg_strings.push_back("-fdeprecated-macro"); + if (kind == LIBNVCC_PCH_DEVICE) + { + arg_strings.push_back("--offload-new-driver"); + arg_strings.push_back("-fskip-odr-check-in-gmf"); + arg_strings.push_back("-fcxx-exceptions"); + arg_strings.push_back("-fexceptions"); + } + else + { + arg_strings.push_back("--offload-new-driver"); + arg_strings.push_back("-fskip-odr-check-in-gmf"); + } + arg_strings.push_back("-O" + std::to_string(config.optimization_level)); + arg_strings.push_back("-std=c++17"); + + if (config.trace_includes) + { + arg_strings.push_back("-H"); + } + + appendExtraClangArgs(arg_strings, config); + arg_strings.push_back("-x"); + arg_strings.push_back("cuda"); + + return generatePCH(source_code, pch_source_path, pch_output_path, arg_strings, diagnostics); + } + LinkResult linkToSharedLibrary( - const std::vector& object_files, const std::string& output_path, const CompilerConfig& config) + const std::vector& object_files, const std::string& output_path, const CompilerOptions& config) { LinkResult result; result.success = false; @@ -1517,21 +2119,18 @@ class CUDACompiler::Impl bool found_cudart = false; for (const auto& lib_path : config.library_paths) { - namespace fs = std::filesystem; - if (!fs::exists(lib_path)) + if (!pathExists(lib_path)) { continue; } - for (const auto& entry : fs::directory_iterator(lib_path)) - { + forEachDirectoryEntry(lib_path, [&](const std::filesystem::directory_entry& entry) { auto fname = entry.path().filename().string(); - if (fname.starts_with("libcudart.so")) + if (!found_cudart && fname.starts_with("libcudart.so")) { arg_strings.push_back(entry.path().string()); found_cudart = true; - break; } - } + }); if (found_cudart) { break; @@ -1582,29 +2181,244 @@ class CUDACompiler::Impl return result; } }; +} // namespace libnvcc + +struct libnvccProgram_st +{ + std::string source; + std::string name; + std::string log; + libnvcc::CompilerImpl compiler; +}; + +namespace +{ +void setProgramLog(libnvccProgram prog, std::string log) +{ + if (prog) + { + prog->log = std::move(log); + } +} + +bool parseProgramOptions(libnvccProgram prog, int num_options, const char* const* raw_options, libnvcc::CompilerOptions& options) +{ + std::string error; + if (!libnvcc::parseOptions(num_options, raw_options, options, error)) + { + setProgramLog(prog, "Option error: " + error); + return false; + } + return true; +} + +} // anonymous namespace + +extern "C" const char* libnvccGetErrorString(libnvccResult result) +{ + switch (result) + { + case LIBNVCC_SUCCESS: + return "LIBNVCC_SUCCESS"; + case LIBNVCC_ERROR_OUT_OF_MEMORY: + return "LIBNVCC_ERROR_OUT_OF_MEMORY"; + case LIBNVCC_ERROR_PROGRAM_CREATION_FAILURE: + return "LIBNVCC_ERROR_PROGRAM_CREATION_FAILURE"; + case LIBNVCC_ERROR_INVALID_INPUT: + return "LIBNVCC_ERROR_INVALID_INPUT"; + case LIBNVCC_ERROR_INVALID_PROGRAM: + return "LIBNVCC_ERROR_INVALID_PROGRAM"; + case LIBNVCC_ERROR_INVALID_OPTION: + return "LIBNVCC_ERROR_INVALID_OPTION"; + case LIBNVCC_ERROR_COMPILATION: + return "LIBNVCC_ERROR_COMPILATION"; + case LIBNVCC_ERROR_LINKING: + return "LIBNVCC_ERROR_LINKING"; + case LIBNVCC_ERROR_PCH_CREATE: + return "LIBNVCC_ERROR_PCH_CREATE"; + case LIBNVCC_ERROR_INTERNAL_ERROR: + return "LIBNVCC_ERROR_INTERNAL_ERROR"; + } + return "LIBNVCC_ERROR_UNKNOWN"; +} + +extern "C" libnvccResult libnvccCreateProgram(libnvccProgram* prog, const char* src, const char* name) +{ + if (!prog || !src) + { + return LIBNVCC_ERROR_INVALID_INPUT; + } + *prog = nullptr; + + auto* program = new libnvccProgram_st; + program->source = src; + program->name = (name && name[0]) ? name : "input.cu"; + *prog = program; + return LIBNVCC_SUCCESS; +} + +extern "C" libnvccResult libnvccDestroyProgram(libnvccProgram* prog) +{ + if (!prog || !*prog) + { + return LIBNVCC_SUCCESS; + } + delete *prog; + *prog = nullptr; + return LIBNVCC_SUCCESS; +} + +extern "C" libnvccResult libnvccCompileProgramToDeviceBitcode( + libnvccProgram prog, + const char* outputBitcodePath, + int numOptions, + const char* const* options) +{ + if (!prog) + { + return LIBNVCC_ERROR_INVALID_PROGRAM; + } + if (!outputBitcodePath || outputBitcodePath[0] == '\0') + { + setProgramLog(prog, "outputBitcodePath must be non-empty"); + return LIBNVCC_ERROR_INVALID_INPUT; + } + + libnvcc::CompilerOptions parsed_options; + if (!parseProgramOptions(prog, numOptions, options, parsed_options)) + { + return LIBNVCC_ERROR_INVALID_OPTION; + } + + auto result = prog->compiler.compileToDeviceBitcode(prog->source, prog->name, outputBitcodePath, parsed_options); + setProgramLog(prog, result.diagnostics); + return result.success ? LIBNVCC_SUCCESS : LIBNVCC_ERROR_COMPILATION; +} + +extern "C" libnvccResult libnvccCompileProgramToObject( + libnvccProgram prog, + const char* outputObjectPath, + const char* outputCubinPath, + int numOptions, + const char* const* options) +{ + if (!prog) + { + return LIBNVCC_ERROR_INVALID_PROGRAM; + } + if (!outputObjectPath || outputObjectPath[0] == '\0') + { + setProgramLog(prog, "outputObjectPath must be non-empty"); + return LIBNVCC_ERROR_INVALID_INPUT; + } + + libnvcc::CompilerOptions parsed_options; + if (!parseProgramOptions(prog, numOptions, options, parsed_options)) + { + return LIBNVCC_ERROR_INVALID_OPTION; + } + + const std::string cubin_path = outputCubinPath ? outputCubinPath : ""; + auto result = prog->compiler.compileToObject(prog->source, prog->name, outputObjectPath, cubin_path, parsed_options); + setProgramLog(prog, result.diagnostics); + return result.success ? LIBNVCC_SUCCESS : LIBNVCC_ERROR_COMPILATION; +} -CUDACompiler::CUDACompiler() - : impl_(new Impl()) -{} -CUDACompiler::~CUDACompiler() +extern "C" libnvccResult libnvccLinkToSharedLibrary( + libnvccProgram prog, + int numObjectFiles, + const char* const* objectFiles, + const char* outputLibraryPath, + int numOptions, + const char* const* options) { - delete impl_; + if (!prog) + { + return LIBNVCC_ERROR_INVALID_PROGRAM; + } + if (numObjectFiles < 0 || (numObjectFiles > 0 && !objectFiles) || !outputLibraryPath || outputLibraryPath[0] == '\0') + { + setProgramLog(prog, "Invalid link input"); + return LIBNVCC_ERROR_INVALID_INPUT; + } + + libnvcc::CompilerOptions parsed_options; + if (!parseProgramOptions(prog, numOptions, options, parsed_options)) + { + return LIBNVCC_ERROR_INVALID_OPTION; + } + + std::vector object_files; + object_files.reserve(static_cast(numObjectFiles)); + for (int i = 0; i < numObjectFiles; ++i) + { + if (!objectFiles[i] || objectFiles[i][0] == '\0') + { + setProgramLog(prog, "Object file path must be non-empty"); + return LIBNVCC_ERROR_INVALID_INPUT; + } + object_files.emplace_back(objectFiles[i]); + } + + auto result = prog->compiler.linkToSharedLibrary(object_files, outputLibraryPath, parsed_options); + setProgramLog(prog, result.diagnostics); + return result.success ? LIBNVCC_SUCCESS : LIBNVCC_ERROR_LINKING; } -BitcodeResult CUDACompiler::compileToDeviceBitcode(const std::string& source_code, const CompilerConfig& config) +extern "C" libnvccResult libnvccCreatePCH( + libnvccProgram prog, + libnvccPCHKind kind, + const char* pchSourcePath, + const char* pchOutputPath, + int numOptions, + const char* const* options) { - return impl_->compileToDeviceBitcode(source_code, config); + if (!prog) + { + return LIBNVCC_ERROR_INVALID_PROGRAM; + } + if (!pchSourcePath || pchSourcePath[0] == '\0' || !pchOutputPath || pchOutputPath[0] == '\0') + { + setProgramLog(prog, "PCH source and output paths must be non-empty"); + return LIBNVCC_ERROR_INVALID_INPUT; + } + + libnvcc::CompilerOptions parsed_options; + if (!parseProgramOptions(prog, numOptions, options, parsed_options)) + { + return LIBNVCC_ERROR_INVALID_OPTION; + } + + std::string diagnostics; + bool success = prog->compiler.createPCH(prog->source, kind, pchSourcePath, pchOutputPath, parsed_options, diagnostics); + setProgramLog(prog, diagnostics); + return success ? LIBNVCC_SUCCESS : LIBNVCC_ERROR_PCH_CREATE; } -CompilationResult CUDACompiler::compileToObject( - const std::string& source_code, const std::string& output_path, const CompilerConfig& config) +extern "C" libnvccResult libnvccGetProgramLogSize(libnvccProgram prog, size_t* logSizeRet) { - return impl_->compileToObject(source_code, output_path, config); + if (!prog) + { + return LIBNVCC_ERROR_INVALID_PROGRAM; + } + if (!logSizeRet) + { + return LIBNVCC_ERROR_INVALID_INPUT; + } + *logSizeRet = prog->log.size() + 1; + return LIBNVCC_SUCCESS; } -LinkResult CUDACompiler::linkToSharedLibrary( - const std::vector& object_files, const std::string& output_path, const CompilerConfig& config) +extern "C" libnvccResult libnvccGetProgramLog(libnvccProgram prog, char* log) { - return impl_->linkToSharedLibrary(object_files, output_path, config); + if (!prog) + { + return LIBNVCC_ERROR_INVALID_PROGRAM; + } + if (!log) + { + return LIBNVCC_ERROR_INVALID_INPUT; + } + std::memcpy(log, prog->log.c_str(), prog->log.size() + 1); + return LIBNVCC_SUCCESS; } -} // namespace hostjit diff --git a/c/parallel.v2/src/hostjit/libnvcc/include/libnvcc/libnvcc.h b/c/parallel.v2/src/hostjit/libnvcc/include/libnvcc/libnvcc.h new file mode 100644 index 00000000000..6292d81de35 --- /dev/null +++ b/c/parallel.v2/src/hostjit/libnvcc/include/libnvcc/libnvcc.h @@ -0,0 +1,203 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief Result codes returned by the libnvcc C API. + * + * Every libnvcc API function returns one of these values. Use + * libnvccGetErrorString to obtain a stable string for logging or diagnostics. + * Detailed compiler, linker, and tool diagnostics are stored on the program + * handle and can be queried with libnvccGetProgramLogSize and + * libnvccGetProgramLog. + */ +typedef enum libnvccResult +{ + LIBNVCC_SUCCESS = 0, + LIBNVCC_ERROR_OUT_OF_MEMORY, + LIBNVCC_ERROR_PROGRAM_CREATION_FAILURE, + LIBNVCC_ERROR_INVALID_INPUT, + LIBNVCC_ERROR_INVALID_PROGRAM, + LIBNVCC_ERROR_INVALID_OPTION, + LIBNVCC_ERROR_COMPILATION, + LIBNVCC_ERROR_LINKING, + LIBNVCC_ERROR_PCH_CREATE, + LIBNVCC_ERROR_INTERNAL_ERROR +} libnvccResult; + +/** + * \brief Selects which Clang compilation mode is used to create a PCH. + * + * `LIBNVCC_PCH_DEVICE` creates a device-side PCH that can later be supplied to + * libnvccCompileProgramToObject or libnvccCompileProgramToDeviceBitcode with + * `--device-pch=`. `LIBNVCC_PCH_HOST` creates a host-side PCH that can + * later be supplied to libnvccCompileProgramToObject with `--host-pch=`. + */ +typedef enum libnvccPCHKind +{ + LIBNVCC_PCH_DEVICE = 0, + LIBNVCC_PCH_HOST = 1 +} libnvccPCHKind; + +/** + * \brief Opaque libnvcc program handle. + * + * A program owns the CUDA source string supplied to libnvccCreateProgram and + * stores diagnostics from the most recent libnvcc operation involving that + * program. Destroy it with libnvccDestroyProgram when no further compilation, + * PCH creation, linking, or log retrieval is required. + */ +typedef struct libnvccProgram_st* libnvccProgram; + +/** + * \brief Return a static string describing a libnvcc result code. + * + * The returned pointer is owned by libnvcc and remains valid for the lifetime + * of the process. Unknown result codes return `"LIBNVCC_ERROR_UNKNOWN"`. + */ +const char* libnvccGetErrorString(libnvccResult result); + +/** + * \brief Create a libnvcc program from a CUDA source string. + * + * \param prog Output location for the new program handle. + * \param src NUL-terminated CUDA C++ source string. libnvcc copies this string. + * \param name Optional logical source name used in diagnostics. When NULL or + * empty, libnvcc uses `"input.cu"`. + * + * The program does not perform compilation during creation. Compile, link, and + * PCH functions accept command-line options independently, similar to NVRTC. + */ +libnvccResult libnvccCreateProgram(libnvccProgram* prog, const char* src, const char* name); + +/** + * \brief Destroy a libnvcc program. + * + * \param prog Address of a program handle previously returned by + * libnvccCreateProgram. On success, `*prog` is set to NULL. Passing NULL or a + * pointer to NULL is accepted and returns LIBNVCC_SUCCESS. + */ +libnvccResult libnvccDestroyProgram(libnvccProgram* prog); + +/** + * \brief Compile a program's device source to LLVM bitcode and write it to a file. + * + * \param prog Program handle created by libnvccCreateProgram. + * \param outputBitcodePath Destination path for the generated LLVM bitcode. + * \param numOptions Number of entries in `options`. + * \param options Array of command-line option strings. The array may be NULL + * when `numOptions` is zero. + * + * Supported options: + * `--cuda-path=`, `--hostjit-include-path=`, + * `--clang-headers-path=`, `-isystem `, `-isystem`, + * `--system-include-path=`, `-I`, `--include-path=`, `-L`, + * `--library-path=`, `--device-bitcode=`, + * `--device-ltoir=`, `-D[=]`, + * `--define-macro=[=]`, `--gpu-architecture=sm_`, + * `--gpu-architecture=`, `-O`, `--optimization-level=`, + * `--debug`, `--verbose`, `--trace-includes`, `--keep-artifacts`, + * `--entry-point=`, `--device-pch=`, `--host-pch=`, + * `-XClang `, and `-XClang=`. + * + * This function uses only file paths for extra LLVM inputs. Source code is the + * only in-memory input accepted by libnvcc. + */ +libnvccResult libnvccCompileProgramToDeviceBitcode( + libnvccProgram prog, + const char* outputBitcodePath, + int numOptions, + const char* const* options); + +/** + * \brief Compile a program to a host object file and optionally a cubin file. + * + * \param prog Program handle created by libnvccCreateProgram. + * \param outputObjectPath Destination path for the generated host object file. + * \param outputCubinPath Optional destination path for the linked device cubin. + * Pass NULL or an empty string when the cubin is not needed. + * \param numOptions Number of entries in `options`. + * \param options Array of command-line option strings. The array may be NULL + * when `numOptions` is zero. + * + * Device LLVM bitcode and LTOIR inputs must be supplied with + * `--device-bitcode=` and `--device-ltoir=`. PCH files are used + * only when explicit `--device-pch=` or `--host-pch=` options are + * present; libnvcc does not create or cache them implicitly. + */ +libnvccResult libnvccCompileProgramToObject( + libnvccProgram prog, + const char* outputObjectPath, + const char* outputCubinPath, + int numOptions, + const char* const* options); + +/** + * \brief Link object files into a shared library. + * + * \param prog Program handle used to store diagnostics from the link step. + * \param numObjectFiles Number of entries in `objectFiles`. + * \param objectFiles Array of object file paths to link. + * \param outputLibraryPath Destination path for the linked shared library. + * \param numOptions Number of entries in `options`. + * \param options Array of command-line option strings. Link-time options use + * the same option parser as compile-time options; currently `--cuda-path`, + * `-L`, `--library-path`, and `--verbose` affect linking. + */ +libnvccResult libnvccLinkToSharedLibrary( + libnvccProgram prog, + int numObjectFiles, + const char* const* objectFiles, + const char* outputLibraryPath, + int numOptions, + const char* const* options); + +/** + * \brief Create a Clang PCH file for a program. + * + * \param prog Program handle whose source string is used as the PCH input. + * \param kind Selects device or host compilation mode. + * \param pchSourcePath Stable source path to write before invoking Clang. + * Clang records this path in the PCH, so callers should use a cache-stable + * location rather than a per-build temporary path. + * \param pchOutputPath Destination path for the generated PCH file. + * \param numOptions Number of entries in `options`. + * \param options Array of command-line option strings. PCH creation uses the + * same include, macro, architecture, optimization, and `-XClang` options as + * compilation. + * + * libnvcc creates exactly the requested PCH file. It does not decide cache + * locations, check freshness, or enable PCH use for later compilations. + */ +libnvccResult libnvccCreatePCH( + libnvccProgram prog, + libnvccPCHKind kind, + const char* pchSourcePath, + const char* pchOutputPath, + int numOptions, + const char* const* options); + +/** + * \brief Get the byte size of the program diagnostic log. + * + * The returned size includes the trailing NUL byte. Warnings and informational + * messages may be present even when the preceding operation returned + * LIBNVCC_SUCCESS. + */ +libnvccResult libnvccGetProgramLogSize(libnvccProgram prog, size_t* logSizeRet); + +/** + * \brief Copy the program diagnostic log into caller-provided storage. + * + * The caller must allocate at least the number of bytes returned by + * libnvccGetProgramLogSize. The copied log is NUL-terminated. + */ +libnvccResult libnvccGetProgramLog(libnvccProgram prog, char* log); + +#ifdef __cplusplus +} +#endif diff --git a/python/cuda_cccl/CMakeLists.txt b/python/cuda_cccl/CMakeLists.txt index 09044f19442..6e02baf590d 100644 --- a/python/cuda_cccl/CMakeLists.txt +++ b/python/cuda_cccl/CMakeLists.txt @@ -75,8 +75,16 @@ set(CMAKE_INSTALL_INCLUDEDIR "${old_includedir}") # pop file(MAKE_DIRECTORY "cuda/compute/${CUDA_VERSION_DIR}/cccl") # Install version-specific binaries +set(_cccl_c_parallel_install_targets ${_cccl_c_parallel_target}) +if (CCCL_PYTHON_USE_V2) + list( + APPEND + _cccl_c_parallel_install_targets + libnvcc + ) +endif() install( - TARGETS ${_cccl_c_parallel_target} + TARGETS ${_cccl_c_parallel_install_targets} DESTINATION cuda/compute/${CUDA_VERSION_DIR}/cccl ) From b24fb0a34a49ea6b26b955381d048c08975535bc Mon Sep 17 00:00:00 2001 From: Ambrose Leeb Date: Wed, 24 Jun 2026 18:16:30 +0000 Subject: [PATCH 02/10] clang-format --- .../src/hostjit/libnvcc/compiler.cpp | 31 ++++++++----------- .../hostjit/libnvcc/include/libnvcc/libnvcc.h | 5 +-- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp b/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp index 529d1e8cb2b..d41022ea142 100644 --- a/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp +++ b/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp @@ -1222,8 +1222,7 @@ class CompilerImpl initialize_llvm(); std::string temp_dir = - (tempDirectoryPath() / ("hostjit_bc_" + std::to_string(reinterpret_cast(this)))) - .string(); + (tempDirectoryPath() / ("hostjit_bc_" + std::to_string(reinterpret_cast(this)))).string(); if (!createDirectories(temp_dir, result.diagnostics)) { return result; @@ -1559,8 +1558,7 @@ class CompilerImpl initialize_llvm(); std::string temp_dir = - (tempDirectoryPath() / ("hostjit_" + std::to_string(reinterpret_cast(this)))) - .string(); + (tempDirectoryPath() / ("hostjit_" + std::to_string(reinterpret_cast(this)))).string(); if (!createDirectories(temp_dir, result.diagnostics)) { return result; @@ -1795,13 +1793,12 @@ class CompilerImpl return result; } - bool createPCH( - const std::string& source_code, - libnvccPCHKind kind, - const std::string& pch_source_path, - const std::string& pch_output_path, - const CompilerOptions& config, - std::string& diagnostics) + bool createPCH(const std::string& source_code, + libnvccPCHKind kind, + const std::string& pch_source_path, + const std::string& pch_output_path, + const CompilerOptions& config, + std::string& diagnostics) { std::string error_msg; if (!validateOptions(config, &error_msg)) @@ -2201,7 +2198,8 @@ void setProgramLog(libnvccProgram prog, std::string log) } } -bool parseProgramOptions(libnvccProgram prog, int num_options, const char* const* raw_options, libnvcc::CompilerOptions& options) +bool parseProgramOptions( + libnvccProgram prog, int num_options, const char* const* raw_options, libnvcc::CompilerOptions& options) { std::string error; if (!libnvcc::parseOptions(num_options, raw_options, options, error)) @@ -2211,7 +2209,6 @@ bool parseProgramOptions(libnvccProgram prog, int num_options, const char* const } return true; } - } // anonymous namespace extern "C" const char* libnvccGetErrorString(libnvccResult result) @@ -2269,10 +2266,7 @@ extern "C" libnvccResult libnvccDestroyProgram(libnvccProgram* prog) } extern "C" libnvccResult libnvccCompileProgramToDeviceBitcode( - libnvccProgram prog, - const char* outputBitcodePath, - int numOptions, - const char* const* options) + libnvccProgram prog, const char* outputBitcodePath, int numOptions, const char* const* options) { if (!prog) { @@ -2390,7 +2384,8 @@ extern "C" libnvccResult libnvccCreatePCH( } std::string diagnostics; - bool success = prog->compiler.createPCH(prog->source, kind, pchSourcePath, pchOutputPath, parsed_options, diagnostics); + bool success = + prog->compiler.createPCH(prog->source, kind, pchSourcePath, pchOutputPath, parsed_options, diagnostics); setProgramLog(prog, diagnostics); return success ? LIBNVCC_SUCCESS : LIBNVCC_ERROR_PCH_CREATE; } diff --git a/c/parallel.v2/src/hostjit/libnvcc/include/libnvcc/libnvcc.h b/c/parallel.v2/src/hostjit/libnvcc/include/libnvcc/libnvcc.h index 6292d81de35..4451be30366 100644 --- a/c/parallel.v2/src/hostjit/libnvcc/include/libnvcc/libnvcc.h +++ b/c/parallel.v2/src/hostjit/libnvcc/include/libnvcc/libnvcc.h @@ -108,10 +108,7 @@ libnvccResult libnvccDestroyProgram(libnvccProgram* prog); * only in-memory input accepted by libnvcc. */ libnvccResult libnvccCompileProgramToDeviceBitcode( - libnvccProgram prog, - const char* outputBitcodePath, - int numOptions, - const char* const* options); + libnvccProgram prog, const char* outputBitcodePath, int numOptions, const char* const* options); /** * \brief Compile a program to a host object file and optionally a cubin file. From 3940bed8ea1b0097f7387ead624c90dbf12fdb47 Mon Sep 17 00:00:00 2001 From: Ambrose Leeb Date: Thu, 25 Jun 2026 16:04:45 +0000 Subject: [PATCH 03/10] Address some review comments --- c/parallel.v2/src/hostjit/codegen/bitcode.cpp | 5 +--- .../src/hostjit/include/hostjit/compiler.hpp | 22 +++++++++----- .../src/hostjit/include/hostjit/config.hpp | 8 ++--- .../src/hostjit/libnvcc/CMakeLists.txt | 29 +++++++++++-------- .../src/hostjit/libnvcc/compiler.cpp | 25 ++++++++++++---- 5 files changed, 56 insertions(+), 33 deletions(-) diff --git a/c/parallel.v2/src/hostjit/codegen/bitcode.cpp b/c/parallel.v2/src/hostjit/codegen/bitcode.cpp index 438a6b0844d..873f9f5a86d 100644 --- a/c/parallel.v2/src/hostjit/codegen/bitcode.cpp +++ b/c/parallel.v2/src/hostjit/codegen/bitcode.cpp @@ -118,10 +118,7 @@ bool BitcodeCollector::compile_and_add(const char* source, size_t source_size, c } auto result = libnvccCompileProgramToDeviceBitcode( - program.program, - path.c_str(), - static_cast(option_ptrs.size()), - option_ptrs.empty() ? nullptr : option_ptrs.data()); + program.program, path.c_str(), static_cast(option_ptrs.size()), option_ptrs.data()); if (result != LIBNVCC_SUCCESS) { auto log = hostjit::detail::get_libnvcc_program_log(program.program); diff --git a/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp b/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp index 95576044518..f6bd14e3710 100644 --- a/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp +++ b/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -11,6 +12,10 @@ struct LibnvccProgramGuard { libnvccProgram program = nullptr; + LibnvccProgramGuard() = default; + LibnvccProgramGuard(const LibnvccProgramGuard&) = delete; + LibnvccProgramGuard& operator=(const LibnvccProgramGuard&) = delete; + ~LibnvccProgramGuard() { libnvccDestroyProgram(&program); @@ -31,19 +36,22 @@ inline std::vector make_libnvcc_option_ptrs(const std::vector 0 && "Log size should include NUL terminator"); + if (log_size == 1) { return {}; } - if (!log.empty() && log.back() == '\0') - { - log.pop_back(); - } + + std::string log(log_size, '\0'); + auto res = libnvccGetProgramLog(program, log.data()); + assert(res == LIBNVCC_SUCCESS && "Copying the log failed even though size calculation succeeded?"); + assert(log.back() == '\0' && "libnvccGetProgramLog() should append a NUL character"); + log.pop_back(); // Drop the extra NUL. return log; } } // namespace hostjit::detail diff --git a/c/parallel.v2/src/hostjit/include/hostjit/config.hpp b/c/parallel.v2/src/hostjit/include/hostjit/config.hpp index f02ce738819..11220fc4f74 100644 --- a/c/parallel.v2/src/hostjit/include/hostjit/config.hpp +++ b/c/parallel.v2/src/hostjit/include/hostjit/config.hpp @@ -12,6 +12,9 @@ struct CompilerConfig std::string hostjit_include_path; // Path to hostjit include directory (for minimal CUDA runtime) std::string clang_headers_path; // Path to Clang's built-in CUDA headers (overrides CLANG_HEADERS_DIR) std::string cccl_include_path; // Path to CCCL headers (overrides CCCL_SOURCE_DIR); contains cub/, thrust/, cuda/ + std::string entry_point_name; // Name of the exported entry point function (used for post-link optimization) + std::string device_pch_path; // Existing device PCH file to load during device compilation + std::string host_pch_path; // Existing host PCH file to load during host compilation std::vector include_paths; std::vector library_paths; std::vector device_bitcode_files; // Raw LLVM bitcode (magic "BC") linked via LLVM's Linker @@ -19,16 +22,13 @@ struct CompilerConfig std::unordered_map macro_definitions; // key=macro name, value=macro value (empty for flag // macros) std::vector extra_clang_args; // Arguments passed directly to Clang via libnvcc's -XClang option - std::string device_pch_path; // Existing device PCH file to load during device compilation - std::string host_pch_path; // Existing host PCH file to load during host compilation int sm_version = 75; int optimization_level = 2; bool debug = false; bool verbose = false; bool trace_includes = false; // Show all included headers during compilation (for debugging header search) bool keep_artifacts = false; // Keep compiled artifacts for inspection (PTX, object files, etc.) - std::string entry_point_name; // Name of the exported entry point function (used for post-link optimization) - bool enable_pch = false; // Let CCCL create/load cached PCH files before invoking libnvcc + bool enable_pch = false; // Let CCCL create/load cached PCH files before invoking libnvcc void appendCommandLineArguments(std::vector& args) const; }; diff --git a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt index f0038a731b9..dd7f29ea134 100644 --- a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt +++ b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt @@ -89,20 +89,25 @@ target_compile_definitions( HOSTJIT_INCLUDE_DIR="${_hostjit_include_dir}" ) -if (CUDAToolkit_FOUND) - target_include_directories( - libnvcc - PRIVATE ${CUDAToolkit_INCLUDE_DIRS} - ) - cmake_path(GET CUDAToolkit_BIN_DIR PARENT_PATH CUDA_TOOLKIT_ROOT_FROM_CMAKE) - target_compile_definitions( - libnvcc - PRIVATE - CUDA_TOOLKIT_PATH="${CUDA_TOOLKIT_ROOT_FROM_CMAKE}" - CUDA_SDK_VERSION="${CUDAToolkit_VERSION_MAJOR}.0" - ) +# A toolkit is required because we need to call into nvfatbin/nvjitlink. +# FIXME: Statically link against those libraries individually rather than +# requiring a toolkit. +if (NOT CUDAToolkit_FOUND) + message(FATAL_ERROR "libnvcc requires a CUDA toolkit") endif() +target_include_directories( + libnvcc + PRIVATE ${CUDAToolkit_INCLUDE_DIRS} +) +cmake_path(GET CUDAToolkit_BIN_DIR PARENT_PATH CUDA_TOOLKIT_ROOT_FROM_CMAKE) +target_compile_definitions( + libnvcc + PRIVATE + CUDA_TOOLKIT_PATH="${CUDA_TOOLKIT_ROOT_FROM_CMAKE}" + CUDA_SDK_VERSION="${CUDAToolkit_VERSION_MAJOR}.0" +) + # Link against LLVM/Clang/LLD target_link_libraries( libnvcc diff --git a/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp b/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp index d41022ea142..5116594b3fa 100644 --- a/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp +++ b/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp @@ -95,6 +95,9 @@ struct CompilerOptions std::string cuda_toolkit_path; std::string hostjit_include_path; std::string clang_headers_path; + std::string device_pch_path; + std::string host_pch_path; + std::string entry_point_name; std::vector system_include_paths; std::vector include_paths; std::vector library_paths; @@ -102,15 +105,12 @@ struct CompilerOptions std::vector device_ltoir_files; std::unordered_map macro_definitions; std::vector extra_clang_args; - std::string device_pch_path; - std::string host_pch_path; int sm_version = 75; int optimization_level = 2; bool debug = false; bool verbose = false; bool trace_includes = false; bool keep_artifacts = false; - std::string entry_point_name; }; struct CompilationResult @@ -1362,7 +1362,14 @@ class CompilerImpl { llvm::WriteBitcodeToFile(*mod, os); os.flush(); - result.success = true; + if (os.has_error()) + { + result.diagnostics = "Failed to write bitcode output file: " + output_bitcode_path + "\n"; + } + else + { + result.success = true; + } } } else @@ -1373,7 +1380,10 @@ class CompilerImpl diag_stream.flush(); result.diagnostics += diag_output; - removeAll(temp_dir); + if (!config.keep_artifacts) + { + removeAll(temp_dir); + } return result; } @@ -1788,7 +1798,10 @@ class CompilerImpl return result; } - removeAll(temp_dir); + if (!config.keep_artifacts) + { + removeAll(temp_dir); + } result.success = true; return result; } From 4a10cdd228c204037edca86446a8fb1e4fc77f2f Mon Sep 17 00:00:00 2001 From: Ambrose Leeb Date: Thu, 9 Jul 2026 20:35:18 +0000 Subject: [PATCH 04/10] format CMake --- c/parallel.v2/src/hostjit/CMakeLists.txt | 19 +++--- .../src/hostjit/libnvcc/CMakeLists.txt | 60 +++++++++---------- python/cuda_cccl/CMakeLists.txt | 6 +- 3 files changed, 38 insertions(+), 47 deletions(-) diff --git a/c/parallel.v2/src/hostjit/CMakeLists.txt b/c/parallel.v2/src/hostjit/CMakeLists.txt index fb9666d989a..df2468a37b8 100644 --- a/c/parallel.v2/src/hostjit/CMakeLists.txt +++ b/c/parallel.v2/src/hostjit/CMakeLists.txt @@ -17,7 +17,10 @@ set(LIBNVCC_CPM_CMAKE_PATH "${_cccl_root}/cmake/CPM.cmake") set(LIBNVCC_HEADER_INSTALL_DESTINATION "cuda/cccl/headers/libnvcc") set(LIBNVCC_CLANG_HEADER_INSTALL_DESTINATION "cuda/cccl/headers/clang") if (CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY) - set(LIBNVCC_LIBRARY_OUTPUT_DIRECTORY "${CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY}") + set( + LIBNVCC_LIBRARY_OUTPUT_DIRECTORY + "${CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY}" + ) endif() add_subdirectory(libnvcc) @@ -39,9 +42,7 @@ add_library( target_include_directories( cccl.c.parallel.v2.hostjit_lib - PUBLIC - ${_hostjit_include_dir} - ${_c_parallel_dir}/include + PUBLIC ${_hostjit_include_dir} ${_c_parallel_dir}/include ) target_compile_definitions( @@ -71,10 +72,7 @@ if (NOT WIN32) target_link_libraries(cccl.c.parallel.v2.hostjit_lib PUBLIC dl) endif() -target_link_libraries( - cccl.c.parallel.v2.hostjit_lib - PUBLIC libnvcc -) +target_link_libraries(cccl.c.parallel.v2.hostjit_lib PUBLIC libnvcc) if (CUDAToolkit_FOUND) target_link_libraries(cccl.c.parallel.v2.hostjit_lib PUBLIC CUDA::cudart) @@ -90,10 +88,7 @@ set_target_properties( ) # Hostjit's minimal CUDA runtime headers (replacements for upstream clang headers) -set( - _hostjit_cuda_minimal_dir - "${_hostjit_include_dir}/hostjit/cuda_minimal" -) +set(_hostjit_cuda_minimal_dir "${_hostjit_include_dir}/hostjit/cuda_minimal") file(GLOB _hostjit_cuda_minimal_headers "${_hostjit_cuda_minimal_dir}/*.h") install( FILES ${_hostjit_cuda_minimal_headers} diff --git a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt index dd7f29ea134..21239ae4435 100644 --- a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt +++ b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt @@ -1,11 +1,19 @@ if (DEFINED LIBNVCC_CPM_CMAKE_PATH AND EXISTS "${LIBNVCC_CPM_CMAKE_PATH}") include("${LIBNVCC_CPM_CMAKE_PATH}") else() - find_file(LIBNVCC_CPM_CMAKE_PATH NAMES CPM.cmake PATHS ${CMAKE_MODULE_PATH} NO_DEFAULT_PATH) + find_file( + LIBNVCC_CPM_CMAKE_PATH + NAMES CPM.cmake + PATHS ${CMAKE_MODULE_PATH} + NO_DEFAULT_PATH + ) if (LIBNVCC_CPM_CMAKE_PATH) include("${LIBNVCC_CPM_CMAKE_PATH}") else() - message(FATAL_ERROR "CPM.cmake not found. Set LIBNVCC_CPM_CMAKE_PATH or add it to CMAKE_MODULE_PATH.") + message( + FATAL_ERROR + "CPM.cmake not found. Set LIBNVCC_CPM_CMAKE_PATH or add it to CMAKE_MODULE_PATH." + ) endif() endif() @@ -23,7 +31,12 @@ if (DEFINED HOSTJIT_LLVM_VERSION AND NOT DEFINED LIBNVCC_LLVM_VERSION) else() set(_libnvcc_llvm_version_default "llvmorg-22.1.1") endif() -set(LIBNVCC_LLVM_VERSION "${_libnvcc_llvm_version_default}" CACHE STRING "LLVM git tag to fetch") +set( + LIBNVCC_LLVM_VERSION + "${_libnvcc_llvm_version_default}" + CACHE STRING + "LLVM git tag to fetch" +) # List options must be set before CPMAddPackage set(LLVM_ENABLE_PROJECTS "clang;lld" CACHE STRING "" FORCE) @@ -63,11 +76,7 @@ file( MAKE_DIRECTORY "${llvm_project_BINARY_DIR}/lib/clang/${LLVM_VERSION_MAJOR}" ) -add_library( - libnvcc - SHARED - compiler.cpp -) +add_library(libnvcc SHARED compiler.cpp) target_include_directories( libnvcc @@ -96,10 +105,7 @@ if (NOT CUDAToolkit_FOUND) message(FATAL_ERROR "libnvcc requires a CUDA toolkit") endif() -target_include_directories( - libnvcc - PRIVATE ${CUDAToolkit_INCLUDE_DIRS} -) +target_include_directories(libnvcc PRIVATE ${CUDAToolkit_INCLUDE_DIRS}) cmake_path(GET CUDAToolkit_BIN_DIR PARENT_PATH CUDA_TOOLKIT_ROOT_FROM_CMAKE) target_compile_definitions( libnvcc @@ -147,32 +153,20 @@ target_link_libraries( ) if (CUDAToolkit_FOUND) - target_link_libraries( - libnvcc - PRIVATE CUDA::cuda_driver CUDA::cudart - ) + target_link_libraries(libnvcc PRIVATE CUDA::cuda_driver CUDA::cudart) if (WIN32) # On Windows, static CUDA libs are built with /MT which conflicts with # the project's dynamic CRT (/MD). Use dynamic variants instead. - target_link_libraries( - libnvcc - PRIVATE CUDA::nvJitLink CUDA::nvfatbin - ) + target_link_libraries(libnvcc PRIVATE CUDA::nvJitLink CUDA::nvfatbin) else() # Prefer static CUDA libs on Linux for self-contained binaries. If the # toolchain (e.g. lite/pip CUDA installs or some Docker images) only ships # the dynamic variants, fall back to those rather than failing configure. foreach (_cudalib nvJitLink nvptxcompiler nvfatbin) if (TARGET "CUDA::${_cudalib}_static") - target_link_libraries( - libnvcc - PRIVATE "CUDA::${_cudalib}_static" - ) + target_link_libraries(libnvcc PRIVATE "CUDA::${_cudalib}_static") elseif (TARGET "CUDA::${_cudalib}") - target_link_libraries( - libnvcc - PRIVATE "CUDA::${_cudalib}" - ) + target_link_libraries(libnvcc PRIVATE "CUDA::${_cudalib}") else() message( FATAL_ERROR @@ -224,7 +218,10 @@ if (NOT LIBNVCC_CLANG_HEADER_INSTALL_DESTINATION) set(LIBNVCC_CLANG_HEADER_INSTALL_DESTINATION "include/libnvcc/clang") endif() -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/libnvcc/libnvcc.h" DESTINATION "${LIBNVCC_HEADER_INSTALL_DESTINATION}") +install( + FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/libnvcc/libnvcc.h" + DESTINATION "${LIBNVCC_HEADER_INSTALL_DESTINATION}" +) # -------------------------------------------------------------------------- # Install clang headers into wheel (for self-sufficient packaging) @@ -262,4 +259,7 @@ set( "${llvm_project_SOURCE_DIR}/clang/lib/Headers/inttypes.h" ${_clang_stddef_headers} ) -install(FILES ${_clang_c_headers} DESTINATION "${LIBNVCC_CLANG_HEADER_INSTALL_DESTINATION}") +install( + FILES ${_clang_c_headers} + DESTINATION "${LIBNVCC_CLANG_HEADER_INSTALL_DESTINATION}" +) diff --git a/python/cuda_cccl/CMakeLists.txt b/python/cuda_cccl/CMakeLists.txt index fdb0c0ec6c2..8cb47b2845d 100644 --- a/python/cuda_cccl/CMakeLists.txt +++ b/python/cuda_cccl/CMakeLists.txt @@ -77,11 +77,7 @@ file(MAKE_DIRECTORY "cuda/compute/${CUDA_VERSION_DIR}/cccl") # Install version-specific binaries set(_cccl_c_parallel_install_targets ${_cccl_c_parallel_target}) if (CCCL_PYTHON_USE_V2) - list( - APPEND - _cccl_c_parallel_install_targets - libnvcc - ) + list(APPEND _cccl_c_parallel_install_targets libnvcc) endif() install( TARGETS ${_cccl_c_parallel_install_targets} From 12a56b3eca28be05e5d7bbfed761dd3cdbadec24 Mon Sep 17 00:00:00 2001 From: Ambrose Leeb Date: Wed, 15 Jul 2026 17:55:39 +0000 Subject: [PATCH 05/10] fix merge error --- .../src/hostjit/libnvcc/compiler.cpp | 583 ------------------ 1 file changed, 583 deletions(-) diff --git a/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp b/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp index d239438a543..24698b301b0 100644 --- a/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp +++ b/c/parallel.v2/src/hostjit/libnvcc/compiler.cpp @@ -692,589 +692,6 @@ static void appendMacroDefinitions(std::vector& args, const Compile } } -struct CompilerOptions -{ - std::string cuda_toolkit_path; - std::string hostjit_include_path; - std::string clang_headers_path; - std::string device_pch_path; - std::string host_pch_path; - std::string entry_point_name; - std::vector system_include_paths; - std::vector include_paths; - std::vector library_paths; - std::vector device_bitcode_files; - std::vector device_ltoir_files; - std::unordered_map macro_definitions; - std::vector extra_clang_args; - int sm_version = 75; - int optimization_level = 2; - bool debug = false; - bool verbose = false; - bool trace_includes = false; - bool keep_artifacts = false; -}; - -struct CompilationResult -{ - bool success = false; - std::string object_file_path; - std::string diagnostics; -}; - -struct BitcodeResult -{ - bool success = false; - std::string diagnostics; -}; - -struct LinkResult -{ - bool success = false; - std::string library_path; - std::string diagnostics; -}; - -static bool pathExists(const std::filesystem::path& path); - -static void addDefaultCudaLibraryPath(CompilerOptions& options) -{ - if (!options.cuda_toolkit_path.empty()) - { - std::filesystem::path lib64_path = std::filesystem::path(options.cuda_toolkit_path) / "lib64"; - std::filesystem::path lib_path = std::filesystem::path(options.cuda_toolkit_path) / "lib"; - - if (pathExists(lib64_path)) - { - options.library_paths.push_back(lib64_path.string()); - } - else if (pathExists(lib_path)) - { - options.library_paths.push_back(lib_path.string()); - } - } -} - -static void setDefaultOptions(CompilerOptions& options) -{ - if (const char* env = std::getenv("CUDA_PATH")) - { - options.cuda_toolkit_path = env; - } - else if (const char* env = std::getenv("CUDA_HOME")) - { - options.cuda_toolkit_path = env; - } -#ifdef CUDA_TOOLKIT_PATH - else - { - options.cuda_toolkit_path = CUDA_TOOLKIT_PATH; - } -#endif - - if (const char* env = std::getenv("HOSTJIT_INCLUDE_PATH")) - { - options.hostjit_include_path = env; - } -#ifdef HOSTJIT_INCLUDE_DIR - else - { - options.hostjit_include_path = HOSTJIT_INCLUDE_DIR; - } -#endif - - if (const char* env = std::getenv("HOSTJIT_CLANG_PATH")) - { - options.clang_headers_path = env; - } -#ifdef CLANG_HEADERS_DIR - else - { - options.clang_headers_path = CLANG_HEADERS_DIR; - } -#endif -} - -static bool pathExists(const std::filesystem::path& path) -{ - std::error_code ec; - return std::filesystem::exists(path, ec); -} - -static std::filesystem::path tempDirectoryPath() -{ - std::error_code ec; - auto path = std::filesystem::temp_directory_path(ec); - if (!ec) - { - return path; - } -#ifdef _WIN32 - if (const char* env = std::getenv("TEMP")) - { - return env; - } - if (const char* env = std::getenv("TMP")) - { - return env; - } -#endif - if (const char* env = std::getenv("TMPDIR")) - { - return env; - } - return "."; -} - -static bool createDirectories(const std::filesystem::path& path, std::string& diagnostics) -{ - std::error_code ec; - std::filesystem::create_directories(path, ec); - if (ec) - { - diagnostics += "Failed to create directory " + path.string() + ": " + ec.message() + "\n"; - return false; - } - return true; -} - -static void removeAll(const std::filesystem::path& path) -{ - std::error_code ec; - std::filesystem::remove_all(path, ec); -} - -template -static void forEachDirectoryEntry(const std::filesystem::path& dir, Fn&& fn) -{ - std::error_code ec; - for (std::filesystem::directory_iterator it(dir, ec), end; !ec && it != end; it.increment(ec)) - { - fn(*it); - } -} - -static bool parseInt(const std::string& value, int& out) -{ - if (value.empty()) - { - return false; - } - - int parsed = 0; - const char* begin = value.data(); - const char* end = begin + value.size(); - auto [ptr, ec] = std::from_chars(begin, end, parsed); - if (ec != std::errc{} || ptr != end) - { - return false; - } - out = parsed; - return true; -} - -static bool parseGpuArchitecture(const std::string& value, int& sm) -{ - std::string arch = value; - if (arch.starts_with("sm_")) - { - arch.erase(0, 3); - } - return parseInt(arch, sm); -} - -static bool parseMacroDefinition(const std::string& value, CompilerOptions& options) -{ - if (value.empty()) - { - return false; - } - auto eq = value.find('='); - if (eq == std::string::npos) - { - options.macro_definitions[value] = ""; - } - else if (eq == 0) - { - return false; - } - else - { - options.macro_definitions[value.substr(0, eq)] = value.substr(eq + 1); - } - return true; -} - -static bool parseOptions(int num_options, const char* const* raw_options, CompilerOptions& options, std::string& error) -{ - if (num_options < 0) - { - error = "Option count must be non-negative"; - return false; - } - if (num_options > 0 && raw_options == nullptr) - { - error = "Options array is null"; - return false; - } - - setDefaultOptions(options); - - auto value_after_equals = [](std::string_view option, std::string_view prefix) -> std::string { - return std::string(option.substr(prefix.size())); - }; - - for (int i = 0; i < num_options; ++i) - { - if (raw_options[i] == nullptr) - { - error = "Option string is null"; - return false; - } - - std::string_view option(raw_options[i]); - if (option.starts_with("--cuda-path=")) - { - options.cuda_toolkit_path = value_after_equals(option, "--cuda-path="); - } - else if (option.starts_with("--hostjit-include-path=")) - { - options.hostjit_include_path = value_after_equals(option, "--hostjit-include-path="); - } - else if (option.starts_with("--clang-headers-path=")) - { - options.clang_headers_path = value_after_equals(option, "--clang-headers-path="); - } - else if (option.starts_with("--system-include-path=")) - { - options.system_include_paths.push_back(value_after_equals(option, "--system-include-path=")); - } - else if (option.starts_with("-isystem") && option.size() > 8) - { - options.system_include_paths.emplace_back(option.substr(8)); - } - else if (option == "-isystem") - { - if (++i >= num_options || raw_options[i] == nullptr) - { - error = "-isystem requires an argument"; - return false; - } - options.system_include_paths.emplace_back(raw_options[i]); - } - else if (option.starts_with("--include-path=")) - { - options.include_paths.push_back(value_after_equals(option, "--include-path=")); - } - else if (option.starts_with("-I") && option.size() > 2) - { - options.include_paths.emplace_back(option.substr(2)); - } - else if (option == "-I") - { - if (++i >= num_options || raw_options[i] == nullptr) - { - error = "-I requires an argument"; - return false; - } - options.include_paths.emplace_back(raw_options[i]); - } - else if (option.starts_with("--library-path=")) - { - options.library_paths.push_back(value_after_equals(option, "--library-path=")); - } - else if (option.starts_with("-L") && option.size() > 2) - { - options.library_paths.emplace_back(option.substr(2)); - } - else if (option == "-L") - { - if (++i >= num_options || raw_options[i] == nullptr) - { - error = "-L requires an argument"; - return false; - } - options.library_paths.emplace_back(raw_options[i]); - } - else if (option.starts_with("--device-bitcode=")) - { - options.device_bitcode_files.push_back(value_after_equals(option, "--device-bitcode=")); - } - else if (option.starts_with("--device-ltoir=")) - { - options.device_ltoir_files.push_back(value_after_equals(option, "--device-ltoir=")); - } - else if (option.starts_with("--define-macro=")) - { - if (!parseMacroDefinition(value_after_equals(option, "--define-macro="), options)) - { - error = "Invalid macro definition: " + std::string(option); - return false; - } - } - else if (option.starts_with("-D") && option.size() > 2) - { - if (!parseMacroDefinition(std::string(option.substr(2)), options)) - { - error = "Invalid macro definition: " + std::string(option); - return false; - } - } - else if (option == "-D") - { - if (++i >= num_options || raw_options[i] == nullptr || !parseMacroDefinition(raw_options[i], options)) - { - error = "-D requires a macro definition"; - return false; - } - } - else if (option.starts_with("--gpu-architecture=")) - { - if (!parseGpuArchitecture(value_after_equals(option, "--gpu-architecture="), options.sm_version)) - { - error = "Invalid GPU architecture: " + std::string(option); - return false; - } - } - else if (option.starts_with("--optimization-level=")) - { - if (!parseInt(value_after_equals(option, "--optimization-level="), options.optimization_level)) - { - error = "Invalid optimization level: " + std::string(option); - return false; - } - } - else if (option.starts_with("-O") && option.size() > 2) - { - if (!parseInt(std::string(option.substr(2)), options.optimization_level)) - { - error = "Invalid optimization level: " + std::string(option); - return false; - } - } - else if (option == "--debug") - { - options.debug = true; - } - else if (option == "--verbose") - { - options.verbose = true; - } - else if (option == "--trace-includes") - { - options.trace_includes = true; - } - else if (option == "--keep-artifacts") - { - options.keep_artifacts = true; - } - else if (option.starts_with("--entry-point=")) - { - options.entry_point_name = value_after_equals(option, "--entry-point="); - } - else if (option.starts_with("--device-pch=")) - { - options.device_pch_path = value_after_equals(option, "--device-pch="); - } - else if (option.starts_with("--host-pch=")) - { - options.host_pch_path = value_after_equals(option, "--host-pch="); - } - else if (option.starts_with("-XClang=")) - { - options.extra_clang_args.emplace_back(option.substr(8)); - } - else if (option == "-XClang") - { - if (++i >= num_options || raw_options[i] == nullptr) - { - error = "-XClang requires an argument"; - return false; - } - options.extra_clang_args.emplace_back(raw_options[i]); - } - else - { - error = "Unknown option: " + std::string(option); - return false; - } - } - - if (options.library_paths.empty()) - { - addDefaultCudaLibraryPath(options); - } - - return true; -} - -static bool validateOptions(const CompilerOptions& options, std::string* error_message) -{ - if (options.cuda_toolkit_path.empty()) - { - if (error_message) - { - *error_message = "CUDA toolkit path not found. Please pass --cuda-path or set CUDA_PATH/CUDA_HOME."; - } - return false; - } - - if (!pathExists(options.cuda_toolkit_path)) - { - if (error_message) - { - *error_message = "CUDA toolkit path does not exist: " + options.cuda_toolkit_path; - } - return false; - } - - std::filesystem::path cuda_h = std::filesystem::path(options.cuda_toolkit_path) / "include" / "cuda.h"; - if (!pathExists(cuda_h)) - { - if (error_message) - { - *error_message = "CUDA headers not found at: " + cuda_h.string(); - } - return false; - } - - for (const auto& include_path : options.include_paths) - { - if (!pathExists(include_path)) - { - if (error_message) - { - *error_message = "Include path does not exist: " + include_path; - } - return false; - } - } - - for (const auto& include_path : options.system_include_paths) - { - if (!pathExists(include_path)) - { - if (error_message) - { - *error_message = "System include path does not exist: " + include_path; - } - return false; - } - } - - for (const auto& library_path : options.library_paths) - { - if (!pathExists(library_path)) - { - if (error_message) - { - *error_message = "Library path does not exist: " + library_path; - } - return false; - } - } - - for (const auto& bitcode_path : options.device_bitcode_files) - { - if (!pathExists(bitcode_path)) - { - if (error_message) - { - *error_message = "Device bitcode path does not exist: " + bitcode_path; - } - return false; - } - } - - for (const auto& ltoir_path : options.device_ltoir_files) - { - if (!pathExists(ltoir_path)) - { - if (error_message) - { - *error_message = "Device LTOIR path does not exist: " + ltoir_path; - } - return false; - } - } - - if (!options.device_pch_path.empty() && !pathExists(options.device_pch_path)) - { - if (error_message) - { - *error_message = "Device PCH path does not exist: " + options.device_pch_path; - } - return false; - } - - if (!options.host_pch_path.empty() && !pathExists(options.host_pch_path)) - { - if (error_message) - { - *error_message = "Host PCH path does not exist: " + options.host_pch_path; - } - return false; - } - - if (options.sm_version < 30 || options.sm_version > 150) - { - if (error_message) - { - *error_message = "Invalid SM version: " + std::to_string(options.sm_version) + " (must be between 30 and 150)"; - } - return false; - } - - if (options.optimization_level < 0 || options.optimization_level > 3) - { - if (error_message) - { - *error_message = - "Invalid optimization level: " + std::to_string(options.optimization_level) + " (must be between 0 and 3)"; - } - return false; - } - - return true; -} - -static void appendExtraClangArgs(std::vector& args, const CompilerOptions& options) -{ - args.insert(args.end(), options.extra_clang_args.begin(), options.extra_clang_args.end()); -} - -static void appendSystemIncludePaths(std::vector& args, const CompilerOptions& options) -{ - for (const auto& include_path : options.system_include_paths) - { - args.push_back("-internal-isystem"); - args.push_back(include_path); - } -} - -static void appendIncludePaths(std::vector& args, const CompilerOptions& options) -{ - for (const auto& include_path : options.include_paths) - { - args.push_back("-I" + include_path); - } -} - -static void appendMacroDefinitions(std::vector& args, const CompilerOptions& options) -{ - for (const auto& [macro_name, macro_value] : options.macro_definitions) - { - if (macro_value.empty()) - { - args.push_back("-D" + macro_name); - } - else - { - args.push_back("-D" + macro_name + "=" + macro_value); - } - } -} - #ifdef _WIN32 // Generate a minimal COFF import library for a given DLL. // This allows linking without requiring the Windows SDK or MSVC .lib files. From 903fb73251a9846ff26513cd99ef888d255d1e11 Mon Sep 17 00:00:00 2001 From: Ambrose Leeb Date: Mon, 20 Jul 2026 16:26:48 +0000 Subject: [PATCH 06/10] fix unused variable warning when assertions are disabled --- c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp b/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp index f6bd14e3710..5802ef6257e 100644 --- a/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp +++ b/c/parallel.v2/src/hostjit/include/hostjit/compiler.hpp @@ -48,7 +48,7 @@ inline std::string get_libnvcc_program_log(libnvccProgram program) } std::string log(log_size, '\0'); - auto res = libnvccGetProgramLog(program, log.data()); + [[maybe_unused]] auto res = libnvccGetProgramLog(program, log.data()); assert(res == LIBNVCC_SUCCESS && "Copying the log failed even though size calculation succeeded?"); assert(log.back() == '\0' && "libnvccGetProgramLog() should append a NUL character"); log.pop_back(); // Drop the extra NUL. From a173c218b555a315f5bebbf44ad3be889b083266 Mon Sep 17 00:00:00 2001 From: Ambrose Leeb Date: Mon, 20 Jul 2026 20:33:56 +0000 Subject: [PATCH 07/10] attempt to fix DLL not found issue on Windows --- .../src/hostjit/libnvcc/CMakeLists.txt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt index 21239ae4435..8487ddc7b0b 100644 --- a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt +++ b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt @@ -200,6 +200,29 @@ if (LIBNVCC_LIBRARY_OUTPUT_DIRECTORY) ARCHIVE_OUTPUT_DIRECTORY "${LIBNVCC_LIBRARY_OUTPUT_DIRECTORY}" RUNTIME_OUTPUT_DIRECTORY "${LIBNVCC_LIBRARY_OUTPUT_DIRECTORY}" ) +elseif (WIN32 AND DEFINED CCCL_EXECUTABLE_OUTPUT_DIR) + # Windows does not have an RPATH equivalent. Keep libnvcc.dll in the same + # runtime directory as CCCL's tests/executables so the loader can find it at + # process startup. + set_target_properties( + libnvcc + PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CCCL_EXECUTABLE_OUTPUT_DIR}" + ) +endif() + +if (WIN32) + # libnvcc links CUDA DLLs dynamically on Windows. Stage those runtime + # dependencies next to libnvcc.dll so test executables that load libnvcc via + # hostjit_lib do not fail before main with STATUS_DLL_NOT_FOUND. + add_custom_command( + TARGET libnvcc + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + $ + $ + COMMAND_EXPAND_LISTS + ) endif() # On Windows with multi-config generators (Visual Studio), exclude libnvcc from From 1226d6e1827853d66017b3f38b5f46e94ec34634 Mon Sep 17 00:00:00 2001 From: Ambrose Leeb Date: Tue, 21 Jul 2026 19:35:51 +0000 Subject: [PATCH 08/10] run cmake formatter --- c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt index 8487ddc7b0b..d7a438db204 100644 --- a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt +++ b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt @@ -218,8 +218,7 @@ if (WIN32) TARGET libnvcc POST_BUILD COMMAND - ${CMAKE_COMMAND} -E copy_if_different - $ + ${CMAKE_COMMAND} -E copy_if_different $ $ COMMAND_EXPAND_LISTS ) From d115becfa45a943a216075d75b9bceac4884c403 Mon Sep 17 00:00:00 2001 From: Ambrose Leeb Date: Tue, 28 Jul 2026 23:04:43 +0200 Subject: [PATCH 09/10] attempt to fix DLL path on windows --- .../src/hostjit/libnvcc/CMakeLists.txt | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt index d7a438db204..4cd2dd727b1 100644 --- a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt +++ b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt @@ -192,6 +192,10 @@ set_target_properties( WINDOWS_EXPORT_ALL_SYMBOLS ON ) +if (WIN32) + set_target_properties(libnvcc PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${OUT_DIR}) +endif() + if (LIBNVCC_LIBRARY_OUTPUT_DIRECTORY) set_target_properties( libnvcc @@ -210,20 +214,6 @@ elseif (WIN32 AND DEFINED CCCL_EXECUTABLE_OUTPUT_DIR) ) endif() -if (WIN32) - # libnvcc links CUDA DLLs dynamically on Windows. Stage those runtime - # dependencies next to libnvcc.dll so test executables that load libnvcc via - # hostjit_lib do not fail before main with STATUS_DLL_NOT_FOUND. - add_custom_command( - TARGET libnvcc - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E copy_if_different $ - $ - COMMAND_EXPAND_LISTS - ) -endif() - # On Windows with multi-config generators (Visual Studio), exclude libnvcc from # Debug builds — the LLVM Debug build causes stack overflows. if (MSVC) From 3dd411a21a76787cab3c4f4bdda4e1249a80d960 Mon Sep 17 00:00:00 2001 From: Ambrose Leeb Date: Thu, 30 Jul 2026 17:41:05 +0200 Subject: [PATCH 10/10] remove invalid set_target_properties call --- c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt index 6241dcbbfd8..e7357953c5d 100644 --- a/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt +++ b/c/parallel.v2/src/hostjit/libnvcc/CMakeLists.txt @@ -192,10 +192,6 @@ set_target_properties( WINDOWS_EXPORT_ALL_SYMBOLS ON ) -if (WIN32) - set_target_properties(libnvcc PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${OUT_DIR}) -endif() - if (LIBNVCC_LIBRARY_OUTPUT_DIRECTORY) set_target_properties( libnvcc