深入淺出LLVM

什么是LLVM

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

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

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

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

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

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

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

Frontend:前端

詞法分析、語法分析、語義分析、生成中間代碼

Optimizer:優(yōu)化器

中間代碼優(yōu)化

Backend:后端

生成機器碼

LLVM架構(gòu)

LLVM架構(gòu)

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

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

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

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

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

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

什么是Clang

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

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

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

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

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

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

設(shè)計清晰簡單,容易理解,易于擴展增強

Clang與LLVM關(guān)系

Clang與LLVM

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

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

OC源文件的編譯過程

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

命令行查看編譯的過程:$ 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.鏈接其他動態(tài)庫靜態(tài)庫

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

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

這個命令敲出,終端就會打印許多信息,大致如下:

# 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

將代碼分成一個個小單元(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=

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

語法樹-AST

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

通過語法樹,我們能知道這個代碼是做什么的。

還是剛剛的test函數(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`-

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

test函數(shù)的語法樹

LLVM IR

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

text:便于閱讀的文本格式,類似于匯編語言,拓展名.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基本語法

注釋以分號 ; 開頭

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

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

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

align,內(nèi)存對齊

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

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

官方語法參考https://llvm.org/docs/LangRef.html

應(yīng)用與實踐

我們的開發(fā)都是基于源碼開發(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的子項目,但是它們的源碼是分開的,我們需要將clang放在llvm/tools目錄下。

源碼編譯

這里我們在終端敲出的clang是xcode默認(rèn)內(nèi)置clang編譯器,我們自己要進(jìn)行LLVM開發(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源碼同級目錄下新建一個【llvm_build】目錄(最終會在【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)選項,可以參考: https://llvm.org/docs/CMake.html

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

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

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

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

方法如下:

在llvm同級目錄下新建一個【llvm_xcode】目錄

$ cd llvm_xcode

$ cmake -G Xcode ../llvm

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

libclang、libTooling

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

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

Clang插件開發(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開發(fā)

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

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

開發(fā)新的編程語言

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)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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