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的更多相关文章

  1. iOS开发系列--Swift语言

    概述 Swift是苹果2014年推出的全新的编程语言,它继承了C语言.ObjC的特性,且克服了C语言的兼容性问题.Swift发展过程中不仅保留了ObjC很多语法特性,它也借鉴了多种现代化语言的特点,在 ...

  2. iOS开发系列文章(持续更新……)

    iOS开发系列的文章,内容循序渐进,包含C语言.ObjC.iOS开发以及日后要写的游戏开发和Swift编程几部分内容.文章会持续更新,希望大家多多关注,如果文章对你有帮助请点赞支持,多谢! 为了方便大 ...

  3. iOS开发系列--App扩展开发

    概述 从iOS 8 开始Apple引入了扩展(Extension)用于增强系统应用服务和应用之间的交互.它的出现让自定义键盘.系统分享集成等这些依靠系统服务的开发变成了可能.WWDC 2016上众多更 ...

  4. iOS开发系列--Swift进阶

    概述 上一篇文章<iOS开发系列--Swift语言>中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用S ...

  5. iOS开发系列--通知与消息机制

    概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣的那么通过通知机制就可以告诉用户此时发生的事情.iOS中通知机制又叫消息机制,其包括两类:一类是本地 ...

  6. iOS开发系列--数据存取

    概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库.例如前面IOS开发系列-Objective-C之Foundation框架的文章中提到归档.plist文件存储, ...

  7. iOS开发系列--网络开发

    概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博.微信等,这些应用本身可能采用iOS开发,但是所有的数据支撑都是基于后台网络服务器的.如今,网络编程越来越普遍,孤立的应用通常是没有生命力 ...

  8. iOS开发系列--C语言之基础知识

    概览 当前移动开发的趋势已经势不可挡,这个系列希望浅谈一下个人对IOS开发的一些见解,这个IOS系列计划从几个角度去说IOS开发: C语言 OC基础 IOS开发(iphone/ipad) Swift ...

  9. iOS开发系列--让你的应用“动”起来

    --iOS核心动画 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌.在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建 ...

随机推荐

  1. 巧用Wget快速建立文件下载中心

    Wget基本用法:1) 从网上下载单个文件 wget http://www.freehao123.com/file.iso 2) 下载一个文件,但以不同的名字存为本地文件 wget –output-d ...

  2. 1-vim-简介

    vi(visual interface)是Linux最经典的文本编辑器 vi的核心设计思想-让程序员的手指始终保持在键盘的核心区域,就能完成所有的编辑操作. vi的特点 没有图形界面 只能编辑文本内容 ...

  3. 36-Ubuntu-用户管理-01-新建用户useradd

    创建用户/设置密码/删除用户/确认用户信息 序号 命令 作用 说明 01 sudo useradd -m -g 组名 新建用户名 添加新用户 -m 自动建立用户家目录 -g 指定用户所在的组,否则会建 ...

  4. wpf mvvm datagrid 中button绑定命令方法

    <DataGridTemplateColumn Header="设备状态" IsReadOnly="True" Width="150" ...

  5. python之保留有限的历史记录(collections.deque)

    1.deque(maxlen=N)创建一个固定长度的队列,当有新的记录加入而队列已经满时,会自动移除老的记录. from collections import deque q = deque(maxl ...

  6. zic - 时区编辑器

    总览 zic [ -v ] [ -d directory ] [ -l localtime ] [ -p posixrules ] [ -L leapsecondfilename ] [ -s ] [ ...

  7. 解决 Onenote 默认全角输入的一种解决办法(输入法已经设置为默认半角)

    环境说明:Windows 7 x64 Ultimate SP1, QQ 拼音输入法 6.1(5306),Onenote 2016 x64 问题描述:每次打开Onenote,在输入法已经设置为默认半角的 ...

  8. 关于hadoop多次format之后,会出现的dataNode消失问题

    如标题,最近我由于想初始化以下hadoop集群,之后却发现启动集群后所有的DataNode都消失了. 问题查找: 由于时所有的DataNode都出了问题,于是我翻找了以下DataNode的日志(默认在 ...

  9. 反编译字节码角度分析synchronized关键字的原理

    1.synchronized介绍 synchronized是java关键字.JVM规范中,synchronized关键字用于在线程并发执行时,保证同一时刻,只有一个线程可以执行某个代码块或方法:同时还 ...

  10. 第一天:powershell外部命令

    首先我们应该怎样打开powershell呢?win+R,之后是一个运行窗口,在里面输入“powerhsell".按回车键,就可以打开powershell命令窗口了.当然也有其他方法打开,比如 ...