编译器发展史
编译器一般构成
传统的编译器通常分为三个部分,前端(frontEnd),优化器(Optimizer)和后端(backEnd)。在编译过程中,前端主要负责词法和语法分析,将源代码转化为抽象语法树;优化器则是在前端的基础上,对得到的中间代码进行优化,使代码更加高效;后端则是将已经优化的中间代码转化为针对各自平台的机器代码。 如图:
GCC(上古时代)
GCC(GNU Compiler Collection,GNU 编译器套装),是一套由 GNU 开发的编程语言编译器。GCC 原名为 GNU C 语言编译器,因为它原本只能处理 C语言。GCC 快速演进,变得可处理 C++、Fortran、Pascal、Objective-C、Java 以及 Ada 等其它语言
早期的OC 程序员都感受过GCC编译程序,但是苹果为什么好好的GCC不用,自己要搞一套呢?
GCC 也将三段式做的比较好,并且实现了很多前端,支持了很多语言。但是上述这些编译器的致命缺陷是,他们是一个完整的可执行文件,没有给其它语言的开发者提供代码重用的接口。即使 GCC 是开源的,但是源代码重用的难度也比较大。
- GCC 的 Objective-C Frontend不给力:GCC的前端不是苹果提供维护的,想要添加一些语法提示等功能还得去求GCC的前端去做。
- GCC 插件、工具、IDE的支持薄弱:很多编译器特性没有,自动补全、代码提示、warning、静态分析等这些流程不是很给力,都是需要IDE调用底层命令完成,结果需要以插件的形式暴露出来,这一块GCC做的不是很好。
- GCC 编译效率和性能不足:Apple的Clang出来以后,其编译效率是GCC的3倍,编译器性能好,编译出的文件小。
- Apple要收回去工具链的控制 (lldb, lld…): Apple在早期从GCC前端到LLVM后端的编译器,到Clang-LVVM的编译器,以及后来的GDB的替换,一步一步收回对编译工具链的控制,也为swift 的出现奠定基础。
LLVM(新时代的来临)
LLVM采用标准的三段式设计架构,它分为前端,中间优化器,后端;前端负责解析、验证和诊断输入代码中的错误,然后将解析后的代码转换为LLVM Intermediate Representation(简称LLVM IR),LLVM IR被设计用来承载在编译器的优化器部分中可以找到的中级分析和转换。 如图:
Clang/Swift + LLVM
苹果目前采用Clang/Swift + LLVM 的编译方式,Clang主要是用作以前OC、OC++编写的代码编译前端,Swift将采用自己的独立前端(SwiftC)来编译。结构如图:
- Clang 的编译流程,如下图:
- SwiftC 的编译流程,如下图:
从图中可以看出,我们使用SwiftC编译时,在将AST转换成IR之前,会再生成一次中间体代码SIL(Swift Intermediate Language)来进行代码分析和优化,我们将从中窥探一些隐藏在语言之下的真相。
SwiftC
就像OC代码可以通过Clang命令clang -rewrite-objc
来将OC源码改写成C++代码,一探底层究竟一样,我们可以用swiftc
命令来生成生成SIL中间体代码来观察Swift的底层实现机制,甚至可以生成可执行文件。 下面是一些常用命令:
- 生成可执行文件 :
swiftc -o main.out main.swift
- 生成抽象语法树:
swiftc main.swift -dump-ast
- 生成中间体语言(SIL):
swiftc main.swift -emit-sil
- 生成LLVM中间体语言 (LLVM IR):
swiftc main.swift -emit-ir
- 生成汇编 :
swiftc main.swift -emit-assembly
其它一些更多的命令功能可以通过-h
选项来获取帮助。
swiftc 默认将输出到标准输出流,如果我们想方便的查看或者保存这些中间体文件,可以将输出重定向到文件进行保存。像这样:
swiftc -emit-sil main.swift > main.sil
当我们阅读这些中间体文件的时候,可能会看到大量的混淆过的类、方法、变量名称,这样做是为了更方便的重载方法、实现多态。幸运的是,在xcode工具链中有对应的swift-demangle
命令可以对这些名称进行逆混淆。我们可以使用管道将swiftc -emit-sil
的输出作为xcrun swift-demangle
的输入来进行处理,像这样:
swiftc -emit-sil main.swift | xcrun swift-demangle
当然,你也可以输出重定向保存这些结果到文件:
swiftc -emit-sil main.swift | xcrun swift-demangle > main.sil
在SIL文件中,我们会看到大量的陌生关键字,这种情况我们可以从GitHub的官方文档来需求帮助。如果我们是工作在iOS平台下,那么我们不可避免的会引用到如UIKit这样的SDK框架,这时我们要添加一些参数,保证编译时能够找到这些框架中的符号,注意将模拟器的版本号修改成你当前使用的版本。
swiftc -emit-sil -target x86_64-apple-ios14.2-simulator -sdk $(xcrun --show-sdk-path --sdk iphonesimulator) ViewController.swift > ViewController.sil