1 不支持类型的隐式转换
int factorial(int val);
int factorial(int val)
{
if (val <= 2)
return 1;
return factorial(val - 1) + factorial(val - 2);
}
int main(int argc, char **argv)
{
return factorial(2) * 7 == 42;
}
生成IR代码
clang++ -emit-llvm -S t3.cpp -o t3.ll
; ModuleID = 't3.cpp'
source_filename = "t3.cpp"
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: mustprogress noinline optnone uwtable
define dso_local noundef i32 @_Z9factoriali(i32 noundef %0) #0 {
%2 = alloca i32, align 4
%3 = alloca i32, align 4
store i32 %0, ptr %3, align 4
%4 = load i32, ptr %3, align 4
%5 = icmp sle i32 %4, 2 /* val <= 2 */
br i1 %5, label %6, label %7 /* if (val <= 2) 真:跳转到6 假:跳转到7 */
6: ; preds = %1
store i32 1, ptr %2, align 4 /* 1存到2号寄存器 */
br label %15
7: ; preds = %1
%8 = load i32, ptr %3, align 4
%9 = sub nsw i32 %8, 1
%10 = call noundef i32 @_Z9factoriali(i32 noundef %9)
%11 = load i32, ptr %3, align 4
%12 = sub nsw i32 %11, 2
%13 = call noundef i32 @_Z9factoriali(i32 noundef %12)
%14 = add nsw i32 %10, %13
store i32 %14, ptr %2, align 4
br label %15
15: ; preds = %7, %6
%16 = load i32, ptr %2, align 4 /* 2号寄存器中取出值 */
ret i32 %16
}
; Function Attrs: mustprogress noinline norecurse optnone uwtable
define dso_local noundef i32 @main(i32 noundef %0, ptr noundef %1) #1 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
%5 = alloca ptr, align 8
store i32 0, ptr %3, align 4
store i32 %0, ptr %4, align 4
store ptr %1, ptr %5, align 8
%6 = call noundef i32 @_Z9factoriali(i32 noundef 2)
%7 = mul nsw i32 %6, 7
%8 = icmp eq i32 %7, 42
%9 = zext i1 %8 to i32
ret i32 %9
}
attributes #0 = { mustprogress noinline optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { mustprogress noinline norecurse optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}
!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 8, !"PIC Level", i32 2}
!2 = !{i32 7, !"PIE Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 2}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"clang version 16.0.6 (https://github.com/llvm/llvm-project.git 7cbf1a2591520c2491aa35339f227775f4d3adf6)"}
严格类型机制,不支持任何形式的隐式转换,例如
ret i32 %16
改成
ret i32 %5
执行opt -passes=verify --color t3.ll
$ opt -passes=verify --color t3.ll
opt: t3.ll:32:11: error: '%5' defined with type 'i1' but expected 'i32'
ret i32 %5
2 善用语法手册
例如上面的%6 = call noundef i32 @_Z9factoriali(i32 noundef 2)
函数调用语法,如何找到call的全部使用方法?
语法手册
语法
案例
递归调用案例
3 Basic Blocks:基本块
基本块在 LLVM 中起着重要的作用,它们用于进行优化、分析和代码生成。基本块可以被视为一个原子操作单元,可以在其中执行各种优化技术,例如常量传播、复制传播、死代码消除等。基本块还可以用于生成目标代码,因为它们提供了代码的基本结构。
在上面案例中:
4 callgraph
IR支持打印callgraph:
$ opt -passes=print-callgraph t3.ll
Call graph node <<null function>><<0x6e5ef20>> #uses=0
CS<None> calls function '_Z9factoriali'
CS<None> calls function 'main'
Call graph node for function: '_Z9factoriali'<<0x6e63490>> #uses=4
CS<0x6e5df60> calls function '_Z9factoriali'
CS<0x6e5e070> calls function '_Z9factoriali'
Call graph node for function: 'main'<<0x6e63570>> #uses=1
CS<0x6e5e8c0> calls function '_Z9factoriali'
5 IR遵循SSA规则
Static Single Assignment (SSA):
- Every variable is assigned exactly once.
- Every variable is defined before it is used.
每个变量只能赋值一次。
变量在使用前必须定义。
为什么要这么做?如果编译器遇到如下代码
x = 100
x = 200
a = x
明显第一个x=100是无效的,但编译器需要去选择保留100还是200。
如果遵循SSA规则:
x1 = 100
x2 = 200
a = x2
编译器无需选择,可以直接抛弃x1的值即可。
当然这只是SSA的一个基本的使用场景,有些更复杂的优化必须基于SSA来简化场景。
5 IR结构
6 todo
用到的话继续把Tutorial-Bridgers-LLVM_IR_tutorial.pdf指针、类型部分看完。
.c -> .ll:clang -emit-llvm -S a.c -o a.ll
.c -> .bc: clang -emit-llvm -c a.c -o a.bc
.ll -> .bc: llvm-as a.ll -o a.bc
.bc -> .ll: llvm-dis a.bc -o a.ll
.bc -> .s: llc a.bc -o a.s