LLVM 是什么

LLVM 是 low level virtual machine(底层虚拟机)的简称,它是一个开源的编译器架构,已经被成功应用到多个应用领域。LLVM 的主要作用是它可以作为多种语言的后端,它可以提供可编程语言无关的优化和针对很多种CPU的代码生成功能。LLVM 核心库提供了与编译器相关的支持,可以作为多种语言编译器的后台来使用。能够进行程序语言的编译期优化、链接优化、在线编译优化、代码生成。LLVM的项目是一个模块化和可重复使用的编译器和工具技术的集合。LLVM是伊利诺伊大学的一个研究项目,提供一个现代化的,基于SSA的编译策略能够同时支持静态和动态的任意编程语言的编译目标。自那时以来,已经成长为LLVM的主干项目,由不同的子项目组成,其中许多正在生产中使用的各种 商业和开源的项目,以及被广泛用于学术研究。

LLVM 常用命令工具

llvm-as 将可读的 .ll 文件汇编成字节代码
llvm-dis 将字节代码文件反编成可读的 .ll 文件
opt 在一个字节代码文件上运行一系列的 LLVM 到 LLVM 的优化
llc 为一个字节代码文件生成本机器代码
lli 直接运行使用 JIT 编译器或者解释器编译成字节代码的程序
llvm-link 将几个字节代码文件连接成一个
llvm-ar 打包字节代码文件
llvm-ranlib 为 llvm-ar 打包的文件创建索引
llvm-nm 在 字节代码文件中打印名字和符号类型
llvm-prof 将 'llvmprof.out' raw 数据格式化成人类可读的报告
llvm-ld 带有可装载的运行时优化支持的通用目标连接器
llvm-config 打印出配置时 LLVM 编译选项、库、等等
llvmc 一个通用的可定制的编译器驱动
llvm-diff 比较两个模块的结构
bugpoint 自动案例测试减速器
llvm-extract 从 LLVM 字节代码文件中解压出一个函数
llvm-bcanalyzer 字节代码分析器 (分析二进制编码本身,而不是它代表的程序)
FileCheck 灵活的文件验证器,广泛的被测试工具利用
tblgen 目标描述阅读器和生成器
lit LLVM 集成测试器,用于运行测试

 LLVM IR

根据编译原理可知,编译器不是直接将源语言翻译为目标语言,而是翻译为一种“中间语言”,即“IR”。之后再由中间语言,利用后端程序和设备翻译为目标平台的汇编语言。由于中间语言相当于一款编译器前端和后端的“桥梁”,不同编译器的中间语言IR是不一样的,而IR可以说是集中体现了这款编译器的特征。

LLVM IR主要有三种格式:一种是在内存中的编译中间语言;一种是硬盘上存储的二进制中间语言(以.bc结尾),最后一种是可读的中间格式(以.ll结尾)。这三种中间格式是完全相等的。

LLVM IR是LLVM优化和进行代码生成的关键。根据可读的IR,我们可以知道再最终生成目标代码之前,我们已经生成了什么样的代码。而且根据IR,我们可以选择使用不同的后端而生成不同的可执行代码。同时,因为使用了统一的IR,所以我们可以重用LLVM的优化功能,即使我们使用的是自己设计的编程语言。

结构化编译器前端 Clang 介绍

Clang ( 发音为 /klæŋ/) 是 LLVM 的一个编译器前端,它目前支持 C, C++, Objective-C 以及 Objective-C++ 等编程语言。Clang 对源程序进行词法分析和语义分析,并将分析结果转换为 Abstract Syntax Tree ( 抽象语法树 ) ,最后使用 LLVM 作为后端代码的生成器。

Clang 的开发目标是提供一个可以替代 GCC 的前端编译器。与 GCC 相比,Clang 是一个重新设计的编译器前端,具有一系列优点,例如模块化,代码简单易懂,占用内存小以及容易扩展和重用等。由于 Clang 在设计上的优异性,使得 Clang 非常适合用于设计源代码级别的分析和转化工具。Clang 也已经被应用到一些重要的开发领域,如 Static Analysis 是一个基于 Clang 的静态代码分析工具。

由于 GNU 编译器套装 (GCC) 系统庞大,而且 Apple 大量使用的 Objective-C 在 GCC 中优先级较低,同时 GCC 作为一个纯粹的编译系统,与 IDE 配合并不优秀,Apple 决定从零开始写 C family 的前端,也就是基于 LLVM 的 Clang 了。Clang 由 Apple 公司开发,源代码授权使用 BSD 的开源授权。

Clang 的特性

相比于 GCC,Clang 具有如下优点:

  • 编译速度快:在某些平台上,Clang 的编译速度显著的快过 GCC。
  • 占用内存小:Clang 生成的 AST 所占用的内存是 GCC 的五分之一左右。
  • 模块化设计:Clang 采用基于库的模块化设计,易于 IDE 集成及其他用途的重用。
  • 诊断信息可读性强:在编译过程中,Clang 创建并保留了大量详细的元数据 (metadata),有利于调试和错误报告。
  • 设计清晰简单,容易理解,易于扩展增强。与代码基础古老的 GCC 相比,学习曲线平缓。

当前 Clang 还处在不断完善过程中,相比于 GCC, Clang 在以下方面还需要加强:

  • 支持更多语言:GCC 除了支持 C/C++/Objective-C, 还支持 Fortran/Pascal/Java/Ada/Go 和其他语言。Clang 目前支持的语言有 C/C++/Objective-C/Objective-C++。
  • 加强对 C++ 的支持:Clang 对 C++ 的支持依然落后于 GCC,Clang 还需要加强对 C++ 提供全方位支持。
  • 支持更多平台:GCC 流行的时间比较长,已经被广泛使用,对各种平台的支持也很完备。Clang 目前支持的平台有 Linux/Windows/Mac OS。

本段内容参考:http://www.ibm.com/developerworks/cn/opensource/os-cn-clang/

Clang 编译选项

clang    [-c|-S|-E] -std=standard -g
         [-O0|-O1|-O2|-O3|-Ofast|-Os|-Oz|-O|-O4]
         -Wwarnings... -pedantic
         -Idir... -Ldir...
         -Dmacro[=defn]
         -ffeature-option...
         -mmachine-option...
         -o output-file
         -stdlib=library
         input-filenames

大部分选项与 gcc 类似:

-c 只是编译不链接,生成目标文件“.o”
-S 只是编译不汇编,生成汇编代码
-E 只进行预编译,不做其他处理
-g 在可执行程序中包含标准调试信息
-o file 把输出文件输出到file里
-I dir 在头文件的搜索路径列表中添加dir目录 -L dir 在库文件的搜索路径列表中添加dir目录

中间码的创建与转化

编译生成二进制的 .bc 文件(bitcode file):

clang -emit-llvm -c xx.c -o xx.bc

编译生成 LLVM 的汇编代码 .ll 文件( LLVM assembly code):

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

.bc 和 .ll 文件都可以直接用 lli 来执行。

将 .ll 文件转化为 .bc 文件:

llvm-as test.ll

将 .bc 文件转化为 .ll 文件:

llvm-dis test.bc

将 .bc 或 .ll 文件转化为本机平台的汇编代码:

llc test.bc

llc test.ll

llc test.bc -o test.s

即时编译器 JIT 简介

LLVM 中间码的执行需要用到 Jit。那么,JIT到底是个什么东西呢。其实,JIT 是一个即时编译器,即 Just-in-time Compiler。对于 JIT 的了解我也知之胜少,它的作用大概就是对中间码进行编译作业,像 JAVA 这种跨平台的语言也是通过 JIT 实现的。在执行 LLVM 的 lli 工具时,会去调用 JIT 将中间码编译成本机架构的机器码再执行。

至于 jit 与 mcjit 的区别,大概是 jit 是 LLVM 旧版本的支持,而 mcjit 是对 jit 新的支持。mc是机器码的意思,即 Machine Code。在 Wikipedia 看到说,以前的 LLVM 会依赖与本机系统的汇编器或者提供一套工具链,然后再翻译成机器码。而通过整合过的 LLVM MC 能够支持大多数的机器架构,包括 x86, x86-64, ARM, ARM64 以及 大部分的 MIPS 架构等。