> For the complete documentation index, see [llms.txt](https://ryukiedev.gitbook.io/wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ryukiedev.gitbook.io/wiki/ios/bian-yi-qi/01.llvm.md).

# 01.LLVM

### 一、 编程语言类型

#### 1.1 解释型语言

`python` 、 `JS` 就属于常见的 `解释型语言` 。他们无需生成 `可执行文件` 就能够执行。

他们的执行过程如下：

![解释型语言](/files/-MkX0zOZeGmeUdDXUQLm)

#### 1.2 编译型语言

我们常用的 `C\C++` 、 `Objective-C` 、 `Swift` 则属于 `编译型语言` 需要经过下面的过程生成 `可执行文件` 后才能执行。

![编译型语言](/files/-MkX0zOau36M1RVeRKqN)

### 二、 LLVM 概述

`LLVM` 是 `构架编译器(compiler)` 的 `框架系统` ，以 `C++` 编写而成，用于优化以任意程序语言编写的程序的 `编译时间(compile-time)` 、 `链接时间(link-time)` 、 `运行时间(run-time)` 以及 `空闲时间(idle-time)` ，对开发者保持开放，并兼容已有脚本。

`LLVM` 计划启动于2000年，最初由美国UIUC大学的 `ChrisLattner` 博士主持开展。2006年 `ChrisLattner` 加盟 `AppleInc` 并致力于 `LLVM` 在 `Apple` 开发体系中的应用。 `Apple` 也是 `LLVM` 计划的主要资助者。

目前 `LLVM` 已经被 `苹果IOS开发工具` 、 `Xilinx Vivado` 、 `Facebook` 、 `Google` 等各大公司采用。

> `ChrisLattner` 也是 `Swift` 之父。给大佬🧎‍♂️了

#### 2.1 传统编译器设计

![传统编译器设计](/files/-MkX0zOc_JC8wBRNhApb)

**编译器前端**

编译器前端的任务是 `解析源代码` 。它会进行： `词法分析` ， `语法分析` ， `语义分析` ，`检查源代码是否存在错误` ，然后构建 `抽象语法树(Abstract Syntax Tree, AST)` 。 `LLVM` 的前端还会生成 `中间代码(intermediate representation，IR)` 。

**优化器**

优化器负责进行 `各种优化` ， `改善代码的运行时间` ，例如消除冗余计算等。

**后端 / 代码生成器**

将代码映射到 `目标指令集` ， `生成机器语言` ， 并且 `进行机器相关的代码优化` 。

#### 2.2 iOS的编译器架构

`ObjectiveC / C / C++` 使用的编译器前端是 `Clang` ， `Swift` 是 `Swift` ， `后端` 都是 `LLVM` 。

![](/files/-MkX0zOeq7w5_flC82D1)

#### 2.3 LLVM的设计

当编译器决定支持多种源语言或多种硬件架构时， `LLVM` 最重要的地方就来了。

其他的编译器如 `GCC` 是非常成功的一款编译器，但由于它是作为整体应用程序设计的，因此它的用途受到了很大的限制。

LLVM 设计的最重要方面是，使用 `通用的代码表示形式(IR)` ，它是用来在编译器中表示代码的形式。所以 `LLVM` 可以为任何编程语言 `独立编写前端` ，并且可以为任意硬件架构 `独立编写后端` 。

### 三、 Clang

对于我们的开发人员来说，接触最多的就是我们的 `Clang` 。

`Clang` 是 `LLVM` 项目中的一个子项目。它是基于 `LLVM架构` 的 `轻量级编译器` ，诞生之初是为了替代 `GCC` ，提供更快的编译速度。它是负责编译 `C、C++、Objecte-C` 的编译器，它属于整个 `LLVM` 架构中的，编译器前端。对于开发者来说，研究 `Clang` 可以给我们带来很多好处。

### 四、 Objective-C 的编译流程探索

我们以下面的代码为例进行探索

```C++
#import <Foundation/Foundation.h>

int addResult(int a, int b) {
    return a + b;
}

#define ADD_FUNCTION(x, y) addResult(x, y)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int numA = 1;
        int b1 = 1;
        int b2 = 2;
        int numB = b1 + b2;
        int result = ADD_FUNCTION(numA, numB);
        NSLog(@"结果是：%d", result);
    }
    return 0;
}
```

#### 4.1 编译阶段概览

通过下面的命令我们可以列出完整的编译阶段：

```
clang -ccc-print-phases main.m
```

![01](/files/-MkX0zOgiwFto-XkwlPc)

* 0:输入文件
  * 找到源文件
* 1:预处理
  * 这个过程处理包括宏的替换，头文件的导入
* 2:编译
  * 进行词法分析、语法分析、检测语法是否正确，最终生成IR
* 3:后端
  * 这里LLVM会通过一个一个的Pass(可以理解为一个节点)去优化，每个Pass做一些事情，最终生成汇编代码
* 4:汇编
  * 生成目标文件
* 5:链接
  * 链接需要的动态库和静态库，生成相应的镜像可执行文件
* 6:绑定结构
  * 根据不同的系统架构，生成对应的可执行文件

#### 4.2 预处理

```
clang -E main.m >> 预处理.m
```

![02](/files/-MkX0zOhupOSGUAMSSik)

通过对比，很明显的发现这里的宏定义被替换掉了。

#### 4.3 编译阶段

**词法分析**

这里会把代码切成一个个 `Token` ，比如 `大小括号` ， `等于号` 还有 `字符串` 等。

```
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
```

![03](/files/-MkX0zOjiEVDyhTBAvCl)

**语法分析**

语法分析的任务是 `验证语法是否正确` 。在词法分析的基础上将单词序列组合成各类语法短语，如 `“程序”` ， `“语句”` ， `“表达式”` 等，然后将所有节点组成 `抽象语法树(AbstractSyntaxTree，AST)` 。语法分析其目的就是 ***对源程序进行分析判断，在结构上是否正确*** 。

```
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
```

![04](/files/-MkX0zOkDjmiVzydhAz1)

**生产中间代码IR**

```
clang -S -fobjc-arc -emit-llvm main.m
```

这里会生成 `.ll` 文件，抽取一段 `addResult` 相关代码简单进行一下分析：

```C++
; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @addResult(i32 %0, i32 %1) #1 {
  %3 = alloca i32, align 4 // 开辟 32 bit 空间， 4个字节， 为 %3
  %4 = alloca i32, align 4
  store i32 %0, i32* %3, align 4 // 将 %0 参数存到 %3
  store i32 %1, i32* %4, align 4
  %5 = load i32, i32* %3, align 4 // 从 %3 中读取到 %5
  %6 = load i32, i32* %4, align 4
  %7 = add nsw i32 %5, %6 // %5 + %6 的值存到 %7
  ret i32 %7 // 返回 %7 32bit
}
```

`main` 函数：

```
; Function Attrs: noinline optnone ssp uwtable
define i32 @main(i32 %0, i8** %1) #2 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  %6 = alloca i32, align 4
  %7 = alloca i32, align 4
  %8 = alloca i32, align 4
  %9 = alloca i32, align 4
  %10 = alloca i32, align 4
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  %11 = call i8* @llvm.objc.autoreleasePoolPush() #3
  store i32 1, i32* %6, align 4
  store i32 1, i32* %7, align 4
  store i32 2, i32* %8, align 4
  %12 = load i32, i32* %7, align 4
  %13 = load i32, i32* %8, align 4
  %14 = add nsw i32 %12, %13
  store i32 %14, i32* %9, align 4
  %15 = load i32, i32* %6, align 4
  %16 = load i32, i32* %9, align 4
  %17 = call i32 @addResult(i32 %15, i32 %16)
  store i32 %17, i32* %10, align 4
  %18 = load i32, i32* %10, align 4
  notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i32 %18)
  call void @llvm.objc.autoreleasePoolPop(i8* %11)
  ret i32 0
}
```

**编译优化**

这里 `debug` 模式下默认没有进行优化。

![5](/files/-MkX0zOmSz64m-qoCsd3)

我们传入对应的优化等级，再生成一次 `.ll` 看看（这里用了和 `release` 一样的 `Os` ）：

```
clang -Os -S -fobjc-arc -emit-llvm main.m -o main优化过.ll
```

***addResult***

```
; Function Attrs: norecurse nounwind optsize readnone ssp uwtable willreturn
define i32 @addResult(i32 %0, i32 %1) local_unnamed_addr #1 {
  %3 = add nsw i32 %1, %0
  ret i32 %3
}
```

***main***

```
; Function Attrs: optsize ssp uwtable
define i32 @main(i32 %0, i8** nocapture readnone %1) local_unnamed_addr #2 {
  %3 = tail call i8* @llvm.objc.autoreleasePoolPush() #3
  notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i32 4) #5, !clang.arc.no_objc_arc_exceptions !9
  tail call void @llvm.objc.autoreleasePoolPop(i8* %3) #3
  ret i32 0
}
```

这里发现优化后的中间代码简介了不少，并且代码中冗余的计算逻辑直接生成了结果

![06](/files/-MkX0zOne40OhkOUyfBx)

**bitCode**

这是 `xcode7` 以后开启 `bitcode` 苹果会做进一步的优化，生成 `bc` 的中间代码。我们可以试试用优化后的 `IR` 代码生成 `bc` 代码。

```
clang -emit-llvm -c main优化过.ll -o main优化过.bc
```

> 这里的 `bc` 文件无法使用文本方式进行查看

**生成汇编代码**

可以通过的 `.bc` 或者 `.ll` 代码生成汇编代码

```
clang -S -fobjc-arc main优化过.bc -o main优化过.s
或
clang -S -fobjc-arc main未优化.ll -o main未优化.s
```

对比一下优化前后的汇编代码，发现优化后的指令少了很多：

![07](/files/-MkX0zOo3WZLS1bEt8zB)

**生成目标文件**

```
clang -fmodules -c main未优化.s -o main未优化.o
```

目标文件的生成，是 `汇编器` 以汇编代码作为 `输入` ，将 `汇编代码` 转换为 `机器代码` ，最后输出 目`标文件(object-file)` ，这个阶段就是属于 `编译器后端` 的工作了。

***查看符号***

```
xcrun nm -nm main未优化.o
```

输出：

```
                 (undefined) external _NSLog
                 (undefined) external ___CFConstantStringClassReference
                 (undefined) external _objc_autoreleasePoolPop
                 (undefined) external _objc_autoreleasePoolPush
0000000000000000 (__TEXT,__text) external _addResult
0000000000000020 (__TEXT,__text) external _main
000000000000008e (__TEXT,__ustring) non-external l_.str
```

* `_NSLog` 是一个是 `undefined external` 的
  * `undefined`
    * 表示在当前文件暂时找不到符号 `_NSLog`
  * `external`
    * 表示这个符号是外部可以访问的

> 有兴趣可以移步 [通过符号表找到符号](/wiki/ni-xiang/10.-tong-guo-fu-hao-biao-zhao-dao-fu-hao.md) 有介绍查找 `_NSLog` 的过程

**生成可执行文件**

`连接器` 把编译产生的 `.o文件` 和 `(dylib .a)文件` ，生成一个 `mach-o文件(可执行文件)` 。

```
clang main.o -o main
```

报错：

```
Undefined symbols for architecture x86_64:
  "_NSLog", referenced from:
      _main in main.o
  "___CFConstantStringClassReference", referenced from:
      CFString in main.o
  "_objc_autoreleasePoolPop", referenced from:
      _main in main.o
  "_objc_autoreleasePoolPush", referenced from:
      _main in main.o
ld: symbol(s) not found for architecture x86_64
```

![08](/files/-MkX0zOqPGfmk0S0VYVs)

因为这里用到了 `Foundation` 框架内的符号 `NSLog` 不能直接生成，我们加上参数

```
clang -framework Foundation main.o -o main
```

生成成功：

![09](/files/-MkX0zOsEeTmXMy73mnj)

***查看符号***

```
xcrun nm -nm main
```

输出：

```
                 (undefined) external _NSLog (from Foundation)
                 (undefined) external ___CFConstantStringClassReference (from CoreFoundation)
                 (undefined) external _objc_autoreleasePoolPop (from libobjc)
                 (undefined) external _objc_autoreleasePoolPush (from libobjc)
                 (undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100003ed0 (__TEXT,__text) external _addResult
0000000100003ef0 (__TEXT,__text) external _main
0000000100008018 (__DATA,__data) non-external __dyld_private
```

这里的符号就已经和对应的框架对应起来了，如： `_NSLog (from Foundation)` 。而且 `mach-o` 文件中的偏移地址也有了。

### 五、 Swift 编译流程

* 分析输出AST
  * swiftc main.swift -dump-parse
* 分析并且检查类型输出AST
  * swiftc main.swift -dump-ast
* 生成中间体语言(SIL)，未优化
  * swiftc main.swift -emit-silgen
* 生成中间体语言(SIL)，优化后的
  * swiftc main.swift -emit-sil
* 生成LLVM中间体语言 (.ll文件)
  * swiftc main.swift -emit-ir
* 生成LLVM中间体语言 (.bc文件)
  * swiftc main.swift -emit-bc
* 生成汇编
  * swiftc main.swift -emit-assembly
* 编译生成可执行.out文件
  * swiftc -o main.o main.swift

### 参考

> [LLVM](https://github.com/llvm/llvm-project)
>
> [Clang](https://clang.llvm.org) 1 [Which OSX library to link against (command line) to use NSLog?](https://stackoverflow.com/questions/40940357/which-osx-library-to-link-against-command-line-to-use-nslog)
