版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
1. LLVM IR
LLVM IR(Intermediate Representation) 是 LLVM 编译框架中的一种中间表示形式,它是一种面向低级的中间代码,是 LLVM 架构的核心部分。LLVM IR 既可以用作 LLVM 编译器的输入,也可以用作输出,供其他编译器或工具链使用。
LLVM IR 的两种存储形式:
-
LLVM IR 语言(文本形式,扩展名 .ll):可读的 LLVM IR 代码。
-
LLVM 位码(Bitcode,扩展名 .bc):二进制形式的 LLVM IR,主要用于高效存储和传输。
LLVM IR 是平台无关的,可以跨不同的硬件架构进行移植。
LLVM IR 语言参考手册:https://llvm.org/docs/LangRef.html
1.1 将 C 文件转换为 LLVM IR
生成文本形式的 LLVM IR(.ll 文件)
clang -S -emit-llvm hello.c -o hello.ll
以 hello.ll 为例,生成的内容大概如下
; Function Attrs: noinline nounwind optnone uwtable
; 这行注释了函数的属性
; - noinline: 该函数不会被内联(inline)。
; - nounwind: 该函数不会引发异常(不会 unwind stack)。
; - optnone: 编译器不会对此函数进行任何优化。
; - uwtable: 该函数有一个可用的异常处理表(unwind table)。
define dso_local i32 @main() #0 {
; define: 定义一个函数。
; dso_local: 该函数在本地动态库中可见(仅限于当前编译单元)。
; i32: 返回类型为 32 位整数。
; @main: 函数名为 'main'。
; #0: 函数使用属性组 0 中定义的属性(即上面注释的那一行)。
entry:
; entry: 函数的入口基本块(Basic Block)的标签。
%retval = alloca i32, align 4
; alloca: 在栈上分配内存。这里分配了一个 32 位整数(i32)。
; align 4: 分配的内存对齐到 4 字节边界。
; %retval: 变量的名称(SSA 变量),用于存储 main 函数的返回值。
store i32 0, ptr %retval, align 4
; store: 将一个值存储到内存地址中。
; i32 0: 要存储的值是 32 位整数 0(即 main 函数的返回值)。
; ptr %retval: 存储的位置是之前分配的栈变量 %retval。
; align 4: 存储操作按 4 字节对齐。
%call = call i32 (ptr, ...) @printf(ptr noundef @"??_C@_0P@MHJMLPNF@Hello?0?5World?$CB?6?$AA@")
; call: 调用一个函数,这里调用了 C 标准库的 printf 函数。
; i32 (ptr, ...): printf 函数的原型。它接受一个指针(通常是 C 字符串格式化字符串)和可变参数列表(...)。
; ptr noundef @"??_C@_0P@MHJMLPNF@Hello?0?5World?$CB?6?$AA@": 这是一个字符串常量的地址(指针)。
; noundef: 表示传递给函数的参数不会是未定义的(undefined)。
; %call: 保存 printf 的返回值(返回打印的字符数)。
ret i32 0
; ret: 返回指令,用于结束函数的执行。
; i32 0: main 函数返回 0(表示正常退出)。
}
1.2 生成二进制 LLVM IR(.bc 文件)
生成二进制 LLVM IR(.bc 文件)
clang -emit-llvm -c hello.c -o hello.bc
可以使用 llvm-dis 转换回文本形式
llvm-dis hello.bc -o hello.ll
1.3 使用 clang 编译 IR 文件
运行以下命令将 .ll 文件编译为可执行程序
clang hello.ll -o hello.exe
2. opt
opt 是 LLVM 工具链中的一个优化工具。它用于读取、优化和转换 LLVM IR 文件。
opt 工具通过应用各种优化 passes,可以显著提高程序的性能。
opt 的功能:
-
优化:对 LLVM IR 进行一系列优化(如常量折叠、循环展开、函数内联等)。
-
分析:生成各种分析报告(如控制流图、数据流分析)。
-
转换:将 LLVM IR 从一种形式转换为另一种形式。
显示所有可用的优化 passes
opt --help
优化 LLVM IR 文件
opt -O3 hello.ll -o hello_opt.bc
参数说明:
-
-O3:表示应用最高级别的优化(与 clang 的 -O3 类似)。
-
-o hello_opt.bc:输出优化后的文件
生成可读的 LLVM IR 文件
opt -O3 hello.bc -S -o hello_opt.ll
参数说明:
- -S:表示输出可读的文本格式(.ll 文件)。
应用特定的优化 pass,并查看优化效果。例如
opt -passes=mem2reg hello.ll -S -o hello_mem2reg.ll
参数说明:
-
-passes:应用特定的优化 pass
-
mem2reg:将栈上的内存变量提升为寄存器变量(即“提升” alloca 指令)。
-
-S:输出可读的 .ll 文件。
函数内联优化,将函数调用替换为函数体,从而减少函数调用的开销。
opt -passes=inline hello.ll -S -o hello_inline.ll
生成函数的控制流图(Control Flow Graph),输出为 .dot 文件,可以用 Graphviz 进行可视化。
opt -passes=dot-cfg hello.ll
3. 使用 Clion 调试 opt
使用 CLion 打开 llvm-project\llvm\CMakeLists.txt
作为项目打开
打开 CMake 设置,工具链使用 Visual Studio,点击应用
等待 Cmake 执行完成后,可以看到 opt 的运行/调试配置
编辑 opt 运行/调试配置,添加程序实参,比如
-O3 "D:\Projects\llvm-project\build\hello.ll" -o "D:\Projects\llvm-project\build\hello_opt.bc"
找到 main 函数(llvm/tools/opt/opt.cpp),并下断点