iOS底层探索-LLVM编译流程

发布时间:2021年10月04日 阅读:13 次

我们都知道iOS的编译器是LLVM,本篇我们就探索llvm的编译流程。

解释型语言和编译型语言区别

解释型语言

解释型语言的特征是:它的执行机制是使用一个解释器来执行,解释器对程序一句一句翻译机器语言来一句一句执行。例如:shellpython等。

比如我们执行一段pythion代码:

///hello.py
print("hello python")复制代码

我们执行上面的代码只需要:python hello.py

编译型语言

编译型语言的特征是:它的执行机制使用编译器来编译成机器语言,然后就可以直接执行编译后的可执行文件。例如:cjava等。

比如我们执行一段c代码:

///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
  printf("hello c");
  return 0;
}复制代码

我们首先要对上面的文件进行编译:

clang hello.c复制代码

执行clang指令后,会生成一个.out的可执行文件,然后执行可执行文件就可以打印出hello c

传统编译器的设计

编译器是由三部分构成的编译器前端优化器编译器后端

iOS底层探索-LLVM编译流程 编译程序 编译器优化 llvm 第1张

编译器前端

编译器前端主要做词法分析,语法分析,语义分析,检查源代码是否存在错误,构建抽象语法树(Abstract Syntax Tree,AST),最后前端会生成中间代码(intermediate representation,IR)。

优化器

优化器主要负责中间代码的优化。改善代码运行时间,例如消除冗余的计算。

编译器后端

编译器后端主要是将中间代码转换成机器码(二进制),并且进行机器相关的代码优化。

llvm架构编译器的设计

Objective C/C/C++使用的编译器前端是Clang,Swift的前端是Swift,后端是LLVM。示例图如下:

iOS底层探索-LLVM编译流程 编译程序 编译器优化 llvm 第2张

编译流程

我们使用main.m文件进行测试查看各阶段编译的结果

#include <stdio.h>
#define CUSTOM_HEIGHT 3
int testAdd(int a,int b){
    return a+b+CUSTOM_HEIGHT;
}
int main(int argc, char * argv[]) {
    return testAdd(1, 2);
}复制代码

通过命令我们可以打印源码的编译阶段

clang -ccc-print-phases main.m复制代码
0: input, "main.m", objective-c
1: preprocessor, {0}, objective-c-cpp-output 
2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, "arm64", {5}, image复制代码

其中含义为:

0:输入文件:找到源文件。

1:预处理阶段:处理包括宏的替换,头文件的导入。

2:编译阶段:进行词法分析、语法分析、检查语法是否正确,最终生成IR.

3:后端:LLVM会通过一个一个的Pass去优化,每个Pass做一些事情,最终生成汇编代码。

4:生成目标文件。

5:链接:链接需要的动态库和静态库,生成可执行文件。

6:通过不同的架构,生成对应的可执行文件。

预处理阶段

执行下面命令

clang -E main.m复制代码

执行完成后会看到头文件的导入和宏的替换。

编译阶段

词法分析

预处理完成后会进行词法分析。代码会被切成一个个Token,比如大小括号、等于号、字符串等。

clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m复制代码

iOS底层探索-LLVM编译流程 编译程序 编译器优化 llvm 第3张

语法分析

词法分析后就是语法分析,它的任务是验证语法是否正确。再次发分析的基础上将单词序列组合成各类语法短语,如"程序","语句","表达式"等,然后将所有节点组成抽象语法树(AST),语法分析阶段程序判断源程序在结构上是否正确。

 clang -fmodules -fsyntax-only -Xclang -ast-dump main.m复制代码

iOS底层探索-LLVM编译流程 编译程序 编译器优化 llvm 第4张

生成中间代码IR(intermediate representation)

完成上面的步骤后就开始生成中间代码IR了,代码生成器(Code Gemeration)会将语法树自定向下便利逐步翻译成LLVM IR。通过下面命令,查看IR代码

clang -S -fobjc-arc -emit-llvm main.m复制代码

Objective C代码这一步会进行runtime的桥接,property合成,ARC处理等。 这一步会生成main.ll文件,内容如下:

///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
  printf("hello c");
  return 0;
}复制代码0

IR的基本语法

@全局标识

%局部标识

alloca开辟空间

align内存对齐

i32 32bit,4字节

store 写入内存

load 读取数据

call 调用函数

ret 返回

IR的优化

LLVM的优化分别是-O0 -O1 -O2 -O3 -Os

///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
  printf("hello c");
  return 0;
}复制代码1

可以看到优化后的代码:

///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
  printf("hello c");
  return 0;
}复制代码2

bitCode(非必须)

生成.bc的中间代码。

///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
  printf("hello c");
  return 0;
}复制代码3
///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
  printf("hello c");
  return 0;
}复制代码4

生成汇编代码

我们通过最终的.bc或.ll代码生成汇编代码

///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
  printf("hello c");
  return 0;
}复制代码5

生成的汇编代码如下:

///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
  printf("hello c");
  return 0;
}复制代码6

汇编代码也可以优化,参数和IR相同

///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
  printf("hello c");
  return 0;
}复制代码7

优化后的汇编代码如下:

///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
  printf("hello c");
  return 0;
}复制代码8

生成目标文件

目标文件生成,汇编器以汇编代码作为输出,将汇编代码转为机器代码,最后输出目标文件(object file)。

///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
  printf("hello c");
  return 0;
}复制代码9

.o文件的内容:

clang hello.c复制代码0

生成可执行文件(链接)

链接器把编译产生的.o文件和.dylib/.a文件,生成一个mach-o文件。

clang hello.c复制代码1

iOS底层探索-LLVM编译流程 编译程序 编译器优化 llvm 第5张

总结

本篇我们主要研究了编译器的编译流程,从源代码可执行文件

  • 预处理:处理包括宏的替换,头文件的导入等。

  • 词法分析:根据一些标识符对源文件进行切割,词法分析产生的记号一般可以分为如下几类:关键字、标识符、字面量和特殊符号。注意这一步是不会检查代码是否有错误。

  • 语法分析:语法分析器对词法分析后的记号进行语法分析,产生抽象语法树(AST),这一步会检查语法是否正确

  • 生成中间代码(IR):将上面生成的语法树转换成中间代码。中间代码使得编译器可以范围内前端和后端。编译器的前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。

  • 生成汇编代码:将中间代码准换成汇编。

  • 生成目标文件(.o):将汇编语言转换成目标文件。

  • 链接目标文件,生成可执行文件:这一步主要是处理多个库依赖的情况。


作者:Jason的Home
链接:https://juejin.cn/post/7014753131842011166


Tag:编译程序 编译器优化 llvm
相关文章

发表评论: