深入淺出LLVM

什么是LLVM

LLVM項(xiàng)目是模塊化、可重用的編譯器以及工具鏈技術(shù)的集合。

美國(guó)計(jì)算機(jī)協(xié)會(huì) (ACM) 將其2012 年軟件系統(tǒng)獎(jiǎng)項(xiàng)頒給了LLVM,之前曾經(jīng)獲得此獎(jiǎng)項(xiàng)的軟件和技術(shù)包括:Java、Apache、 Mosaic、the World Wide Web、Smalltalk、UNIX、Eclipse等等

創(chuàng)始人:Chris Lattner,亦是Swift之父

備注:有些文章把LLVM當(dāng)做Low Level Virtual Machine(低級(jí)虛擬機(jī))的縮寫(xiě)簡(jiǎn)稱,官方描述如下

The name "LLVM" itself is not an acronym; it is the full name of the project. “LLVM”這個(gè)名稱本身不是首字母縮略詞; 它是項(xiàng)目的全名。

傳統(tǒng)的編譯器架構(gòu)

傳統(tǒng)編譯器架構(gòu)

Frontend:前端

詞法分析、語(yǔ)法分析、語(yǔ)義分析、生成中間代碼

Optimizer:優(yōu)化器

中間代碼優(yōu)化

Backend:后端

生成機(jī)器碼

LLVM架構(gòu)

LLVM架構(gòu)

不同的前端后端使用統(tǒng)一的中間代碼LLVM Intermediate Representation (LLVM IR)

如果需要支持一種新的編程語(yǔ)言,那么只需要實(shí)現(xiàn)一個(gè)新的前端

如果需要支持一種新的硬件設(shè)備,那么只需要實(shí)現(xiàn)一個(gè)新的后端

優(yōu)化階段是一個(gè)通用的階段,它針對(duì)的是統(tǒng)一的LLVM IR,不論是支持新的編程語(yǔ)言,還是支持新的硬件設(shè)備,都不需要對(duì)優(yōu)化階段做修改

相比之下,GCC的前端和后端沒(méi)分得太開(kāi),前端后端耦合在了一起。所以GCC為了支持一門(mén)新的語(yǔ)言,或者為了支持一個(gè)新的目標(biāo)平臺(tái),就 變得特別困難

LLVM現(xiàn)在被作為實(shí)現(xiàn)各種靜態(tài)和運(yùn)行時(shí)編譯語(yǔ)言的通用基礎(chǔ)結(jié)構(gòu)(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等)

什么是Clang

LLVM項(xiàng)目的一個(gè)子項(xiàng)目,基于LLVM架構(gòu)的C/C++/Objective-C編譯器前端。

相比于GCC,Clang具有如下優(yōu)點(diǎn)

編譯速度快:在某些平臺(tái)上,Clang的編譯速度顯著的快過(guò)GCC(Debug模式下編譯OC速度比GGC快3倍)

占用內(nèi)存小:Clang生成的AST所占用的內(nèi)存是GCC的五分之一左右

模塊化設(shè)計(jì):Clang采用基于庫(kù)的模塊化設(shè)計(jì),易于 IDE 集成及其他用途的重用

診斷信息可讀性強(qiáng):在編譯過(guò)程中,Clang 創(chuàng)建并保留了大量詳細(xì)的元數(shù)據(jù) (metadata),有利于調(diào)試和錯(cuò)誤報(bào)告

設(shè)計(jì)清晰簡(jiǎn)單,容易理解,易于擴(kuò)展增強(qiáng)

Clang與LLVM關(guān)系

Clang與LLVM

LLVM整體架構(gòu),前端用的是clang,廣義的LLVM是指整個(gè)LLVM架構(gòu),一般狹義的LLVM指的是LLVM后端(包含代碼優(yōu)化和目標(biāo)代碼生成)。

源代碼(c/c++)經(jīng)過(guò)clang--> 中間代碼(經(jīng)過(guò)一系列的優(yōu)化,優(yōu)化用的是Pass) --> 機(jī)器碼

OC源文件的編譯過(guò)程

這里用Xcode創(chuàng)建一個(gè)Test項(xiàng)目,然后cd到main.m的上一路徑。

命令行查看編譯的過(guò)程:$ clang -ccc-print-phases main.m

$ clang -ccc-print-phases main.m 0: input,"main.m", objective-c1: preprocessor, {0}, objective-c-cpp-output2: compiler, {1}, ir3: backend, {2}, assembler4: assembler, {3}, object5: linker, {4}, image6:bind-arch,"x86_64", {5}, image

0.找到main.m文件

1.預(yù)處理器,處理include、import、宏定義

2.編譯器編譯,編譯成ir中間代碼

3.后端,生成目標(biāo)代碼

4.匯編

5.鏈接其他動(dòng)態(tài)庫(kù)靜態(tài)庫(kù)

6.編譯成適合某個(gè)架構(gòu)的代碼

查看preprocessor(預(yù)處理)的結(jié)果:$ clang -E main.m

這個(gè)命令敲出,終端就會(huì)打印許多信息,大致如下:

# 1"main.m"# 1"<built-in>"1# 1"<built-in>"3# 353"<built-in>"3# 1"<command line>"1# 1"<built-in>"2# 1"main.m"2...intmain(intargc,constchar* argv[]) {@autoreleasepool{NSLog(@"Hello, World!");}return0;}

詞法分析

詞法分析,生成Token: $ clang -fmodules -E -Xclang -dump-tokens main.m

將代碼分成一個(gè)個(gè)小單元(token)

舉例如下:

voidtest(inta,intb){intc = a + b -3;? }

void'void'[StartOfLine]? Loc=identifier'test'[LeadingSpace] Loc=l_paren'('Loc=int'int'Loc=identifier'a'[LeadingSpace] Loc=comma','Loc=int'int'[LeadingSpace] Loc=identifier'b'[LeadingSpace] Loc=r_paren')'Loc=l_brace'{'Loc=int'int'[StartOfLine] [LeadingSpace]? Loc=identifier'c'[LeadingSpace] Loc=equal'='[LeadingSpace] Loc=identifier'a'[LeadingSpace] Loc=plus'+'[LeadingSpace] Loc=identifier'b'[LeadingSpace] Loc=minus'-'[LeadingSpace] Loc=numeric_constant'3'[LeadingSpace] Loc=semi';'Loc=r_brace'}'[StartOfLine]? Loc=eof''Loc=

可以看出,詞法分析的時(shí)候,將上面的代碼拆分一個(gè)個(gè)token,后面數(shù)字表示某一行的第幾個(gè)字符,例如第一個(gè)void,表示第18行第一個(gè)字符。

語(yǔ)法樹(shù)-AST

語(yǔ)法分析,生成語(yǔ)法樹(shù)(AST,Abstract Syntax Tree): $ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

通過(guò)語(yǔ)法樹(shù),我們能知道這個(gè)代碼是做什么的。

還是剛剛的test函數(shù)

生成語(yǔ)法樹(shù)如下:

|-FunctionDecl 0x7fa1439f5630 line:18:6test'void (int, int)'| |-ParmVarDecl 0x7fa1439f54b0 col:15 used a'int'| |-ParmVarDecl 0x7fa1439f5528 col:22 used b'int'| `-CompoundStmt 0x7fa142167c88 |? `-DeclStmt 0x7fa142167c70 |? ? `-VarDecl 0x7fa1439f5708 col:9 c'int'cinit|? ? ? `-BinaryOperator 0x7fa142167c48 'int''-'|? ? ? ? |-BinaryOperator 0x7fa142167c00 'int''+'|?| |-ImplicitCastExpr 0x7fa1439f57b8 'int'|? ? ? ? | | `-DeclRefExpr 0x7fa1439f5768 'int'lvalue ParmVar 0x7fa1439f54b0'a''int'|?| `-ImplicitCastExpr 0x7fa1439f57d0 'int'|? ? ? ? |? `-DeclRefExpr 0x7fa1439f5790 'int'lvalue ParmVar 0x7fa1439f5528'b''int'|? ? ? ? `-IntegerLiteral 0x7fa142167c28 'int'3`-

在終端敲出的時(shí)候,終端很直觀的幫我們用顏色區(qū)分。我們可以用圖形顯示如下:

test函數(shù)的語(yǔ)法樹(shù)

LLVM IR

LLVM IR有3種表示形式(本質(zhì)是等價(jià)的)

text:便于閱讀的文本格式,類似于匯編語(yǔ)言,拓展名.ll, $ clang -S -emit-llvm main.m

memory:內(nèi)存格式

bitcode:二進(jìn)制格式,拓展名.bc, $ clang -c -emit-llvm main.m

我們以text形式編譯查看:

; Function Attrs: noinline nounwind optnone ssp uwtabledefinevoid@test(i32, i32)#2 {%3= alloca i32, align4%4= alloca i32, align4%5= alloca i32, align4store i32 %0, i32* %3, align4store i32 %1, i32* %4, align4%6= load i32, i32* %3, align4%7= load i32, i32* %4, align4%8= add nsw i32 %6, %7%9= sub nsw i32 %8,3store i32 %9, i32* %5, align4retvoid}

IR基本語(yǔ)法

注釋以分號(hào) ; 開(kāi)頭

全局標(biāo)識(shí)符以@開(kāi)頭,局部標(biāo)識(shí)符以%開(kāi)頭

alloca,在當(dāng)前函數(shù)棧幀中分配內(nèi)存

i32,32bit,4個(gè)字節(jié)的意思

align,內(nèi)存對(duì)齊

store,寫(xiě)入數(shù)據(jù)

load,讀取數(shù)據(jù)

官方語(yǔ)法參考https://llvm.org/docs/LangRef.html

應(yīng)用與實(shí)踐

我們的開(kāi)發(fā)都是基于源碼開(kāi)發(fā),所以我們首先要進(jìn)行源碼下載和編譯。

源碼下載

下載LLVM$ git clonehttps://git.llvm.org/git/llvm.git/下載clang$ cd llvm/tools$ git clonehttps://git.llvm.org/git/clang.git/備注:clang是llvm的子項(xiàng)目,但是它們的源碼是分開(kāi)的,我們需要將clang放在llvm/tools目錄下。

源碼編譯

這里我們?cè)诮K端敲出的clang是xcode默認(rèn)內(nèi)置clang編譯器,我們自己要進(jìn)行LLVM開(kāi)發(fā)的話,需要編譯屬于我們自己的clang編譯器

首先安裝cmake和ninja(先安裝brew,https://brew.sh/)$ brew install cmake$ brew install ninjaninja如果安裝失敗,可以直接從github獲取release版放入【/usr/local/bin】中https://github.com/ninja-build/ninja/releases在LLVM源碼同級(jí)目錄下新建一個(gè)【llvm_build】目錄(最終會(huì)在【llvm_build】目錄下生成【build.ninja】$ cd llvm_build$ cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=LLVM的安裝路徑備注:生成build.ninja,就表示編譯成功,-DCMAKE_INSTALL_PREFIX 表示編譯好的東西放在指定的路徑,-D表示參數(shù)。更多cmake相關(guān)選項(xiàng),可以參考: https://llvm.org/docs/CMake.html

接下來(lái)依次執(zhí)行編譯、安裝指令

$ ninja編譯完畢后, 【llvm_build】目錄大概21.05G(這個(gè)真的是好大?。? ninja install

然后到這里我們的編譯就完成了。

另一種方式是通過(guò)Xcode編譯,生成Xcode項(xiàng)目再進(jìn)行編譯,但是速度很慢(可能需要1個(gè)多小時(shí))。

方法如下:

在llvm同級(jí)目錄下新建一個(gè)【llvm_xcode】目錄

$ cd llvm_xcode

$ cmake -G Xcode ../llvm

應(yīng)用與實(shí)踐的參考

libclang、libTooling

官方參考:https://clang.llvm.org/docs/Tooling.html

應(yīng)用:語(yǔ)法樹(shù)分析、語(yǔ)言轉(zhuǎn)換等

Clang插件開(kāi)發(fā)

官方參考

1、https://clang.llvm.org/docs/ClangPlugins.html

2、https://clang.llvm.org/docs/ExternalClangExamples.html

3、https://clang.llvm.org/docs/RAVFrontendAction.html

應(yīng)用:代碼檢查(命名規(guī)范、代碼規(guī)范)等

Pass開(kāi)發(fā)

官方參考:https://llvm.org/docs/WritingAnLLVMPass.html

應(yīng)用:代碼優(yōu)化、代碼混淆等

開(kāi)發(fā)新的編程語(yǔ)言

1、https://llvm-tutorial-cn.readthedocs.io/en/latest/index.html

2、https://kaleidoscope-llvm-tutorial-zh-cn.readthedocs.io/zh_CN/latest/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、什么是KVC? KVC是Key Value Coding鍵值編碼,是一種通過(guò)字符串的名字(Key)來(lái)訪問(wèn)類屬性...
    DeveloperBlock閱讀 529評(píng)論 0 0
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,684評(píng)論 1 32
  • KVC KVC定義 KVC(Key-value coding)鍵值編碼,就是指iOS的開(kāi)發(fā)中,可以允許開(kāi)發(fā)者通過(guò)K...
    暮年古稀ZC閱讀 2,295評(píng)論 2 9
  • KVC(Key-value coding)鍵值編碼,單看這個(gè)名字可能不太好理解。其實(shí)翻譯一下就很簡(jiǎn)單了,就是指iO...
    朽木自雕也閱讀 1,709評(píng)論 6 1
  • KVC是Key Value Coding的簡(jiǎn)稱。它是一種可以通過(guò)字符串的名字(key)來(lái)訪問(wèn)類屬性的機(jī)制。而不是通...
    153037c65b0c閱讀 11,750評(píng)論 15 17

友情鏈接更多精彩內(nèi)容