没有合适的资源?快使用搜索试试~ 我知道了~
首页LLVM IR入门指南
LLVM IR入门指南
5星 · 超过95%的资源 需积分: 45 70 下载量 149 浏览量
更新于2023-03-03
评论 6
收藏 705KB PDF 举报
转自:https://github.com/Evian-Zhang/llvm-ir-tutorial 一份不错的LLVM中文学习文档,LLVM作为将来主流的现代编译器发展方向,非常强大,值得深入学习
资源详情
资源评论
资源推荐
LLVM架构简介
LLVM是什么
随着计算机技术的不断发展以及各种领域需求的增多,近⼏年来,许多编程语⾔如⾬后春笋般出现,⼤
多为了解决某⼀些特定领域的需求,⽐如说为JavaScript增加静态类型检查的TypeScript,为解决服务
器端⾼并发的Golang,为解决内存安全和线程安全的Rust。随着编程语⾔的增多,编程语⾔的开发者往
往都会遇到⼀些相似的问题:
怎样让我的编程语⾔能在尽可能多的平台上运⾏
怎样让我的编程语⾔充分利⽤各个平台⾃身的优势,做到最⼤程度的优化
怎样让我的编程语⾔在汇编层⾯实现「定制」,能够控制如符号表中的函数名、函数调⽤时参数的
传递⽅法等汇编层⾯的概念
有的编程语⾔选择了使⽤C语⾔来解决这种问题,如早期的Haskell等。它们将使⽤⾃⼰语⾔的源代码编
译成C代码,然后再在各个平台调⽤C编译器来⽣成可执⾏程序。为什么要选择C作为⽬标代码的语⾔
呢?有⼏个原因:
第⼀,绝⼤部分的操作系统都是由C和汇编语⾔写成,因此平台⼤多会提供⼀个C编译器可以使⽤,这样
就解决了第⼀个问题。
第⼆,绝⼤部分的操作系统都会提供C语⾔的接⼝,以及C库。我们的编程语⾔因此可以很⽅便地调⽤相
应的接⼝来实现更⼴泛的功能。
第三,C语⾔本身并没有笨重的运⾏时,代码很贴近底层,可以使⽤⼀定程度的定制。
以上三个理由让许多的编程语⾔开发者选择将⾃⼰的语⾔编译成C代码。
然⽽,我们知道,⼀个平台最终运⾏的⼆进制可执⾏⽂件,实际上就是在运⾏与之等价的汇编代码。与
汇编代码⽐起来,C语⾔还是太抽象了,我们希望能更灵活地操作⼀些更底层的部分。同时,我们也希
望相应代码在各个平台能有和C语⾔⼀致,甚⾄⽐其更好的优化程度。
因此,LLVM出现后,成了⼀个更好的选择。我们可以从LLVM官⽹中看到:
The LLVM Core libraries provide a modern source- and target-independent optimizer, along
with code generation support for many popular CPUs (as well as some less common ones!)
These libraries are built around a well specified code representation known as the LLVM
intermediate representation ("LLVM IR"). The LLVM Core libraries are well documented, and
it is particularly easy to invent your own language (or port an existing compiler) to use LLVM
as an optimizer and code generator.
简单地说,LLVM代替了C语⾔在现代语⾔编译器实现中的地位。我们可以将⾃⼰语⾔的源代码编译成
LLVM中间代码(LLVM IR),然后由LLVM⾃⼰的后端对这个中间代码进⾏优化,并且编译到相应的平
台的⼆进制程序。
LLVM的优点正好对应我们之前讲的三个问题:
LLVM后端⽀持的平台很多,我们不需要担⼼CPU、操作系统的问题(运⾏库除外)
LLVM后端的优化⽔平较⾼,我们只需要将代码编译成LLVM IR,就可以由LLVM后端作相应的优化
LLVM IR本身⽐较贴近汇编语⾔,同时也提供了许多ABI层⾯的定制化功能
因为LLVM的优越性,除了LLVM⾃⼰研发的C编译器Clang,许多新的⼯程都选择了使⽤LLVM,我们可
以在其官⽹看到使⽤LLVM的项⽬的列表,其中,最著名的就是Rust、Swift等语⾔了。
LLVM架构
要解释使⽤LLVM后端的编译器整体架构,我们就拿最著名的C语⾔编译器Clang为例。
在⼀台x86_64指令集的macOS系统上,我有⼀个最简单的C程序 test.c :
我们使⽤
究竟经历了哪⼏个步骤呢?
前端的语法分析
⾸先,Clang的前端编译器会将这个C语⾔的代码进⾏预处理、语法分析、语义分析,也就是我们常说的
parse the source code。这⾥不同语⾔会有不同的做法。总之,我们是将「源代码」这⼀字符串转化为
内存中有意义的数据,表示我们这个代码究竟想表达什么。
我们可以使⽤
输出我们 test.c 经过编译器前端的预处理、语法分析、语义分析之后,⽣成的抽象语法树(AST):
int main() {
return 0;
}
1
2
3
clang test.c -o test1
clang -Xclang -ast-dump -fsyntax-only test.c1
TranslationUnitDecl 0x7fc02681ea08 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x7fc02681f2a0 <<invalid sloc>> <invalid sloc> implicit
__int128_t '__int128'
| `-BuiltinType 0x7fc02681efa0 '__int128'
|-TypedefDecl 0x7fc02681f310 <<invalid sloc>> <invalid sloc> implicit
__uint128_t 'unsigned __int128'
| `-BuiltinType 0x7fc02681efc0 'unsigned __int128'
|-TypedefDecl 0x7fc02681f5f8 <<invalid sloc>> <invalid sloc> implicit
__NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x7fc02681f3f0 'struct __NSConstantString_tag'
| `-Record 0x7fc02681f368 '__NSConstantString_tag'
|-TypedefDecl 0x7fc02681f690 <<invalid sloc>> <invalid sloc> implicit
__builtin_ms_va_list 'char *'
| `-PointerType 0x7fc02681f650 'char *'
| `-BuiltinType 0x7fc02681eaa0 'char'
1
2
3
4
5
6
7
8
9
10
11
这⼀⻓串输出看上去就让⼈眼花缭乱,然⽽,我们只需要关注最后四⾏:
这才是我们源代码的AST。可以很⽅便地看出,经过Clang前端的预处理、语法分析、语义分析,我们的
代码被分析成⼀个函数,其函数体是⼀个复合语句,这个复合语句包含⼀个返回语句,返回语句中使⽤
了⼀个整型字⾯量 0 。
因此,总结⽽⾔,我们基于LLVM的编译器的第⼀步,就是将源代码转化为内存中的抽象语法树AST。
前端⽣成中间代码
第⼆个步骤,就是根据内存中的抽象语法树AST⽣成LLVM IR中间代码(有的⽐较新的编译器还会先将
AST转化为MLIR再转化为IR)。
我们知道,我们写编译器的最终⽬的,是将源代码交给LLVM后端处理,让LLVM后端帮我们优化,并编
译到相应的平台。⽽LLVM后端为我们提供的中介,就是LLVM IR。我们只需要将内存中的AST转化为
LLVM IR就可以放⼿不管了,接下来的所有事都是LLVM后端帮我们实现。
关于LLVM IR,我在下⾯会详细解释。我们现在先看看将AST转化之后,会产⽣什么样的LLVM IR。我们
使⽤
这时,会⽣成⼀个 test.ll ⽂件:
|-TypedefDecl 0x7fc02681f968 <<invalid sloc>> <invalid sloc> implicit
__builtin_va_list 'struct __va_list_tag [1]'
| `-ConstantArrayType 0x7fc02681f910 'struct __va_list_tag [1]' 1
| `-RecordType 0x7fc02681f770 'struct __va_list_tag'
| `-Record 0x7fc02681f6e8 '__va_list_tag'
`-FunctionDecl 0x7fc02585a228 <test.c:1:1, line:3:1> line:1:5 main 'int
()'
`-CompoundStmt 0x7fc02585a340 <col:12, line:3:1>
`-ReturnStmt 0x7fc02585a330 <line:2:5, col:12>
`-IntegerLiteral 0x7fc02585a310 <col:12> 'int' 0
12
13
14
15
16
17
18
19
`-FunctionDecl 0x7fc02585a228 <test.c:1:1, line:3:1> line:1:5 main 'int ()'
`-CompoundStmt 0x7fc02585a340 <col:12, line:3:1>
`-ReturnStmt 0x7fc02585a330 <line:2:5, col:12>
`-IntegerLiteral 0x7fc02585a310 <col:12> 'int' 0
1
2
3
4
clang -S -emit-llvm test.c1
; ModuleID = 'test.c'
source_filename = "test.c"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.15.0"
; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @main() #0 {
%1 = alloca i32, align 4
store i32 0, i32* %1, align 4
1
2
3
4
5
6
7
8
9
这看上去更加让⼈迷惑。然⽽,我们同样地只需要关注五⾏内容:
这是我们AST转化为LLVM IR中最核⼼的部分,可以隐约感受到这个代码所表达的意思。
LLVM后端优化IR
LLVM后端在读取了IR之后,就会对这个IR进⾏优化。这在LLVM后端中是由 opt 这个组件完成的,它会
根据我们输⼊的LLVM IR和相应的优化等级,进⾏相应的优化,并输出对应的LLVM IR。
我们可以⽤
对相应的代码进⾏优化,也可以直接⽤
优化,并输出相应的优化结果:
ret i32 0
}
attributes #0 = { noinline nounwind optnone ssp uwtable "correctly-
rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "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" "probe-stack"="___chkstk_darwin"
"stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-
features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x8
7" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}
!0 = !{i32 2, !"SDK Version", [3 x i32] [i32 10, i32 15, i32 4]}
!1 = !{i32 1, !"wchar_size", i32 4}
!2 = !{i32 7, !"PIC Level", i32 2}
!3 = !{!"Apple clang version 11.0.3 (clang-1103.0.32.62)"}
10
11
12
13
14
15
16
17
18
19
20
21
define i32 @main() #0 {
%1 = alloca i32, align 4
store i32 0, i32* %1, align 4
ret i32 0
}
1
2
3
4
5
opt test.ll -S -O31
clang -S -emit-llvm -O3 test.c1
; ModuleID = 'test.c'
source_filename = "test.c"
1
2
我们观察 @main 函数,可以发现其函数体确实减少了不少。
LLVM后端⽣成汇编代码
LLVM后端帮我们做的最后⼀步,就是由LLVM IR⽣成汇编代码,这是由 llc 这个组件完成的。
我们可以⽤
⽣成 test.s :
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.15.0"
; Function Attrs: norecurse nounwind readnone ssp uwtable
define i32 @main() local_unnamed_addr #0 {
ret i32 0
}
attributes #0 = { norecurse nounwind readnone ssp uwtable "correctly-
rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "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" "probe-stack"="___chkstk_darwin"
"stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-
features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x8
7" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}
!0 = !{i32 2, !"SDK Version", [3 x i32] [i32 10, i32 15, i32 4]}
!1 = !{i32 1, !"wchar_size", i32 4}
!2 = !{i32 7, !"PIC Level", i32 2}
!3 = !{!"Apple clang version 11.0.3 (clang-1103.0.32.62)"}
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
llc test.ll1
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15, 4
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
### %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
1
2
3
4
5
6
7
8
9
10
11
剩余52页未读,继续阅读
昵称什么的最烦了啊
- 粉丝: 3
- 资源: 20
上传资源 快速赚钱
- 我的内容管理 收起
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
会员权益专享
最新资源
- c++校园超市商品信息管理系统课程设计说明书(含源代码) (2).pdf
- 建筑供配电系统相关课件.pptx
- 企业管理规章制度及管理模式.doc
- vb打开摄像头.doc
- 云计算-可信计算中认证协议改进方案.pdf
- [详细完整版]单片机编程4.ppt
- c语言常用算法.pdf
- c++经典程序代码大全.pdf
- 单片机数字时钟资料.doc
- 11项目管理前沿1.0.pptx
- 基于ssm的“魅力”繁峙宣传网站的设计与实现论文.doc
- 智慧交通综合解决方案.pptx
- 建筑防潮设计-PowerPointPresentati.pptx
- SPC统计过程控制程序.pptx
- SPC统计方法基础知识.pptx
- MW全能培训汽轮机调节保安系统PPT教学课件.pptx
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功
评论5