1、简述
编译器中端所进行的工作是在前端的基础上,先将整个语法树转换为中间代码,再通过进行一系列优化遍(pass)对程序生成的中间代码(IR)进行优化,包括中间代码生成和中间代码优化两个部分。
2、中间代码生成
在讲中间代码生成之前,要先明白中间代码是什么。
LLVM中间表示有三种格式:
(1)在内存中的编译中间语言;
(2)硬盘上存储的二进制中间语言,以.bc文件保存
(3)可读的中间代码格式,以.ll文件保存
LLVM 中间代码结构包括四个部分:
(1)模块(Module)是LLVM IR的顶层容器,对应于编译器前端的每个翻译单元。每个模块由目标机器信息、全局符号(全局变量和函数)及元信息组成。
(2)函数(Function)就是编译语言中的函数,包括函数签名和若干个基本块,函数内的第一个本块叫入口基本块(entry)。
(3)基本块(BasicBlock)是一组顺序执行的指令集合,只有一个入口和一个出口,非头尾指令执行时不会违背顺序跳转到其他指令上去。每个基本块最后一条指令一般是跳转指令(跳转到其它基本块上),函数内最后一个基本块的最后一条指令是函数返回指令。
(4)指令(Instruction)是LLVM IR中的最小可执行单元。
3、中间代码优化
代码优化阶段的任务是对中间代码进行变换或者改造,目的是使生成的代码更为高效。LLVM中优化器opt可对中间代码实施优化,常见的有删除公共子表达式、循环优化、复写传播、无用赋值的删除等。
-O优化选项用以控制编译器在对程序编译时的优化级别,常用的有-O0、-O1、-O2、-O3、-Ofast,不同优化选项对应的优化选项:
选项 |
所包含的优化选项 |
-O1 |
在-O0的基础上添加-instcombine、-simplifycfg、-loops、-loop-unroll等 |
-O2 |
在-O1的基础上添加-inline、-fvectorize、-fslp-vectorize等 |
-O3 |
在-O2的基础上添加-aggressive-instcombine、-callsite-splitting、-domtree等 |
-Ofast |
在-O3的基础上添加-fno-signed-zeros、-freciprocal-math、-ffp-contract=fast、-menable-unsafe-fp-math、-menable-no-nans、-menable-no-infs、-mreassociate、-fno-trapping-math、-ffast-math、-ffinite-math-only等。 |
4、示例讲解
编写示例文件test.c,内容如下:
#include <stdio.h>
#define N 1024
int main()
{
int a,b,c;
a = 2;
b = 4;
c = a + b * 3;
print("%d\n", c);
return 0;
}
这是一个简单计算的程序,用来演示中间代码生成的过程。通过中间代码生成的相关编译选项,来生成LLVM的该文件的中间代码。
(1)源代码生成.ll形式的可读中间代码
使用命令
clang -emit-llvm -S test.c -o test.ll
得到结果test.ll
(2)源代码生成.bc形式的字节码
使用命令:
clang -emit-llvm -c test.c -o test.bc
生成的.bc文件是不可读的字节码,这里就不展示了
(3)中间代码的转换
将.ll文件转成.bc文件:
llvm-as test.ll -o test.bc
将.bc文件转成.ll文件:
llvm-dis test.bc -o test.ll