前几天在看到 iOS 老司机周报上看到了这个帖子,在 Swift 论坛上有人提了这一个问题:

我想在 2019 年给 Swift 编译器做一些贡献
我大部分的软件开发经验都是在 iOS 开发商,从来没有做过编译器相关的工作,自从 3 年前在大学期间学习数据结构和算法再也没有接触过 C> 有没有特定的 C++ 知识需要了解?有没有编译器开发相关的东西需要学习。

为什么别人的目标这么宏伟!

楼下有很多大佬回答了,这里看到了 Alejandro 的回复,很具体。下面是这个回答的翻译。

首先要说的是,不同的人学习方式不同,所以我的方式不一定适用于你,但它确实有所帮助!值得注意的是,Swift 编译器有许多个部分。这意味着,有很多不同的项目,比如编译器,标准库,基础(foundation),调度(dispatch),xctest,包管理器,新的 SourceKit-LSP和很多其他的项目。很多这样的项目在 Swift 本身,因此很容易做出贡献。

Swift Standard Library | Swift 标准库 - https://github.com/apple/swift/tree/master/stdlib/public/core
Swift Package Manager | Swift 包管理器 - https://github.com/apple/swift-package-manager
Foundation | 基础- https://github.com/apple/swift-corelibs-foundation
SourceKit-LSP - https://github.com/apple/sourcekit-lsp 

So you want to contribute to the actual Swift compiler? | 所以你是想对真正的 Swift 编译器做贡献吗?

在开始之前,首先要了解一下编译器的基本概念,这对理解新变化和要修复哪些部分有所帮助。在每个部分,我将给出一个例子以及在每个部分结尾,我会给你一个在这个阶段会出现的错误。

Lexer | 词法分析器

这是编译的第一部分,编译器会把源代码分解成小部分,我们称之为 token。

// Source code
let x = 16

// The Lexer turns this into tokens
[kw_let, identifier("x"), equal, integer_literal(16)]
// kw means keyword

词法分析器的目的是为了生成 token 序列为了让语法分析器构建 AST (Abstract Syntax Tree) 抽象语法分析树。
在这个阶段会出现的一个错误示例:

// error: unterminated string literal
let x = "
        ^

词法分析器想生成一个 string_literal 的 token,但是没有匹配的

相关链接

Lexer.h (Header file) - https://github.com/apple/swift/blob/master/include/swift/Parse/Lexer.h
Lexer.cpp (Implementation) - https://github.com/apple/swift/blob/master/lib/Parse/Lexer.cpp
Lexer Diagnostics (errors, warnings, notes) - https://github.com/apple/swift/blob/master/include/swift/AST/DiagnosticsParse.def#L40-L197
Lexical Analysis - https://en.wikipedia.org/wiki/Lexical_analysis

Parser | 语法分析器

语法分析器将得到的 tokens 构建一棵 AST (Abstract Syntax Tree) 抽象语法分析树,这是编译器理解源代码的关键一步。

// Tokens from lexer
[kw_left, identifier("x"), equal, integer_literal(16)]

// The Parser transforms this into the AST
let x = 16

这里我们可以向编译器添加一些参数,来了解编译器如何理解源代码。

$ swiftc -dump-parse example.swift

这将输出基本的预类型检查(pre-typechecked)的 AST

(source_file
	(top_level_code_decl
		(brace_stmt
			(pattern_binding_decl
				(pattern_named 'x')
				(integer_literal_expr type='<null>' value=16))))
	(var_decl "x" type='<null type>' let storage_kind=stored))

我们可以看到 x 的变量定义以及 integer_literal(16) 变成了一种叫做模式绑定声明(pattern binding declaration)的表达式。(目前为止我们还不需要理解这里发生了什么,但是模式绑定声明是将表达式绑定到模式,在这个例子中,模式是 x)

在这个阶段的一个错误例子:

// Tokens produced from lexer for this source code
// [kw_struct, l_brace, r_brace]

// error: expected identifier in struct declaration
struct {}
       ^

语法分析器看到 kw_struct 这个 token 想把它转变成结构体声明,但是下一个 token 并不是一个命名这个结构的标识,而是一个 l_brace(左括号)。

相关链接

Parse Headers - https://github.com/apple/swift/tree/master/include/swift/Parse 
Parse Implementation files - https://github.com/apple/swift/tree/master/lib/Parse
Parse diagnostics - https://github.com/apple/swift/blob/master/include/swift/AST/DiagnosticsParse.def
Parsing - https://en.wikipedia.org/wiki/Parsing 
Abstract Syntax Tree - https://en.wikipedia.org/wiki/Abstract_syntax_tree 

Sema (Semantic Analysis) 语义分析

语义分析是编译过程的下一步。这部分包括类型检测(Type Checker)和约束系统(Constraint System)。这一步实现了很多事,比如表达式分配类型,确保结构正确的符合协议,自动推倒类型的协议一致性等等

// AST from the parser
let x = 16

// Sema refined AST
internal let x: Int = 16

也可以通过添加参数查看更多内容:

$ swiftc -dump-ast example.swift

这将输出类型检测后的 AST

(source_file
  (top_level_code_decl
    (brace_stmt
      (pattern_binding_decl
        (pattern_named type='Int' 'x')
        (call_expr implicit type='Int' location=<source>:1:9 range=[<source>:1:9 - line:1:9] nothrow arg_labels=_builtinIntegerLiteral:
          (constructor_ref_call_expr implicit type='(_MaxBuiltinIntegerType) -> Int' location=<source>:1:9 range=[<source>:1:9 - line:1:9] nothrow
            (declref_expr implicit type='(Int.Type) -> (_MaxBuiltinIntegerType) -> Int' location=<source>:1:9 range=[<source>:1:9 - line:1:9] decl=Swift.(file).Int.init(_builtinIntegerLiteral:) function_ref=single)
            (type_expr implicit type='Int.Type' location=<source>:1:9 range=[<source>:1:9 - line:1:9] typerepr='Int'))
          (tuple_expr implicit type='(_builtinIntegerLiteral: Int2048)' location=<source>:1:9 range=[<source>:1:9 - line:1:9] names=_builtinIntegerLiteral
            (integer_literal_expr type='Int2048' location=<source>:1:9 range=[<source>:1:9 - line:1:9] value=16))))))
  (var_decl "x" type='Int' interface type='Int' access=internal let storage_kind=stored))

这里有很多输出,让我们分析一下:

(var_decl "x" type='Int' interface type='Int' access=internal let storage_kind=stored))

我们可以看到 x 的变量声明,并且现在有了一个类型 Int 同时看到访问控制级别 internal

(pattern_binding_decl
  (pattern_named type='Int' 'x')
  (call_expr implicit type='Int' location=<source>:1:9 range=[<source>:1:9 - line:1:9] nothrow arg_labels=_builtinIntegerLiteral:
    (constructor_ref_call_expr implicit type='(_MaxBuiltinIntegerType) -> Int' location=<source>:1:9 range=[<source>:1:9 - line:1:9] nothrow
      (declref_expr implicit type='(Int.Type) -> (_MaxBuiltinIntegerType) -> Int' location=<source>:1:9 range=[<source>:1:9 - line:1:9] decl=Swift.(file).Int.init(_builtinIntegerLiteral:) function_ref=single)
      (type_expr implicit type='Int.Type' location=<source>:1:9 range=[<source>:1:9 - line:1:9] typerepr='Int'))
    (tuple_expr implicit type='(_builtinIntegerLiteral: Int2048)' location=<source>:1:9 range=[<source>:1:9 - line:1:9] names=_builtinIntegerLiteral
      (integer_literal_expr type='Int2048' location=<source>:1:9 range=[<source>:1:9 - line:1:9] value=16))))))

这里我们看到 integer_literal_expr 变成了 call_expr

internal let x: Int = Int(_builtinIntegerLiteral: 16)

这就是编译器和标准库之间的关联,你可以在这里找到 Int 的初始化: https://github.com/apple/swift/blob/master/stdlib/public/core/IntegerTypes.swift.gyb#L1111 

这是一个语义分析的简单例子,但我鼓励你深入了解因为这是我最喜欢的一部分。

在这个部分的一个错误:

// error: cannot convert value of type 'Bool' to specified type 'Int'
let x: Int = true
			 ^~~~

我们给一个 Int 变量赋了一个 Bool 型的值,类型检查捕捉到了这个错误。

相关链接

Sema - https://github.com/apple/swift/tree/master/lib/Sema
Semantic Analysis - https://en.wikipedia.org/wiki/Semantic_analysis_(compilers)

SIL (Swift Intermediate Language) | Swift 中间语言

SIL 为我们的 Swift 代码提供了高级优化,数据流诊断等等。

// Type checked AST from Sema
internal let x: Int = 16

// SIL
sil_stage canonical

import Builtin
import Swift
import SwiftShims

let x: Int

sil_global hidden [let] @$S6output1xSivp : $Int

sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$S6output1xSivp // id: %2
  %3 = global_addr @$S6output1xSivp : $*Int // user: %6
  %4 = integer_literal $Builtin.Int64, 16 // user: %5
  %5 = struct $Int (%4 : $Builtin.Int64) // user: %6
  store %5 to %3 : $*Int // id: %6
  %7 = integer_literal $Builtin.Int32, 0 // user: %8
  %8 = struct $Int32 (%7 : $Builtin.Int32) // user: %9
  return %8 : $Int32 // id: %9
} // end sil function 'main'

sil public_external [transparent] [serialized] @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int {
bb0(%0 : $Builtin.Int2048, %1 : $@thin Int.Type):
  %2 = builtin "s_to_s_checked_trunc_Int2048_Int64"(%0 : $Builtin.Int2048) : $(Builtin.Int64, Builtin.Int1) // user: %3
  %3 = tuple_extract %2 : $(Builtin.Int64, Builtin.Int1), 0 // user: %4
  %4 = struct $Int (%3 : $Builtin.Int64) // user: %5
  return %4 : $Int // id: %5
} // end sil function '$SSi22_builtinIntegerLiteralSiBi2048__tcfC'

这里有很多有意思的地方,但我们只讨论 main 函数中的各部分。

sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32

它和这个是一样的

int main(int argc, char **argv)

Main 函数的主题是

alloc_global @$S6output1xSivp // id: %2
%3 = global_addr @$S6output1xSivp : $*Int // user: %6
%4 = integer_literal $Builtin.Int64, 16 // user: %5
%5 = struct $Int (%4 : $Builtin.Int64) // user: %6
store %5 to %3 : $*Int // id: %6
%7 = integer_literal $Builtin.Int32, 0 // user: %8
%8 = struct $Int32 (%7 : $Builtin.Int32) // user: %9
return %8 : $Int32 // id: %9

首先分配一个全局变量 x,然后把它的地址存到 %3.然后我们构造一个值为 16 的 Int 把它存到 %5.然后我们在赋给 x。最后三行其实就是 return 0

值得注意的事,这个步骤有两个小步,SILGen 和 SIL。SILGen 从 Sema 获取 AST 并生成 SIL 的原始版本,然后将其传递给 SIL 进行强制优化和诊断,然后生成 SIL 的规范版本。

这里我们也可以添加参数

// This emits the raw version of SIL from SILGen
$ swiftc -emit-silgen example.swift

// This emits the canonical version of SIL
$ swiftc -emit-sil example.swift

这个阶段的一个错误:

// error: missing return in a function expected to return 'Int'
func add(_ x: Int, _ y: Int) -> Int {
	let sum = x + y
}
^

这是数据流诊断的一部分。SIL 确定没有”终止符“或返回命令,并发出错误。

相关链接

SIL Header files - https://github.com/apple/swift/tree/master/include/swift/SIL
SIL Diagnostics - https://github.com/apple/swift/blob/master/include/swift/AST/DiagnosticsSIL.def
SIL Optimizer Header files - https://github.com/apple/swift/tree/master/include/swift/SILOptimizer
SILGen Implementation files - https://github.com/apple/swift/tree/master/lib/SILGen
SIL Implementation files - https://github.com/apple/swift/tree/master/lib/SIL
SIL Optimizer Implementation files - https://github.com/apple/swift/tree/master/lib/SILOptimizer
SIL Documentation - https://github.com/apple/swift/blob/master/docs/SIL.rst
SIL Programmer's Manual (Work in Progress) - https://github.com/apple/swift/blob/master/docs/SILProgrammersManual.md
Video with Joe Groff and Chris Lattner at LLVM conf - https://www.youtube.com/watch?v=Ntj8ab-5cvE 

IR(LLVM IR Generation) | LLVM 中间代码生成

这时编译过程中的最后一步了(至少对于 Swift 编译器是这样的)。这一步的目标是采用我们进过改进和优化的 SIL 并发出 LLVM IR (LLVM 中间表示)。

%TSi = type <{ i64 }>

define protected i32 @main(i32, i8**) #0 !dbg !25 {
  %2 = bitcast i8** %1 to i8*
  store i64 16, i64* getelementptr inbounds (%TSi, %TSi* @"$S6output1xSivp", i32 0, i32 0), align 8, !dbg !30
  ret i32 0, !dbg !30
}

这里首先定义 %TSi 为一个包含 i64 的类型,在 LLVM 中是一个 64 位的整型。这里将给出 Swift 中 %TSi 的更好理解

public struct Int {
	var _value: Builtin.Int
}

%TSi 就是 Swift 中的 Int!

下一步,我们定义 main 函数然后 store i64 16 到全局变量 x 最后返回 i32 0

这里也可以添加编译参数

$ swiftc -emit-ir example.swift

到这里为止,我们将中间表示转变成 LLVM 输出一个原生二进制文件,如果想看编译生成的:

$ swiftc -emit-assembly example.swift

相关链接

IRGen - https://github.com/apple/swift/tree/master/lib/IRGen
IRGen Diagnostics - https://github.com/apple/swift/blob/master/include/swift/AST/DiagnosticsIRGen.def 

So we know the pipeline now, but now what? How do I actually contribute?

是的,你需要了解一些 C++ 才能做出贡献,别怕,编译器开发中的 C++ 并没有那么复杂。

Swift 编译器用到的是 LLVM C++,他并不是一门实际的语言。

原文地址:[https://forums.swift.org/t/what-should-i-learn-if-i-want-to-contribute-to-the-swift-compiler/18144/5 ]