LLVM IR官方文档:https://llvm.org/docs/Reference.html#llvm-ir
本文只是学习并记录笔记,如有错误或不足请谅解指正,谢谢!
IR概述
LLVM IR(Intermediate Representation)是LLVM的代码表示,是用于在编译器中表示代码的形式。旨在轻量级和低级,同时具有表现力、类型和可扩展性。旨在成为某种“通用 IR”,通过处于足够低的级别,可以将高级想法清晰地映射到它。
LLVM 是一种基于静态单一分配 (SSA) 的表示。
LLVM IR有三种形式:
①内存中的编译中间表示
②磁盘上的二进制码 bitcode(例如.bc文件)
③可读汇编文本(例如.ll文件)
这三种形式之间是等价的。
IR结构:
- Module,可以被视为一个.c文件的 IR 表示,每个 Module都是相对独立的东西,主要包含了声明或者定义的函数、声明或定义的全局变量、当前 Module的基本信息
- Function,是 Module中以List的方式存放的。函数内的第一个基本块叫做入口基本块。无法单独存在,必须是在某个 Module里,包含了大量 BasicBlock 、参数和返回值类型、可变参数列表、函数的attribute和其他函数的基本信息
- BasicBlock,是 Function 中以List方式存放。包含了大量的 Instruction ,前驱、后继的 BasicBlock,以及一些基本信息,每个 BasicBlock都可以视为一段顺序执行的代码
- Instruction,是 BasicBlock 中以List方式存放的,LLVM IR中的最小可执行单位,每一条指令都单占一行
IR头部:
LLVM IR头部是一些Target Information
; ModuleID = 'hello_llvm.c' |
信息为:
ModuleID
:编译器用于区分不同模块的IDsource_filename
:源文件名target datalayout
:目标机器架构数据布局e
:内存存储模式为小端模式m:o
:目标文件的格式是Mach格式i64:64
:64位整数的对齐方式是64位,即8字节对齐f80:128
:80位扩展精度浮点数的对齐方式是128位,即16字节对齐n8:16:32:64
:整型数据有8位的、16位的、32位的和64位的S128
:128位栈自然对齐
target triple
:用于描述目标机器信息的一个元组,一般形式是<architecture>-<vendor>-<system>[-extra-info]
IR语法
标识符
LLVM 标识符有两种基本类型:全局和本地
- 全局标识符(函数、全局变量)以
'@'
字符作为开头前缀 - 本地标识符(寄存器名称、类型)以
'%'
字符作为开头前缀
此外标识符还有三种不同形式,用于不同的目的:
- 已命名的值:表示带有前缀的字符串。例如
%foo
,@DivisionByZero
- 未命名的值:表示带有前缀的无符号数值。例如:
%12
,@2
,%44
- 常量
LLVM 要求值以前缀开头有两个原因:编译器不需要担心名称与保留字的冲突,并且保留字集将来可能会扩展而不会受到惩罚。此外,未命名的标识符允许编译器快速提出临时变量,而不必避免符号表冲突 |
LLVM代码示例:#将整数变量”%X”乘以 8
1、使用mul乘
%result = mul i32 %X, 8 |
2、使用shl移位
%result = shl i32 %X, 3 |
3、使用add加
%0 = add i32 %X, %X ; yields i32:%0 |
最后一种方法说明了LLVM的几个词汇特征:
- 注释用 ‘ ;’ 分隔
- 当计算结果未分配给命名值时,需要创建未命名的临时对象
- 未命名的临时对象按顺序编号(递增计数器,从 ’0’ 开始)
高层结构
- 模块结构: LLVM 程序由
Module's
组成,每个Module's
都是输入程序的转换单元。每个模块由函数、全局变量和符号表组成。多个模块可以被LLVM 链接器(linker)组合在一起。 通常,一个模块由一个全局值列表组成(其中函数和全局变量都是全局值),全局值由指向内存位置的指针 - 链接类型:
private
:该类型的全局变量只能被当前模块内的对象直接访问。private的值不会出现在目标代码的符号表中。internal
:与private类似,不过它的值会作为局部变量出现在目标文件中。类似C语言中static的概念。available_externally
:该类型的变量不会被输出到与 LLVM 模块对应的目标文件中。对于链接者来说,该类型相当于一个外部声明。这种类型只允许在定义时使用,而不能在声明时使用。linkonce
:链接时,这种类型的变量会和其他同名变量merge。可用于实现某些形式的内联函数、模板或其他代码。- 官网还有其他许多类型,这里不一一翻译介绍
- 调用约定:
ccc
:C调用约定。如果没有指定其他类型,那么就默认是这种类型fastcc
:快速调用约定。使生成的目标代码尽可能快- 其他调用约定参见官网
部分常见参数
align <n>
:这表明指针值或指针向量具有指定的对齐方式
部分常见指令
alloca
- 语法:
<result> = alloca [inalloca] <type> [, <ty> <NumElements>] [, align <alignment>] [, addrspace(<num>)] ; yields type addrspace(num)*:result
- 功能:指令在当前执行函数的栈帧上分配内存,当这个函数返回给它的调用者时自动释放
%ptr = alloca i32 ; yields i32*:ptr
%ptr = alloca i32, i32 4 ; yields i32*:ptr
%ptr = alloca i32, i32 4, align 1024 ; yields i32*:ptr
%ptr = alloca i32, align 1024 ; yields i32*:ptr- 语法:
store
- 功能:写入内存
%ptr = alloca i32 ; yields i32*:ptr
**store i32 3, i32* %ptr** ; yields void
%val = load i32, i32* %ptr ; yields i32:val = i32 3load
- 功能:从内存中读取
%ptr = alloca i32 ; yields i32*:ptr
store i32 3, i32* %ptr ; yields void
**%val = load i32, i32* %ptr** ; yields i32:val = i32 3getelementptr
- 功能:获取聚合数据结构的子元素的地址。它只执行地址计算,不访问内存
使用方法看文档感觉较多,主要知道是获取元素地址
%1 = getelementptr i64* %0, i32 0
ptrtoint…to
- 语法:
<result> = ptrtoint <ty> <value> to <ty2> ; yields ty2
- 功能:“ptrtoint”指令将指针或指针向量值转换为整数(或整数向量)类型 ty2。
- 参数:“ptrtoint”指令需要一个值进行强制转换,该值必须是指针类型的值或指针向量,以及将其强制转换为 ty2 的类型,该类型必须是整数或整数类型的向量。
%2 = ptrtoint i64* %1 to i64
- 语法:
inttoptr...to
- 语法:
<result> = inttoptr <ty> <value> to <ty2>[, !dereferenceable !<deref_bytes_node>][, !dereferenceable_or_null !<deref_bytes_node>] ; yields ty2
- 功能:“inttoptr”指令将整数值转换为指针类型 ty2。
- 参数:’ inttoptr’ 指令需要一个整数值来转换,以及一个类型来转换它,它必须是一个指针 类型
%4 = inttoptr i64 %3 to i64*
- 语法: