llvm学习(七):IR 的基础结构

手头的整个项目差不多完结了,一个月左右的时间,对 llvm 的 pass 理解感觉还行,稍微总结一下从易到难的知识点吧。

一、llvm 的背景知识

简介的话网上搜吧,说下我的理解。llvm 的前端是从 源代码 转化为 IR  的,之后 IR  经过 N 个 Pass 的处理、优化后,生成一份最终的 IR  表示,再生成 llvm 专用的 bitcode ,最后使用后端,将这个 bitcode转化为目标架构的可执行文件。

本文的重点是 IR  的结构,属于前端的内容,因为后面要讲 Pass  的编写,所以先把基础知识讲一下。

二、Module

Module 可以被视为一个.c文件的 IR  表示,如果用Java的描述的话, Module相当于Java里的类,是独立存在的一个东西。

每个 Module都是相对独立的东西,主要包含了声明或者定义的函数、声明或定义的全局变量、当前 Module的基本信息(例如目标架构、数据布局等我不太懂的东西),因为在开发 Pass的过程中,基本打交道的就是 Function 和 GlobalVariable 这两个东西。

多个 Module之间是相互隔离的、无法获取对方的内容,跟Java里类的设定真的非常像。平时为了获取 Module 的主要信息,使用它的 M.dump() 方法就会在屏幕上打印出全部信息,非常非常的方便。

三、Function

Function,是 Module中以List的方式存放的,如果用Java的描述的话, Function相当于Java里的Method,无法单独存在,必须是在某个 Module里的。主要包含了大量 BasicBlock 、参数和返回值类型、可变参数列表、函数的attribute和其他函数的基本信息(例如函数名、所在 Module等)

Function由无数个 BasicBlock组成,使用列表存放,有且仅有一个 EntryBlock ,是列表中的第一个 BasicBlock,代码真正执行的时候,就从 EntryBlock开始执行。因为函数不一定只有一个出口,所以 Function是无法知道它退出时的Block的,没有这个API,如果非要知道的话可以手动去判断每个 BasicBlock是否有后继。

Function有两个很实用的函数, F.dump() 可以打印出全部信息, F.viewCfg() 可以将ControlFlowGraph按照dot的方式存到文件里,使用第三方工具可以很舒服地观察它,在Mac上叫Graphviz.app,效果如图。

四、BasicBlock

BasicBlock,是 Function 中以List方式存放的,和Soot里的 BasicBlock概念很像,无法单独存在,必须是在某个Function里的,是真正存放可执行代码的容器。主要包含了大量的 Instruction ,前驱、后继的 BasicBlock,以及一些基本信息(例如名字什么的),相比 Function ,它的校验也更加严格,例如不可以凭空出现、不可以处于游离状态。

BasicBlock由很多 Instruction组成,按照是否为 TerminatorInst  可以将指令分为两类,一类是普通的指令,一类是可以成为 TerminatorInst 的指令;因此 BasicBlock一定要以 TerminatorInst类的指令结尾,而且除了最后一个指令是 TerminatorInst,其他指令都是普通指令。常见的 TerminatorInst 有 BranchInst 、 IndirectBrInst 、 SwitchInst 和 Return ,C++里还有一些异常处理的懒得列出了。

每个 BasicBlock都可以视为一段顺序执行的代码,完全不会有任何的分支,有分支就会通过 TerminatorInst进行跳转。

BasicBlock 有两种创建方式,一种是凭空创建,然后插入到之前的CFG里;一种比较方便,使用 SplitBasicBlock ,切为相连的两块,可能会遇到PhiNode的问题。

五、Instruction

Instruction ,是 BasicBlock 中以List方式存放的,和Soot里的Stmt的概念很像,无法单独存在,必须是在某个 BasicBlock 里的,可以视为 IR  层面的汇编语句,或者说指令。类型非常多,一开始觉得很混乱,干什么都得查API,用的多了就记住了,多看官方的那个继承关系图。随便举个例子,例如在栈上申请一个int32,就用 AllocaInst ,再加上int32的数据类型。

在开发过程中,一旦遇到什么 Instruction不会写,千万千万不要去查文档,应该先写一份c代码,看看自动生成的 IR  长什么样,再朝着这个方向去努力(绝大部分问题都是这么解决的)。

将来会新开一篇,专门讲 Instruction的常见用法。

六、Operand

这个其实比较宽泛,表示 Instruction里的各个参数(如果有的话)。每个 Operand 都是 Value ,就是任意的东西,怎么说呢, Function可以作为某些指令的参数、 BasicBlock也可以作为某些指令的参数、甚至 Instruction本身也可以作为某些指令的参数,所以其实这是一个很开发的概念。大部分指令在构造时候,对参数类型都是有要求的,会直接抛 assert 错误,所以也不用太担心用错类型。

七、万物皆为Value(不够严谨)

上文所说的各个类,都有一个共同的父类,那就是 Value,我把它理解为了Java里的Object,到处都可以用,平时不知道用什么类型去描述一个变量时,直接上 Value *就可以了,将来想知道类型可以使用 isa<Instruction>(V) 来确认,再用 cast<Instruction>(V) 来进行转换。是一个非常好用的类型,(因为我不喜欢用C++里的auto)

八、总结

本文就是随便写写,把常见概念科普一下。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code