我正在尝试使用CMake从C源文件(hello.c)生成LLVM字节码文件。以下是我的CMakeLists文件。
###### 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)
我认为你最终想要的是能够用CMake和clang构建一个C程序项目,其中源文件被编译为LLVM bitcode,可执行文件从bitcode文件链接。
使用CMake,要求clang链接bitcode文件意味着要求它在LTO mode中与-flto
链接选项链接。
你可以使用-flto
编译选项或使用-emit-llvm
选项编译成LLVM bitcode。
这里是一个Hello World项目,包括两个源文件和一个头文件:
$ ls -R
.:
CMakeLists.txt hello.c hello.h main.c
这里是:
的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)
它同样适用于:
#target_compile_options(hello PUBLIC ${CMAKE_C_FLAGS} -flto)
target_compile_options(hello PUBLIC ${CMAKE_C_FLAGS} -emit-llvm)
为CMake制作一个构建目录并去那里:
$ mkdir build
$ cd build
生成构建系统:
$ cmake ..
建立:
$ 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
您不会在Makefile中找到任何*.bc
目标,也不会生成任何*.bc
文件:
$ egrep -r '.*\.bc'; echo Done
Done
$ find -name '*.bc'; echo Done
Done
因为编译选项-flto
或-emit-llvm
会产生一个输出文件:
CMakeFiles/hello.dir/main.c.o
CMakeFiles/hello.dir/hello.c.o
遵循通常的CMake命名约定但实际上不是目标文件而是LLVM bitcode文件,如您所见:
$ file $(find -name '*.o')
./CMakeFiles/hello.dir/hello.c.o: LLVM IR bitcode
./CMakeFiles/hello.dir/main.c.o: LLVM IR bitcode
该程序通常做的事情:
$ ./hello
Hello World!
后来
当我尝试“make hello.o”时,它应该生成对象文件吗? cmd执行成功但无法找到生成的目标文件。我做得对吗?
你是以一种正确的方式做到这一点,虽然不是唯一正确的方式,但你的期望是错误的。再看一下:
$ file $(find -name '*.o')
./CMakeFiles/hello.dir/hello.c.o: LLVM IR bitcode
./CMakeFiles/hello.dir/main.c.o: LLVM IR bitcode
你可以看到由CMake生成的makefile从.o
和hello.c
制作的main.c
文件不是hello.o
和main.o
,而是hello.c.o
和main.c.o
。 CMake更喜欢编译的文件名以保留源文件的扩展名,并附加.o
。这是一种相当普遍的做法。因此,如果你想使用makefile来编译hello.c
,最明显的正确方法是make hello.c.o
。
让我们看看究竟发生了什么。在我的CMake构建目录中:
$ 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'
没有什么可做的,因为我的hello.c.o
是最新的。所以我会删除它并重复:
$ 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'
现在它已被重新编译。
然而,因为许多人 - 比如你 - 期望从hello.o
汇编hello.c
,CMake有助于将hello.o
定义为依赖于.PHONY
target的hello.c.o
:
$ egrep -A3 'hello.o.*:.*hello.c.o' Makefile
hello.o: hello.c.o
.PHONY : hello.o
所以事实上我可以这样做:
$ 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
是另一种制作hello.c.o
的方式
问题是使用-emit-llvm
标志不会产生最终的二进制文件,并且一旦CMake执行该标志就会停止CMake执行的配置测试。
除了已经有关使用LTO基础设施的文章之外,您还有3个(或2个半)其他选择。
一种是使用Whole-Program LLVM并使用提供的命令来提取相关的bitcode部分。
另一种是在CMake二进制目标上设置自定义目标(请参阅add_custom_target
和add_custom_command
)的手动方式,这些目标将在更改时触发,并将重现所需的结果,就像每次在命令行上手动执行一样。
现在,在最后一点上,我有类似的需求,所以我创建了一个提供该功能的CMake项目(llvm-ir-cmake-utils),但允许您根据需要将这些自定义目标连接到现有的目标,并且无需重写所有内容每次刮擦。
回购中有一些例子,但简而言之,它允许您在已有的CMake目标上附加自定义目标,例如
[...]
add_executable(qux ${SOURCES})
[...]
# this will create a bitcode generating target
# and allow it to depend on the initial target in order to detect source code changes
llvmir_attach_bc_target(qux_bc qux)
add_dependencies(qux_bc qux)
[...]