本文主要简述LLVM的概念与Clang插件开发
LLVM
- 官网:https://llvm.org
- LLVM项目是模块化、可重用的
编译器
以及工具链
技术的集合 - ”LLVM“这个名称不是缩写,是项目全称
传统的编译器架构
- Frontend:前端
- 词法分析、语法分析、语义分析、生成中间代码
- Optimizer:优化器
- 中间代码优化
- Backend:后端
- 生成机器码
LLVM架构
- 不同的前端后端使用统一的中间代码
LLVM Intermediate Representation(LLVM IR)
- 如果需要支持一种新的编程语言,那么只需要实现一个新的前端
- 如果需要支持一种新的硬件设备,那么只需要实现一个新的后端
- 优化阶段是一个通用的阶段,他针对的是统一的
LLVM IR
,不论是支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做需修改 - 相比之下,GCC的前端和后端没分得太开,前端后端耦合在了一起。所以GCC为了支持一门新的语言,或者为了支持一个新的目标平台,就变得相当困难
- LLVM现在被作为实现各种静态和运行时编译语言的通用基础结构(GCC家族,Java,.Net,Python,Ruby,Scheme,Haskell,D等)
Clang
Clang是LLVM的一个子项目
基于LLVM架构的C/C++/Objective-C编译器前端
相比于GCC,Clang具有如下优点
- 编译速度快:在某些平台上,Clang的编译速度显著地快过GCC(Debug模式下编译OC速度比GCC快3倍)
- 占用内存小:Clang生成的AST所占用的内存是GCC的五分之一左右
- 模块化设计:Clang采用基于库的模块化设计,易于IDE集成及其他用途的重用
- 诊断信息可读性强:在编译过程中,Clang创建并保留了大量详细的元数据(metadata),有利于调试和错误报告
- 设计清晰简单,容易理解,易于扩展增强
Clang与LLVM
IR:中间代码
OC源文件的编译过程
- 命令行查看编译的过程:
$ clang -ccc-print-phases main.m
1 | arclin@ArcdeMacBook-Pro TestObjC % clang -ccc-print-phases main.m |
preprocessor: 预处理器,处理宏定义,展开引入的头文件内容等
complier: 编译,编译成ir中间代码
backend: 后端,转成汇编代码
assemler: 汇编,转成目标代码
linker: 链接,链接动态库、静态库等
bind-arch: 绑定当前处理器架构
查看preprocesser(预处理)的结果:
$ clang -E main.m
词法分析,生成Token:
$ clang -fmodules -E -Xlang -dump-tokens main.m
语法分析,生成语法树(AST,Abstract Syntax Tree):
$ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
词法分析、语法树
词法分析,生成Token: $ clang -fmodules -E -Xclang -dump-tokens main.m
语法分析,生成语法树(AST,Abstract Syntax Tree): $ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
LLVM IR
LLVM IR 有三种表现形式
text:便于阅读的文本格式,类似于汇编语言,拓展名
.ll
,$ clang -S -emit-llvm main.m
memory:内存格式
bitcode:二进制格式,拓展名
.bc
,$ clang -c -emit-llvm main.m
IR基本语法
- 注释以分号开头
- 全局标识以@开头,局部标识符以%开头
- alloca, 在当前函数栈帧中分配内存
- i32,32bit,4个字节的意思
- align,内存对齐
- store,写入数据
- load,读取数据
官方语法参考
https://llvm.org/docs/LangRef.html
Clang插件开发
我们可以通过开发Clang插件来对我们的代码进行静态分析,大概步骤是我们先把LLVM源码和clang源码下载到本地,然后进行编译,编译完之后我们就可以得到我们自己的编译器,然后把我们的clang插件放入指定目录,然后在Xcode里面把Xcode原来的编译器配置为我们自己的编译器,然后就可以使用我们的Clang插件了。
举个例子,通过Clang我们可以对类名命名进行一些约束,比如NSString不用copy修饰的时候就警告一下。
接下来我们讲解一下配置和开发步骤
下载LLVM源码
可以直接在github上面下载到源码,目前最新版本号为12.0.1,直接点击页面的Source Code
下载,github地址
下载Clang源码
同样也是这个地址,目前的最新版本是12.0.1,点击clang-12.0.1.src.tar.xz
下载
下载后解压,文件夹命名为clang,把文件夹置入LLVM源码目录llvm
文件夹的tools
文件夹中
安装cmake和ninja(先安装brew,https://brew.sh)
使用cmake和ninja工具是为了让编译速度更快
$ brew install cmake
$ brew install ninja
ninja如果安装失败,可以直接从github获取release版(ninja-mac.zip)放入/usr/local/bin
中
编译
使用ninja模板进行编译
在llvm源码目录内新建一个文件夹命名为llvm_build
目录备用,用来放置ninja模板,然后在新建一个llvm_release
文件夹,用来放置编译后的成品
1 | $ cd llvm_build |
命令执行完成后就cd llvm_build
进入模板文件夹,然后依次执行以下命令
1 | $ ninja |
之后就可以在llvm_release
内看到编译成品了
使用Xcode进行编译
也可以生成Xcode项目再进行编译,但是速度很慢(可能需要1个多小时)
在llvm源码目录内新建一个文件夹命名为llvm_xcode
目录备用
1 | $ cd llvm_xcode |
生成完之后打开llvm_xcode
内的LLVM.xcodeproj
选择自动生成Scheme
选择ALL_BUILD
Scheme 然后就可以Cmd+R开始编译了
编译后的成品在llvm_xcode/Debug/bin
中
创建Clang插件
在llvm源码的/llvm/tools/clang/tools
文件夹内,新建一个文件夹,命名为my-plugin
(举例名字)
在同目录下打开文件CMakeLists.txt
,在最后一行写入add_clang_subdirectory(my-plugin)
后保存
在my-plugin
文件夹内新建一个文件命名为MyPlugin.cpp
,再新建一个CMakeLists.txt
文件到该文件夹内
编辑CMakeLists.txt
文件,写入
1 | add_llvm_library(MyPlugin MODULE BUILDTREE_ONLY MyPlugin.cpp) |
表示可加载模块
如果有很多cpp文件的话,那么也可以这么写
1 | add_llvm_library( MyPlugin MODULE BUILDTREE_ONLY |
编写Clang插件
因为在文本编辑器中编辑c++代码很麻烦,所以我们一般会生成一个Xcode模板(也就是上面提到那个使用Xcode进行编译
的Xcode模板)帮我们辅助编写C++代码
所以我们在llvm源码目录的llvm_xcode
文件夹内执行一下cmake -G Xcode ../llvm
命令,就可以得到一个Xcode模板,打开工程之后,就可以看到我们的插件目录
基本结构
1 | /// 必要的头文件,主要用来解析语法树 |
编译MyPlugin
Scheme
最后得到成品MyPlugin.dylib
使用Clang插件
Show in finder
取出MyPlugin.dylib
之后,我们新建一个测试工程
在新建的Xcode项目中指定加载插件:Build Settings > OTHER_CFLAGS
,双击输入框后输入
-Xclang -load -Xclang 动态库路径 -Xclang -add-plugin -Xclang 插件名称
,回车
因为Xcode自带的编译器不允许加载插件,所以我们使用刚才自己编译好的编译器
首先下载Xcode破解插件
进入目录XcodeHacking/HackedClang.xcplugin/Contents/Resources
修改HackedClang.xcspec
1 | ExecPath = "/opt/llvm/llvm_build/bin/clang"; |
改为我们刚才编译好的clang的全路径
1 | ExecPath = "/Users/arclin/Downloads/llvm-project-llvmorg-12.0.1/llvm-release/bin/clang"; |
然后在XcodeHacking目录下进行命令行,
将XcodeHacking的内容剪切到Xcode内部
1 | $ sudo mv HackedClang.xcplugin `xcode-select -p`/../PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins |
这时候Xcode就应该有多一个编译器可选项,我们选择Clang LLVM Trunk
然后就可以Clean一下后编译进行测试。
自己在测试的时候发生了一些报错的问题(可能我是用的M1笔记本的原因),可以修改Build System
的值为Legacy Build System
解决
如果发生了CADisplayLink' is unavailable: not available on macOS
的问题,那尝试新建一个Mac项目
最后贴出完整的插件代码仅供参考
1 |
|