LLVM

解釋性語言和編譯性語言的區(qū)別劣像?
解釋性語言可以通過解釋器直接執(zhí)行相應的代碼粘衬,比如python語言项玛;而
編譯性語言要經(jīng)過編譯器編譯成相應的可執(zhí)行文件貌笨,然后才可以執(zhí)行。

傳統(tǒng)的編譯器設計

傳統(tǒng)的編譯器

編譯器前端(Frontend)

編譯器前端的任務是解析源代碼稍计。它會進行:詞法分析躁绸,語法分析,語義分析臣嚣,檢查源代碼是否存在錯誤净刮,然后構建抽象語法樹(Abstract Syntax Tree,AST)硅则,LLVM的前端還會生成中間代碼(IR)淹父。

優(yōu)化器(Optimizer)

優(yōu)化器負責各種優(yōu)化。改善代碼的運行時間怎虫,例如消除冗余計算等暑认。

后端(Backend)/代碼生成器(CodeGenerator)

將代碼映射到目標指令集,生成機器語言大审,并且進行機器相關的代碼優(yōu)化蘸际。

iOS的編譯器架構

clang和llvm的聯(lián)系?
Objective C/C/C++使用的編譯器前端是Clang徒扶,swift是Swift粮彤。后端都是LLVM。LLVM是編譯器的框架系統(tǒng),clang只是編譯器的前端部分导坟。

iOS編譯器架構

LLVM的設計

其他的編譯器如GCC屿良,作為了一個整體的程序設計的,所以不夠靈活惫周。
LLVM最重要的設計就是尘惧,使用的通用的代碼表示形式IR,它是用來在編譯器中表示代碼的形式递递。所以LLVM可以靈活的增加前端和后端喷橙,只要增加的前端和后端使用同一的規(guī)則IR就可以。


LLVM靈活的前后端

Clang

Clang是LLVM的一個子項目登舞,它的誕生是為了替代GCC重慢。它屬于LLVM架構中的前端。
通過下面的命令可以打印源碼的編譯階段逊躁。

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:預處理過程隅熙,包括宏的替換稽煤,頭文件的導入等;
2:編譯階段囚戚,詞法分析酵熙、語法分析,最終生成IR代碼驰坊。
3:后端匾二,代碼優(yōu)化處理,最終生成匯編拳芙;
4:匯編生成目標文件察藐;
5:連接:鏈接需要的動態(tài)庫和靜態(tài)庫,生成可執(zhí)行文件舟扎;
6:通過不同的架構分飞,生成對象的可執(zhí)行文件;

預處理階段
clang -E main.m

執(zhí)行完成后可以看到文件的導入和宏的替換睹限。

編譯階段

詞法分析:把代碼切成一個個的Token譬猫,比如大小括號,字符串等羡疗。

clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

語法分析:在詞法分析的基礎上將單詞序列組成起來染服,形成了語法樹,判斷程序代碼在結構上是否正確叨恨。

clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

生成中間代碼
通過下面的代碼可以生成.ll的IR代碼文件

clang -S -fobjc-arc -emit-llvm main.m

IR生成時可以通過參數(shù)選擇優(yōu)化柳刮,優(yōu)化級別分別為-O0 -O1 -O2 -O3 -Os

clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll

bitCode:Xcode7以后可通過bitcode開啟進一步的優(yōu)化,生成.bc的中間代碼。

clang -emit-llvm -c main.ll -o main.bc

生成匯編代碼:最終通過.ll或者.bc生成匯編代碼诚亚。

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 main.m -o main.s

生成目標文件(匯編器)

clang -fmodules -c main.s -o main.o

生成可執(zhí)行文件(鏈接)

clang main.o -o main

創(chuàng)建Clang插件

  • 在LLVM的目錄 /llvm/tools/clang/tools 創(chuàng)建新的插件:HKPlugin晕换。


    插件目錄
  • 修改/llvm/tools/clang/tools目錄下的CMakeLists.text文件,新增add_clang_subdirectory(HKPlugin)站宗。


    配置文件
  • 在HKPlugin目錄下新增一個HKPlugin.cpp的文件以及CMakeLists.text配置文件闸准。在CMakeLists.text中填寫配置信息:

插件的配置信息
  • 接下來我們利用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 {

    class HKConsumer : public ASTConsumer{
    public:
        //解析完一個頂級的申明就會來這里執(zhí)行!
        bool HandleTopLevelDecl(DeclGroupRef D) {
            cout<<"正在解析..."<<endl;
            return true;
        }
        
        //解析完整個文件后被調用
        void HandleTranslationUnit(ASTContext &Ctx) {
            cout<<"文件解析完畢!"<<endl;
        }
        
    };


    //繼承PluginASTAction實現(xiàn)我們自定義的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);
        }
        
    };


}
//注冊插件
static FrontendPluginRegistry::Add<HKPlugin::HKASTAction> X("HKPlugin", "This is the description of the plugin");

測試插件代碼

自己編寫的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 源碼路徑

測試結果:


插件測試結果

自定義clang---屬性修飾符copy

在clang的語法分析階段,判斷屬性類型為NSString敏释,NSArray库快,NSDictionay的,是否使用copy修飾钥顽,如果不是copy修飾報出警告义屏。

@interface ViewController ()
/**  */
@property (nonatomic, strong) NSString *name;
/**  */
@property (nonatomic, strong) NSArray *arrs;

@end

我們使用clang的語法分析命令翻譯該文件

clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.2.sdk??????S DK?????? -fmodules -fsyntax-only -Xclang -ast-dump ViewController.m

得到的了編譯階段的語法樹:


語法樹

可以看到屬性在語法樹中被翻譯為ObjCPropertyDecl。我們可以在語法分析的階段監(jiān)聽ObjCPropertyDecl這種類型的節(jié)點蜂大,然后進行判斷闽铐。想要監(jiān)聽某個類型的節(jié)點,我們可以使用MatchFinder來獲取奶浦。

class ZFConsumer : public ASTConsumer{
private:
    MatchFinder matcher;
    ZFMatchCallback callBack;
public:
    ZFConsumer() {
        matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callBack);
    }
    
    //解析完整個文件后被調用
    void HandleTranslationUnit(ASTContext &Ctx) {
        cout<<"ZF文件解析完畢!"<<endl;
        matcher.matchAST(Ctx);
    }
};

為Consumer增加了成員變量matcher兄墅,在Consumer的初始化中通過matcher來添加objcPropertyDecl類型節(jié)點的監(jiān)聽。然后在文件解析完的回調方法HandleTranslationUnit中澳叉,為matcher傳遞ASTContext隙咸。另外我們還需要創(chuàng)建回調的對象ZFMatchCallback。

class ZFMatchCallback: public MatchFinder::MatchCallback {
public:
    virtual void run(const MatchFinder::MatchResult &Result) {
    //通過結果獲取到節(jié)點成洗。
    const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
    
    if (propertyDecl) {//如果節(jié)點有值
        //拿到屬性的類型
        string typeStr = propertyDecl->getType().getAsString();
        cout<<typeStr<<"應該用copy修飾"<<endl;
    }
}
};

監(jiān)聽到objcPropertyDecl類型的節(jié)點后五督,會回調run方法。這樣我們就可以獲取到節(jié)點以及節(jié)點的類型等信息瓶殃。
這樣進行監(jiān)聽話概荷,還會把引入的頭文件的信息全部都給攔截監(jiān)聽了,所以我們需要過濾下碌燕,如果是路徑包含"/Applications/Xcode.app/"就為系統(tǒng)的頭文件中的信息误证,需要過濾掉。

//判斷是否是自己的文件
bool isUserSourceCode(const string filename) {
    if (filename.empty()) return false;
    
    // 非Xcode中的源碼都認為是用戶源碼
    if (filename.find("/Applications/Xcode.app/") == 0) return false;
    
    return true;
}
//獲取文件名稱
string filename = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
if (propertyDecl && isUserSourceCode(filename)) {//如果節(jié)點有值,并且是用戶文件
  .......
}

其中的CI為CompilerInstance類型修壕,是通過ZFConsumer傳遞過來的愈捅。

然后我們還需要判斷當前的屬性是否正確使用了copy

//判斷是否應該用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;
}
//attrKind & ObjCPropertyDecl::OBJC_PR_copy 如果實行修飾符為copy返回true
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {

}

如果該使用copy的地方?jīng)]有使用copy慈鸠,使用DiagnosticsEngine診斷引擎進行警告處理

//診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 報告
diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0這個地方推薦用Copy"))<<typeStr;

這樣我們的clang插件就寫的差不多了蓝谨,使用自定義的clang和clang插件對ViewController.m文件的屬性就行語法檢測,結果如下:

NSString *應該用copy修飾而沒用Copy,發(fā)出警告譬巫!
demo1/ViewController.m:13:1: warning: NSString *這個地方推薦用Copy
@property (nonatomic, strong) NSString *name;
^
NSArray *應該用copy修飾而沒用Copy咖楣,發(fā)出警告!
demo1/ViewController.m:15:1: warning: 
      NSArray *這個地方推薦用Copy
@property (nonatomic, strong) NSArray *arrs;
^
2 warnings generated.

最后附上我們自定義的clang插件的完成代碼:

#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 ZFPlugin {
    
    class ZFMatchCallback: 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:
        ZFMatchCallback(CompilerInstance &CI):CI(CI) {}
        
        virtual void run(const MatchFinder::MatchResult &Result) {
            //通過結果獲取到節(jié)點诱贿。
            const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
            //獲取文件名稱
            string filename = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
            
            if (propertyDecl && isUserSourceCode(filename)) {//如果節(jié)點有值,并且是用戶文件
                //拿到屬性的類型
                string typeStr = propertyDecl->getType().getAsString();
                //拿到節(jié)點的描述信息
                ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
                
                //attrKind & ObjCPropertyDecl::OBJC_PR_copy 如果實行修飾符為copy返回true
                if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
                    cout<<typeStr<<"應該用copy修飾而沒用Copy,發(fā)出警告咕缎!"<<endl;
                    //診斷引擎
                    DiagnosticsEngine &diag = CI.getDiagnostics();
                    //Report 報告
                    diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0這個地方推薦用Copy"))<<typeStr;
                }
            }
        }
    };
    
    class ZFConsumer : public ASTConsumer{
    private:
        MatchFinder matcher;
        ZFMatchCallback callBack;
    public:
        ZFConsumer(CompilerInstance &CI): callBack(CI) {
            matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callBack);
        }
        
        //解析完整個文件后被調用
        void HandleTranslationUnit(ASTContext &Ctx) {
            cout<<"ZF文件解析完畢!"<<endl;
            matcher.matchAST(Ctx);
        }
        
    };
    
    
    //繼承PluginASTAction實現(xiàn)我們自定義的Action
    class ZFASTAction: 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 <ZFConsumer>(new ZFConsumer(CI));
        }
        
    };
    
    
}
//注冊插件
static FrontendPluginRegistry::Add<ZFPlugin::ZFASTAction> X("ZFPlugin", "This is the description of the plugin");

Xcode集成自定義插件

前面我們寫好了自定義的功能插件珠十,下面我們把插件集成到Xcode中去。

加載插件

打開測試的xcode項目凭豪,在Build Settings -> Other C Flags添加如下內容:

-Xclang -load -Xclang (.dylib)插件路徑 -Xclang -add-plugin -Xclang ZFPlugin焙蹭。


配置自定義插件

設置編譯器

  • 設置編譯器clang為自己的clang。
    clang插件需要對應版本的clang去加載嫂伞,如果版本不一致會導致編譯出錯孔厉。需要再Build Setting中新增兩項用戶定義的設置


    image.png
  • 分別為CC和CXX。
    CC對應的是自己編譯的clang路徑帖努;
    CXX對應的是自己編譯的clang++路徑烟馅;


    image.png
  • 接下來在Build Settings欄目中搜索Index,將Enable Index-While-Building Functionality的Default該為NO然磷。


    image.png
  • 然后可以達到我們的最終目的,使用自定義的clang插件檢測我們的代碼的語法了刊驴。


    檢測語法
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末姿搜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子捆憎,更是在濱河造成了極大的恐慌舅柜,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躲惰,死亡現(xiàn)場離奇詭異致份,居然都是意外死亡,警方通過查閱死者的電腦和手機础拨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門氮块,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诡宗,你說我怎么就攤上這事滔蝉。” “怎么了塔沃?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵蝠引,是天一觀的道長。 經(jīng)常有香客問我,道長螃概,這世上最難降的妖魔是什么矫夯? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮吊洼,結果婚禮上训貌,老公的妹妹穿的比我還像新娘。我一直安慰自己融蹂,他們只是感情好旺订,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著超燃,像睡著了一般区拳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上意乓,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天樱调,我揣著相機與錄音,去河邊找鬼届良。 笑死笆凌,一個胖子當著我的面吹牛,可吹牛的內容都是我干的士葫。 我是一名探鬼主播乞而,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼慢显!你這毒婦竟也來了爪模?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤荚藻,失蹤者是張志新(化名)和其女友劉穎屋灌,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體应狱,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡共郭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了疾呻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片除嘹。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖岸蜗,靈堂內的尸體忽然破棺而出憾赁,到底是詐尸還是另有隱情,我是刑警寧澤散吵,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布龙考,位于F島的核電站蟆肆,受9級特大地震影響,放射性物質發(fā)生泄漏晦款。R本人自食惡果不足惜炎功,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缓溅。 院中可真熱鬧蛇损,春花似錦、人聲如沸坛怪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽袜匿。三九已至更啄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間居灯,已是汗流浹背祭务。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留怪嫌,地道東北人义锥。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像岩灭,于是被迫代替她去往敵國和親拌倍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容