解释器和编译器
首先创建两个文件,一个后缀是.py,一个后缀是.m
使用python时直接运行了。而使用clang的时候生成了一个a.out的二进制文件。这说明了解释性语言和编译型语言最大的区别在于:解释性语言读到代码直接执行,编译型的需要先翻译成CPU能读取的二进制文件。
我们使用open /usr/bin可以看到我们系统的编译器
clang可以编译
c/c++/oc,刚才的解释器python也在这个目录之下。
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等各大公司采用。
传统编译器设计
编译器前端:解析源代码,词法分析,语法分析,语义分析抽象语法树AST,LLVM前端还会生成中间代码IR 优化器:各种优化,改善代码的运行时间,例如消除冗余计算 编译器后端/代码生成器:将代码映射到目标指令集。生成机器语言,并且进行机器相关的代码优化
LLVM的设计
当编译器决定支持多种源语言或多种硬件架构时,LLVM最重要的地方就来了。其他的编译器如GCC,它方法非常成功,但由于它是作为整体应用程序设计的,因此它们的用途受到了很大的限制。LLVM设计的最重要方面是,使用通用的代码表示形式(IR),它是用来在编译器中表示代码的形式。所以LLVM可以为任何编程语言独立编写前端,并且可以为任意硬件架构独立编写后端。
LLVM牛逼之处在于:它将前端和后端分离开来,前端读完之后生成IR交给优化器,优化器优化完之后把这个IR交给后端,所以只要出现一款设备,就新增一款新的设备的后端,只要出现一个高级语言,就研发一款新的高级语言的前端。这样所有的高级语言和设备都能支持。
Clang
Clang是LLVM项目中的一个子项目。它是基于LLVM架构的轻量级编译器,诞生之初是为了替代GCC,提供更快的编译速度。它是负责编译C、C++、Objecte C语言的编译器,它属于整个LLVM架构中的编译器前端。对于开发者来说,研究Clang可以给我们带来很多好处。
编译整体流程
clang -ccc-print-phases main.m
1.预处理阶段 导入头文件 宏定义
2.编译阶段:前端处理,词法分析-> IR
3.后端:通过pass节点优化成最终的汇编代码,优化就是就是对IR的优化
4.汇编:生成目标文件
5.链接:把库的代码链接生成镜像文件,磁盘里的是可执行文件,运行到内存的是镜像
6.根据不同的架构生成不同的可执行文件
编译流程-预处理阶段
#import <stdio.h>
#define C 30
int test(int a,int b){
return a + b + 3;
}
int main(int argc, const char * argv[]) {
int a = test(1,2);
printf("%d",a + C);
return 0;
}
clang -E main.m >> main1.m得到main1.m文件,打开该文件感受下
这里实际上就是把头文件导入和宏展开了,拉到最下面
main函数这里,这里的C已经替换成了30
int main(int argc, const char * argv[]) {
int a = test(1,2);
printf("%d",a + 30);
return 0;
}
tips:typedef不是预处理指令,经过clang -E之后并不会替换掉。
编译流程-编译阶段
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
词法分析会把这里的代码切成一个个
token,比如括号,等号等。在词法分析的基础上进行语法、语义分析,生成语法树clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
左边的test方法在词法分析之后,生成了右边的
FunctionDecl,如果当前的语法有问题,在词法分析的时候会报错,在预处理阶段和词法分析则不会。
编译流程-中间代码IR
完成以上步骤后就开始生成中间代码IR了,代码生成器(CodeGeneration)会将语法树自顶向下遍历逐步翻译成LLVMIR。通过下面命令可以生成II的文本文件,查看IR代码。
clang -S -fobjc-arc -emit-llvm main.m 生成了一个main.ll文件
ObjectiveC代码在这一步会进行runtime的桥接、property合成、ARC处理等IR的基本语法
- @全局标识
- %局部标识
- alloca开辟空间
- align内存对齐
- i32 32个bit,4个字节
- store写入内存
- load 读取数据
- call调用函数
- ret 返回
上面是没有优化的加法运算,可以看到IR中的计算非常繁琐。我们可以使用下面命令来优化
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll,LLVM的优化级别分别是
-0s -00 -01 -02-03
对应我们Xcode的设置选项
优化之后的
main.ll文件中的test函数是
xcode7以后开启bitcode苹果会做进一步的优化, 生成bc的中间代码,我们通过优化后的IR代码生成bc代码
clang -emit-llvm -c main.ll -o main.bc
编译流程-生成汇编代码
我们通过最终的bc或者ll代码生成汇编代码
clang -S -fobjc-arc main.bc -o main.s
clang -S -fobjc-arc main.ll -o mainll.s
分别使用bc和ll文件已经原始m文件生成汇编
经过对比发现通过bitcode和Os优化之后的代码在main函数的时候都没有调用test函数,而是直接把test计算好的结果拿到,而原始代码未经过优化的会按照方法调用顺序来运行。
汇编流程-生成目标文件
目标文件的生成,是汇编器以汇编代码为输入,将汇编代码转换成机器代码,最后输出目标文件.o
clang -fmodules -c main.s -o main.o
我们发现
printf函数找不到,此时少了一步链接, 使用clang main.o -o main生成可执行文件。我们再查看当前的可执行文件,发现与之前的.o文件有所差别,这中间的差别就是链接器在发挥作用。
此时发现链接了外部的两个函数。一个是链接函数一个是绑定函数,链接只是打个标记,告诉需要去找哪个库。绑定是在执行的时候
external _printf (from libSystem) :_printf函数是在libSystem这个库中
external dyld_stub_binder (from libSystem):执行的时候,把外部的_printf函数地址和当前的符号进行绑定,绑定是在执行期,链接是在编译期,所以当前的阶段中binder函数是一定存在的
Clang插件环境配置
LLVM下载:由于国内的网络限制,我们需要借助镜像下载LLVM的源码链接
- 下载LLVM项目
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
- 在LLVM的tools目录下下载Clang:clang是LLVM的子项目
cd llvm/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git
- 在LLVM的projects目录下下载
compiler-rt,libcxx,libcxxabi
cd ../projects
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.g it
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxx.git
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.git
- 在Clang的tools下安装extra工具
cd ../tools/clang/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-e xtra.git
- LLVM编译,由于最新的LLVM只支持cmake来编译了,我们还需要安装cmake
brew install cmake
- 通过xcode编译LLVM,cmake编译成Xcode项目:注意cmake的工程路径,编译完之后就可以使用Xcode打开
mkdir build xcode
cd build_xcode
cmake -G Xcode ../llvm
- 使用Xcode编译Clang,如果选择自动创建Schemes,会把所有的工程都创建非常耗时,我们可以选择只添加这两个Schemes,然后这两个都进行编译,预计1H+
Clang创建插件
- 在
/llvm/tools/clang/tools目录下新建插件
- 修改
/llvm/tools/clang/tools下的CMakeLists.text文件,把新增的插件添加上去
add_clang_subdirectory(libclang) // 系统的
add_clang_subdirectory(xxx) // 自己新增的插件
- 新建的插件加上配置文件,可以参考下其他的系统带的插件,都是包含了这个.cpp文件和
CMakeLists.text
在这个
CMakeLists.text文件写入
add_llvm_library( xxx MODULE BUILDTREE_ONLY xxx.cpp)
具体的实现代码,下篇见。