【LLVM】LLVM系列一 - 初识LLVM

什么是LLVM?官网:https://llvm.org

翻译:LLVM项目是模块化和可重用的编译器和工具链技术的集合。尽管名为LLVM,但它与传统虚拟机几乎没有什么关系。“LLVM”这个名字本身不是一个缩写词;它是项目的全称

创始人:Chris Lattner,也是Swift之父。

一、LLVM架构

1.1. 传统的编译器架构

  • Frontend: 前端

    • 词法分析、语法分析、语义分析、生成中间代码
  • Optimizer:优化器

    • 中间代码优化
  • Backend:后端

    • 生成机器码

1.2. LLVM架构

不管是什么语言,只要遵守LLVM架构规范,最终生成的中间代码格式(LLVM IR)是一样的。

  • 不同的前端和后端使用统一的中间代码LLVM Intermedicate Representation(简称:LLVM IR)。
  • 如果需要支持一种新的编程语言,那么只需要实现一个新的前端。
  • 如果需要支持一种新的硬件设备,那么只需要实现一个新的后端。
  • 优化阶段是一个通用的阶段,它针对的是统一的LLVM IR,不论是支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改。

相比之下,GCC的前端和后端没分的太开,前端后端耦合在了一起,所以GCC为了支持一门新的语言,或者为了支持一个新的目标平台,就变得特别困难(排列组合形式)。

LLVM现在被作为实现各种静态和运行时编译语言的通用基础结构(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等)。

二、Clang

Clang是LLVM项目的一个子项目(官网:http://clang.llvm.org/),是基于LLVM架构的C/C++/Objective-C编译器前端

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

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

2.1. Clang与LLVM

  • 广义的LLVM: 整个LLVM架构(前端和后端)。

  • 狭义的LLVM: LLVM后端(代码优化、目标代码生成等)。

如果想要自己优化中间代码,只需要插入一段Pass就可以。

2.2. OC源文件的编译过程

  1. 尝试编译main.m

  1. 命令行查看编译的过程:
1
$ clang -ccc-pring-phases main.m

  • input: 输入源文件
  • preprocessor: 预处理器(把import、include、define等替换掉)
  • compiler: 编译成中间代码(IR)
  • backend: 生成汇编代码
  • assembler: 生成目标代码
  • linker: 链接动态库和静态库
  • bind-arch: 生成适合对应架构的机器码
  1. 查看preprocessor(预处理)的结果,为了方边查看,我们把main文件改造下

  2. 控制台输入命令后就可以看到,已经把宏定义的AGE自动替换了。

1
$ clang -E main.m

2.3. 词法分析

词法分析,其实就是把代码生成一个个token。

1
$ clang -fmodules -E -Xclang -dump-tokens main.m

2.4. 语法树

语法分析,生成语法树(Abstract Syntax Tree,缩写:AST),其实就是把词法分析的结果拼接起来。

1
$ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

  • FunctionDecl: 函数声明
  • ParmVarDecl: 参数声明
  • CompoundStmt/DeclStmt:: 具体语句
  • VarDecl: 变量声明
  • BinaryOperator: 操作符

2.5. LLVM IR

LLVM IR有3中表示形式(但本质是等价的,就好比水可以有气体、液体、固体3种形态):

  • text:便于阅读的文本格式,类似于汇编语言,扩展名.ll

    1
    $ clang -S -emit-llvm main.m
  • memory:内存格式

  • bitcode:二进制格式,扩展名.bc

    1
    $clang -c -emit-llvm main.m
  1. 编写main.m源代码

  2. 输入命令,生成main.ll文件

1
$ clang -S -emit-llvm main.m

IR基本语法:

  • 注释以分号;开头
  • 全局标识符以@开头,局部标识符以%开头
  • alloca:在当前函数栈帧中分配内存(创建局部变量)
  • i3232bit:4个字节
  • align:内存对齐
  • store:写入数据
  • load:读取数据

官方语法参考https://llvm.org/docs/LangRef.html

三、源码

3.1. 源码下载

  1. 下载LLVM(大概795MB)

    1
    $ git clone https://git.llvm.org/git/llvm.git/
  2. 下载Clang(一定要等到LLVM下载完成后再操作,因为Clang要在LLVM项目下面的tools文件夹中下载。大概272.4MB)

    1
    2
    $ cd llvm/tools
    $ git clone https://git.llvm.org/git/clang.git/

Clang没有下载的时候,为什么也能使用?是因为Xcode内置了该工具。之所以再次下载,是为了能够编译出自己的编译器(定制化)。

3.2. 源码编译

  1. 安装cmake和ninja(先安装brew,https://brew.sh/),这个工具是为了快速编译LLVM。

    1
    2
    $ brew install cmake
    $ brew install ninja

    如果ninja安装失败,可以直接从Github获取release版本,然后放入/usr/local/bin

  2. 生成模板:在LLVM源码同级目录下新建一个llvm_build目录(最终会在llvm_build目录下生成build.ninja)。

    1
    2
    $ cd llvm_build
    $ cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=最终LLVM的安装路径(比如:../llvm_release)

    更多cmake相关选项,可以参考:https://llvm.org/docs/CMake.html。

  3. 执行编译指令(编译完毕后,llvm_build目录大概21.05GB)。

    1
    $ ninja
  4. 执行安装指令(生成的文件在llvm_release目录,安装完毕后,安装目录大概11.92GB)。

    1
    $ ninja install

    完成之后在可以看到clang执行文件(llvm_release/bin/clang)。

另外一种编译方式(使用Xcode)

  1. 生成Xcode项目(速度很慢很慢很慢……)。
1
2
$ cd llvm_xcode
$ cmake -G XCODE ../llvm -DCMAKE_INSTALL_PREFIX=最终LLVM的安装路径(比如:../llvm_xcode)
  1. 生成之后,可以看到执行文件,用Xcode打开之后选择Scheme名称为ALL_BUILD的项目,开始编译,之后在llvm_xcode/Debug/bin可以看到编译后的文件。

四、应用与实践

LLVM相关工具:libclang、libTooling

Clang插件开发:

Pass开发: