LLVM
傳統(tǒng)編譯器
LLVM區(qū)別于傳統(tǒng)的編譯器笨枯,它前端和后端分開了
LLVM流程
1.所有的代碼會經(jīng)過[詞法分析,語法分析爆哑,語義分析]生成抽象語法數(shù)
2.將抽象語法樹編譯成IR
3.將IR輸送給Optimizer(優(yōu)化器)面哼,進(jìn)行優(yōu)化IR代碼
4.優(yōu)化
之后的IR輸送給后端
,根據(jù)架構(gòu)(x86 & arm)生成不同的指令集代碼(機(jī)器語言)
clang & swift
都隸屬于LLVM,它們分別是LLVM針對前端語言編譯的一種格式
乔妈,之后都會生成IR
clang
iOS里面 c,c++,oc 使用的是clang
這個前
端
swift
iOS里面 swift ,使用的是swift
這個前端
- 查看前端clang流程
clang -ccc-print-phases main.m
- 查看
預(yù)處理
流程
// 生成main1.m文件氓皱,展開`頭`文件路召,展開`宏`
clang -E main.m >> mian1.m
typedef不
是預(yù)處理指令(只是定義了一個別名)-
#define
才是
預(yù)處理指令
???通過
詞法分析
,將代碼加切成一段一段的token
clang -fmodules -fsyntax-only -Xclang -dump-tokens mian.m
- 生成抽象語法樹(包含
語法分析
)
// 地址是當(dāng)前文件中的偏移地址波材,并非真實地址
clang -fmodules -fsyntax-only -Xclang -ast-dump mian.m
- 生成IR
clang -S -fobjc-arc -emit-llvm main.m
ls
// main.ll 即是生成的IR
- llvm的優(yōu)化級別
O0/O1/O2/O3/Os
(注意字母O是大寫) - 生成優(yōu)化之后的IR
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
-
bitcode一般不開啟
股淡,優(yōu)化后的IR再次優(yōu)化生成.bc的中間代碼
clang -emit-llvm -c main.ll -o main.bc
- 生成匯編代碼([通過最終的.bc或.ll代碼生成匯編代碼] -> IR ->匯編)
clang -S -fobjc-arc main.bc -o main.s
或
clang -S -fobjc-arc main.ll -o main.s
- 優(yōu)化匯編
clang -Os -S -fobjc-arc mian.m -o main.s
或
clang -Os -S -fobjc-arc mian.ll -o main.s
或
clang -Os -S -fobjc-arc mian.bc -o main.s
當(dāng)選擇了優(yōu)化等級,不同的節(jié)點上還能進(jìn)行優(yōu)化
- 生成目標(biāo)文件(匯編器以匯編代碼作為輸入各聘,將
匯編
代碼轉(zhuǎn)換成機(jī)器
代碼,形成目標(biāo)文件
)
// .o文件是不能執(zhí)行的
clang -fmoduls -c main.s -o main.o
- 查看目標(biāo)文件main.o
xcrun nm -nm main.o
- 找不到外部的_printf,這時候就需要
鏈接
image(鏡像文件揣非,所依賴的庫)
// linker
clang main.o -o main
- 查看main 可執(zhí)行文件
xcrun nm -nm main
// 鏈接是在編譯期,綁定是在執(zhí)行期(dyld_stub_binder(from libsystem) 負(fù)責(zé)綁定操作)
// 外部函數(shù)都會生成符號表(符號表的地址在沒有運(yùn)行之前為NULL躲因,然后執(zhí)行dyld_stub_binder找到對應(yīng)庫的符號綁定地址)
- 執(zhí)行 main可執(zhí)行文件
./main
// 打印結(jié)果:6%
寫一個clang插件
- 1.下載llvm工程
由于國內(nèi)網(wǎng)絡(luò)限制早敬,需要借助鏡像下載llvm的源碼
llvm源碼
- 下載llvm項目
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
- 在llvm的tools目錄下下載clang
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來編譯了搞监,so還需要安裝cmake
安裝cmake
- 查看
brew
是否安裝cmake
如果有調(diào)過下面步驟
brew list
// 如果沒有找到cmake才去安裝
- 通過brew安裝cmake
brew install cmake
通過xcode編譯llvm
- cmake編譯成xcode項目
mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm
- 使用xcode編譯clang
- 選擇自動創(chuàng)建Schemes
-
編譯選擇
ALL_BUILD Secheme進(jìn)行編譯,預(yù)計1+小時
編譯時間胡比較長
創(chuàng)建插件
- 在/llvm/tools/clang/tools目錄下新建插件HKPlugin
- 修改/llvm/tools/clang/tools目錄下的CMakeLists.txt文件镰矿,新增add_clang_subdirectory(HKPlugin)
- 在HKPlugin目錄下新建一個名為HKPlugi.cpp文件和CMakeLists.txt文件琐驴,在CMakeLists.txt中寫上
add_llvm_library( HKPlugin MODULE BUILDTREE_ONLY
HKPlugin.cpp
)
- 接下來利用cmake重新生成一下Xcode項目,在build_xcode中
cmake -G Xcode ../llvm
- 最后可以在LLVM的Xcode項目中可以看到Loadable modules目錄下有自己的Plugin目錄,可以在里面編寫插件代碼
編寫插件代碼
#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 HKPlugin {
//第三步:掃描完畢的回調(diào)函數(shù)
//4绝淡、自定義回調(diào)類,繼承自MatchCallback
class HKMatchCallback: public MatchFinder::MatchCallback {
private:
//CI傳遞路徑:HKASTAction類中的CreateASTConsumer方法參數(shù) - HKConsumer的構(gòu)造函數(shù) - HKMatchCallback的私有屬性苍姜,通過構(gòu)造函數(shù)從HKASTConsumer構(gòu)造函數(shù)中獲取
CompilerInstance &CI;
//判斷是否是用戶源文件
bool isUserSourceCode(const string filename) {
//文件名不為空
if (filename.empty()) return false;
//非xcode中的源碼都認(rèn)為是用戶的
if (filename.find("/Applications/Xcode.app/") == 0) return false;
return true;
}
//判斷是否應(yīng)該用copy修飾
bool isShouldUseCopy(const string typeStr) {
//判斷類型是否是NSString | NSArray | NSDictionary
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) {}
//重寫run方法
void run(const MatchFinder::MatchResult &Result) {
//通過result獲取到相關(guān)節(jié)點 -- 根據(jù)節(jié)點標(biāo)記獲壤谓汀(標(biāo)記需要與HKASTConsumer構(gòu)造方法中一致)
const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
//判斷節(jié)點有值,并且是用戶文件
if (propertyDecl && isUserSourceCode(CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str()) ) {
//15衙猪、獲取節(jié)點的描述信息
ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
//獲取節(jié)點的類型馍乙,并轉(zhuǎn)成字符串
string typeStr = propertyDecl->getType().getAsString();
// cout<<"---------拿到了:"<<typeStr<<"---------"<<endl;
//判斷應(yīng)該使用copy,但是沒有使用copy
if (propertyDecl->getTypeSourceInfo() && isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
//使用CI發(fā)警告信息
//通過CI獲取診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//通過診斷引擎 report報告 錯誤垫释,即拋出異常
/*
錯誤位置:getBeginLoc 節(jié)點開始位置
錯誤:getCustomDiagID(等級丝格,提示)
*/
diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0 - 這個地方推薦使用copy!!"))<< typeStr;
}
}
}
};
//第二步:掃描配置完畢
//3、自定義HKASTConsumer棵譬,繼承自ASTConsumer显蝌,用于監(jiān)聽AST節(jié)點的信息 -- 過濾器
class HKASTConsumer: public ASTConsumer {
private:
//AST節(jié)點的查找過濾器
MatchFinder matcher;
//定義回調(diào)類對象
HKMatchCallback callback;
public:
//構(gòu)造方法中創(chuàng)建matcherFinder對象
HKASTConsumer(CompilerInstance &CI) : callback(CI) {
//添加一個MatchFinder,每個objcPropertyDecl節(jié)點綁定一個objcPropertyDecl標(biāo)識(去匹配objcPropertyDecl節(jié)點)
//回調(diào)callback订咸,其實是在HKMatchCallback里面重寫run方法(真正回調(diào)的是回調(diào)run方法)
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
//實現(xiàn)兩個回調(diào)方法 HandleTopLevelDecl 和 HandleTranslationUnit
//解析完一個頂級的聲明曼尊,就回調(diào)一次(頂級節(jié)點扭屁,相當(dāng)于一個全局變量、函數(shù)聲明)
bool HandleTopLevelDecl(DeclGroupRef D){
// cout<<"正在解析..."<<endl;
return true;
}
//整個文件都解析完成的回調(diào)
void HandleTranslationUnit(ASTContext &context) {
// cout<<"文件解析完畢!"<<endl;
//將文件解析完畢后的上下文context(即AST語法樹) 給 matcher
matcher.matchAST(context);
}
};
//2涩禀、繼承PluginASTAction,實現(xiàn)我們自定義的Action然眼,即自定義AST語法樹行為
class HKASTAction: public PluginASTAction {
public:
//重載ParseArgs 和 CreateASTConsumer方法
bool ParseArgs(const CompilerInstance &ci, const std::vector<std::string> &args) {
return true;
}
//返回ASTConsumer類型對象艾船,其中ASTConsumer是一個抽象類,即基類
/*
解析給定的插件命令行參數(shù)高每。
- param CI 編譯器實例屿岂,用于報告診斷。
- return 如果解析成功鲸匿,則為true爷怀;否則,插件將被銷毀带欢,并且不執(zhí)行任何操作运授。該插件負(fù)責(zé)使用CompilerInstance的Diagnostic對象報告錯誤。
*/
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {
//返回自定義的CJLASTConsumer,即ASTConsumer的子類對象
/*
CI用于:
- 判斷文件是否使用戶的
- 拋出警告
*/
return unique_ptr<HKASTConsumer> (new HKASTConsumer(CI));
}
};
}
//第一步:注冊插件乔煞,并自定義AST語法樹Action類
//1吁朦、注冊插件
static FrontendPluginRegistry::Add<HKPlugin::HKASTAction> HK("HKPlugin", "This is HKPlugin");
測試插件
自己編譯的clang文件路徑 -isysroot /Applications/Xcode.app/Contents/Deve loper/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulat or12.2.sdk/ -Xclang -load -Xclang 插件(.dylib)路徑 -Xclang -add-plugin
-Xclang 插件名 -c 源碼路徑
測試結(jié)果:
Xcode集成插件
加載插件
- 打開測試項目,在Build Settings -> Other C Flags添加上如下內(nèi)容
-Xclang -load -Xclang (.dylib)動態(tài)庫路徑 -Xclang -add-plugin -Xclang H KPlugin
設(shè)置編譯器
-
由于Clang插件需要使用對應(yīng)的版本去加載渡贾,如果版本不一致就會導(dǎo)致編譯錯誤逗宜,會出現(xiàn)如下圖所示:
在Build Settings欄中新增亮相用戶定義的設(shè)置
- 分別是
CC & CXX
- CC對應(yīng)的是自己編譯的clang的絕對路徑
- CXX對應(yīng)的是自己編譯的clang++的絕對路徑
- 接下來在Build Settings欄中
搜
索index
,將Enable Index-Wihle-Building Functionality的Default改
為NO
- 拓展 c 語法
chanr *argv[] == chanr **argv