llvm学习笔记(2)

1.概述

llvm是一个由若干工具(汇编器、编译器和调试器等)所组成的工具集合,并被设计为与Unix系统上现有的工具兼容。尽管llvm有很多独特的功能,并且有一些伟大的工具(例如,Clang编译器,在很多方面优于gcc)。但是llvm最与众不同的还是他的内部结构。从200012月它的诞生之日起,llvm的设计目标就是具有良好定义接口的可重用的库的集合。而很多开源软件,例如gcc,被实现为目的单一的一个巨大的可执行程序。例如,很难重用gcc的语法分析器来做静态分析或者反射(refactoring)。

除了编译器的结构之外,围绕语言的社区也呈两极化。要么提供传统的静态编译器如gccFreePascalFreeBASIC等,要么以解释器或者JIT的形式提供一个运行时的编译器。同时支持两者的非常少见。过去的十年里,llvm改变了这种状况。llvm被用作编译广泛的静态或者动态语言(例如,gccjava.NETPythonRubySchemeHaskell等支持的以及其他一些很少见的语言)的通用架构。

2.现存的编译框架

为了方便移植与重用,编译器的理想架构如下:

这样模块化的结构下,N个前端和M个后端,就可以达到非模块化结构的N*M种编译器。

这种模块化的结构有三个成功案例。第一个是Java,.NET等虚拟机。他们提供了JIT编译器,运行时(runtime)和良好定义的字节码格式。任何程序只要能转换为字节码格式,就能狗利用虚拟机提供的各种功能,包括JIT和运行时等。其缺点就是,其运行时提供的灵活性不够,强制实施JIT编译和垃圾收集,并且使用了一种特殊的模型,当处理类似C这样的不太适合与该模型的语言时,就有性能上的折扣。

第二个成功的案例也许是不幸,但也是被广泛使用的重用编译器的方法:把输入的源程序转化为C代码,然后输入到现存的C编译器中。这种方法能够重用优化和代码生成机制,有比较好的灵活性,并且能够控制运行时,易于为前端开发者所理解、实施和维护。但是不幸的是,这种做法不能有效实施异常处理,不利于调试,并且减缓了编译速度,对于那些具有C不支持的特性的语言来说,还可能出现问题。

最后一个成功的案例是GCC4。GCC支持很多前端和后端,并且有一个庞大而活跃的开发团队。GCC在很长时间里是充当C语言的编译器,支持若干不同的目标机器。随着时间推移,GCC社区的设计更加清晰,例如在GCC4.4中,就有一个供优化使用的中间表示(GIMPLE),跟以前相比,其与前端的联系更加松散。Fortran和Ada使用的是简单的AST。

尽管非常成功,这三种方法都有局限性。他们都是被设计成一个单一的应用程序。例如很难把GCC嵌入到其他的应用中,或者重用并提取片段的GCC代码。而LLVM则具有清晰良好的设计,使得其很容易被重用。

3.LLVM的中间表示与简单使用

LLVM的中间表示为bitcode,通常后缀为bc。以简单的hello world程序为例。

#include <stdio.h>

int main() {
  printf("hello world\n");
  return 0;
}

可以直接把它编译为本地的可执行文件,命令为

clang hello.c -o hello
clang是llvm编译器前端的名字,也是其整个编译器的驱动,类似与gcc。

也可以把编译成为一个bc格式的文件,使用的编译选项为-emit-llvm -c,如下

clang -emit-llvm -c hello.c -o hello.bc
bc格式的也可以执行,执行命令如下:

lli hello.bc
如果要想生成bc的文本格式(后缀为ll),使用的命令是-emit-llvm -S

clang -emit-llvm -S hello.c -o hello.ll

bc文件也可以被转换为本地的汇编码,命令为:

llc hello.bc -o hello.s

生成汇编码之后,可以使用gcc完成链接并执行:
gcc hello.s -o hello.native
./hello.native

另外,llvm-as可以把.ll格式的文件转化为.bc,llvm-dis可以把.bc文件转化为ll。

llvm的中间(IR)表示是其最重要的部分,IR可以支持编译器优化部分的分析和转换,包括轻量级的运行时优化、过程间优化、全程序(whole program analysis)优化以及激进的重构优化。最为重要的是,它具有一个良好定义的语义。如下所示。

define i32 @add1(i32 %a, i32 %b) {
entry:
  %tmp1 = add i32 %a, %b
  ret i32 %tmp1
}

define i32 @add2(i32 %a, i32 %b) {
entry:
  %tmp1 = icmp eq i32 %a, 0
  br i1 %tmp1, label %done, label %recurse

recurse:
  %tmp2 = sub i32 %a, 1
  %tmp3 = add i32 %b, 1
  %tmp4 = call i32 @add2(i32 %tmp2, i32 %tmp3)
  ret i32 %tmp4

done:
  ret i32 %b
}

===========================================================

unsigned add1(unsigned a, unsigned b) {
  return a+b;
}

// Perhaps not the most efficient way to add two numbers.
unsigned add2(unsigned a, unsigned b) {
  if (a == 0) return b;
  return add2(a-1, b+1);
}
上面的是中间表示,下面的是对应的C代码。从中可以看出,LLVM IR是类是RISC的虚拟指令集,指令为三地址格式,很像是汇编程序。但是与机器对应的汇编程序不同的是:1) 其IR是有类型的,i32表示32位的整型值;2) call和ret的细节被抽象掉了,只是跟了参数,接近于高级语言的形式;3) 有无穷多个寄存器,使用“%”表示。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值