胡琪

为今天工作,为明天投资,为未来孵化一些东西

llvm IR指令与llvm开发中对应的类与API

llvm中间语言IR介绍中介绍了llvm中间语言IR相关的语法,重点介绍了指令相关的语法,llvm相关的语法远不止这些,之所以重点介绍指令相关的语法是因为我们学些llvm更多的是为了进行相关中间代码的优化,即基于llvm框架进行开发。而llvm开发本质上就类似用指令去完成函数功能,所以除了学习掌握中间语言IR的语法之外,更需要学会使用llvm提供给我们的API去实现这些功能,也就是需要掌握IR中相应指令在llvm中对应的类与API接口。

llvm开发官方文档

llvm的官方文档地址为:http://llvm.org/doxygen/modules.html ,这里详细介绍了llvm的所有模块,如下是其core部分

《llvm IR指令与llvm开发中对应的类与API》

这一部分也是和我们使用llvm进行开发最相关的一部分。可以看到llvm的core模块主要包括以下几个部分

  1. 环境相关部分,如Contexts,Modules
  2. 类型Types
  3. 值Values
  4. 元数据Metadata
  5. 基本块Basic Block
  6. 指令Instructions

其中比较重要的是值Values,基本块BasicBlock和指令Instructions,llvm开发基本上使用最多的就是和这几个类相关的API接口,其中Values中使用最多就是常量Constant,从上图可以看到Constant主要可以概括为以下几个部分

  • 标量常量Scalar Constants,比如整形常量ConstantInt,浮点型常量ConstantFP
  • 组合常量Composite Constants,比如常量结构体ConstStruct,常量数组ConstantDataArray等
  • 常量表达式Constant Expressions,主要是ConstantExpr这个类,该类有一个及其重要的API接口getGetElementPtr用来从多元素常量中获取首地址,比如获取数组,字符串的首地址
  • 全局值Global Values,是全局变量和函数值的间接基类,换而言之就是全局值包括全局变量GlobalVariable和函数Function,全局值都有一个很重要的属性就是连接属性
  • 全局变量Global Variables
  • 全局别名Global Aliases
  • 函数值Function values

从上面的图例中也可以看到我们开发中经常操作的一些元素在llvm中都是常量Constant的直接或间接子类,比如整形,浮点型,数组,字符串等,换而言之就是都是常量,这里的常量不是针对c/c++源码而言的,而是针对IR系统而言的

指令构建帮助类IRBuilder

IRBuilder可以理解为是为方便开发者创建和插入指令而提供的一个帮助类,其作用是提供大量的用于创建/插入指令的API接口,这样做的好处是把原本需要在很多单个类中创建指令的API的功能集中到了IRBuilder这一个类中,举个例子:要创建一个IR中的gep指令,如果使用对应的类的话需要调用GetElementPtrInst类的Create函数,要创建一个alloca指令使用对应类的话需要使用AllocaInst类的构造函数AllocaInst,如果使用IRBuilder类则创建gep指令只需调用IRBuilder类的CreateGEP函数,创建alloca指令只需调用IRBuilder类的CreateAlloca函数,这样开发者就不需要记住很多类,如果有很多指令要创建,使用IRBuilder来创建指令比使用该指令对应的原始类来创建要方便的多。关于IRBuilder的官方介绍可以参看:http://llvm.org/doxygen/classllvm_1_1IRBuilder.html

IRBuilder类一些重要的API接口和功能如下所示:

SetInsertPoint系列

SetInsertPoint系列API的作用是设置要插入指令的位置,比如在基本块的后面或某指令的前面等,包括3个重载函数,这3个重载函数的详细定义可参看官网http://llvm.org/doxygen/classllvm_1_1IRBuilderBase.html#ace45cae6925c65e9d6916e09dd5b17cc,以参数为基本块的API为例:

TheBB形参表示要将创建的指令追加到哪个基本块的末尾,也就是插入的指令属于哪个基本块,换而言之,调用该语句之后,所有的创建指令的语句创建的指令都将依次添加至该基本块末尾,举个简单例子:

如上一些代码则创建的指令所属的基本块如上面的分割线所示

常量

整形常量

整形常量在llvm中用ConstantInt表示,创建一个整形常量可以使用get函数,其定义如下:

第一个参数Ty表示整形的类型,如int8,int32等,第二个参数是要创建的常量的值,第三个参数isSigned表示是否是有符号数,默认为false。比如在c/c++代码中有如下一段语句

即创建一个整数(32位)值为0,那么对应的llvm代码如下所示:

Value是ConstantInt的间接基类,可以看到在llvm中几乎所有的操作都是通过指针进行的,get函数返回的是指向了值为0的那块内存的地址。

如前面所述,如果使用IRBuilder那么代码可以简化为:

浮点型常量

浮点型常量在llvm中用ConstantFP表示,创建一个浮点形常量也使用get函数,其定义如下:

第一个参数Ty表示浮点数类型,第二个参数表示要创建的浮点型常量的值。如下是一个简单示例代码:

 

常量数组

在llvm中常量数组用ConstantDataArray类表示,该类继承自ConstantDataSequential(常量数据序列,后面简称为CDS)

ConstantDataSequential

CDS有一些重要的API接口

  • getElementAsXX系列,获取第i处索引的值按照XX类型,比如uint64_t getElementAsInteger(unsigned i) float getElementAsFloat(unsigned i)
  • getNumElements()返回该CDS的元素个数
  • getElementByteSize()返回CDS每个元素的大小按照byte计算
  • isString()/isCString()该CDS是否是字符串/C风格字符串
  • StringRef getAsString()/StringRef getAsCString() 如果该CDS是字符串类型,则返回其对应的字符串的StringRef表示(返回值StringRef是llvm中的字符串类)
  • StringRef getRawDataValues()返回该CDS的原始的底层字节(Return the raw, underlying, bytes of this data)

CDS提供的API接口不止上面这些,但是上面这些是相对而言非常重要的,在实际开发中可能会经常使用到的一些API接口,比如如果我们想要修改IR当中的某些全局字符串变量(在llvm中用GlobalVariable类表示)的值,可以先将全局变量(假设叫做gv)转为CDS,然后使用getRawDataValues()方法得到该CDS的StringRef表示,然后取其data字段即可得到该字符串变量的起始地址的指针。然后就可以通过指针操作修改其值。如下代码是个简单示例:

ConstantDataArray

常量数组中有一个重要API接口getString,因为该API接口很重要,所以看下其定义与注释:

《llvm IR指令与llvm开发中对应的类与API》

从注释可以看到该函数可以构造一个CDS通过传入的字符串StringRef。也正是因为这个特性,在llvm中创建一个字符串可以使用常量数组ConstantDataArray这个类,然后将其传递给一个全局变量GlobalVariable进行初始化(原因在llvm中间语言IR介绍中讲过c/c++源码中定义的所有的字符串在IR的Module中是以全局变量存在的,以@开头),如下是一个示例代码:

字符串

在llvm中没有专门的字符串类,因为实际上对于计算机底层而言都是对内存操作,字符串类可以看做是char[],而每一个char是8位,所以字符串可以看做是一个常量序列,该常量序列的每一个元素是无符号int8类型。正如前面所说的创建字符串对象可以使用ConstantDataArray的getString方法,然后将其传递给一个全局变量作为初始化的参数,另外一般而言c/c++中的很多函数对于字符串的操作都是通过传递该字符串首地址的指针进行的,比如printf函数。所以我们可以在前面那段代码基础上增加一个获取该变量首地址的操作,获取字符串首地址可以使用ConstantExpr这个类的getGetElementPtr函数。此时对字符串的操作的代码如下所示:

另外llvm中临时字符串可以使用StringRef和Twine来描述。

全局值GlobalValue

http://llvm.org/doxygen/classllvm_1_1GlobalValue.html, 全局值也是常量,继承自Constant,主要包括函数Function和全局变量GlobalVariable

GlobalVariable

全局变量GlobalVariable,前面说过GlobalVariable实际上也是常量Constant,那为何叫做全局变量呢?我的理解是这里的全局是从生命周期角度来说的,而不是作用域,变量是相对原c/c++代码来说的,而常量是相对于IR而言的。比如在c/c++代码的函数中创建的一个局部的字符串变量,最终在IR中会以GlobalVariable的形式存在。该类的构造函数主要包括如下2中形式:

《llvm IR指令与llvm开发中对应的类与API》

一般而言创建一个全局变量使用第2中重载形式,即包括Module参数,如下是一个简单的创建全局变量的示例代码:

其各个参数的含义如下:

  • Module:该GV所在的Module
  • Ty:该GV的类型,比如上面那个示例代码表示是数组类型
  • isConstant:是否是常量,如果传递true,则表示该GV在程序中不可变,此时就对应c/c++中的常量概念了
  • Linkage:链接属性
  • Initializer:初始化常量,也就是该GV指针指向的内存。
  • Name:该GV的名称,会体现在中间语言IR中

对于全局变量GlobalVariable而言,必须进行初始化,也就是说必须调用setInitializer()函数或者在创建时在构造函数中通过Initializer参数设置该GV所指向的内存,否则在后端进行编译处理时会抛出异常。如果该值等于NULL则会将原GV指向的地址取消,重新指向NULL。这和c语言中的指针使用是很像的,举个例子就很容易明白了。

Function

Function是llvm层次结构中较复杂的类,通常包括基本块,形参列表,形参和指令的符号表。

其中FunctonType参数用来指定该函数的返回值类型,参数类型等,其定义如下:

如下是一个简单的创建函数的示例代码:

该函数无基本块,所以相当于c/c++中的函数声明而不是定义,如果要定义一个函数需要在该函数中插入基本块,如下是个简单示例

所以一个简单的完整的创建函数的示例代码如下:

 

打赏

点赞

发表评论

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