iOS開發系列-LLVM、Clang

LLVM

LLVM計劃啓動於2000年,最初由University of Illinois at Urbana-Champaign的Chris Lattner主持開展。
咱們能夠認爲LLVM是一個完整的編譯器架構,也能夠認爲它是一個用於開發編譯器、解釋器相關的庫
在理解LLVM時,咱們能夠認爲它包括了一個狹義的LLVM和一個廣義的LLVM。廣義的LLVM其實就是指整個LLVM編譯器架構,包括了前端、後端、優化器、衆多的庫函數以及不少的模塊;而狹義的LLVM其實就是聚焦於編譯器後端功能(代碼生成、代碼優化、JIT等)的一系列模塊和庫。

對應到這個圖中,咱們就能夠很是明確的找出它們的對應關係。Clang其實大體上能夠對應到編譯器的前端,主要處理一些和具體機器無關的針對語言的分析操做;
編譯器的優化器部分和後端部分其實就是咱們以前談到的LLVM後端(狹義的LLVM);而總體的Compiler架構就是LLVM架構。前端

編譯流程

目前iOS 開發中 Objective-C 和 Swift 都用的是 Clang / LLVM 來編譯的。Clang 是 LLVM 的子項目,是 C,C++ 和 Objective-C 編譯器,目的是提供驚人的快速編譯,比 GCC 快3倍。
其中的 clang static analyzer 主要是進行語法分析,語義分析和生成中間代碼,固然這個過程會對代碼進行檢查,出錯的和須要警告的會標註出來。macos

LLVM 核心庫提供一個優化器,對流行的 CPU 作代碼生成支持。lld 是 Clang / LLVM 的內置連接器,clang 必須調用連接器來產生可執行文件。後端

編譯細節

源文件從編譯到生成可執行文件流程大體以下圖
ruby

在列出詳細的編譯步驟以前先看咱們編寫的源文件是如何完成一次性編譯的。新建一個main.m文件,代碼以下架構

#include <stdio.h>
#define VALUE 6
int main(){
    
    int a = VALUE;
    
    printf("Hello Clang\n");
    
    return 0;
}

在命令行編譯、連接app

clang -c main.m -o main.o // 編譯
clang main.o -o main // 連接

這樣還沒發看清clang的所有過程,下面開始說下在編譯前段clang編譯的細節。less

預處理

clang -E main.m -o main.e

執行完後打開main.eide

extern int __vsprintf_chk (char * restrict, int, size_t,
      const char * restrict, va_list);
extern int __vsnprintf_chk (char * restrict, size_t, int, size_t,
       const char * restrict, va_list);

int main(){
    
    int a = 6;
    
    printf("Hello Clang\n");
    
    return 0;
}

預處理流程內部處理包括宏的替換、頭文件導入,以及相似的#if的處理。函數

語法分析

預處理完成後就會進行詞法分析,這裏會把代碼切成一個個 Token,好比大小括號,等於號還有字符串等。優化

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

執行完畢能夠看到文件

annot_module_include '#include <st'     Loc=<main.m:1:2>
int 'int'    [StartOfLine]  Loc=<main.m:3:1>
identifier 'main'    [LeadingSpace] Loc=<main.m:3:5>
l_paren '('     Loc=<main.m:3:9>
r_paren ')'     Loc=<main.m:3:10>
l_brace '{'     Loc=<main.m:3:11>
int 'int'    [StartOfLine] [LeadingSpace]   Loc=<main.m:5:5>
identifier 'a'   [LeadingSpace] Loc=<main.m:5:9>
equal '='    [LeadingSpace] Loc=<main.m:5:11>
numeric_constant '6'     [LeadingSpace] Loc=<main.m:5:13 <Spelling=main.m:2:15>>
semi ';'        Loc=<main.m:5:18>
identifier 'printf'  [StartOfLine] [LeadingSpace]   Loc=<main.m:7:5>
l_paren '('     Loc=<main.m:7:11>
string_literal '"Hello Clang\n"'        Loc=<main.m:7:12>
r_paren ')'     Loc=<main.m:7:27>
semi ';'        Loc=<main.m:7:28>
return 'return'  [StartOfLine] [LeadingSpace]   Loc=<main.m:9:5>
numeric_constant '0'     [LeadingSpace] Loc=<main.m:9:12>
semi ';'        Loc=<main.m:9:13>
r_brace '}'  [StartOfLine]  Loc=<main.m:10:1>
eof ''      Loc=<main.m:10:2>

而後就是語法分析,驗證語法是否正確,而後將全部的節點組成抽象語法樹AST。

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

截取生成的抽象語法樹一部分

完成語法的分析後就能夠開始中間IR代碼的生成了,CodeGen 會負責將語法樹自頂向下遍歷逐步翻譯成 LLVM IR,IR 是編譯過程的前端的輸出後端的輸入。

IR中間代碼的生成

clang -S -fobjc-arc -emit-llvm main.m -o main.ll

打開查看man.ll

; ModuleID = 'main.m'
source_filename = "main.m"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.13.0"

@.str = private unnamed_addr constant [13 x i8] c"Hello Clang\0A\00", align 1

; Function Attrs: noinline optnone ssp uwtable
define i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 6, i32* %2, align 4
  %3 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str, i32 0, i32 0))
  ret i32 0
}

declare i32 @printf(i8*, ...) #1

attributes #0 = { noinline optnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6}
!llvm.ident = !{!7}

!0 = !{i32 1, !"Objective-C Version", i32 2}
!1 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!2 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!3 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!4 = !{i32 1, !"Objective-C Class Properties", i32 64}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{i32 7, !"PIC Level", i32 2}
!7 = !{!"Apple LLVM version 9.1.0 (clang-902.0.39.1)"}

LLVM優化

在 Xcode 的編譯設置裏也能夠設置優化級別-01,-03,-0s,還能夠寫些本身的 Pass。

Pass 是 LLVM 優化工做的一個節點,一個節點作些事,一塊兒加起來就構成了 LLVM 完整的優化和轉化。
若是開啓了 bitcode 蘋果會作進一步的優化,有新的後端架構仍是能夠用這份優化過的 bitcode 去生成。

clang -emit-llvm -c main.m -o main.bc

生成彙編

clang -S -fobjc-arc main.m -o main.s

打開main.s能夠看到代碼對應的彙編

.section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 13
    .globl  _main                   ## -- Begin function main
    .p2align    4, 0x90
_main:                                  ## @main
    .cfi_startproc
## BB#0:
    pushq   %rbp
Lcfi0:
    .cfi_def_cfa_offset 16
Lcfi1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Lcfi2:
    .cfi_def_cfa_register %rbp
    subq    $16, %rsp
    leaq    L_.str(%rip), %rdi
    movl    $0, -4(%rbp)
    movl    $6, -8(%rbp)
    movb    $0, %al
    callq   _printf
    xorl    %ecx, %ecx
    movl    %eax, -12(%rbp)         ## 4-byte Spill
    movl    %ecx, %eax
    addq    $16, %rsp
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function
    .section    __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
    .asciz  "Hello Clang\n"

    .section    __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
    .long   0
    .long   64


.subsections_via_symbols

生成目標文件

clang -fmodules -c main.m -o main.o

目標文件就是對應cpu架構的二進制的機器指令了。

能夠經過連接命令生成可執行文件,執行程序

clang main.o -o main
至執行
./main
輸出
Hello Clang

Clang警告的處理

#pragma clang diagnostic push // 處理警告代碼的其實位置
#pragma clang diagnostic ignored "-Wdeprecated-declarations" // -Wdeprecated-declarations須要處理警告的表示
        sizeLabel = [self sizeWithFont:font constrainedToSize:size lineBreakMode:NSLineBreakByWordWrapping]; // 須要處理警告的代碼
#pragma clang diagnostic pop // 處理警告代碼結束位置

對過警告的類型咱們能夠經過Xcodeshow the Report navigator查看,

相關文章
相關標籤/搜索