LLVM学习笔记
- 【Getting Started with LLVM Core Libraries】P30 3.4 实验 使用独立工具
-
- 我们来看一个由分散在多个源文件中的函数组成的简单的C程序。
- 但是,我们使用独立工具也可以获得相同的结果。
- 为了继续完成编译,后续步骤可以采取以下两种方式:
-
- 从每个LLVM位码文件生成特定于目标的目标文件,并通过将其链接到系统链接器来构建可执行程序:
- 先将两个LLVM位码文件连接成最终的LLVM位码文件。然后,从最终的位码文件构建特定于目标的目标文件,并通过电泳系统链接程序来生成可执行程序:
- **main.ll** 文件内容
- **sum.ll** 文件内容
【Getting Started with LLVM Core Libraries】P30 3.4 实验 使用独立工具
我们可以通过使用LLVM独立工具来练习编译工作流程,这会将一个工具的输出链接到另一个工具的输出。虽然将中间文件写入磁盘会导致编译速度减慢但是观察编译流水过程是一个有趣的教学练习。这个过程也让你有机会微调中间工具的参数,一种一些工具如下:
- opt:这是一个旨在IR级对程序进行优化的工具。输入必须是LLVM位码文件(编码的LLVM IR),并且生成的输出文件必须具有相同的类型。
- llc:这是一个通过特定后端将LLVM位码转换成目标机器汇编语言文件或目标文件的工具。你可以通过传递参数来选择优化级别、打开调试选项以及启用或禁用特定于目标的优化。
- llvm-mc:这个工具能够汇编指令并生成诸如ELF、MachO和PE等对象格式的目标文件。它也可以反汇编相同的对象,从而转储这些指令的相应的汇编信息和内部LLVM机器指令数据结构。
- lli:这个工具是LLVM IP的解释器和JIT编译器。
- llvm-link:这个工具将几个LLVM位码链接在一起,以产生一个包含所有输入的LLVM位码。
- llvm-as:该工具将人工可读的LLVM IR文件(称为LLVM汇编码)转换为LLVM位码。
- llvm-dis:这个工具将LLVM位码解码成LLVM汇编码。
我们来看一个由分散在多个源文件中的函数组成的简单的C程序。
第一个源文件是main.c,它的内容如下:
#include <stdio.h>int sum(int x, int y);int main() {
int r = sum(3, 4);printf("r = %d\n", r);return 0;
}
第二个文件是sum.c,它的内容如下:
int sum(int x, int y) {
return x + y;
}
我们可以用下面的命令编译这个C程序:
clang main.c sum.c -o sum
运行结果:会生成sum文件。
main.c sum sum.c
但是,我们使用独立工具也可以获得相同的结果。
首先,我们改变clang命令以便为每个C源文件生成LLVM位码文件,然后停下来,而不是继续完成编译:
clang -emit-llvm -c main.c -o main.bc
clang -emit-llvm -c sum.c -o sum.bc
运行结果:会生成main.bv和sum.bc文件。
main.bc main.c sum sum.bc sum.c
-emit-llvm标志告诉clang根据是否存在**-c或-S标志来生成LLVM位码或LLVM汇编码文件。在前面的示例中,-emit-llvm和-c标志一起使用,将告诉clang以LLVM位码格式生成一个目标文件。使用-flto -c**标志组合可以得到相同的结果。如果你打算生成人工可读的LLVM汇编码,请使用下述两个命令:
clang -emit-llvm -S -c main.c -o main.ll
clang -emit-llvm -S -c sum.c -o sum.ll
运行结果:会生成main.ll和sum.ll文件。
main.bc main.c main.ll sum sum.bc sum.c sum.ll
生成的文件内容可读,具体内容附在文章最后。
请注意,如果没有-emit-llvm或-flto标志,则-c标志将生成一个包含目标机器语言的目标文件,而-S将生成目标汇编语言文件。这种行为与GCC兼容。
.bc和.ll分别是LLVM位码和汇编文件的文件扩展名。
为了继续完成编译,后续步骤可以采取以下两种方式:
从每个LLVM位码文件生成特定于目标的目标文件,并通过将其链接到系统链接器来构建可执行程序:
llc -filetype=obj main.bc -o main.o
llc -filetype=obj sum.bc -o sum.o
clang main.o sum.o -o sum1
运行结果:会生成main.o和sum.o文件,并生成可执行文件sum1(为与前面生成的文件区分,改名为sum1)。
main.bc main.c main.ll main.o sum sum1 sum.bc sum.c sum.ll sum.o
先将两个LLVM位码文件连接成最终的LLVM位码文件。然后,从最终的位码文件构建特定于目标的目标文件,并通过电泳系统链接程序来生成可执行程序:
llvm-link main.bc sum.bc -o sum.linked.bc
llc -filetype=obj sum.linked.bc -o sum.linked.o
clang sum.linked.o -o sum
运行结果:会生成sum.linked.bc和sum.linked.o文件,并生成可执行文件sum2(为与前面生成的文件区分,改名为sum2)。
main.bc main.c main.ll main.o sum sum1 sum2 sum.bc sum.c sum.linked.bc sum.linked.o sum.ll sum.o
-filetype=obj参数指定输出一个目标文件,而不是目标汇编文件。我们使用Clang驱动程序clang来调用链接器,但是,如果知道系统链接器与系统库链接所需要的所有参数,则可以直接使用系统链接器。
通过在后端调用(llc)之前链接IR文件,将允许最终生成的IR能够被opt工具提供的链接时优化机制进一步优化。另外,llc工具可以生成汇编输出,可以使用llvm-mc对该输出进行进一步汇编。
main.ll 文件内容
; ModuleID = 'main.c'
source_filename = "main.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"@.str = private unnamed_addr constant [8 x i8] c"r = %d\0A\00", align 1; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
entry:%retval = alloca i32, align 4%r = alloca i32, align 4store i32 0, i32* %retval, align 4%call = call i32 @sum(i32 3, i32 4)store i32 %call, i32* %r, align 4%0 = load i32, i32* %r, align 4%call1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str, i64 0, i64 0), i32 %0)ret i32 0
}declare dso_local i32 @sum(i32, i32) #1declare dso_local i32 @printf(i8*, ...) #1attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }!llvm.module.flags = !{
!0}
!llvm.ident = !{
!1}!0 = !{
i32 1, !"wchar_size", i32 4}
!1 = !{
!"clang version 10.0.0 (http://llvm.org/git/clang.git 65acf43270ea2894dffa0d0b292b92402f80c8cb) (http://llvm.org/git/llvm.git 2c4ca6832fa6b306ee6a7010bfb80a3f2596f824)"}
sum.ll 文件内容
; ModuleID = 'sum.c'
source_filename = "sum.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @sum(i32 %x, i32 %y) #0 {
entry:%x.addr = alloca i32, align 4%y.addr = alloca i32, align 4store i32 %x, i32* %x.addr, align 4store i32 %y, i32* %y.addr, align 4%0 = load i32, i32* %x.addr, align 4%1 = load i32, i32* %y.addr, align 4%add = add nsw i32 %0, %1ret i32 %add
}attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }!llvm.module.flags = !{
!0}
!llvm.ident = !{
!1}!0 = !{
i32 1, !"wchar_size", i32 4}
!1 = !{
!"clang version 10.0.0 (http://llvm.org/git/clang.git 65acf43270ea2894dffa0d0b292b92402f80c8cb) (http://llvm.org/git/llvm.git 2c4ca6832fa6b306ee6a7010bfb80a3f2596f824)"}