(Xcode) 編譯器小白筆記 – LLVM前端Clang

jamesdouble發表於2019-03-03

本文為筆記型式呈現,並非全部原創,來源見文末

Compiler

Three-Phase編譯器架構

Clang – LLVM

Apple(包括中後期的NeXT) 一直使用GCC作為官方的編譯器。GCC作為開源世界的編譯器標準一直做得不錯,但Apple對編譯工具會提出更高的要求。

Clang這個軟體專案在2005年由蘋果電腦發起,是LLVM編譯器工具集的前端(front-end),目的是輸出程式碼對應的抽象語法樹(Abstract Syntax Tree, AST),並將程式碼編譯成LLVM Bitcode。接著在後端(back-end)使用LLVM編譯成平臺相關的機器語言 。

MDCC2016的 session

先看結果

main.m

#import <Foundation/Foundation.h>
#define DEFINEEight 8

int main(){
    @autoreleasepool {
        int eight = DEFINEEight;
        int six = 6;
        NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
        int rank = eight + six;
        NSLog(@"%@ rank %d", site, rank);
    }
    return 0;
}

複製程式碼

直接編譯成執行檔

clang -fmodules main.m

產出 .out (executable)

clang

Clang (Frontend前端)

是一個C、C++、Objective-C和Objective-C++程式語言的編譯器前端

Clang原始碼結構

source:https://llvm.org/devmtg/2017-06/2-Hal-Finkel-LLVM-2017.pdf

Clang步驟

clang -ccc-print-phases main.m

Clang-LLVM步驟
source: https://blog.csdn.net/u014795020/article/details/72514109

1.Input (Driver)

指定語言 , 架構, 輸入file

clang -x objective-c main.m

2.Preprocessor(預處理)

import 標頭檔案, include標頭檔案等 ,macro巨集展開,處理`#`開頭指令

單做預處理, 並取得預處理結果

clang -E main.m

預處理最終結果:

# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3
# 185 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
# 2 "main.m" 2


int main(){
    @autoreleasepool {
        int eight = 8;
        int six = 6;
        NSString* site = [[NSString alloc] initWithUTF8String:"starming"];
        int rank = eight + six;
        NSLog(@"%@ rank %d", site, rank);
    }
    return 0;
}

複製程式碼

依據上面的結果能明顯看到 header被換成了明確的全域性位置,常量DEFINEEight也被替換進程式碼裡,講到import 就不得不提 modules

2.1 Modules 模組 (-fmodules)

參考 LLVM modules文件

文章裡提及:
Modules provide an alternative, simpler way to use software libraries that provides better compile-time scalability and eliminates many of the problems inherent to using the C preprocessor to access the API of a library.

Clang 以簡單的 import std.io 概念取代 原本冗餘的函式庫(libraries)引進 #include <stdio.h>,類似java的package,目前Clang

  1. #include的機制是 編譯器會去遞迴檢查每個Header,header inculde的 header
    文章裡提了幾個弱點:
    Compile-time scalability:耗時編譯
    Fragility:多引入順序或導致巨集衝突
    Conventional workarounds:C語言長久的息慣,導致程式碼較醜
    Tool confusion

  2. 然而編譯器在碰到import時,會直接載入module對應的二進位制檔案並取得他的api,一個module不依賴外部header,只編譯一次,api也只解析一次,
    當然module也有些缺點包括 namesspace(可能重名), 改庫程式碼, 無法適應各種機器的Arch。

3 Lexical Analysis (詞法分析 Lex, Tokenization) -> .i (Tokens)

此步驟是Compiler裡的基本程式,將字元一個一個的讀進Lexer裡,並根據構詞規則識別 Token(單詞),此處還不會校驗語法

做詞法分析並把Token分析結果展示出來

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

每一個標記都包含了對應的原始碼內容和其在原始碼中的位置。注意這裡的位置是巨集展開之前的位置,這樣一來,如果編譯過程中遇到什麼問題,clang 能夠在原始碼中指出出錯的具體位置。

展示Token(部分)

-fsyntax-only: Run the preprocessor, parser and type checking stages.

4 語法分析(Semantic Analysis) -> AST

語法分析,在Clang中有Parser和Sema兩個模組配合完成,驗證語法是否正確,並給出正確的提示。

4.1 Parser

遍歷每個Token做詞句分析,生成一個 節點(Nodes)該有的資訊

4.2 Semantic

在Lex 跟 syntax Analysis之後, 也就是在這個階段已經確保 詞 句 語法已經是正確的形式了,semantic 接著做return values, size boundaries, uninitialized variables 等檢查,之後根據當前的資訊,生成語意節點(Nodes),並將所有節點組合成抽象語法書(AST)

做 語法分析 並展示 AST

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

AST

5 抽象語法樹 Abstract Syntax Tree

可以說是Clang的核心,大部分的優化, 判斷都在AST處理(例如尋找Class, 替換程式碼…等)

此步驟會將 Clang Attr 轉換成 AST 上的 AttributeList,能在clang外掛上透過 Decl::getAttr<T> 獲取

Clang Attributes 是 Clang 提供的一種原始碼註解,方便開發者向編譯器表達某種要求,參與控制如 Static Analyzer、Name Mangling、Code Generation 等過程, 一般以 __attribute__(xxx) 的形式出現在程式碼中, Ex: NS_CLASS_AVAILABLE_IOS(9_0)

結構跟其他Compiler的AST相同與其他編譯器不同的是 Clang的AST是由C++構成類似Class,Variable的層級表示,其他的則是以組合語言編寫。

這代表著AST也能有對應的api,這讓AST操作, 獲取資訊 都比較容易,甚至還夾帶著地址跟程式碼位置。

AST Context: 儲存所有AST相關資訊, 且提供ASTMatcher等遍歷方法

Node三大Class Decl - Declarations(宣告), Stmt - Statements(陳述句), type(型別) 子類過於詳細不在這多寫

source: https://blog.csdn.net/u014795020/article/details/72514109

6 程式碼生成 CodeGen -> IR中間程式碼(.ll)

CodeGen負責將語法樹從頂至下遍歷,翻譯成LLVM IR,是LLVM Backend 的輸入,是前後端的橋接語言。

產出IR: clang -S -fobjc-arc -emit-llvm main.m -o main.ll

LLVM IR 有三種表示格式,第一種是 bitcode 這樣的儲存格式,以 .bc 做字尾,第二種是可讀的以 .ll,第三種是用於開發時操作 LLVM IR 的記憶體格式。

產出Bit clang -emit-llvm -c main.m -o main.bc

檢視BitCode llvm-dis < main.bc | less

6.1 IR 優化 Optimization

IR提供了多種優化選項,-01 -02 -03 -0s…. 對應著不同的入參,有比如類似死程式碼清理,內聯化,表示式重組,迴圈變數移動這樣的 Pass。

project Build Setting
http://clang.llvm.org/docs/CommandGuide/clang.html#code-generation-options

Extra: Clang 外掛

使用 libclan g, clang, LibTooling 外掛

可以改變 clang 生成程式碼的方式,增加更強的型別檢查,或者按照自己的定義進行程式碼的檢查分析等等。要想達成以上的目標,

reference:

Clang外掛
瞭解Clang-ast
Understanding the Clang AST
AST Detail
ClangAST
clang.llvm.org/
# 深入剖析-iOS-編譯-Clang
llvm.org/devmtg/2017…
# 從Swift橋接檔案到Clang-LLVM

相關文章