ios的编译器组成分为前端和后端,clang是前端,llvm是后端,swift语言的前端编译器是swift。前端编译器把代码最终编译成IR中间代码,然后后端进行优化生成汇编,以及目标文件,最后经过链接生成可执行文件。
文件编译过程
有如下代码:
#import <stdio.h>
#define C 30
int test(int a,int b){
return a + b + C;
}
int main(int argc, const char * argv[]) {
int a = test(1, 2);
printf("%d",a);
return 0;
}
//查看编译阶段
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
各阶段编译器命令如下:
//预编译
clang -E main.m
//词法分析
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
//语法分析
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
//找不到头文件时
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.2.sdk -fmodules -fsyntax-only -Xclang -ast-dump main.m
//生成中间代码
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
//生成bitCode
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 main.s
//增加优化参数
clang -Os -S -fobjc-arc main.m -o main.s
//生成目标文件
clang -fmodules -c main.s -o main.o
//查看目标文件内容
xcrun nm -nm main.o
//生成可执行文件
clang main.o -o main
源码编译llvm工程
一、下载源码
首先到llvm-github上下载最新版本的llvm11源代码(因为我的电脑系统是11,最好对应上,否则编译有问题),有下面几个源码包需求下载:
llvm-11.0.0.src.tar.xz
clang-11.0.0.src.tar.xz
clang-tools-extra-11.0.0.src.tar.xz
compiler-rt-11.0.0.src.tar.xz
libcxx-11.0.0.src.tar.xz
libcxxabi-11.0.0.src.tar.xz
二、准备环境
下载后解压,然后执行以下动作:
1.把clang放到llvm项目的tools目录下。
2.把clang-tools-extra放到clang项目的tools目录下
3.把compiler-rt、libcxx、libcxxabi都放到llvm项目的projects目录下
4.安装cmake,brew install cmake
三、编译出xcode工程
在llvm项目的同级目录下建一个编译目录build_xcode,然后编译xcode工程
mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm
四、编译clang
编译完后会在build_xcode目录下生成一个xcode工程出来,双击LLVM.xcodeproj(注意此时选择Manually Manage Scheme,因为自动导入时间会比较长,也用不到)。然后手动添加clang和clangTooling这两个scheme。最后先编译clangTooling,再编译clang。这个过程会非常漫长,编译后build_xcode这个文件夹也会变得非常大,大概20G左右。
clang插件开发
经过上面的编译后就可以进行clang插件开发了,cmake的过程是增量的,所以后面的编译就非常快了。
新建插件
1.在llvm/tools/clang/tools下建一个文件夹HKPlugin,然后修改llvm/tools/clang/tools目录下的CMakeLists.txt文件,新增 add_clang_subdirectory(HKPlugin)
2.在TTPlugin目录下新建HKPlugin.cpp和CMakeLists.txt文件,并在CMakeLists.txt中增加
add_llvm_library( HKPlugin MODULE BUILDTREE_ONLY
HKPlugin.cpp
)
3.接下来复用cmake重新生成一下Xcode项目,在build_xcode中 cmake -G Xcode ../llvm,最后在xcode项目的Loadable modules目录下就可以看到HKPlugin目录了
编写插件代码
插件开发代码也可以参考Loadable modules目录下的LLVMHello插件。
namespace HKPlugin {
class HKMatchCallback: public MatchFinder::MatchCallback{
private:
CompilerInstance &CI;
bool isUserSourceCode(const string fileName){
if (fileName.empty()) return false;
//非xcode中的源码都认为是用户的
if (fileName.find("/Applications/Xcode.app/") == 0) return false;
return true;
}
//判断是否应该用copy修饰
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:
HKMatchCallback(CompilerInstance &CI):CI(CI){}
//真正的回调!!
void run(const MatchFinder::MatchResult &Result) {
//通过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();
//拿到节点的描述信息
ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
//判断应该使用copy但是没有使用copy
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
// cout<<typeStr<<"应该使用;copy修饰!但是你没有!"<<endl;
//诊断引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 报告
diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0这个地方推荐使用copy!!"))<<typeStr;
}
}
}
};
//自定义的HKConsumer
class HKConsumer: public ASTConsumer{
private:
//AST节点的查找过滤器!
MatchFinder matcher;
HKMatchCallback callback;
public:
HKConsumer(CompilerInstance &CI):callback(CI){
//添加一个MatchFinder去匹配objcPropertyDecl节点
//回调在HKMatchCallback里面run方法!
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
//解析完一个顶级的声明就回调一次!
bool HandleTopLevelDecl(DeclGroupRef D) {
// cout<<"正在解析...."<<endl;
return true;
}
//整个文件都解析完成的回调!
void HandleTranslationUnit(ASTContext &Ctx) {
// cout<<"文件解析完毕!"<<endl;
matcher.matchAST(Ctx);
}
};
//1.继承PluginASTAction 实现我们自定义的Action
class HKASTAction: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<HKConsumer>(new HKConsumer(CI));
}
};
}
//2.注册插件
static FrontendPluginRegistry::Add<HKPlugin::HKASTAction> HK("HKPlugin","this is HKPlugin");
整个插件的开发逻辑就是在clang编译器的抽象语法树生成过程中进行一些自定义的动作,也就是增加了一些回调方法。然后利用一些编译器内置的api进行ui展示。
编译过程中如果想测试插件情况可以通过以下命令:
生成的clang编译器路径 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.2.sdk/ -Xclang -load -Xclang 生成的HKPlugin.dylib路径 -Xclang -add-plugin
-Xclang HKPlugin -c 某个源码路径
网友评论