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查看,

相关文章
相关标签/搜索