本文為L_Ares個(gè)人寫作,以任何形式轉(zhuǎn)載請表明原文出處。

內(nèi)容較多,本文盡量不廢話,看起來會有點(diǎn)刻板,但都是干貨,請各位先仔細(xì)并且充分的理解以下三個(gè)問題 :
- 為什么要了解
LLVM和Clang?- 什么是
LLVM?- 什么是
Clang?注 : 概念性的資料源于百科(
維基百科或LLVM官網(wǎng))。
前言 : 為什么要了解LLVM?
因?yàn)?code>Objective-C和
Swift語言都是編譯型語言,所以需要編譯器對其轉(zhuǎn)換成機(jī)器語言才會被系統(tǒng)識別,而它們的編譯器都是基于LLVM體系開發(fā)的。對
編譯器、編譯型語言有所遺忘的童鞋看這里。
一、簡述什么是LLVM
前言
第一 :
本節(jié)的重點(diǎn)雖然是LLVM,但是主要還是以iOSer的角度來看的。第二 :
要想深入學(xué)習(xí)LLVM的話,本節(jié)的這些內(nèi)容只能算是一個(gè)概念的大體簡述,尤其主要是iOS的視角,所以想了解更多LLVM的同學(xué),還需自行查看LLVM官網(wǎng)。第三 :
如果覺得LLVM官網(wǎng)真的很難懂,又全是英文,翻譯無法精準(zhǔn)的話,那么可以看一下知乎上的藍(lán)色大大對于LLVM的講解,再配合LLVM官網(wǎng)進(jìn)行學(xué)習(xí)。
概念
這里的概念全部引自LLVM官網(wǎng)。
LLVM現(xiàn)在已經(jīng)不是一個(gè)名稱的首字母縮寫了(最初還真是),而是一個(gè)項(xiàng)目名稱,是一個(gè)模塊化的、可重用的編譯器和工具鏈技術(shù)的集合。簡言之,
LLVM是架構(gòu)編輯器的框架系統(tǒng)。
所謂工具鏈技術(shù),舉個(gè)栗子 :
嵌入式
Linux就提供了一套完整的工具鏈,它利用GNU的gcc做編譯器,利用gdb、xgdb做調(diào)試工具,可以很方便的實(shí)現(xiàn)從操作系統(tǒng)的內(nèi)核態(tài)到用戶態(tài)的應(yīng)用軟件各個(gè)級別的調(diào)試。
所謂框架系統(tǒng) :
就是說
LLVM不僅僅是某一樣具體的事物,而是由很多模塊組合起來的框架。所謂框架就是你可以基于它提供的功能開發(fā)自己的模塊,并且集成到
LLVM系統(tǒng)上,增加LLVM的功能。或者你也可以利用
LLVM作為一種底層支持,去完成自己的軟件開發(fā)。
概述
LLVM曾是Low Level Virtual Machine(低等級虛擬機(jī))的縮寫,現(xiàn)在它是一個(gè)項(xiàng)目體系、是一套框架系統(tǒng)。
LLVM項(xiàng)目包括了非常多的子項(xiàng)目,包括iOS開發(fā)者常見的Clang,LLDB,libc++和libc++ ABI,這都是LLVM的主要子項(xiàng)目。
LLVM雖然包含了這么多編譯器作為子項(xiàng)目,但是它本身并不是編譯器,也不是編譯器后端。
它提供的是編譯器所需的一系列的庫。例如程序分析、代碼優(yōu)化、機(jī)器代碼生成等,并且提供了調(diào)用這些庫的相關(guān)工具。也為這些庫提供了方便簡單的、具備類型的、與平臺無關(guān)的統(tǒng)一中間代碼語言——LLVM IR。
LLVM以C++編寫而成,用于優(yōu)化以任意程序語言編寫的 :
編譯時(shí)間 (
compile-time)鏈接時(shí)間 (
link-time)運(yùn)行時(shí)間 (
run-time)空閑時(shí)間 (
idle-time)
LLVM對開發(fā)者保持開放,并且兼容現(xiàn)有的腳本。
二、編譯器的設(shè)計(jì)
在探索LLVM的設(shè)計(jì)之前,先看一下傳統(tǒng)的編譯器設(shè)計(jì),來比較兩者的區(qū)別,以探索LLVM的優(yōu)勢。
1. 傳統(tǒng)編譯器的設(shè)計(jì)
如圖2.1.0所示

傳統(tǒng)的編譯器整體流程是 :
源碼 --> 編譯器前端 --> 優(yōu)化器 --> 編譯器后端/代碼生成器(CodeGenetator) --> 機(jī)器語言
1.1 編譯器前端(Frontend)
傳統(tǒng)的編譯器前端主要任務(wù)就是讀取源代碼,對讀取的源代碼做詞法分析、語法分析、語義分析,通過這些分析來檢查源代碼是否存在錯(cuò)誤,然后構(gòu)建
語法樹 (Abstract Syntax Tree, AST)。
1.2 優(yōu)化器(Optimizer)
優(yōu)化器的責(zé)任就是各種優(yōu)化。改善代碼的運(yùn)行時(shí)間,例如消除冗余計(jì)算等。
1.3 編譯器后端(Backend)/代碼生成器(CodeGenerator)
將代碼映射到目標(biāo)指令集,生成機(jī)器語言,并進(jìn)行機(jī)器相關(guān)的代碼優(yōu)化。
后端或者說代碼生成器,最后生成的是對應(yīng)不同架構(gòu)的二進(jìn)制文件,之所以需要生成不同的二進(jìn)制文件,就是因?yàn)椴煌募軜?gòu)有不同的指令集,不同的指令集對相同的二進(jìn)制文件的指令識別是不同的,所以要將代碼映射到對應(yīng)的指令集。
2. iOS的編譯器架構(gòu)
所謂iOS的編譯器架構(gòu)其實(shí)就是LLVM編譯器架構(gòu)中的一部分。包括Objective-C、C、C++在內(nèi),它們使用的編譯器前端都是Clang。而Swift的編譯器前端就是Swift。它們的編譯器后端則全部都是LLVM Code Generator。
而Clang也只是整個(gè)LLVM架構(gòu)中的一個(gè)子項(xiàng)目,屬于編譯器前端。
來看iOS的編譯器架構(gòu)圖 :

很明顯,iOS的編譯器是通過Clang或者Swift來完成詞法分析、語法分析、語義分析的步驟,來完成對源代碼的錯(cuò)誤排查,然后生成抽象語法樹(AST)。
這里利用的就是LLVM的前端,和傳統(tǒng)編譯器的不同點(diǎn)是,Clang不止做完了上述的排查,還會生成一個(gè)非常重要的東西——IR。
什么是IR?
LLVM的編譯器前端不僅會完成分析排查和生成抽象語法樹的工作,還會生成中間代碼,這個(gè)中間代碼在LLVM中被稱作IR (intermediate representation)。
那么用語言來描述iOS的編譯器架構(gòu)設(shè)計(jì)流程就是 :
Clang/Swift讀取源代碼,檢查詞法、語法、語義的分析,排查錯(cuò)誤,生成抽象語法樹(AST),并生成中間代碼IR。然后將
IR給到LLVM的優(yōu)化器(Optimizer),改善代碼的運(yùn)行時(shí)間,消除冗余計(jì)算等。最后將優(yōu)化過的
IR再傳給后端或者說代碼生成器,由后端/代碼生成器完成將IR轉(zhuǎn)換成對應(yīng)不同架構(gòu)的二進(jìn)制文件,完成機(jī)器代碼的相關(guān)優(yōu)化。
3. LLVM的設(shè)計(jì)
LLVM的設(shè)計(jì)核心 : 中間代碼IR。
LLVM的設(shè)計(jì)重點(diǎn)和優(yōu)勢 : 面對多種源語言(編譯器前端來做),或者多種硬件架構(gòu)(編譯器后端來做)的情況時(shí),對比傳統(tǒng)的編譯器例如GCC,優(yōu)勢就是LLVM的IR設(shè)計(jì)。
由于GCC是以一個(gè)整體應(yīng)用程序設(shè)計(jì)的,也就是說,如果你的前端源語言或者后端硬件架構(gòu),任一一個(gè)發(fā)生了變化的話,你需要重新設(shè)計(jì)一整套前端--優(yōu)化器--后端的編譯器。
LLVM的設(shè)計(jì)圖 :

前端讀取源碼,經(jīng)過傳統(tǒng)步驟后,多增加一步生成中間代碼
IR,然后輸出IR給優(yōu)化器。優(yōu)化器讀取的是前端傳輸過來的
IR,優(yōu)化的也是IR,然后輸出的還是IR。所以優(yōu)化器只需要對傳入過來的IR做優(yōu)化即可,無論前端是什么樣的源語言類型,都會被轉(zhuǎn)成IR。后端/代碼生成器處理的則是優(yōu)化器傳來的
IR,也是只需要針對IR做處理,不需要管前端到底是什么類型的源語言。
優(yōu)勢 :
如果前端出現(xiàn)了新的源語言類型,
LLVM只需要針對新語言適配一個(gè)IR轉(zhuǎn)換器即可,優(yōu)化器和后端都不需要進(jìn)行大的改變,甚至不用改變。如果后端出現(xiàn)了新的硬件架構(gòu),
LLVM只需要針對新的硬件架構(gòu)適配一個(gè)IR轉(zhuǎn)換器即可,優(yōu)化器和前端則不需要進(jìn)行大的改變,甚至不用改變。
三、關(guān)于Clang
1. 什么是Clang
內(nèi)容引自Clang官網(wǎng),個(gè)人翻譯,如有不準(zhǔn)確的地方,還請不吝賜教。
Clang是LLVM原生的針對C、C++、Objective-C的編譯器。它的存在是為了提供一個(gè)平臺,
Clang做到了 :
- 非常快的編譯速度
- 非常有用的
error信息和warning警告- 可以構(gòu)建非常優(yōu)秀的源代碼
Clang Static Analyzer和Clang -tidy都是可以自動(dòng)發(fā)現(xiàn)你代碼里bug的工具。它們都是Clang工具中很好的例子,它們可以將Clang編譯器前端作為一個(gè)庫去解析C/C++的代碼。
2. Clang的概述
總的來說,
Clang是LLVM的子項(xiàng)目,它是基于LLVM架構(gòu)的輕量級編譯器,誕生之初是為了替代GCC,因?yàn)?code>GCC是傳統(tǒng)型的編譯器,雖然非常好用,但是GCC的源碼太長了,又晦澀難懂,而且編譯速度還可提升。于是基于LLVM架構(gòu)的Clang就逐步的擴(kuò)大了影響力。
3. 一些LLVM或Clang中的名詞解釋
3.1 詞法分析
詞法分析是計(jì)算機(jī)科學(xué)中將字符序列轉(zhuǎn)換為單詞序列的過程。
詞法分析是整個(gè)編譯流程的第一個(gè)階段,是編譯的基礎(chǔ)。詞法分析要做的是從左到右一個(gè)字符一個(gè)字符的讀入源程序,對構(gòu)成源程序的字符進(jìn)行掃描,然后根據(jù)構(gòu)詞規(guī)則識別單詞(也稱單詞符號或符號,英文叫做
Token)。所謂單詞就是一個(gè)字符串,是構(gòu)成源代碼的最小單位。
詞法分析一般都是經(jīng)過詞法分析的程序或者專門的函數(shù)來完成的,這種程序或者函數(shù)叫做詞法分析器,也叫掃描器(
Scanner)。
3.2 語法分析
語法分析是整個(gè)編譯流程中的一個(gè)邏輯階段。語法分析的任務(wù)是在詞法分析的基礎(chǔ)上,將單詞序列的組合成各種語法短語,如程序、語句、表達(dá)式等。
語法分析是判斷源程序在語法的結(jié)構(gòu)上是否正確。它和詞法分析分別像英語中的語法和單詞,詞法分析標(biāo)明了單詞的含義,語法分析則判斷單詞組合成的語句是否符合語法規(guī)則。
3.3 語義分析
語義分析也是整個(gè)編譯流程中的一個(gè)邏輯階段。語義分析的任務(wù)是對結(jié)構(gòu)上正確的源程序的上下文進(jìn)行有關(guān)性質(zhì)的檢查和類型檢查。
語義分析是檢查源程序有無語義錯(cuò)誤,為代碼生成階段收集類型信息。
語義分析檢查的是源程序是否符合規(guī)定的語言規(guī)范。
語義分析是編譯程序最實(shí)質(zhì)性的工作,它是第一次對源代碼做出解釋的階段,引起源程序的實(shí)質(zhì)變化。
語義和語法的區(qū)別,我舉個(gè)例子,方便大家理解。比如 : 我學(xué)習(xí)編程和編程學(xué)習(xí)我。兩者的語法都是沒有問題的,符合語法的規(guī)范——主謂賓。但是后者并不符合語義規(guī)范,后者的語言意義是一種空泛的表達(dá)。
3.4 抽象語法樹
這是源代碼的語法結(jié)構(gòu)的一種抽象表示。所謂抽象語法,是指不會表示出所有細(xì)節(jié)的語法。
4. iOS中可能遇到的Clang常用指令
這里只說一些iOS中可能常用的Clang指令,如果想要全部的Clang指令集,可以在terminal終端中輸入clang --help查看。
如果認(rèn)為上面的命令查看不方便的,這里還有github版本,內(nèi)容就是copy的clang --help。
或者可以直接在LLVM官網(wǎng)的Clang Document查詢。
| Clang命令 | 釋義 |
|---|---|
| -ccc-print-phases | 打印源碼的編譯階段,得到的打印結(jié)果就是整個(gè)源碼到機(jī)器代碼的整體流程步驟。 |
| -rewrite-objc | 將OC源碼編譯成C++源碼 |
| -E | 查看預(yù)處理階段詳細(xì)步驟(在terminal中查看,也可以生成一個(gè)新文件 : clang -E main.m >> new_main.m) 。>>就是文件重定向。 |
| -c | 必須在-E之后才可以使用,例如clang -E main.m -c。只進(jìn)行預(yù)處理,編譯,匯編步驟,不進(jìn)行鏈接步驟。執(zhí)行完成后生成的是.o鏈接文件。 |
| -S | 只進(jìn)行預(yù)處理和編譯步驟,不進(jìn)行匯編,鏈接等后續(xù)步驟。執(zhí)行完成后,生成的是.s匯編代碼文件。 |
| -o <file> | 完成預(yù)編譯-->編譯-->匯編-->鏈接后,生成可執(zhí)行文件到<file>文件。 |
| -g | 在生成的可執(zhí)行文件中,包含標(biāo)準(zhǔn)的調(diào)試信息。 |
-i <路徑>(應(yīng)該是大寫i最標(biāo)準(zhǔn)) |
在頭文件中的搜索路徑中添加<路徑>。 |
| -fmodules | 啟用模塊化語言特性。 |
| -fsyntax-only | 編譯器并不生成代碼,后續(xù)的操作只是語法級別的修改。 |
| -Xclang <arg> | 向clang編譯器傳遞參數(shù)<arg>。 |
| -dump-tokens | 運(yùn)行預(yù)處理器,將源碼內(nèi)部拆分成各種單詞(Token)。 |
| -ast-dump | 構(gòu)建抽象語法樹AST,然后對其進(jìn)行拆解和調(diào)試。 |
| -fobjc-arc | 生成Objective-C指針的retain和release的調(diào)用。 |
| -emit-llvm | 對匯編文件和目標(biāo)文件生成LLVM IR代碼。 |