做者:Varun Tomarshell
一个程序在运行到一台设备以前经历了不少转换的步骤。和其它的编程语言处理系统同样,Xcode构建系统为了确保执行顺序和各类依赖库,须要运行不少命令行指令,传递各类各样的参数。整个构建过程分为如下五个阶段:编程
一、预处理swift
二、编译后端
三、汇编xcode
四、连接bash
五、加载架构
预处理的目的是把咱们的程序转换成能被编译器识别的形式。它用宏的定义替换宏,发现依赖关系并解析预处理器指令。若是Swift编译器没有预处理器,咱们则不能在Swift项目中定义宏命令。在Xcode8中是容许咱们经过Build Setting
中的SWIFT_ACTIVE_COMPILATION_CONDITIONS
定义预处理器标记位的。它跟OC中的预处理器宏定义是一致的。app
编译是整个过程当中很是重要的一环。编译器是一个程序,它把高级语言像是Swift&Objective C转换成低级语言,像是目标文件。在iOS中有两类编译器『Clange & swiftc』。该过程能够描述为:less
注意:编译器包含两个主要部分:前端和后端。Clange是C/C++/Objective-C的编译前端,swiftc是Swift的编译前端,LLVM是后端。这些乱七八糟的东西是什么,LLVM是从哪里来的?
不要担忧😉,我将会对它进行简明扼要的介绍,尽管其中细节说明可能要单独出一篇文章才能说明白(以后的文章,我会就这方面写一篇文章)。
LLVM(Low Level Virtual Machine)是一个后端编译器,用于在其上构建编译器。它处理优化和生产适应目标架构(ARM、x86)的代码。Clang/swiftc是一个前端编译器,能够解析C、C++、Objective C和Swift代码,并将其转换为适合LLVM的中间表示(IR)。在本文后面,您将更好地理解它。
让咱们更细粒度的理解下Swift语言的编译过程,参考下面的图示:
接下来介绍Swift编译器示如何一步一步工做的:
一、Swift代码解析成AST (Abstract Syntax Tree抽象语法树)。AST是一个表明源码结构的抽象语法树,树的每一个节点表明一个结构。它是什么样的呢,让咱们经过一个例子来理解它。下面是咱们将要进行分析的Swift代码:
//
// Test.swift
//
// Created by Varun Tomar
// Copyright © 2020 Varun Tomar. All rights reserved.
//
import Foundation
class Bird {
func fly() { }
}
func isFlyHigh(bird: Bird) -> Bool { return false }
class Sparrow: Bird {
override func fly() { }
func add(x: Int, y: Int) -> Int { return x + y }
}
复制代码
咱们能够将这段代码转换成抽象语法树格式内容,这须要运行
xcrun swiftc -dump-ast <filename>.swift
复制代码
抽象语法树输出的结果颇有趣,这里只展现了部分。若是你去阅读这段输出内容,会理解一些AST背后发生的事情。下面这段代码展现了一些有意思的东西:
(func_decl range=[Test.swift:12:3 - line:12:16] "fly()" interface type='(Bird) -> () -> ()' access=internal
(parameter "self")
复制代码
注意:如上面所示,当咱们建立一个函数时,swift会传递一个参数self
,这就是为何咱们能够不传入self直接访问这些函数的缘由。
(class_decl range=[Test.swift:17:1 - line:20:1] "Sparrow" interface type='Sparrow.Type' access=internal non-resilient inherits: Bird
复制代码
上面这段代码展现了语法树是如何描述继承关系的。
二、如今咱们来到了AST构建完成以后的语义分析。语义分析负责将AST转换为格式漂亮,类型彻底检查的AST格式。它移除了一些源码语义问题上的警告和错误信息。
三、下一步是SIL的生成和优化。为了在这个阶段以后获得SIL,咱们能够运行:
xcrun swiftc -emit-silgen <filename>.swift
复制代码
看到这些终端输出的SIL生成内容,你可能会发出这样的惊讶:"OMG😮。@$s4Test4BirdC3flyyyF
是个什么鬼东西?"。别担忧,这没你想的那么可怕,它只是一种名称混淆,用于将实体的附加信息压缩到单个字符中。这个处理过的名称包含了一些类型(class/struct/enum)、module、上下文等信息。例如,在@$s4Test4BirdC3flyyyF
中,Bird后面的字母C
表示Bird
是一个class。它还能够表达不少东西,但这不是本文的重点。若是你对它有兴趣,能够在反馈部分添加评论。此外咱们可使用swift-demangle
追溯一个混淆的字符串直到它最初可读的文本样式。
可读的SIL能够经过以下命令实现:
xcrun swiftc -emit-silgen <filename>.swift | xcrun swift-demangle
复制代码
咱们能够看下它的结果:
此次它变得更易读了。那就跟着我🚶♂️一块儿探索SIL吧:
nil
开始的。hidden
跟Swift代码中的internal
是对应的。@main.Bird.fly() -> ()
是从混淆的文本@$s4Test4BirdC3flyyyF
中解析出来的,表明着函数名。$@convention(method)
意味着调用该函数须要一个上下文(context)。例如在self.fly()
中,self
就是函数调用的上下文。$@convention(thin)
表明着这是一个自由函数,它的调用不须要上下文。owned
的标记。有没有感受到颇有趣🧐?我其实有更有趣的发现。。。
四、在SIL的生成和优化以后,就来到了IR(中间件)阶段。IR是LLVM的输入内容。运行:
xcrun swiftc -emit-ir <filename>.swift
复制代码
看到输出的机器语言时,你可能会一脸懵逼🤯🤯
在这里,控制由汇编程序来将输入转换为可重定位的机器码。它生成Mach-O文件。
Mach-O文件用于对象文件、可执行文件和库。它是以一些有意义的字节码组成的集合,将在iOS设备的ARM处理器或Mac设备的英特尔处理器上运行。
连接器是一个将多个目标文件和库合并为一个Mach-O执行文件的程序。最后,操做系统中的加载器将程序装载入内存并执行。