编译原理3-LLVM

LLVM-IR

结构

LLVM IR文件的基本单位称为module,一个module中可以拥有多个顶层实体(比如function和global variable)

一个function define里面至少有一个basicblock,每个basicblock中有若干instruction,并且都以terminator instruction结尾

例子

@a = global i32 5

define i32 @foo(i32 %0, i32 %1)  {
  %3 = alloca i32
  %4 = alloca i32

  store i32 %0, i32* %3
  store i32 %1, i32* %4

  %5 = load i32, i32* %3
  %6 = load i32, i32* %4

  %7 = add nsw i32 %5, %6

  ret i32 %7
}

define i32 @main() {
  %1 = alloca i32
  %2 = alloca i32

  store i32 0, i32* %1
  store i32 4, i32* %2

  %3 = load i32, i32* @a
  %4 = load i32, i32* %2

  %5 = call i32 @foo(i32 %3, i32 %4)

  ret i32 %5
}

全局变量与局部变量

全局变量和局部变量用前缀作为区分,全局变量和函数名以@为前缀,局部变量以%为前缀

局部变量的作用域为单个函数,两个不同局部变量的%1指代不同的内容

函数定义

define i32 @main() {
    ret i32 0
}
define i32 @foo(i32 %a, i32 %b) {
    return i32 0
}

函数声明

我们在一个module里,如果想要调用别的模块的函数,就需要在本模块先声明这个函数

declare i32 @getint()
declare i32 @getarray(i32*)
declare i32 @getch()
declare void @putint(i32)
declare void @putch(i32)
declare void @putarray(i32,i32*)

基本块

一个基本块是包含了若干指令和一个终结指令的代码序列

基本块只会从终结指令退出,并且基本块的执行是原子性的,这个约束是通过代码的语义实现的。基本块内部没有控制流,控制流是多个基本块直接通过跳转指令实现的

指令

指令是LLVM IR中的非分支指令,通常用来进行某种计算或者是访存,这些指令不会改变程序的控制流

call指令也是非分支指令(仅仅是跳转,没有分支)

类型

类型 描述
void 不代表值,不占用空间,占位用
i1/i32... 表示1bit/32bit长的integer
label 标签类型,用作代码标签,跳转时用

SSA

静态单赋值(Static Single Assignment,SSA)是编译器中间表示中非常重要的一个概念,它是一种变量的命名约定。当程序中每个变量有且仅有一个赋值语句时,称一个程序是SSA形式的

在LLVM IR中,每个变量在使用前都必须被定义,且每个变量只能被赋值一次

有的时候生成SSA不太方便,就是会出现本质为变量重新赋值的情况,我们有两种解决方案

phi

TODO

memory

利用对一个局部变量进行多次load/store操作,利用内存不要求是SSA形式的特点来妥协,但是把每次变量的存取都变成了访存操作,导致严重的性能问题