日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS之LLVM编译流程和Clang插件开发集成

發(fā)布時間:2024/5/28 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS之LLVM编译流程和Clang插件开发集成 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

LLVM 簡介

一、什么是 LLVM?
  • LLVM 是構(gòu)架編譯器(compiler)的框架系統(tǒng),以 C++ 編寫而成,用于優(yōu)化以任意程序語言編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)、運(yùn)行時間(runtime)以及空閑時間(idle-time),對開發(fā)者保持開放,并兼容已有腳本。
  • LLVM 最早的時候是 Illinois 的一個研究項目,主要負(fù)責(zé)人是 Chris Lattner,他現(xiàn)在就職于Apple。Apple 目前也是 LLVM 項目的主要贊助者之一。
  • 在理解 LLVM 時,我們可以認(rèn)為它包括了一個狹義的 LLVM 和一個廣義的 LLVM。廣義的 LLVM 其實(shí)就是指整個 LLVM 編譯器架構(gòu),包括了前端、后端、優(yōu)化器、眾多的庫函數(shù)以及很多的模塊;而狹義的 LLVM 其實(shí)就是聚焦于編譯器后端功能(代碼生成、代碼優(yōu)化、JIT等)的一系列模塊和庫。
二、傳統(tǒng)編譯器設(shè)計
  • 傳統(tǒng)編譯器分三個階段: 前端(Frontend)-> 優(yōu)化器(Optimizer)-> 后端(Backend);
    • 前端Frontend:負(fù)責(zé)分析源代碼,可以檢查語法級錯誤(包括詞法分析、語法分析、語義分析),并構(gòu)建針對語言的抽象語法樹(AST:Abstract Syntax Tree);抽象語法樹可以進(jìn)一步轉(zhuǎn)換為優(yōu)化,最終轉(zhuǎn)為新的表示方式,然后再交給讓優(yōu)化器和后端處理,LLVM 的前端還會生成中間代碼(intermediate representation,簡稱IR);最終由后端生成可執(zhí)行的機(jī)器碼(可以理解為 LLVM 是編譯器 + 優(yōu)化器, 接收的是 IR 中間代碼,輸出的還是 IR,給后端,經(jīng)過后端翻譯成目標(biāo)指令集);
    • 優(yōu)化器 Optimizer:優(yōu)化器負(fù)責(zé)進(jìn)行各種優(yōu)化,改善代碼的運(yùn)行時間,例如消除冗余計算等;
    • 后端 Backend(代碼生成器 Code Generator):將代碼映射到目標(biāo)指令集,生成機(jī)器代碼,并且進(jìn)行機(jī)器代碼相關(guān)的代碼優(yōu)化;
  • 源碼 Source Code + 前端 Frontend + 優(yōu)化器 Optimizer + 后端 Backend(代碼生成器 CodeGenerator)+ 機(jī)器碼 Machine Code,如下所示:

  • LLVM 的優(yōu)點(diǎn)在于,中間表示IR代碼編寫良好,而且不同的前端語言最終都轉(zhuǎn)換成同一種的IR。
三、iOS 的編譯器架構(gòu)
  • OC、C、C++ 使用的編譯器前端是Clang,Swift是swift,后端都是LLVM,如下圖所示:

四、LLVM 的設(shè)計
  • LLVM 設(shè)計的最重要方面是,使用通用的代碼表示形式(IR),它是用來在編譯器中表示代碼的形式,所有 LLVM 可以為任何編程語言獨(dú)立編寫前端,并且可以為任意硬件架構(gòu)獨(dú)立編寫后端,如下所示:

  • LLVM的優(yōu)點(diǎn):
    • 中間表示IR代碼編寫良好,而且不同的前端語言最終都轉(zhuǎn)換成統(tǒng)一的中間代碼LLVM IR(LLVM Intermediate Representation);
    • 如果需要支持一種新的變成語言,那么只需要實(shí)現(xiàn)一個新的前端;
    • 如果需要支持一種新的硬件設(shè)備,那么只需要實(shí)現(xiàn)一個新的后端;
    • 優(yōu)化階段是一個通用的階段,它只針對統(tǒng)一的LLVM IR,不論是支持新的編程語言,還是支持新的硬件設(shè)備,都不需要對優(yōu)化階段做修改;
    • LLVM現(xiàn)在被座位實(shí)現(xiàn)何種靜態(tài)和運(yùn)行時編譯語言的通用基礎(chǔ)結(jié)構(gòu)(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等);
    • 相比之下,GCC的前端和后臺沒分的太開,前端后端耦合在一起,所以GCC為了支持一門新的語言,或者為了支持一個新的目標(biāo)平臺,就變的特別困難;
五、Clang
  • clang 是 LLVM 項目中的一個子項目,它是基于 LLVM 架構(gòu)圖的輕量級編譯器,誕生之初是為了替代 GCC,提供更快的編譯速度,它是負(fù)責(zé) C、C++、OC 語言的編譯器,屬于整個 LLVM 架構(gòu)中的編譯器前端,對于開發(fā)者來說,研究 Clang 可以給我們帶來很多好處。
  • 相比于 GCC,Clang 具有如下優(yōu)點(diǎn):
    • 編譯速度塊:在某平臺上,Clang 的編譯速度顯著的快過 GCC(Debug 模式下編譯 OC 速度比 GCC 快 3 倍);
    • 占用內(nèi)存小:Clang 生成的AST所占用的內(nèi)存是 GCC 的五分之一左右;
    • 模塊化設(shè)計:Clang 采用基于庫的模塊化設(shè)計,易于 IDE 集成及其他用途的重用;
    • 診斷信息可讀性強(qiáng):在編譯過程中,Clang 創(chuàng)建并保留了大量纖細(xì)的元數(shù)據(jù);(metadata),有利于調(diào)試和錯誤信息更加友善;
    • 設(shè)計清晰簡單,易于理解,擴(kuò)展性強(qiáng)。
六、Clang 與 LLVM

LLVM編譯流程

一、通過命令打印源碼編譯階段
  • clang -ccc-print-phases main.m
    • 輸入文件:找到源文件
      ± 0: input, “main.m”, objective-c
    • 預(yù)處理階段:這個過程處理包括宏的替換,頭文件的導(dǎo)入
      ± 1: preprocessor, {0}, objective-c-cpp-output
    • 編譯階段:進(jìn)行詞法分析、語法分析、檢測語法是否正確,最終生成IR
      ± 2: compiler, {1}, ir
    • 后端:這里L(fēng)LVM會通過一個一個的pass去優(yōu)化,每個pass做一些事情,最終生成匯編代碼
      ± 3: backend, {2}, assembler
    • 匯編代碼生成目標(biāo)文件
      ± 4: assembler, {3}, object
    • 鏈接:鏈接需要的動態(tài)庫和靜態(tài)庫,生成可執(zhí)行文件
      ± 5: linker, {4}, image(鏡像文件)
    • 綁定:通過不同的架構(gòu),生成對應(yīng)的可執(zhí)行文件
      6: bind-arch, “x86_64”, {5}, image
  • 新建一個工程,cd 到 main.m 路徑,然后執(zhí)行 clang -ccc-print-phases main.m,結(jié)果如下:
yangdw@Kody 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}, image6: bind-arch, "x86_64", {5}, image
二、預(yù)處理編譯階段
  • 在 main.m 中鍵入以下代碼:
int test(int a,int b){return a + b + 3;}int main(int argc, const char * argv[]) {@autoreleasepool {int a = test(1, 2);printf("%d",a);}return 0;}
  • 然后執(zhí)行 clang -E main.m,可以看到,結(jié)果如下:
# 9 "main.m" 2int test(int a,int b){return a + b + 3;}int main(int argc, const char * argv[]) {@autoreleasepool {int a = test(1, 2);printf("%d",a);}return 0;}
  • 不難看出,這個階段主要是處理了包括宏的替換和頭文件的導(dǎo)入;
三、編譯階段
① 詞法分析
  • 預(yù)處理完成后就會進(jìn)行詞法分析,這里會把代碼切成一個個Token,比如大小括號、等于號還有字符串等。
  • 通過如下命令查看詞法分析的結(jié)果:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
  • 執(zhí)行結(jié)果如下:
yangdw@Kody LLVM % clang -fmodules -fsyntax-only -Xclang -dump-tokens main.mannot_module_include '#import <Foundation/Foundation.h>int test(int a,int b){return a + b + 3;}int main(int argc, con' Loc=<main.m:8:1>int 'int' [StartOfLine] Loc=<main.m:10:1>identifier 'test' [LeadingSpace] Loc=<main.m:10:5>l_paren '(' Loc=<main.m:10:9>int 'int' Loc=<main.m:10:10>identifier 'a' [LeadingSpace] Loc=<main.m:10:14>comma ',' Loc=<main.m:10:15>int 'int' Loc=<main.m:10:16>identifier 'b' [LeadingSpace] Loc=<main.m:10:20>r_paren ')' Loc=<main.m:10:21>l_brace '{' Loc=<main.m:10:22>return 'return' [StartOfLine] [LeadingSpace] Loc=<main.m:11:5>identifier 'a' [LeadingSpace] Loc=<main.m:11:12>plus '+' [LeadingSpace] Loc=<main.m:11:14>identifier 'b' [LeadingSpace] Loc=<main.m:11:16>plus '+' [LeadingSpace] Loc=<main.m:11:18>numeric_constant '3' [LeadingSpace] Loc=<main.m:11:20>
  • 如果頭文件找不到,指定sdk:
clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -dump-tokens main.mclang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -dump-tokens main.m
② 語法分析
  • 詞法分析完成后就是語法分析,它的任務(wù)是驗證語法是否正確,在詞法分析的基礎(chǔ)上將單詞序列組合成各類此法短語,如程序、語句、表達(dá)式等,然后將所有節(jié)點(diǎn)組成抽象語法樹(Abstract Syntax Tree􏰊AST),語法分析程序判斷程序在結(jié)構(gòu)上是否正確。
  • 可以通過下面命令查看語法分析的結(jié)果:
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
  • 如果導(dǎo)入頭文件找不到,可以指定SDK:
clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -ast-dump main.mclang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -ast-dump main.m
  • 語法分析的結(jié)果如下所示:

③ 生成中間代碼IR
  • 生成中間代碼IR,代碼生成器(Code Generation)會將語法樹自頂向下遍歷逐步翻譯成LLVM IR;
  • 可以通過下面命令可以生成.ll的文本文件,查看IR代碼。
clang -S -fobjc-arc -emit-llvm main.m
  • OC 代碼在這一步會進(jìn)行 runtime 橋接:property合成、ARC處理等;
  • IR 的基本語法:
    • @ 全局標(biāo)識
    • % 局部標(biāo)識
    • alloca 開辟空間
    • align 內(nèi)存對齊
    • i32 32bit 4個字節(jié)
    • store 寫入內(nèi)存
    • load 讀取數(shù)據(jù)
    • call 調(diào)用函數(shù)
    • ret 返回
  • 生成的中間代碼.ll文件如下:

  • 其中,test 函數(shù)的參數(shù)為:

  • IR 文件在OC中是可以進(jìn)行優(yōu)化的,一般設(shè)置是在target - Build Setting - Optimization Level(優(yōu)化器等級)中設(shè)置。LLVM的優(yōu)化級別分別是-O0 -O1 -O2 -O3 -Os(第一個是大寫英文字母O),下面是帶優(yōu)化的生成中間代碼IR的命令:
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
  • 優(yōu)化之后如下所示:

  • Xcode7 以后開啟 bitcode,蘋果會做進(jìn)一步優(yōu)化,生成.bc的中間代碼,通過優(yōu)化后的IR代碼生成 .bc 代碼:
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
  • 生成匯編代碼也可以進(jìn)行優(yōu)化:
clang -Os -S -fobjc-arc main.m -o main.s
  • 此時生成的main.s文件的格式為匯編代碼:
yangdw@Kody LLVM % clang -emit-llvm -c main.ll -o main.bcyangdw@Kody LLVM % clang -S -fobjc-arc main.bc -o main.s yangdw@Kody LLVM % clang -Os -S -fobjc-arc main.m -o main.syangdw@Kody LLVM % file main.smain.s: assembler source text, ASCII text
五、生成目標(biāo)文件(編譯器)
  • 目標(biāo)文件的生成,是匯編器以匯編代碼作為插入,將匯編代碼轉(zhuǎn)換為機(jī)器代碼,最后輸出目標(biāo)文件(object file)
clang -fmodules -c main.s -o main.o
  • 可以通過 nm 命令,查看下 main.o 中的符號:
$xcrun nm -nm main.o
  • 以下是 main.o 中的符號,其文件格式為目標(biāo)文件:
yangdw@Kody LLVM % clang -fmodules -c main.s -o main.oyangdw@Kody LLVM % $xcrun nm -nm main.o(undefined) external _objc_autoreleasePoolPop(undefined) external _objc_autoreleasePoolPush(undefined) external _printf0000000000000000 (__TEXT,__text) external _test000000000000000a (__TEXT,__text) external _mainyangdw@Kody LLVM % file main.o main.o: Mach-O 64-bit object x86_64
  • 分析說明:
    • _printf 函數(shù)是一個是undefined 、external 的
    • undefined 表示在當(dāng)前文件暫時找不到符號_printf
    • external 表示這個符號是外部可以訪問的
六、鏈接
  • 鏈接主要是鏈接需要的動態(tài)庫和靜態(tài)庫,生成可執(zhí)行文件,其中
    • 靜態(tài)庫會和可執(zhí)行文件合并;
    • 動態(tài)庫是獨(dú)立的;
  • 連接器把編譯生成的 .o 文件和 .dyld .a 文件鏈接,生成一個mach-o文件:
clang main.o -o main
  • 查看鏈接之后的符號:
$xcrun nm -nm main
  • 其中的undefined表示會在運(yùn)行時進(jìn)行動態(tài)綁定:
clang main.o -o main$xcrun nm -nm main(undefined) external _printf(from libSystem) (undefined) external dyld_stub_binder(from libSystem)0000000100000000 (__TEXT,__text)[referenced dynamically] external __execute_header0000000100003f20 (__TEXT,__text) external _test0000000100003f40 (__TEXT,__text) external _main0000000100008008 (__DATA,__data) non_external _dyld_private
  • 查看 main 是什么格式,此時是 mach-o可執(zhí)行文件:
yangdw@Kody LLVM % file mainmain:Mach-O 64-bit executable x86_64
七、綁定

綁定主要是通過不同的架構(gòu),生成對應(yīng)的mach-o格式可執(zhí)行文件

八、LLVM 的編譯流程如下

Clang插件開發(fā)

一、LLVM 下載
  • 由于國內(nèi)網(wǎng)絡(luò)限制,需要借助鏡像下載 LLVM 的源碼:LLVM 的鏡像鏈接
  • 下載 LLVM 項目:
git clone https://github.com/llvm/llvm-project.git或者git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
  • 在 LLVM 的 tools 目錄下下載 Clang:
cd llvm/toolsgit clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git
  • 在 LLVM 的 projects 目錄下下載 compiler-rt、libcxx、libcxxabi:
cd ../projectsgit clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.gitgit clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxx.gitgit clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.git
  • 在 Clang 的 tools 下安裝 extra 工具:
cd ../tools/clang/toolsgit clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-extra.git
二、LLVM 編譯

由于最新的 LLVM 只支持 cmake 來編譯,所以需要安裝 cmake。

① 安裝 cmake
  • 查看brew是否安裝cmake,如果已經(jīng)安裝,則跳過下面步驟:
brew list
  • 通過 brew 安裝 cmake:
brew install cmake
② 通過 Xcode 編譯 LLVM
  • cmake 編譯成 Xcode 項目:
// 在llvm同級目錄下新建一個build_xcode文件mkdir build_xcodecd build_xcode// 編譯llvmcmake -G Xcode ../llvm
  • 如果編譯過程中遇到如下的錯誤:刪除構(gòu)建目錄下的 CMakeCache.txt 即可。
CMake Error: Error: generator : XcodeDoes not match the generator used previously: NinjaEither remove the CMakeCache.txt file and CMakeFiles directory or choose a different binary directory.
  • 使用 Xcode 編譯 Clang,選擇自動創(chuàng)建 Schemes:

  • 編譯(CMD + B),選擇 ALL_BUILD Secheme 進(jìn)行編譯(時間較長,預(yù)計一個小時以上)

  • 如果編譯過程中遇到錯誤:error: The i386 architecture is deprecated. You should update your ARCHS build setting to remove the i386 architecture
    只需要將對應(yīng)中的 Build Settings 選項 Architectures 中的值切換為 Standard Architectures(64-bit Intel) 即可。
    或者:選擇手動創(chuàng)建Schemes,然后編譯編譯 Clang + ClangTooling 即可。
③ 通過 ninja 編譯 LLVM
  • 使用 ninja 進(jìn)行編譯則還需要安裝 ninja,使用以下命令安裝ninja:
brew install ninja
  • 在 LLVM 源碼根目錄下新建 build_ninja 目錄,最終會在 build_ninja 目錄下生成build.ninja;
  • 在 LLVM 源碼根目錄下新建 llvm_release 目錄,最終編譯文件會在 llvm_release 文件夾路徑下:
cd llvm_build// -DCMAKE_INSTALL_PREFIX 指定 LLVM 的安裝路徑,注意:DCMAKE_INSTALL_PREFIX后面不能有空格cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX= 安裝路徑(本機(jī)為/ Users/xxx/xxx/LLVM/llvm_release)
  • 一次執(zhí)行編譯,安裝指令:
ninjaninja install
三、創(chuàng)建插件
  • 在 /llvm/tools/clang/tools 目錄下新建插件 YDWPlugin:

  • 在 /llvm/tools/clang/tools 目錄下的 CMakeLists.txt 文件,新增add_clang_subdirectory(YDWPlugin),此處的 YDWPlugin 即為上一步創(chuàng)建的插件名稱;

  • 在 YDWPlugin 目錄下新建兩個文件,分別是 YDWPlugi.cpp 和 CMakeLists.txt,并在CMakeLists.txt 中加上以下代碼:
// 通過終端在YDWPlugin目錄下創(chuàng)建touch YDWPlugin.cpptouch CMakeLists.txt// CMakeLists.txt中添加以下代碼add_llvm_library(YDWPlugin MODULE BUILDTREE_ONLY YDWPlugin.cpp )
  • 利用 cmake 重新生成 Xcode 項目,在 build_xcode 目錄下執(zhí)行以下命令:
cmake -G Xcode ../llvm
  • 最后可以在 LLVM 的 Xcode 項目中可以看到 Loadable modules 目錄下由自定義的YDWPlugin 目錄,然后就可以在里面編寫相應(yīng)的插件代碼。

四、編寫插件代碼
  • 在 YDWPlugin 目錄下的 YDWPlugin.cpp 文件中,加入以下代碼:
// create by YDW // 2020/11/25#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 YDWPlugin {//第三步:掃描完畢的回調(diào)函數(shù) //4、自定義回調(diào)類,繼承自MatchCallback class YDWMatchCallback: public MatchFinder::MatchCallback {private://CI傳遞路徑:YDWASTAction類中的CreateASTConsumer方法參數(shù) - YDWConsumer的構(gòu)造函數(shù) - YDWMatchCallback的私有屬性,通過構(gòu)造函數(shù)從YDWASTConsumer構(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 | NSDictionaryif (typeStr.find("NSString") != string::npos ||typeStr.find("NSArray") != string::npos ||typeStr.find("NSDictionary") != string::npos/*...*/){return true;}return false;}public:YDWMatchCallback(CompilerInstance &CI) :CI(CI) {}//重寫run方法void run(const MatchFinder::MatchResult &Result) {//通過result獲取到相關(guān)節(jié)點(diǎn) -- 根據(jù)節(jié)點(diǎn)標(biāo)記獲取(標(biāo)記需要與YDWASTConsumer構(gòu)造方法中一致)const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");//判斷節(jié)點(diǎn)有值,并且是用戶文件if (propertyDecl && isUserSourceCode(CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str()) ) {//15、獲取節(jié)點(diǎn)的描述信息ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();//獲取節(jié)點(diǎn)的類型,并轉(zhuǎn)成字符串string typeStr = propertyDecl->getType().getAsString(); // cout<<"---------拿到了:"<<typeStr<<"---------"<<endl;//判斷應(yīng)該使用copy,但是沒有使用copyif (propertyDecl->getTypeSourceInfo() && isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {//使用CI發(fā)警告信息//通過CI獲取診斷引擎DiagnosticsEngine &diag = CI.getDiagnostics();//通過診斷引擎 report報告 錯誤,即拋出異常/*錯誤位置:getBeginLoc 節(jié)點(diǎn)開始位置錯誤:getCustomDiagID(等級,提示)*/diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0 - 這個地方推薦使用copy!!"))<< typeStr;}}} };//第二步:掃描配置完畢 //3、自定義YDWASTConsumer,繼承自ASTConsumer,用于監(jiān)聽AST節(jié)點(diǎn)的信息 -- 過濾器 class YDWASTConsumer: public ASTConsumer { private://AST節(jié)點(diǎn)的查找過濾器MatchFinder matcher;//定義回調(diào)類對象YDWMatchCallback callback;public://構(gòu)造方法中創(chuàng)建matcherFinder對象YDWASTConsumer(CompilerInstance &CI) : callback(CI) {//添加一個MatchFinder,每個objcPropertyDecl節(jié)點(diǎn)綁定一個objcPropertyDecl標(biāo)識(去匹配objcPropertyDecl節(jié)點(diǎn))//回調(diào)callback,其實(shí)是在YDWMatchCallback里面重寫run方法(真正回調(diào)的是回調(diào)run方法)matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);}//實(shí)現(xiàn)兩個回調(diào)方法 HandleTopLevelDecl 和 HandleTranslationUnit//解析完一個頂級的聲明,就回調(diào)一次(頂級節(jié)點(diǎn),相當(dāng)于一個全局變量、函數(shù)聲明)bool HandleTopLevelDecl(DeclGroupRef D){ // cout<<"正在解析..."<<endl;return true;}//整個文件都解析完成的回調(diào)void HandleTranslationUnit(ASTContext &context) { // cout<<"文件解析完畢!"<<endl;//將文件解析完畢后的上下文context(即AST語法樹) 給 matchermatcher.matchAST(context);} };//2、繼承PluginASTAction,實(shí)現(xiàn)我們自定義的Action,即自定義AST語法樹行為 class YDWASTAction: public PluginASTAction {public://重載ParseArgs 和 CreateASTConsumer方法bool ParseArgs(const CompilerInstance &ci, const std::vector<std::string> &args) {return true;}//返回ASTConsumer類型對象,其中ASTConsumer是一個抽象類,即基類/*解析給定的插件命令行參數(shù)。- param CI 編譯器實(shí)例,用于報告診斷。- return 如果解析成功,則為true;否則,插件將被銷毀,并且不執(zhí)行任何操作。該插件負(fù)責(zé)使用CompilerInstance的Diagnostic對象報告錯誤。*/unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {//返回自定義的YDWASTConsumer,即ASTConsumer的子類對象/*CI用于:- 判斷文件是否使用戶的- 拋出警告*/return unique_ptr<YDWASTConsumer> (new YDWASTConsumer(CI));}};}// 第一步:注冊插件,并自定義AST語法樹Action類 // 1、注冊插件 static FrontendPluginRegistry::Add<YDWPlugin::YDWASTAction> YDW("YDWPlugin", "This is YDWPlugin");
  • 原理分析:
    • ① 注冊插件,并自定義AST語法樹Action類
      • 繼承自 PluginASTAction,自定義 ASTAction,需要重載兩個方法 ParseArgs 和 CreateASTConsumer,其中的重點(diǎn)方法是 CreateASTConsumer,方法中有個參數(shù) CI 即編譯實(shí)例對象,主要用于判斷文件是否是屬于用戶拋出警告
      • 通過 FrontendPluginRegistry 注冊插件,需要關(guān)聯(lián)插件名與自定義的 ASTAction 類;
    • ② 掃描配置完畢
      • 繼承自 ASTConsumer 類,實(shí)現(xiàn)自定義的子類 YDWASTConsumer,有兩個參數(shù) MatchFinder 對象 matcher 以及 YDWMatchCallback 自定義的回調(diào)對象 callback;
      • 實(shí)現(xiàn)構(gòu)造函數(shù),主要是創(chuàng)建 MatchFinder 對象,以及將 CI 傳遞給回調(diào)對象;
      • 實(shí)現(xiàn)兩個回調(diào)方法:HandleTopLevelDecl:解析完一個頂級的聲明,就回調(diào)一次;
        HandleTranslationUnit:整個文件都解析完成的回調(diào),將文件解析完畢后的上下文context(即AST語法樹) 給 matcher;
    • ③ 掃描完畢的回調(diào)函數(shù)
      • 繼承自 MatchFinder::MatchCallback,自定義回調(diào)類 YDWMatchCallback;
      • 定義 CompilerInstance 私有屬性,用于接收 ASTConsumer 類傳遞過來的 CI 信息;
      • 重寫 run 方法:
        • 通過 result,根據(jù)節(jié)點(diǎn)標(biāo)記,獲取相應(yīng)節(jié)點(diǎn),此時的標(biāo)記需要與 YDWASTConsumer 構(gòu)造方法中一致;
        • 判斷節(jié)點(diǎn)有值,并且是用戶文件即 isUserSourceCode 私有方法;
        • 獲取節(jié)點(diǎn)的描述信息;
        • 獲取節(jié)點(diǎn)的類型,并轉(zhuǎn)成字符串;
        • 判斷應(yīng)該使用 copy,但是沒有使用 copy;
        • 通過 CI 獲取診斷引擎;
        • 通過診斷引擎報告錯誤;
五、測試插件
  • 在終端中測試插件:
// 命令格式自己編譯的clang文件路徑 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -Xclang -load -Xclang 插件(.dyld)路徑 -Xclang -add-plugin -Xclang 插件名 -c 源碼路徑// 舉例/Users/XXX/Desktop/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -Xclang -load -Xclang /Users/XXXX/Desktop/build_xcode/Debug/lib/YDWPlugin.dylib -Xclang -add-plugin -Xclang YDWPlugin -c /Users/XXXX/Desktop/XXX/XXXX/測試demo/testClang/testClang/ViewController.m
  • 結(jié)果展示:
/Users/XXX/Desktop/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -Xclang -load -Xclang /Users/XXXX/Desktop/build_xcode/Debug/lib/YDWPlugin.dylib -Xclang -add-plugin -Xclang YDWPlugin -c /Users/XXXX/Desktop/XXX/XXXX/測試demo/testClang/testClang/ViewController.m...Controller.m:12: Warning:------- NSString* 沒用 copy 修飾 --------@property (nonatomic, strong) NSString *name;------- NSArray* 沒用 copy 修飾 --------@property (nonatomic, strong) NSArray *list;...
六、Xcode 集成插件
  • 加載插件,打開測試項目,在 target -> Build Settings -> Other C Flags 添加以下內(nèi)容:
-Xclang -load -Xclang (.dylib)動態(tài)庫路徑 -Xclang -add-plugin -Xclang YDWPlugin

  • 設(shè)置編譯器,由于 Clang 插件需要使用對應(yīng)的版本去加載,如果版本不一致會導(dǎo)致編譯失敗,如下所示:

  • 在 Build Settings 欄目中新增兩項用戶定義的設(shè)置,分別是 CC 和 CXX;
    • CC 對應(yīng)的是自己編譯的 Clang 的絕對路徑;
    • CXX 對應(yīng)的是自己編譯的 Clang++ 的絕對路徑;

  • 接下來在 Build Settings 中搜索 index,將 Enable Index-Wihle-Building Functionality 的 Default 改為 NO;

  • 最后,重新編譯測試項目,會出現(xiàn)下面的效果:

總結(jié)

以上是生活随笔為你收集整理的iOS之LLVM编译流程和Clang插件开发集成的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。