LLVM插件开发

1,768 阅读7分钟

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 

在Build Settings 新增两个用户自定义设置

名称CC就是自己clang的绝对路径

名称CXX就是自己编译的clang++的绝对路径

在Build Settings 搜索index,将Enable Index-While-Building-Functionality的Default设置为NO

以上就是自己开发xcode的插件的流程。