zoukankan      html  css  js  c++  java
  • 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 主要是进行语法分析,语义分析和生成中间代码,当然这个过程会对代码进行检查,出错的和需要警告的会标注出来。

    LLVM 核心库提供一个优化器,对流行的 CPU 做代码生成支持。lld 是 Clang / LLVM 的内置链接器,clang 必须调用链接器来产生可执行文件。

    编译细节

    源文件从编译到生成可执行文件流程大致如下图

    在列出详细的编译步骤之前先看我们编写的源文件是如何完成一次性编译的。新建一个main.m文件,代码如下

    #include <stdio.h>
    #define VALUE 6
    int main(){
        
        int a = VALUE;
        
        printf("Hello Clang
    ");
        
        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
    ");
        
        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
    "'		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 ClangA0", 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
    "
    
    	.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查看,

  • 相关阅读:
    海量数据处理:十道面试题与十个海量数据处理方法总结
    C++中的static及内存分配
    面试时如何向面试官提问
    面试题3:斐波那契数列与爬楼梯
    面试题1:二进制中1的个数
    面试题:单链表的几种处理
    js网页下载csv格式的表格
    解决背景图文字盖住html里面的dom元素
    使用element-ui的常见问题
    Promise-async-await处理函数
  • 原文地址:https://www.cnblogs.com/CoderHong/p/8983917.html
Copyright © 2011-2022 走看看