1.概念
1.LLVM
LLVM是架構(gòu)編譯器(compiler)的框架系統(tǒng)蝶糯,以c++編寫(xiě)而成琉雳,用于優(yōu)化以任意程序語(yǔ)言編寫(xiě)的程序的編譯事件(complie-time),鏈接時(shí)間(link-time),運(yùn)行時(shí)間(run-time)割笙,以及空閑時(shí)間(idle-time)权烧,對(duì)開(kāi)發(fā)者保持開(kāi)放,并兼容以有腳本咳蔚。LLVM計(jì)劃啟動(dòng)于2000年豪嚎,最初由美國(guó)UIUC大學(xué)的Chris Lattner博士主持開(kāi)展。2006Chris Lattner加盟Apple Inc.并致力于LLVM在A(yíng)pple 開(kāi)發(fā)體系中的應(yīng)用谈火。Apple也是LLVM計(jì)劃的主要資助者侈询。
目前LLVM已經(jīng)被蘋(píng)果ios開(kāi)發(fā)工具,Xilinx Vivado糯耍,F(xiàn)acebook扔字,Google等各大公司采用。
2.傳統(tǒng)編譯器設(shè)計(jì)
2.1.編譯前端(Frontend)
編譯器前端的任務(wù)是解析源代碼温技。它會(huì)進(jìn)行:詞法分析革为,語(yǔ)法分析,語(yǔ)義分析舵鳞,檢查源代碼是否是否存在錯(cuò)誤震檩,然后構(gòu)建抽象語(yǔ)法樹(shù)??(Abstract Syntax Tree?? AST),LLVM的前端還會(huì)生成中間代碼(intermediate representation ??IR)??.
2.2.優(yōu)化器(Optimizer)
優(yōu)化器負(fù)責(zé)進(jìn)行各種優(yōu)化。改善代碼運(yùn)行時(shí)間蜓堕,例如消除冗余計(jì)算等抛虏。
2.3.后段(Backed)/代碼生成器(CodeGenerator)
將代碼映射到目標(biāo)指令集。生成機(jī)器語(yǔ)言套才,并且進(jìn)行機(jī)器相關(guān)代碼優(yōu)化迂猴。
3.iOS的編譯器架構(gòu)
Objective C/C/C++使用的編譯器前端是Clang,Swift是Swift背伴,后段都是LLVM.
2.LLVM的設(shè)計(jì)
當(dāng)編譯器決定支持多種源語(yǔ)言或多種硬件架構(gòu)時(shí)沸毁,LLVM最重要的地方就來(lái)了峰髓。其他的編譯器如GCC,它方法非常成功,但由于它是作為整體應(yīng)用程序設(shè)計(jì)的息尺,因此它們的用途受到很大的限制携兵。
LLVM設(shè)計(jì)的最重要方面是,使用通用代碼表示形式(IR)掷倔,它是用來(lái)在編譯器中表示代碼的形式眉孩。所以L(fǎng)LVM可以以任何編程語(yǔ)言獨(dú)立編寫(xiě)前端,并且可以為任意硬件架構(gòu)獨(dú)立編寫(xiě)后端勒葱。
2.1.Clang
Clang??是LLVM項(xiàng)目中的一個(gè)子項(xiàng)目。它是基于????????????????????????????LLVM????????????????????????架構(gòu)的輕量級(jí)編譯器巴柿,誕生之初是為了替代 ????????????GCC凛虽,提供更快的編譯速度。它負(fù)責(zé)編譯??????????????????????????????????C ??C++ ??Objecte- C???????????????????????? 語(yǔ)言的編譯器广恢。它屬于整個(gè)LLVM架構(gòu)中的編譯器前端凯旋。對(duì)于開(kāi)發(fā)者來(lái)說(shuō),研究???????????????????????????????????????? ??Clang????????????????????????可以給我們帶來(lái)很多好處钉迷。
3.編譯流程
通過(guò)命令可以打印源碼的編譯階段至非。
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.預(yù)編譯處理階段:這個(gè)過(guò)程處理包括宏的替換,頭文件的導(dǎo)入糠聪。
2.編譯階段:進(jìn)行詞法分析荒椭,語(yǔ)法分析,檢查語(yǔ)法是否正確舰蟆,最終生成IR.
3.后端:這里L(fēng)LVM會(huì)通過(guò)一個(gè)一個(gè)的Pass去優(yōu)化趣惠,每個(gè)Pass做一些事情,最終生成匯編代碼身害。
4.生成目標(biāo)文件
5.鏈接:鏈接需要的動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)味悄,生成可執(zhí)行文件。
6.通過(guò)不同的架構(gòu)塌鸯,生成對(duì)應(yīng)的可執(zhí)行文件侍瑟。
3.1預(yù)處理階段
執(zhí)行如下命令
clang -E main.m
執(zhí)行完畢可以看到頭文件的導(dǎo)入和宏的替換。
3.2編譯階段
1.詞法分析
預(yù)處理完成后就會(huì)進(jìn)行詞法分析丙猬,這里會(huì)把代碼切成一個(gè)個(gè)Token涨颜,比如大小括號(hào),等于號(hào)還有字符串淮悼。
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
2.語(yǔ)法分析
詞法分析完成之后就是語(yǔ)法分析咐低,它的任務(wù)是驗(yàn)證語(yǔ)法是否正確。在詞法分析的基礎(chǔ)上將單詞序列組合成各類(lèi)語(yǔ)法短語(yǔ)袜腥,如程序见擦,語(yǔ)句钉汗,表達(dá)式,等等鲤屡,然后將所有節(jié)點(diǎn)組成抽象語(yǔ)法樹(shù)(Abstract Syntax Tree?? AST),語(yǔ)法分析程序判斷源程序在結(jié)構(gòu)上是否正確损痰。
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
如果導(dǎo)入頭文件找不到,那么可以指定SDK酒来,
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.2.sdk ?????? -fmodules -fsyntax-only -Xclang -ast-dump main.m
3.3生成中間代碼IR(intermediate representation)
完成以上步驟后就開(kāi)始生成中間代碼IR了卢未,代碼生成器(Code Generation),會(huì)將語(yǔ)法樹(shù)自頂向下遍歷逐步翻譯成LLVM IR堰汉。通過(guò)下面命令可以生成.ll的文本文件查看IR代碼
clang -S -fobjc-arc -emit-llvm main.m
Objective C代碼在這一步會(huì)進(jìn)行runtime的橋接:property合成辽社,ARC處理等。
IR的基本語(yǔ)法
@全局標(biāo)識(shí)
%局部????標(biāo)識(shí)
alloca開(kāi)辟空間
align內(nèi)存對(duì)齊
i32 32個(gè)bit翘鸭,4個(gè)字節(jié)
store寫(xiě)入內(nèi)存
load讀取內(nèi)存
call調(diào)用函數(shù)
ret返回
IR的優(yōu)化
LLVM的優(yōu)化級(jí)別分別是-O0 -O1 -O2 -O3 -Os(第一個(gè)是大寫(xiě)英文字母O)
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
bitCode
xcode7以后開(kāi)啟bitCode蘋(píng)果會(huì)做進(jìn)一步的優(yōu)化滴铅。生成.bc的中間代碼。我們通過(guò)優(yōu)化后的IR代碼生成.bc代碼
clang -emit-llvm -c main.ll -o main.bc
3.4生成匯編代碼
我們通過(guò)最終生成的.bc或者.ll代碼生成匯編代碼就乓。
clang -S -fobjc-arc main.bc -o main.s
clang -S -fobjc-arc main.ll -o main.s
生成匯編代碼也可以進(jìn)行優(yōu)化
clang -Os -S -fobjc-arc main.m -o main.s
3.5生成目標(biāo)文件(匯編器)
目標(biāo)文件的生成汉匙,是匯編器以匯編代碼作為輸入,將匯編代碼轉(zhuǎn)換為機(jī)器代碼生蚁,最后輸出目標(biāo)文件(object file)
clang -fmodules -c main.s -o main.o
通過(guò)nm命令噩翠,查看下main.o中的符號(hào)。
$xcrun nm -nm main.o
(undefined) external _printf
0000000000000000 (__TEXT,__text) external _test
000000000000000a (__TEXT,__text) external _main
_printf是一個(gè)undefined external的
undefined表示在當(dāng)前文件暫時(shí)找不到符號(hào)_printf
external表示這個(gè)符號(hào)是外部可以訪(fǎng)問(wèn)的邦投。
3.6生成可執(zhí)行文件(鏈接)
連接器把編譯產(chǎn)生的.o文件和(.dylib .a)文件伤锚,生成一個(gè)mach-o文件
clang main.o -o main
查看鏈接之后的符號(hào)
xcrun nm -nm main
(undefined) external _printf (from libSystem)
(undefined) external dyld_stub_binder (from libSyste 0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
000000100000f6d (__TEXT,__text) external _test
000000100000f77 (__TEXT,__text) external _main
4.開(kāi)發(fā)插件
4.1.LLVM下載
由于國(guó)內(nèi)的網(wǎng)絡(luò)限制,我們需要借助鏡像下載LLVM的源碼https://mirror.tuna.tsinghua.edu.cn/help/llvm/
4.1.1下載LLVM項(xiàng)目
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
4.1.2.在LLVM的tools目錄下載Clang
cd llvm/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git
4.1.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.1.4.在Clang的tools下安裝extra工具
cd ../tools/clang/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-extra.git
4.2.LLVM編譯
由于最新的LLVM只支持cmake來(lái)編譯了尼摹,我們還需要安裝cmake见芹。
4.2.1.安裝cmake
查看brew是否安裝cmake如果安裝就跳過(guò)下面步驟
brew list
如果沒(méi)有安裝cmake
brew install cmake
4.2.2.編譯LLVM
mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm
使用Xcode編譯
用Xcode打開(kāi)LLVM.xcodeproj
選擇手動(dòng)創(chuàng)建Schemes
選擇clang和clangtooling兩個(gè)Schemes進(jìn)行編譯
4.3.創(chuàng)建插件
4.3.1在/llvm/tools/clang/tools目錄下新建插件MyPlugin
修改/llvm/tools/clang/tools目錄下的CMakeLists.txt文件并????新增
add_clang_subdirectory(MyPlugin)
4.3.2.配置MyPlugin文件
在MyPlugin文件下新建一個(gè)名為MyPlugin.cpp和CMakeLists.txt文件
在CMakeLists.txt文件中寫(xiě)上
add_llvm_library( MyPlugin MODULE BUILDTREE_ONLY MyPlugin.cpp)
使用cmake重新生成Xcode項(xiàng)目,在build_xcode中
cmake -G Xcode ../llvm
4.3.3.添加MyPlugin的Schemes
4.4.編寫(xiě)插件代碼
在cpp文件里編寫(xiě)插件代碼
#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 MyPlugin {
class MyMatchCallBack: public MatchFinder::MatchCallback {
private:
CompilerInstance &CI;
bool isUserSourceCode(const string fileName){
if (fileName.empty()) return false;
if (fileName.find("/Applications/Xcode.app/Contents") == 0) {
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:
MyMatchCallBack(CompilerInstance &CI):CI(CI){}
void run(const MatchFinder::MatchResult &Result) {
const ObjCPropertyDecl*propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl >("objcPropertyDecl");
//獲取文件名稱(chēng)
string fileName = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin());
if (propertyDecl&& isUserSourceCode(fileName)) {
string typeStr = propertyDecl->getType().getAsString();
ObjCPropertyDecl::PropertyAttributeKind attributeKind = propertyDecl->getPropertyAttributes();
if (isShouldUseCopy(typeStr) && !(attributeKind &ObjCPropertyDecl::OBJC_PR_copy)) {
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 報(bào)告
diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0這個(gè)地方推薦使用copy!!"))<<typeStr;
}
}
}
};
class MyConsumer:public ASTConsumer {
private:
//AST節(jié)點(diǎn)過(guò)濾器
MatchFinder matcher;
MyMatchCallBack callback;
public:
MyConsumer(CompilerInstance &CI):callback(CI){
//添加matchFinder
//回調(diào)類(lèi)是MyMatchCallBack蠢涝,回調(diào)到run方法中
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
//解析完頂級(jí)的聲明就回調(diào)一次
bool HandleTopLevelDecl(DeclGroupRef D) {
return true;
}
//整個(gè)文件都解析完成的回調(diào)
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完畢!"<<endl;
matcher.matchAST(Ctx);
}
};
class MYASTAction: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<MyConsumer>(new MyConsumer(CI));
}
};
}
//注冊(cè)插件
static FrontendPluginRegistry::Add<MyPlugin::MYASTAction> HK("MyPlugin","this is MyPlugin");
4.5.測(cè)試插件
自己編譯的??????????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 源碼路徑 ????????
/Users/MacW/Desktop/MyPriject/LLVMLearn/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.0.sdk/ -Xclang -load -Xclang /Users/MacW/Desktop/MyPriject/LLVMLearn/build_xcode/Debug/lib/MyPlugin.dylib -Xclang -add-plugin -Xclang MyPlugin -c ./ViewController.m
./ViewController.m:13:1: warning: NSString *這個(gè)地方推薦使用copy!!
@property(nonatomic, strong) NSString* name;
^
./ViewController.m:14:1: warning: NSArray *這個(gè)地方推薦使用copy!!
@property(nonatomic, strong) NSArray* arrs;
4.6.Xcode集成插件
4.6.1加載插件
打開(kāi)測(cè)試項(xiàng)目玄呛,在Build Settings -> Other C Flags添加如下內(nèi)容
-Xclang -load -Xclang (.dylib)動(dòng)態(tài)庫(kù)路徑?????????? -Xclang -add-plugin -Xclang MyPlugin
//我項(xiàng)目的設(shè)置信息
-Xclang -load -Xclang /Users/MacW/Desktop/MyPriject/LLVMLearn/build_xcode/Debug/lib/MyPlugin.dylib ?????????? -Xclang -add-plugin -Xclang MyPlugin
4.6.2設(shè)置編譯器
由于Clang插件需要使用對(duì)應(yīng)的版本加載,如果版本不一致會(huì)導(dǎo)致編譯錯(cuò)誤和二,會(huì)出現(xiàn)如下圖所示徘铝。
在Build Settings欄目中新增兩項(xiàng)用戶(hù)定義的設(shè)置
分別是CC和CXX
CC對(duì)應(yīng)的是自己編譯的clang的絕對(duì)路徑
CXX對(duì)應(yīng)的是自己編譯的clang++的絕對(duì)路徑
接下來(lái)在Build Settings欄目中搜索index,將Enable Index-Wihle-Building Functionality??Default改為NO
我們做出插件的效果如下: