LLVM简介
LLVM是架构编译器的框架系统,以C++编写而成,用于优化任意程序语言编写的程序的编译时间,链接时间,运行时间以及空闲时间,对于开发者保持开发,并且兼容已有的脚本。LLVM计划启动于2000年,最初由美国UIUC大学的一个博士主持开展和开发。
传统编译器的设计
sourceCode(普通代码) -> frontend(编译器前端) -> Optimaizer -> Backend -> Machine Code(机器语言,一般指汇编语言)
1、frontend(编译器前端)
任务:词法分析,语法分析,语义分析,检查代码是否存在错误,生成抽象语法树(AST),LLVM的前端还会生成中间代码(IR)。
2、Optimaizer(优化器)
任务:负责代码的优化,改善代码的运行时间,消除冗余计算等。
3、Backend(后端)
任务:将代码映射到指令集。生成机器语言,并且进行机器相关代码的优化。
编译流程
打印源码的编译流程,命令:
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优化,最终生成汇编代码。
4:生成目标文件。
5:链接需要的静态库和动态库,生成可执行文件。
6:通过不同的架构生成不同的可执行文件。
预处理阶段
命令
clang -E main.m
源码
#import <stdio.h>
#define Text 1
int main(int argc, const char * argv[]) {
printf("123==后面是定义的宏=%d",Text);
return 0;
}
执行完命令后查看代码
......此处很多头文件被省略(自己查看)
int main(int argc, const char * argv[]) {
printf("123==后面是定义的宏=%d",1);
return 0;
}
总结
可以看出执行完毕后头文件和宏已经被替换,上面代码只是粘贴了一部分宏的,头文件太长就忽略了。
编译阶段
词法分析
分析的时候会把代码生成一个一个Token
命令:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
生成的代码格式如下:
此处省略.....
int main(int argc, const char * argv[]) {
printf("123==后面是定义的宏=%d",Text);
' Loc=<main.m:9: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>
此处省略.....
上面词法分析总结
可以看出生成好多的标识,代码被切割称为很多的Token,可以看出代码的大概流程和样子,包含了起始位置和长度等。
语法分析
词法分析完成后就是词法分析,任务是验证语法是否正确。再词法的基础上将词法组合成各类的语法,例如:“程序”、“语法”、“表达式”等等,然后生成抽象语法树(AST),语法分析判断源程序在结构上是否是正确的。
命令
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
生成的代码格式如下:
此处省略.....
FunctionDecl 0x7fb434837b10 <line:12:1, line:15:1> line:12:5 main 'int (int, const char **)'
| |-ParmVarDecl 0x7fb4348378c0 <col:10, col:14> col:14 argc 'int'
| |-ParmVarDecl 0x7fb4348379d0 <col:20, col:38> col:33 argv 'const char **':'const char **'
| `-CompoundStmt 0x7fb4340adf70 <col:41, line:15:1>
| |-CallExpr 0x7fb4340adee0 <line:13:5, col:48> 'int'
此处省略.....
如果需要引入指定的SDK的,命令如下:
clang -isysroot SDK路径 -fmodules -fsyntax-only -Xclang -ast-dump main.m
生成中间代码IR
完成了上面的步骤就开始生成中间的代码IR,代码生成器会将语法树自顶部向下逐步翻译成LLVM IR,通过下面的命令可以查看中间的代码IR。
命令
clang -S -fobjc-arc -emit-llvm main.m
说明:objc代码在这一步会进行runtime的桥接:prooperty的合成,ARC的处理。
IR的语法树的说明:
@全局标识
%局部标识
alloca开辟内存
align内存对齐
i32 32bit,4个字节
store写入内存
load读取数据
call调用函数
ret返回
......等等
IR优化
LLVM的优化级别分别是 -O0 -O1 -O2 -O3 -Os(前一个字母都是大写的字母O)
命令:
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
苹果的bitCode,是xcode7以后引入的进一步做代码优化。生成。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 main2.s
生成汇编代码后还可以再次进行优化
命令
clang -Os -S -fobjc-arc main.m -o main.s
生成目标文件(汇编器)
目标文件的生成,是汇编以汇编代码作为输入,将汇编代码转化为机器代码,最后输出目标文件。
命令
clang -fmodules -c main.s -o main.o
通过nm命令查看main.o中的符号。
命令:nm -nm mian.o
代码输出如下
(undefined) external _printf
0000000000000000 (__TEXT,__text) external _main
可以看出_printf是一个没有定义的符号,这是因为没有链接外部的库,当出现external表示这个符号是外部可以访问的。
生成链接可执行文件(链接)
链接器把编译生成的.o文件和(.dylib .a)生成一个mach-o的文件
命令
clang main.o -o main
查看链接之后的符号,命令
nm -nm main
代码输入格式如下:
(undefined) external _printf (from libSystem)
(undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100000f5f (__TEXT,__text) external _main
0000000100002008 (__DATA,__data) non-external __dyld_private
Clang 插件
LLVM镜像下载链接
https://mirror.tuna.tsinghua.edu.cn/help/llvm
下载LLVM项目
git clone https://mirror.tuna.tsinghua.edu.cn/help/llvm.llvm.git
在LLVM的tools目录下面下载Clang
cd llvm/tools
git clong https://mirror.tuna.tsinghua.edu.cn/git/llvm/clang.git
在LLVm的projects的目录下载compiler-rt ,libcxx,libcxxabi
cd ../projects
git clong https://mirror.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.git
git clong https://mirror.tuna.tsinghua.edu.cn/git/llvm/libcxx.git
git clong https://mirror.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.git
在Clang的tools下面安装extra工具
cd ../tools/clang/tools
git clone https://mirror.tuna.tsinghua.edu.cn/git/llvm/clang-tools-extra.git
LLVM 编译
由于最新的LLVM只支持cmake来编译,所以需要安装cmake。
安装cmake
查看brew 是否安装了cmake
brew list
通过brew安装cmake
brew install make
编译LLVM
通过xcode编译LLVM
cmake 编译xcode的项目
mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm
xcode编译Clang
还可以通过ninja编译LLVM,这块自己查看资料,不详细说了。
用xcode编译完了LLVM项目后,创建插件
cd /llvm/tools/clang/tools/新建插件
如下图
修改/llvm/tools/clang/tools/目录下面的CMakeList.txt ,新增加一个自己创建的组件 HWPlugin,在文件末尾添加命令如下:
add_clang_subdirectory(HWPlugin)
在HWPlugin中新建一个cpp文件和CMakeList.txt文件,CMakeList.txt文件的书写参考原有的插件。
CMakeList.txt内容
add_llvm_lib(HWPlugin 省略){
hello.cpp
}
接下来利用cmake从新生成Xcode的项目,在build_xcode中cmake -G Xcode ../llvm
LLVM 的xcode项目中在loadable modules目录下看自己的插件,cpp文件中可以尽情的写自己的代码。不会的话,最好查看下C++的代码。
测试插件
自己编写的clang路径 -isysrooot XcodeSDK路径 -Xclang -load -Xclang 插件(.dylib)路径 Xclang -add-plugin-Xclang 插件名列 -c 源码路径
xcode集成
加载插件,在Build Settings -> Other Flags 添加如下
-Xclang -load -Xclang (.dylib)动态库路径 -Xclang -add-plugin -Xclang HWPlugin
设置完编译器后会报错
Expected in : flat namespace