Swift编译器结构分析

Swift介绍

Swift是一种高性能的语言,拥有整洁现代的语法。swift可以和C、OC的代码和框架无缝衔接,并且swift默认是内存安全的。 Swift的代码仓库包含了Swift编译器和标准库的源码,还有例如用于IDE集成的SourceKit等相关组件。

Swift编译器

概况来说,Swift编译器负责将Swift源码转成高效的可执行机器码。下面我们对Swift编译器的几个主要组件进行说明。

编译器基本知识

构建过程

构建过程分为

  • 预处理(pre-process) 用于导入文件和展开宏.纯文本操作,不考虑语言语法含义

  • (狭义的)编译 对预处理器的输出进行编译,生成汇编语言(assemble language)。一般汇编语言代码的文件扩展名.s

  • 汇编 汇编器转为机器语言,这个过程称为汇编(assemble),输出为目标文件(object file),一般扩展名为.o

  • 链接 目标文件本身不能使用。将目标文件转换为最终可以使用的形式的过程,称为链接(link)。可执行文件默认名为a.out

Swift编译器结构

我们下面分析的Swift编译器,负责的流程是上面构建过程中的第二步:(狭义的)编译

Swift的编译器主要组件包含以下几部分:

Parsing:语法分析

Swift的语法分析器是一个简单的,对整体通过递归向下的方式进行语法分析的手工编码词法分析器,他是在lib/Parse内实现的。该分析器负责生成不包含语义和类型信息的抽象语法树,简称AST(Abstract Syntax Tree)。这个阶段生成的AST也不包含警告和错误的注入。

lib/Parse

我们分析一下lib/Parse内的文件结构

文件管理:CMakeLists.txt 内部包含一个CMakeLists.txt文件和多个以cpp为后缀名的C++文件。 CMakeLists.txt是CMake的管理文件,想了解CMake可以在文末查看。CMakeLists.txt内容为

add_swift_library(swiftParse STATIC
 xxx.cpp
 ...
 LINK_LIBRARIES
 swiftAST
 swiftSyntax
)

源码分析文件:ParseXXX.cpp 内部的cpp文件是用于对代码进行语法分析的,例如Lexer.cppParseDecl.cppParseGeneric.cppParseStmt.cpp等文件,从命名我们就能知道他们的功能是对声明,表达式或者泛型等进行语法分析。 语法分析是将代码逐字分析,通过特定的正则表达式匹配,在词的层面上拆分成多个token,之后生成AST,想了解具体的知识可以查看之前整理的编译器学习系列,这里不展开细节。

流程管理文件 除了用于对源码进行具体分析的文件外,lib/Parse内部还包含一些流程控制文件。 通过文件PersistentParserState.cppSyntaxParsingCache.cppSyntaxParsingContext.cpp的命名和描述我们可以推断他们是用于对语法分析状态进行存储,缓存以及通过编译器不同条件的指令执行或者略过某些步骤的。

语法分析总结

语法分析使用以上的几种文件,对Swift源码进行逐字分析,并且可以通过编译器指令控制分析的流程和细节,词法分析生成的AST是不包含语义和类型信息的,也不包含警告和错误等信息。

Senmantic analysis:语义分析

语义分析负责接收语法分析生成的AST,并将其转换成格式正确,进行了全面类型检查的AST,并且在源码中嵌入警告和错误等信息。这些功能是在lib/Sema内实现的。语义分析包含类型推断,确保可以从进行了类型检查的AST安全的生成代码。

lib/Sema

我们对lib/Sema内的结构进行一下分析:

CMakeLists.txt

语义分析的CMakeLists.txt内容如下

if (SWIFT_FORCE_OPTIMIZED_TYPECHECKER)
 set(EXTRA_TYPECHECKER_FLAGS "FORCE_BUILD_OPTIMIZED")
endif()
​
add_swift_library(swiftSema STATIC
 xxx.cpp
 ...
 LINK_LIBRARIES
 swiftParse
 swiftAST
 swiftSerialization
 ${EXTRA_TYPECHECKER_FLAGS}
)

和语法分析不同的是,

  1. 对条件SWIFT_FORCE_OPTIMIZED_TYPECHECKER是否进行强制类型检查优化进行了判断,这些条件可以在终端调试时通过指令进行控制,之后我们会在调试Swift编译器内进行说明。

  2. 链接库为AST库,语法分析库和Serialization库,以及是否增加额外类型检查的标记

语义分析文件

lib/Sema内部有大量使用C和C++语言编写的分析类,语义分析因为要进行符合当前语言规则对应的检查,所以相比语法分析,文件量要大很多。

我们进行粗略的归纳,可以划分为:

  1. CSxxx.cpp/.h

    约束系统的解决文件(solution to constraint system)

  2. xxxDiag.cpp/.h

    生成诊断的文件,我们在编译过程中的诊断,用于静态代码检查的诊断都是在语义分析阶段,通过这些诊断类去管理的。

  3. CodeSyncxxx.cpp/.h

    用于对表达式,声明等各种类型进行语义分析的文件。

  4. Constraintxxx.cpp/.h

    各种约束类

  5. Debugxxx.cpp/.h & Instrumenterxxx.cpp/.h

    用于支持调试的类

  6. Derivedxxx.cpp/.h

    各种协议的隐式派生实现

  7. NameBinding.cpp

    实现Swift的name binding

  8. TypeCheckxxx.cpp/.h

    对各种类型进行检查和语义分析,以及检查修正

语义分析总结

语义分析生成经过了类型检查的AST,嵌入警告和错误,以及进行类型推断。这一阶段将语法分析生成的不带有语义内容的AST转换成符合Swift语义规则的AST,这些推断和类型检查为后续的优化提供了基础。

Clang importer

这个模块用于导入Clang组件Clang modules ,并且将C和Objective-C输出的API对应到正确的Swift API。导入的AST的结果也会传递给语义分析。lib/ClangImporter内实现的。

lib/ClangImporter

我们分析一下lib/ClangImporter内的结构

CMakeLists.txt

set(SWIFT_GYB_FLAGS
 "-DCFDatabaseFile=${SWIFT_SOURCE_DIR}/lib/ClangImporter/CFDatabase.def")
​
add_gyb_target(generated_sorted_cf_database
 SortedCFDatabase.def.gyb)
​
add_swift_library(swiftClangImporter STATIC
 xxx.cpp
 LINK_LIBRARIES
 swiftAST
 swiftParse
)
​
# This property is only set by calls to clang_tablegen. It will not be set on
# standalone builds, so it can always be safely passed.
get_property(CLANG_TABLEGEN_TARGETS GLOBAL PROPERTY CLANG_TABLEGEN_TARGETS)
add_dependencies(swiftClangImporter
 generated_sorted_cf_database
 ${CLANG_TABLEGEN_TARGETS})

GYB(Generate Your Boilerplate)指模板生成,用于添加数据库模块。

链接库为swiftASTswiftParseAST和语法分析。

工具类

内容还有使用C和C++语言编写的工具类,用于方便的导入Clang实体提供正式的接口。内部的类有ImportEnumInfo.cpp/.hImportName.cpp/.hImportType.cpp/.h等,通过查看这些类的内容,我们发现包含#include "swift/AST/ASTContext.h"#include "clang/AST/ASTContext.h"等内容。

由此我们可以知道,Swift在使用Objective-C的代码时,通过lib/ClangImporter导入的不是Objective-C的源码,而是OC通过Clang编译器生成的AST.

Swfit不能使用Objective-C的宏

Swift是不同使用OC的宏的,原因如下:

上面的编译器基本知识说到了,宏的展开是在编译的前一个阶段,预处理阶段进行的。在预处理阶段将宏展开之后,Clang才对代码进行编译。Swift导入的是OC代码通过Clang生成的抽象语法树,OC内的宏是未经处理的代码段,所以Swift不能使用OC的宏。

SIL generation:SIL生成

SIL(Swift Intermediate Language),Swift的中间语言是一个Swift特定的用于进一步分析和优化Swift代码的高级的中间语言。

SIL的生成阶段将类型检查的AST降级为"raw"SIL。这些内容是在lib/SILGen内实现的。SIL的设计文档为docs/SIL.rst

lib/SILGen

分析组件结构:

CMakeLists.txt

add_swift_library(swiftSILGen STATIC
 xxx.cpp
 ...
 LINK_LIBRARIES
 swiftSIL
)

链接库只有swiftSIL

生成SIL的工具类

其他的SILGenxxx.cpp是用于生成SIL的工具类。

SIL详细分析

SIL是Swift定制的中间语言,针对Swift进行了大量的优化,使得Swift性能得到提升。SIL也是Swift编译器的精髓所在。这一部分内容我们在SIL详解内进行专项说明。

SIL guaranteed transformations:确保转换

该模块负责对影响程序准确性的数据流进行额外的诊断,例如使用未初始化的变量。转换的结果是生成"正式"(canonical)SIL。这些内容在lib/Analysislib/ARClib/LoopTransformslib/Transforms内实现。

LLVM IR Generation:生成LLVM的中间语言

该模块将SIL降级为LLVM IR,LLVM的中间语言,使得LLVM在此基础上可以进行进一步的优化,并且生成机器码。这些内容是在lib/IRGen内实现的。

信息补充

Cmake和Ninja介绍

我们这里介绍一下CMakeNinja,对它们有个基本的认识. CMake是一个开源,跨平台的用于构建,测试和打包的软件工具.CMake使用简单的平台和编译器单独配置文件来控制软件的编译流程, 通过生成本地makefiles文件和workspace,之后用于用户选择编译环境.

Ninja是一个小而快的构建系统.它和其他构建系统的区别主要有两点: 1.它旨在将输入文件通过高级别的构建系统来生成. 2.致力于快速构建,极致的追求速度.Ninja意在取代在执行增量构建时非常慢的Make.Make在处理类似于Google Chrome这种单次就会编译超过30,000个输入文件的大型项目时,速度缓慢会体现的更加明显.Google Chrome是Ninja的主要用户,Ninja还支持安卓编译,也支持大部分使用LLVM的项目.

参考资料

推荐阅读更多精彩内容