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
主要是進行語法分析,語義分析和生成中間代碼,當然這個過程會對代碼進行檢查,出錯的和需要警告的會標注出來。
LLVM 核心庫提供一個優化器,對流行的 CPU 做代碼生成支持。lld 是 Clang / LLVM 的內置鏈接器,clang 必須調用鏈接器來產生可執行文件。
編譯細節
源文件從編譯到生成可執行文件流程大致如下圖
在列出詳細的編譯步驟之前先看我們編寫的源文件是如何完成一次性編譯的。新建一個main.m文件,代碼如下
#include <stdio.h>
#define VALUE 6
int main(){
int a = VALUE;
printf("Hello Clang\n");
return 0;
}
在命令行編譯、鏈接
clang -c main.m -o main.o // 編譯
clang main.o -o main // 鏈接
這樣還沒發看清clang的全部過程,下面開始說下在編譯前段clang編譯的細節。
預處理
clang -E main.m -o main.e
執行完后打開main.e
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 // 處理警告代碼結束位置
對過警告的類型我們可以通過Xcode show the Report navigator
查看,