c++makefilecompilationcudaconditional-compilation

Creating conditional Makefiles with different compilers, C++ and CUDA


I've been trying to create a Makefile that does the following. I have a C++ program that calls CUDA functions, so I've got a set of .cpp and .cu files, with all CUDA kernels in the .cu files.

For every kernel call, I also have a CPU version of that function, so I have code in my .cpp files that looks like this

if (with_cuda)
{
    call_kernel(parameters);
}
else
{
    call_cpu_function(parameters);
}

The call_kernel function is defined in the .cu files and then calls the actual kernel kernel<<<X,X>>>(...).

I want to be able to compile my program in a system without either a GPU or the CUDA toolkit. I can easily encapsulate the functions that will call kernels with some macro, for instance, the code above might look like

if (with_cuda)
{
#ifdef WITH_CUDA
    call_kernel(parameters);
#endif
}
else
{
    call_cpu_function(parameters);
}

And then compile either by g++ files.cpp without CUDA support, or nvcc files.cpp other_files.cu -DWITH_CUDA

And I should encapsulate all other includes to CUDA libraries and so on with the same macro #ifdef...

How would I make a Makefile that looks for the CUDA toolkit installed and then does a compilation with nvcc, or with g++ if it's not installed? I've looked into either writing a Makefile directly, using cmake, or autoconf with a configure script, but I'm quite lost, so I'm open to using any other method that might be preferable.

Additionally, if that macro encapsulation of every call to a kernel is not the standard or the best way to do this kind of thing, I'm also happy to change how I'm approaching this. I can see a potential issue of having to do an if statement for each call as in the code above besides the macro ifdef, so I might compile without CUDA. Still, then the bool with_cuda is set to true for whatever reason, and no function is called, so I can see why this might not be a good solution.


Solution

  • You don't need to jump through all of these hoops, like conditionally-compiling the same file with different compilers, or spelling out an if-cuda-else condition in many locations. Instead, consider using linking mechanisms.

    So, when in your C++ file, you have a:

    do_stuff(params);
    

    call - only provide a declaration of this function, but no definition.

    Now, either manually in your Makefile - or better yet, or auto-generated Makefile, see below - you choose between one of two possibilities:

    ... and link the object file with calls do_stuff() with either one implementation or the other.

    Here's how you would do this with CMake: You write this CMakeLists.txt file in your project directory:

    cmake_minimum_required(VERSION 3.8)
    include(CheckLanguage)
    
    project(myapp LANGUAGES CXX)
    
    add_executable(myapp calls_dostuff.cpp) 
    
    check_language(CUDA)
    if (CMAKE_CUDA_COMPILER)
        enable_language(CUDA)
        add_dependencies(myapp dostuff_impl_calling_gpu_kernel.cu) 
    else()
        add_dependencies(myapp dostuff_cpu_impl.cpp) 
    endif()
    

    ... and then invoke:

    cmake -B my_build_path -S project_dir_path -G "Unix Makefiles"
    

    to generate a Makefile into my_build_path for a project locate at project_dir_path.