Search code examples
linuxmakefilecmakellvmllvm-clang

CMakeList file to generate LLVM bitcode file from C source file


I am trying to generate LLVM bytecode file from a C source file (hello.c) using CMake. And below is my CMakeLists file.

###### CMakelists.txt ############
cmake_minimum_required(VERSION 2.8.9)
set(CMAKE_C_COMPILER "clang")
set(CMAKE_C_FLAGS "-emit-llvm")

project (hello)
add_executable(hello hello.c)
  1. I am new to CMake and not sure if this is the right way. I could not find any rules to make *.bc in the generated MakeFile . Please correct me here. I also tried "-save-temps"

  2. Considering this for a single .c file. It would be really helpful if you could give me some hints on generating the same for a complete C project.


Solution

  • I think what you ultimately want is to be able to build a C-program project with CMake and clang in which source files are compiled to LLVM bitcode and the executable is linked from the bitcode files.

    With CMake, asking clang to to link bitcode files means asking it to link in LTO mode, with the -flto linkage option.

    And you can get clang to compile to LLVM bitcode with the -flto compilation option, or with the -emit-llvm option.

    For illustration here is a Hello World project comprising two source files and one header:

    $ ls -R
    .:
    CMakeLists.txt  hello.c  hello.h  main.c
    

    Here is the:

    CMakeLists.txt

    cmake_minimum_required(VERSION 3.0.2)
    project (hello)
    set(CMAKE_C_COMPILER clang)
    set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} "-flto")
    add_executable(hello main.c hello.c)
    target_compile_options(hello PUBLIC ${CMAKE_C_FLAGS} -flto)
    #target_compile_options(hello PUBLIC ${CMAKE_C_FLAGS} -emit-llvm)
    

    It will work equally well with:

    #target_compile_options(hello PUBLIC ${CMAKE_C_FLAGS} -flto)
    target_compile_options(hello PUBLIC ${CMAKE_C_FLAGS} -emit-llvm)
    

    Make a build directory for CMake and go there:

    $ mkdir build
    $ cd build
    

    Generate the build system:

    $ cmake ..
    

    Build:

    $ make
    Scanning dependencies of target hello
    [ 33%] Building C object CMakeFiles/hello.dir/main.c.o
    [ 66%] Building C object CMakeFiles/hello.dir/hello.c.o
    [100%] Linking C executable hello
    [100%] Built target hello
    

    You will not find any *.bc targets in the Makefiles, nor any *.bc files generated:

    $ egrep -r '.*\.bc'; echo Done
    Done
    $ find -name '*.bc'; echo Done
    Done
    

    because the compilation option -flto or -emit-llvm results in an output file:

    CMakeFiles/hello.dir/main.c.o
    CMakeFiles/hello.dir/hello.c.o
    

    that adheres to the usual CMake naming convention but is in fact not an object file but an LLVM bitcode file, as you see:

    $ file $(find -name '*.o')
    ./CMakeFiles/hello.dir/hello.c.o: LLVM IR bitcode
    ./CMakeFiles/hello.dir/main.c.o:  LLVM IR bitcode
    

    The program does the usual thing:

    $ ./hello 
    Hello World!
    

    Later

    When I try " make hello.o " it should generate the object file right? the cmd executes successfully but, could not find the generated object file. Am I doing it right?

    You are doing it in one way that is right, though not the only way that is right, but your expectations are wrong. Look again at:

    $ file $(find -name '*.o')
    ./CMakeFiles/hello.dir/hello.c.o: LLVM IR bitcode
    ./CMakeFiles/hello.dir/main.c.o:  LLVM IR bitcode
    

    You can see there that the .o files that are made from hello.c and main.c by the CMake-generated makefile are not called hello.o and main.o but hello.c.o and main.c.o. CMake prefers a compiled filename to preserve the extension of the source file, and append .o. That is a fairly common practice. So if you wanted to use the makefile to compile hello.c, the most obviously right way would be make hello.c.o.

    Let's see what actually happens. In my CMake build directory:

    $ make VERBOSE=1 hello.c.o
    make -f CMakeFiles/hello.dir/build.make CMakeFiles/hello.dir/hello.c.o
    make[1]: Entering directory '/home/imk/develop/so/scrap/build'
    make[1]: 'CMakeFiles/hello.dir/hello.c.o' is up to date.
    make[1]: Leaving directory '/home/imk/develop/so/scrap/build'
    

    There was nothing to be done, because my hello.c.o was up to date. So I'll delete it and repeat:

    $ rm CMakeFiles/hello.dir/hello.c.o
    $ make VERBOSE=1 hello.c.o
    make -f CMakeFiles/hello.dir/build.make CMakeFiles/hello.dir/hello.c.o
    make[1]: Entering directory '/home/imk/develop/so/scrap/build'
    Building C object CMakeFiles/hello.dir/hello.c.o
    clang   -flto -o CMakeFiles/hello.dir/hello.c.o   -c /home/imk/develop/so/scrap/hello.c
    make[1]: Leaving directory '/home/imk/develop/so/scrap/build'
    

    Now it has been recompiled.

    However, because many people - like you - would expect hello.o to be compiled from hello.c, CMake helpfully defines hello.o as a .PHONY target that depends on hello.c.o:

    $ egrep  -A3 'hello.o.*:.*hello.c.o' Makefile 
    hello.o: hello.c.o
    
    .PHONY : hello.o
    

    So in fact I can do:

    $ rm CMakeFiles/hello.dir/hello.c.o
    $ make VERBOSE=1 hello.o
    make -f CMakeFiles/hello.dir/build.make CMakeFiles/hello.dir/hello.c.o
    make[1]: Entering directory '/home/imk/develop/so/scrap/build'
    Building C object CMakeFiles/hello.dir/hello.c.o
    clang   -flto -o CMakeFiles/hello.dir/hello.c.o   -c /home/imk/develop/so/scrap/hello.c
    make[1]: Leaving directory '/home/imk/develop/so/scrap/build'
    

    make hello.o is another way of making hello.c.o