一.LLVM概述
LLVM是架构编译器(compiler)的框架系统,以C++编写而成,用于优化任意程序语言编写的程序的编译时间(compile-time),链接时间(link-time), 运行时间(run-time), 以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。
1.传统编译器设计
- 编译器前端(Frontend)
编译器前端的任务是解析源代码。它会进行:词法分析,语法分析,语义分析,检查源代码是否存在错误,然后构建抽象语法树(AST),LLVM的前端还会生成中间代码(IR) - 优化器
优化器负责进行各种优化,改善代码的运行时间,例如消除冗余计算等 - 后端/代码生成器
将代码映射到目标指令集,生成机器语言,并且进行机器相关的代码优化
2.iOS的编译器架构
Objective C/C/C++使用的编译器前端是Clang,Swift是Swift,后端都是LLVM
3.LLVM的设计
当编译器决定支持多种源语言或者多种硬件架构时,LLVM最核心的地方就在于此,LLVM使用的是通用的代码表示形式IR,它是用来在编译器中表示代码的形式,所以LLVM可以为任何编程语言编写前端,也可以为任意硬件架构独立编写后端
二.Clang编译流程
创建一个新工程,在main.m中添加代码
int main(int argc, const char * argv[]) {
return 0;
}
通过指令clang -ccc-print-phases main.m,查看编译流程:
总结流程大致为:
- 1.输入文件:找到源文件
- 2.预处理阶段:这个过程处理包括宏的替换,头文件的导入
- 3.编译阶段:进行词法分析、语法分析、检测语法是否正确,最终生成IR
- 4.后端:这里LLVM会通过一个一个的Pass(节点)去优化,每个Pass做一些事情,最终生成汇编代码
- 5.生成目标文件
- 6.链接:链接需要的动态库和静态库,生成可执行文件
- 7.通过不同的架构,生成对应的可行文件
当然呢,这些流程我也可以单独拆分执行,去验证,这里我就附上终端指令
1.预处理
clang -E main.m
2.编译阶段
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
3.后端生成汇编代码
//我们通过最终的.bc或者.ll代码生成汇编代码:
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 //生成汇编代码也可以进行优化
4.生成目标文件(汇编器)
clang -fmodules -c main.s -o main.o
这里的操作其实有很多地方可以研究,这里我就没有附上我操作的过程,详情可参看文章LLVM编译流程
二.clang插件开发
1.LLVM下载
由于国内的网络限制,我们需要借助镜像下载LLVM的源码。
https://mirror.tuna.tsinghua.edu.cn/help/llvm/
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
2.在LLVM的tools目录下下载Clang
cd llvm/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git
注意:如果在clone时报xcrun: error: active developer path…错误,终端执行sudo xcode-select -switch/Applications/Xcode.app/Contents/Developer命令,然后再输入一次系统的密码,完成。
3.在LLVM的projects目录下下载compiler-rt,libcxx,libcxxabi
cd ../projects
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.git
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxx.git
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.git
4.在Clang的tools下安装extra工具
cd ../tools/clang/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-extra.git
5.安装cmake
编译最新的LLVM需要cmake来编辑,所以需要安装cmake,看自己是否需要安装
brew list //查看
brew install cmake //安装
6.LLVM编译
cmake编译成Xcode项目
mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm
这个过程也有些漫长,建议可以睡个觉,看个小电影啥的😄
7.使用Xcode编译Clang
选择手动创建,不要选自动创建,否则会引入一些不必要的scheme,拖累Xcode速度。 原则:使用哪个scheme,就引入哪个。 自定义插件需要添加clang和clangTooling: 编译选择ALL_BUILD Secheme,进行编译,这个时间很漫长。。。。
8.使用ninja编译LLVM
$ cd llvm_build
$ cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=.../LLVM/llvm_release 注意DCMAKE_INSTALL_PREFIX后面不能有空格
$ ninja
$ ninja install
- 先安装ninja,使用$ brew install ninja 命令安装
- 在llvm源码目录下新建一个build_ninja目录
- 在llvm源码目录下新建一个llvm_release目录
9.创建插件
- 在/llvm/tools/clang/tools目录下心间插件YCXPlugin
-
修改/llvm/tools/clang/tools目录下的CMakeLists.txt文件,新增add_clang_subdirectory(YCXPlugin) -
在YCXPlugin目录下新建一个名为YCXPlugin.cpp的文件和CMakeList.txt的文件,在CMakeList.txt中写上 -
接下来利用cmake重新生成一下Xcode项目,在build_xcode中 cmake -G Xcode …/llvm -
最后可以在LLVM的Xcode项目中可以看到 Loadable modules目录下有自己的Plugin目录了,我们可以在里面编写插件代码
三.Clang插件编写
1.代码编写
在YCXPlugin.cpp的代码如下
#include <iostream>
#include "clang/AST/AST.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
using namespace clang;
using namespace std;
using namespace llvm;
using namespace clang::ast_matchers;
namespace YCXPlugin {
class XJMatchCallback : public MatchFinder::MatchCallback {
private:
CompilerInstance &CI;
bool isUserSourceCode(const string fileName) {
if (fileName.empty()) return false;
if (0 == fileName.find("/Applications/Xcode.app/")) return false;
return true;
}
bool isShouldUseCopy(const string typeStr) {
if (typeStr.find("NSString") != string::npos ||
typeStr.find("NSArray") != string::npos ||
typeStr.find("NSDictionary") != string::npos) {
return true;
}
return false;
}
public:
XJMatchCallback(CompilerInstance &CI):CI(CI) {}
void run(const MatchFinder::MatchResult &Result) {
const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
string fileName = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
if (propertyDecl && isUserSourceCode(fileName)) {
string typeStr = propertyDecl->getType().getAsString();
ObjCPropertyAttribute::Kind attrKind = propertyDecl->getPropertyAttributes();
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyAttribute::kind_copy)) {
DiagnosticsEngine &diag = CI.getDiagnostics();
diag.Report(propertyDecl->getLocation(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0 - 这个属性推荐使用copy修饰!!"))<< typeStr;
}
}
}
};
class XJASTConsumer : public ASTConsumer {
private:
MatchFinder matcher;
XJMatchCallback callback;
public:
XJASTConsumer(CompilerInstance &CI):callback(CI) {
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
bool HandleTopLevelDecl(DeclGroupRef D) {
return true;
}
void HandleTranslationUnit(ASTContext &Ctx) {
matcher.matchAST(Ctx);
}
};
class YCXASTAction : public PluginASTAction {
public:
bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg) {
return true;
}
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
return unique_ptr<XJASTConsumer>(new XJASTConsumer(CI));
}
};
}
static FrontendPluginRegistry::Add<YCXPlugin::YCXASTAction> X("YCXPlugin", "this is YCXPlugin");
2.测试插件
命令格式 自己编译的clang文件路径 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.5.sdk(SDK路径)/ -Xclang -load -Xclang 插件(.dyld)路径 -Xclang -add-plugin -Xclang 插件名 -c 源码路径
// 例子 /Users/用户名/llvm-project/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.5.sdk/ -Xclang -load -Xclang /Users/用户名/llvm-project/build_xcode/Debug/lib/XJPlugin.dylib -Xclang -add-plugin -Xclang XJPlugin -c /Users/用户名/Desktop/DemoCode/PluginTestDemo/PluginTestDemo/ViewController.m
测试结果
3.插件集成
1.加载插件 打开测试项目,在Build Settings->Other C Flags 添加上如下内容
-Xclang -load -Xclang (.dylib)插件动态库路径 -Xclang -add-plugin -Xclang 插件名
2.设置编译器
分别是CC和CXX CC对应的是自己编译的clang的绝对路径 CXX对应的是自己编译的clang++的绝对路径
- 接下来在Build Settings中搜索index,将Enable Index-Wihle-Building Functionality的Default改为NO
- 最后,重新编译测试项目,会出现我们想要的效果了,警告的提示变成中文了哦😄
实现过程中间可能会发生很多意想不到的状态,需要大家耐心去找解决方案,我也只是把我遇到的情况进行了描述
|