本文已参与[新人创作礼]活动,一起开启掘金创作之路。
概述
llvm 是一种架构编译器(compiler)的框架系统,以c++编写而成,用于优化以任意程序语言编写的程序的编译时间、链接时间、运行时间以及空闲时间,兼容现有脚本。
传统的编译器设计
graph LR
SOURCE-CODE --> Frontend-Optimizer-Backend --> MACHINE-CODE
编译器前端
它的任务是解析源代码,进行词法分析、语法分析、语义分析、检查源代码是否存在错误,然后构建语法抽象树(ast),llvm 的前端还会生成中间代码 IR。
优化器
负责进行各种优化,改善代码运行时间,例如消除冗余运算。
后端/代码生成器
将代码映射到目标指令集,生成机器语言,并且进行机器相关代码的优化。
iOS 的编译架构
Objective c/c/c++ 使用前端编译器都是clang,swift使用的是swift,后端都是llvm
graph LR
Clang --> llvm-Optimizer --> llvm-code-generator
Swift --> llvm-Optimizer
llvm 设计
llvm 设计的重要方面使用通用的代码表示形式 IR,它是用来在编译器中表现代码的形式,所以llvm可以为任何形式的语言编写前端,也可以为任意的硬件家都模式编写后端,
graph LR
c --> Clang-c/c++/oc-frontend --> llvm-Optimizer
fortian --> llvm-gcc-frontend --> llvm-Optimizer
haskell --> GHC-frontend --> llvm-Optimizer
llvm-Optimizer --> llvm-x86-backend --> x86
llvm-Optimizer --> llvm-PowerPC-backend --> PowerPC
llvm-Optimizer --> llvm-arm-backend --> arm
clang 是 LLVM中的一个子项目,他是基于llvm架构的轻量级编译器。
编译流程
通过以下命令可以打印源码的编译阶段
clang -ccc-print-phases main.m
❯ 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, "x86_64", {5}, image
0:输入代码:找到源文件。
1:预编译处理:头文件的导入、宏定义的替换等。
2:编译阶段:进行词法分析,语法分析,检查语法是否正确,最终生成 IR。
3:后端:llvm会通过一个个pass去优化,每个pass做一些事情,最终生成汇编代码。
4:生成目标文件。
5:链接:链接所需要的动态库和静态库,生成可执行文件。
6:通过不同的架构,生成指定的可执行文件。
预处理阶段
clang -E main.m
执行以上命令,可以看到头文件的导入和宏的替换
编译阶段
词法分析
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
执行以上命令,会进行词法分析,这里会把代码切成一个个token,比如括号字符串等号等。
❯ clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
annot_module_include '#import <stdio.h>
#define A 10
int main(int argc, const char * argv[]) {
print' Loc=<main.m:8:1>
int 'int' [StartOfLine] Loc=<main.m:12:1>
identifier 'main' [LeadingSpace] Loc=<main.m:12:5>
l_paren '(' Loc=<main.m:12:9>
int 'int' Loc=<main.m:12:10>
identifier 'argc' [LeadingSpace] Loc=<main.m:12:14>
comma ',' Loc=<main.m:12:18>
const 'const' [LeadingSpace] Loc=<main.m:12:20>
char 'char' [LeadingSpace] Loc=<main.m:12:26>
star '*' [LeadingSpace] Loc=<main.m:12:31>
identifier 'argv' [LeadingSpace] Loc=<main.m:12:33>
语法分析
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
执行以上代码进行语法分析,检测语法是否报错,在词法分析的基础上把单词进行组合排列形成各种语法短语,如语句,程序等。然后将所有节点组成抽象语法树(AST)。语法分析判断源程序在结构上是否正确。
❯ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
TranslationUnitDecl 0x7fef8203d008 <<invalid sloc>> <invalid sloc> <undeserialized declarations>
|-TypedefDecl 0x7fef8207e958 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'struct __va_list_tag [1]'
| `-ConstantArrayType 0x7fef8207e900 'struct __va_list_tag [1]' 1
| `-RecordType 0x7fef8207e740 'struct __va_list_tag'
| `-Record 0x7fef8207e6a0 '__va_list_tag'
|-ImportDecl 0x7fef8207f1d0 <main.m:8:1> col:1 implicit Darwin.C.stdio
`-FunctionDecl 0x7fef8207f4a0 <line:12:1, line:15:1> line:12:5 main 'int (int, const char **)'
|-ParmVarDecl 0x7fef8207f228 <col:10, col:14> col:14 argc 'int'
|-ParmVarDecl 0x7fef8207f350 <col:20, col:38> col:33 argv 'const char **':'const char **'
`-CompoundStmt 0x7fef85018e28 <col:41, line:15:1>
|-CallExpr 0x7fef85018d98 <line:13:5, col:23> 'int'
| |-ImplicitCastExpr 0x7fef85018d80 <col:5> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
| | `-DeclRefExpr 0x7fef85018c60 <col:5> 'int (const char *, ...)' Function 0x7fef85018808 'printf' 'int (const char *, ...)'
| |-ImplicitCastExpr 0x7fef85018de0 <col:12> 'const char *' <NoOp>
| | `-ImplicitCastExpr 0x7fef85018dc8 <col:12> 'char *' <ArrayToPointerDecay>
| | `-StringLiteral 0x7fef85018cb8 <col:12> 'char [3]' lvalue "%d"
| `-BinaryOperator 0x7fef85018d18 <line:10:11, line:13:21> 'int' '+'
| |-IntegerLiteral 0x7fef85018cd8 <line:10:11> 'int' 10
| `-IntegerLiteral 0x7fef85018cf8 <line:13:21> 'int' 20
`-ReturnStmt 0x7fef85018e18 <line:14:5, col:12>
`-IntegerLiteral 0x7fef85018df8 <col:12> 'int' 0
生成中间代码IR
完成上面步骤可以生成中间代码
clang -S -fobjc-arc -emit-llvm main.m
objective c 代码会在这一步进行runtime 桥接,property的合成ARC处理等等。
IR 的基本语法
@ 全局标识
% 局部标识
alloca 开辟空间
align 内存对齐
i32 32个bit,4个字节
store 写入内存
load 读取数据
call 调用函数
ret 返回
IR 的优化
LLVM 优化级别是 -O0 -O1 -O2 -Os
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
bitcode
xocde7 之后,苹果对代码开启进一步优化,生成 .bc中间代码,我们通过优化后的IR代码生成.bc代码
clang -emit-llvm -c main.ll -o main.bc
生成汇编代码
我们通过.ll 或者 .bc 代码生成汇编代码
clang -S -fobjc-arc main.bc -o main.s
clang -S -fobjc-arc main.ll -o main.s
生成的汇编代码也可以进行优化
clang -Os -S -fobjc-arc main.m -o main.s
生成目标文件(汇编器) 目标文件的生成,是汇编器以汇编代码为输入,将汇编代码转换为机器代码,最后输出object file
clang -fmodules -c main.s -o main.o
通过命令查看生成的目标文件中的符号
❯ xcrun nm -nm main.o
(undefined) external _printf
0000000000000000 (__TEXT,__text) external _main
_printf是一个 undefined external
undefined 表示在当前文件找不到符号_printf
external 表示这个符号是外部可以访问
生成的可执行文件(链接) 通过链接器把编译生成的.o文件和.dylib .a 文件,生成一个可执行的match-o文件
clang main.o -o main
通过命令查看链接之后的符号
❯ xcrun nm -nm main
(undefined) external _printf (from libSystem)
(undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100003f77 (__TEXT,__text) external _main
0000000100008008 (__DATA,__data) non-external __dyld_private