从 C 源文件生成 LLVM 位码文件的 CMakeList 文件

CMakeList file to generate LLVM bitcode file from C source file

我正在尝试使用 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)
  1. 我是 CMake 的新手,不确定这是否正确。我在生成的 MakeFile 中找不到制作 *.bc 的任何规则 .请在这里纠正我。我也试过“-save-temps”

  2. 考虑将此用于单个 .c 文件。如果您能给我一些关于为完整的 C 项目生成相同内容的提示,那将非常有帮助。

我认为你最终想要的是能够构建一个 C 程序 使用 CMake 和 clang 的项目,其中源文件被编译为 LLVM 位码 并且可执行文件是 link 从位码文件中编辑出来的。

使用 CMake,要求 clang 到 link 位码文件意味着要求它到 LTO mode 中的 link, 使用 -flto linkage 选项。

你可以通过 -flto 编译将 clang 编译 为 LLVM 位码 选项,或使用 -emit-llvm 选项。

这里是一个包含两个源文件和一个头文件的 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 位码文件,如您所见:

$ 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!

稍后

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?

你正在以一种正确的方式做这件事,虽然不是唯一正确的方式,但是 你的期望是错误的。再看看:

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

你可以看到 .o 文件是由 hello.cmain.c 由 CMake 生成的 makefile 不称为 hello.omain.o,而是 hello.c.omain.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 执行的配置测试。

除了已经写过的关于使用 LTO 基础设施的内容之外,您还有 3 个(或 2 个半)其他选择。

一种是使用Whole-Program LLVM,使用提供的命令提取相关的bitcode部分。

另一种是在您的 CMake 二进制目标上手动设置自定义目标(参见 add_custom_target and add_custom_command),这将在更改时触发并重现所需的结果,就像在每次命令行。

现在,关于最后一点,我有类似的需求,所以我创建了一个提供该功能的 CMake 项目 (llvm-ir-cmake-utils),但允许您根据需要将这些自定义目标连接到现有目标上并且无需每次都从头开始重写所有内容。

repo 中有示例,但简而言之,它允许您在现有的 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)
[...]

制作完成后,

$>file CMakeFiles/hello.dir/hello.c.o
CMakeFiles/hello.dir/hello.c.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

如果 设置(CMAKE_C_FLAGS“-emit-llvm”)

之前写过

project (hello)

为了获取IR bitcode,我写了:

###### CMakelists.txt ############
cmake_minimum_required(VERSION 2.8.9)
project (hello)
set(CMAKE_C_COMPILER "clang")
set(CMAKE_C_FLAGS "-flto")
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} "-flto")
add_executable(hello hello.c)
target_compile_options(hello PUBLIC ${CMAKE_C_FLAGS} -flto)

我工作了几个小时才让 Makefile 可以从 IR 编译 使用 lld 将代码转换为本机代码,然后使用 cmake 速度更快。 然后阅读 cmake 生成的 Makefile,我能够更正我的 Makefile:

clang  -flto  -flto <hello.c.o> ..

这有效,但我不知道为什么 -flto 写了两次。

非常感谢你 post,将 clang 显示为各种 llvm 提供的命令的集中前端。