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

iOS开发系列-LLVM、Clang的更多相关文章
- iOS开发系列--Swift语言
		
概述 Swift是苹果2014年推出的全新的编程语言,它继承了C语言.ObjC的特性,且克服了C语言的兼容性问题.Swift发展过程中不仅保留了ObjC很多语法特性,它也借鉴了多种现代化语言的特点,在 ...
 - iOS开发系列文章(持续更新……)
		
iOS开发系列的文章,内容循序渐进,包含C语言.ObjC.iOS开发以及日后要写的游戏开发和Swift编程几部分内容.文章会持续更新,希望大家多多关注,如果文章对你有帮助请点赞支持,多谢! 为了方便大 ...
 - iOS开发系列--App扩展开发
		
概述 从iOS 8 开始Apple引入了扩展(Extension)用于增强系统应用服务和应用之间的交互.它的出现让自定义键盘.系统分享集成等这些依靠系统服务的开发变成了可能.WWDC 2016上众多更 ...
 - iOS开发系列--Swift进阶
		
概述 上一篇文章<iOS开发系列--Swift语言>中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用S ...
 - iOS开发系列--通知与消息机制
		
概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣的那么通过通知机制就可以告诉用户此时发生的事情.iOS中通知机制又叫消息机制,其包括两类:一类是本地 ...
 - iOS开发系列--数据存取
		
概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库.例如前面IOS开发系列-Objective-C之Foundation框架的文章中提到归档.plist文件存储, ...
 - iOS开发系列--网络开发
		
概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博.微信等,这些应用本身可能采用iOS开发,但是所有的数据支撑都是基于后台网络服务器的.如今,网络编程越来越普遍,孤立的应用通常是没有生命力 ...
 - iOS开发系列--C语言之基础知识
		
概览 当前移动开发的趋势已经势不可挡,这个系列希望浅谈一下个人对IOS开发的一些见解,这个IOS系列计划从几个角度去说IOS开发: C语言 OC基础 IOS开发(iphone/ipad) Swift ...
 - iOS开发系列--让你的应用“动”起来
		
--iOS核心动画 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌.在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建 ...
 
随机推荐
- Codeforces 340B - Maximal Area Quadrilateral (计算几何)
			
Codeforces Round #198 (Div. 2) 题目链接:Maximal Area Quadrilateral Iahub has drawn a set of \(n\) points ...
 - C语言——二维数组
			
目录 二维数组 一.二维数组的定义 二.二维数组的初始化 三.通过赋初值定义二维数组的大小 四.二维数组与指针 二维数组 一.二维数组的定义 类型名 数组名[ 常量表达式1 ][ 常量表达式2 ] i ...
 - jquery 弥补ie6不支持input:hover状态
			
<!doctype html><html> <head> <meta charset="utf-8"> <t ...
 - 如何删除github里的项目
			
1.登录github网站后,就会看到如下画面,在头像下面有个你的项目资源这一栏,假如我要删除名为“hhh”的项目,现在鼠标点进这个项目里面 2.进去后点setting 点进去后,默认是选择了OPtio ...
 - SYSTEM_HANDLE_TABLE_ENTRY_INFO
			
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO { USHORT UniqueProcessId; USHORT CreatorBackTraceInde ...
 - linux 挂载iso文件,挂载ntfs文件系统
			
映像档不可录就挂载使用.通过loop命令来执行 好吧.跟同事要了一个win10系统盘.插入,竟然是灰色的. ,一点击,提示无法挂载,仔细看了一下下面的内容,原来不支持ntfs格式,好吧,win10系统 ...
 - 反编译字节码角度分析synchronized关键字的原理
			
1.synchronized介绍 synchronized是java关键字.JVM规范中,synchronized关键字用于在线程并发执行时,保证同一时刻,只有一个线程可以执行某个代码块或方法:同时还 ...
 - 43. 守护线程 和 join方法
			
1.守护线程(后台线程): 我们在使用一款软件的时候,有的软件会让我们在不知道的情况下下载一些东西,那么这个就是后台线程. 一般用于提高软件的下载量(也就是 ...
 - leetcode-140-单词拆分②*
			
题目描述: 第一次提交:超时 O(N**N) class Solution: def wordBreak(self, s: str, wordDict: List[str]) -> List[s ...
 - 区别 |python |[-1]、[:-1]、[::-1]、[2::-1]的使用
			
格式 list[start :end :方向] start——>开始下标位置 end——>结束下标位置 方向——> 读取方向.默认正向,-1表示反方向读取 如: import num ...