iOS-OC底層25:LLVM和自定義LLVM插件

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ì)

image.png
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.


image.png

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ě)后端勒葱。


image.png

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
image.png

如果沒(méi)有安裝cmake

brew install cmake

4.2.2.編譯LLVM

mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm

使用Xcode編譯


image.png

用Xcode打開(kāi)LLVM.xcodeproj
選擇手動(dòng)創(chuàng)建Schemes


image.png

選擇clang和clangtooling兩個(gè)Schemes進(jìn)行編譯
image.png

4.3.創(chuàng)建插件

4.3.1在/llvm/tools/clang/tools目錄下新建插件MyPlugin


image.png

修改/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


image.png

4.4.編寫(xiě)插件代碼

image.png

在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
image.png

4.6.2設(shè)置編譯器
由于Clang插件需要使用對(duì)應(yīng)的版本加載,如果版本不一致會(huì)導(dǎo)致編譯錯(cuò)誤和二,會(huì)出現(xiàn)如下圖所示徘铝。


image.png

在Build Settings欄目中新增兩項(xiàng)用戶(hù)定義的設(shè)置


image.png

分別是CC和CXX
CC對(duì)應(yīng)的是自己編譯的clang的絕對(duì)路徑
CXX對(duì)應(yīng)的是自己編譯的clang++的絕對(duì)路徑
image.png

接下來(lái)在Build Settings欄目中搜索index,將Enable Index-Wihle-Building Functionality??Default改為NO


image.png

我們做出插件的效果如下:
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惯吕,一起剝皮案震驚了整個(gè)濱河市惕它,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌废登,老刑警劉巖淹魄,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異堡距,居然都是意外死亡甲锡,警方通過(guò)查閱死者的電腦和手機(jī)兆蕉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缤沦,“玉大人虎韵,你說(shuō)我怎么就攤上這事「追希” “怎么了包蓝?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)企量。 經(jīng)常有香客問(wèn)我测萎,道長(zhǎng),這世上最難降的妖魔是什么梁钾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任绳泉,我火速辦了婚禮,結(jié)果婚禮上姆泻,老公的妹妹穿的比我還像新娘。我一直安慰自己冒嫡,他們只是感情好拇勃,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著孝凌,像睡著了一般方咆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蟀架,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天瓣赂,我揣著相機(jī)與錄音,去河邊找鬼片拍。 笑死煌集,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捌省。 我是一名探鬼主播苫纤,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纲缓!你這毒婦竟也來(lái)了卷拘?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤祝高,失蹤者是張志新(化名)和其女友劉穎栗弟,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體工闺,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乍赫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年瓣蛀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耿焊。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揪惦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出罗侯,到底是詐尸還是另有隱情器腋,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布钩杰,位于F島的核電站纫塌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏讲弄。R本人自食惡果不足惜措左,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望避除。 院中可真熱鬧怎披,春花似錦、人聲如沸瓶摆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)群井。三九已至状飞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間书斜,已是汗流浹背诬辈。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荐吉,地道東北人焙糟。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像稍坯,于是被迫代替她去往敵國(guó)和親酬荞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容