前言

书接上文,本文将介绍 Swift 语言的编译主要前端流程,后端的流程与上文同步,就不过多赘述,详情见 iOS 编译过程,同时本文就 SIL 介绍其特点。

Clang 与 SwiftC

为什么要引入一种新的编译器,整体而言 Swift 作为一种高级语言,其很多高级特性 Clang 不支持,同时也由于 Clang 自身的一些弊端。进而催生出了 SwiftC,将 ClangSwiftC 的编译流程进行对比:

其中 Clang 的劣势如下:

  • 语义不可重复表示,导致予以分析会依赖于 Parse(可看成生成 token 表,进行词法分析)。
  • IR 中间代码不适用于进行 Analysis,采用 CFG 进行分析,导致生成 IRAnalysis 是两部分 (Analysis 有别与词语分析与语法分析,它单独对源代码进行分析,如查找【不会执行的代码】、【不会被初始化的变量】、【静态分析】等)。
  • CFGIR 降级的时候会做很多重复分析,做无用功。

相较而言 Swift 解决了 Clang 中的很多问题,引入了 SILSwift Intermediate Language ) 中间代码,既可进行 分析 又可进行 IR 代码生成,并引入了许多新的高级优化特性。

img

img

SwiftC 编译流程(Frontend)

源码:

class Person {
    var name = "nihao"
    func getName() -> String {
        print(name)
        return name
    }
}

Parse(词法分析):

命令:swiftc -dump-parse main.swift >> ./main.parse

(source_file "main.swift"
  (class_decl range=[main.swift:8:1 - line:15:1] "Person"
    (pattern_binding_decl range=[main.swift:9:5 - line:9:16]
      (pattern_named 'name')
      Original init:
      (string_literal_expr type='<null>' encoding=utf8 value="nihao" builtin_initializer=**NULL** initializer=**NULL**)
      Processed init:
      (string_literal_expr type='<null>' encoding=utf8 value="nihao" builtin_initializer=**NULL** initializer=**NULL**))
    (var_decl range=[main.swift:9:9 - line:9:9] "name" type='<null type>')
    (func_decl range=[main.swift:11:5 - line:14:5] "getName()"
      (parameter "self")
      (parameter_list range=[main.swift:11:17 - line:11:18])
      (result
        (type_ident
          (component id='String' bind=none)))
      (brace_stmt range=[main.swift:11:30 - line:14:5]
        (call_expr type='<null>' arg_labels=_:
          (unresolved_decl_ref_expr type='<null>' name=print function_ref=unapplied)
          (paren_expr type='<null>'
            (unresolved_decl_ref_expr type='<null>' name=name function_ref=unapplied)))
        (return_stmt range=[main.swift:13:9 - line:13:16]
          (unresolved_decl_ref_expr type='<null>' name=name function_ref=unapplied))))))

可以看到有别于 Clang 生成 token 流的的形式,Parse阶段是一个递归至顶向下的解析过程,这也解释了其语义可重复表示的原因。

语义分析并生成AST

命令:swiftc -dump-ast main.swift >> ./main.ast

(source_file "main.swift"
  (class_decl range=[main.swift:8:1 - line:15:1] "Person" interface type='Person.Type' access=internal non-resilient
    (pattern_binding_decl range=[main.swift:9:5 - line:9:16]
      (pattern_named type='String' 'name')
      Original init:
      (string_literal_expr type='String' location=main.swift:9:16 range=[main.swift:9:16 - line:9:16] encoding=utf8 value="nihao" builtin_initializer=Swift.(file).String extension.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) initializer=**NULL**)
      Processed init:
      (string_literal_expr type='String' location=main.swift:9:16 range=[main.swift:9:16 - line:9:16] encoding=utf8 value="nihao" builtin_initializer=Swift.(file).String extension.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) initializer=**NULL**))
    (var_decl range=[main.swift:9:9 - line:9:9] "name" type='String' interface type='String' access=internal readImpl=stored writeImpl=stored readWriteImpl=stored
      (accessor_decl implicit range=[main.swift:9:9 - line:9:9] 'anonname=0x7fcbc202f0d8' interface type='(Person) -> () -> String' access=internal get_for=name
        (parameter "self" type='Person' interface type='Person')
        (parameter_list)
        (brace_stmt implicit range=[main.swift:9:9 - line:9:9]
          (return_stmt implicit
            (member_ref_expr implicit type='String' decl=main.(file).Person.name@main.swift:9:9 direct_to_storage
              (declref_expr implicit type='Person' decl=main.(file).Person.<anonymous>.self@main.swift:9:9 function_ref=unapplied)))))
      (accessor_decl implicit range=[main.swift:9:9 - line:9:9] 'anonname=0x7fcbc202f330' interface type='(Person) -> (String) -> ()' access=internal set_for=name
        (parameter "self" type='Person' interface type='Person')
        (parameter_list range=[main.swift:9:9 - line:9:9]
          (parameter "value" type='String' interface type='String'))
......

此步进行语法分析,并生成抽象语法树,可以看到生成的 AST 与 Parse 阶段的产于语法一直,不过在其基础上补充了很多内容,如类型、访问权限、构造函数、析构函数等。

Raw(原始) SIL 生成

命令:swiftc -emit-silgen main.swift >> ./main.silgen

sil_stage raw

import Builtin
import Swift
import SwiftShims

class Person {
  @_hasStorage @_hasInitialValue var name: String { get set }
  func getName() -> String
  @objc deinit
  init()
}

// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  %2 = integer_literal $Builtin.Int32, 0          // user: %3
  %3 = struct $Int32 (%2 : $Builtin.Int32)        // user: %4
  return %3 : $Int32                              // id: %4
} // end sil function 'main'

// variable initialization expression of Person.name
sil hidden [transparent] [ossa] @$s4main6PersonC4nameSSvpfi : $@convention(thin) () -> @owned String {
bb0:
  %0 = string_literal utf8 "nihao"                // user: %5
  %1 = integer_literal $Builtin.Word, 5           // user: %5
  %2 = integer_literal $Builtin.Int1, -1          // user: %5
  %3 = metatype $@thin String.Type                // user: %5
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %4 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %5
  %5 = apply %4(%0, %1, %2, %3) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %6
  return %5 : $String                             // id: %6
} // end sil function '$s4main6PersonC4nameSSvpfi'    
......

Raw SIL,由于 目前的 SIL 还未进行确保优化与诊断检查,此时的 SIL 还比较脆弱,所以称其为 Raw SIL

Canonical(正式) SIL生成

命令:swiftc -emit-sil main.swift >> ./main.sil

sil_stage canonical

import Builtin
import Swift
import SwiftShims

class Person {
  @_hasStorage @_hasInitialValue var name: String { get set }
  func getName() -> String
  @objc deinit
  init()
}

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  %2 = integer_literal $Builtin.Int32, 0          // user: %3
  %3 = struct $Int32 (%2 : $Builtin.Int32)        // user: %4
  return %3 : $Int32                              // id: %4
} // end sil function 'main'

// variable initialization expression of Person.name
sil hidden [transparent] @$s4main6PersonC4nameSSvpfi : $@convention(thin) () -> @owned String {
bb0:
  %0 = string_literal utf8 "nihao"                // user: %5
  %1 = integer_literal $Builtin.Word, 5           // user: %5
  %2 = integer_literal $Builtin.Int1, -1          // user: %5
  %3 = metatype $@thin String.Type                // user: %5
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %4 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %5
  %5 = apply %4(%0, %1, %2, %3) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %6
  return %5 : $String                             // id: %6
} // end sil function '$s4main6PersonC4nameSSvpfi'

// String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
sil [always_inline] [readonly] [_semantics "string.makeUTF8"] @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String
......

这一步主要进行确保优化以及诊断检查工作:

特定流程:

  • 强制内联:对于透明函数进行内联(透明函数:如果一个函数值受到入参的变化,每次调用相同的入参会有相同的返回值。)
  • 内存提升:1. 将alloc_box结构优化为alloc_stack 2. 提升无暴露地址(non_address-exposed)的alloc_stack说明到SSA注册。
  • 常数传播:一种优化常数手段,如表达式可以在编译器求值;常量值可代替常量变量;过程的部分参数是常量,减少涉及状态向量的大小可以避免代码的扩展等内容。具体可参考这里
  • 返回分析:验证每个方法在每个代码路径只返回一个值,并且不会在定义的末端出现无返回值的错误。
  • 临界拆分:不支持任意的基础block参数通过终端进行临界拆分,使得运算更加高效。

优化SIL

命令:swiftc -emit-sil main.swift >> ./main.sil -O

也是在与上一步命令相同,但加上 -O参数,这一步不是必要的,如果在 Xcode 中设置 -Onone,则不会进行。

特定优化:

  • 泛型特化:分析泛型函数的特定调用,并生成新的特定版本的函数,将泛型的特定用法全部重写为对应的特定函数调用。
  • witness和虚函数表的去虚拟化:通过给定类型去查找关联的类的虚函数表或者类型的witness表,并将虚函数调用替换为调用函数映射,简化了查找流程从而提高了效率。
  • 性能内联
  • 引用计数优化
  • 内存提升/优化
  • 高级领域特定优化:swift编译器对基础的swift类型容器(类似Array或String)实现了高级优化,如 Copy On Wirte

IR代码生成

命令:swiftc -emit-ir main.swift >> ./main.ir

; ModuleID = '<swift-imported-modules>'
source_filename = "<swift-imported-modules>"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx12.0.0"

%T4main6PersonC = type <{ %swift.refcounted, %TSS }>
%swift.refcounted = type { %swift.type*, i64 }
%swift.type = type { i64 }
%TSS = type <{ %Ts11_StringGutsV }>
%Ts11_StringGutsV = type <{ %Ts13_StringObjectV }>
%Ts13_StringObjectV = type <{ %Ts6UInt64V, %swift.bridge* }>
%Ts6UInt64V = type <{ i64 }>
%swift.bridge = type opaque
%swift.full_type = type { i8**, %swift.type }
%objc_class = type { %objc_class*, %objc_class*, %swift.opaque*, %swift.opaque*, i64 }
%swift.opaque = type opaque
%swift.method_descriptor = type { i32, i32 }
%swift.type_metadata_record = type { i32 }
%swift.metadata_response = type { %swift.type*, i64 }
%"$s4main6PersonC4nameSSvM.Frame" = type { [24 x i8] }
%Any = type { [24 x i8], %swift.type* }
%TSa = type <{ %Ts12_ArrayBufferV }>
%Ts12_ArrayBufferV = type <{ %Ts14_BridgeStorageV }>
%Ts14_BridgeStorageV = type <{ %swift.bridge* }>

可以看到,生成的语法结构和 Objective-C 文件生成的结构一致。

至此,SwiftC 编译流程就已经全部梳理完了,其中还有 clang importer 模块的工作并未介绍,clang importer 负责导入 Clang 模块,并将 CObjective-C 的API映射到 Swift API 中。

SIL 介绍

Swift 中间语言(Swift Intermediate Language,SIL)是一门高级且专用于 Swift 的中间语言,适用于对 Swift 代码的进一步分析和优化。SIL 依赖于 Swift 的类型系统和声明,所以 SIL 语法是 Swift 的延伸。一个 .sil 文件是一个增加了 SIL 定义的 Swift 源文件。 .swift 源文件只针对声明进行语法分析,其 func 方法体和最高阶代码将会被 SIL 语法分析器忽略。 在 .sil 文件中没有隐式 import。如果使用 swift 或者 Buildin 标准组件的话必须明确的引用。

地址类型

地址类型 $*T 指针指向的是任意引用的值或者 $T。地址不是引用计数指针,不能被 retainedreleased

Box类型

本地变量和非直接的数值类型都是存储在堆上,@box T 是一个引用计数类型,指向的是包含了多个 T 的盒子。盒子使用的是 Swift 的原生引用计数。

Metatype 类型

SIL 内的 metatype 类型必须描述自身表示:

  • @thin 表示不需要内存空间
  • @thick 指存储的是类型的引用或者类型子类的引用
  • @objc 指存储的是一个 OC 类对象的表示。

VTables

用来表示类对象方法的动态派发,如果看到 SIL 代码中出现 class_method 或者 super_method,这些都是通过 sil_vtable 进行追踪的;sil_table 中包含类对象中的所有方法的映射,包括从父对象继承的方法。

Witness Table

用来代表泛型类型的方法动态派发,一个泛型类型的所有的所有泛型实例共享一个 Witness Table,同样衍生类也都会集成基类的 Witness Table

每个遵循协议的对象都会有一个唯一标识,会生成一张 Witness table

总结

本文梳理了 Swift 的编译流程,可以看出,Swift 作为一种高级的安全类型语言,编译器对其做了许多优化,算是弥补了对 Clang 许多不足。SIL 是 Swift 编译的重点,从中可以窥探一些平时开发中难以琢磨的问题,如方法派发、引用计数等内容。以下附swiftc 的命令行帮助。

USAGE: swiftc

MODES:
  -dump-ast               Parse and type-check input file(s) and dump AST(s)
  -dump-parse             Parse input file(s) and dump AST(s)
  -dump-pcm               Dump debugging information about a precompiled Clang module
  -dump-scope-maps <expanded-or-list-of-line:column>
                          Parse and type-check input file(s) and dump the scope map(s)
  -dump-type-info         Output YAML dump of fixed-size types from all imported modules
  -dump-type-refinement-contexts
                          Type-check input file(s) and dump type refinement contexts(s)
  -emit-assembly          Emit assembly file(s) (-S)
  -emit-bc                Emit LLVM BC file(s)
  -emit-executable        Emit a linked executable
  -emit-imported-modules  Emit a list of the imported modules
  -emit-irgen             Emit LLVM IR file(s) before LLVM optimizations
  -emit-ir                Emit LLVM IR file(s) after LLVM optimizations
  -emit-library           Emit a linked library
  -emit-object            Emit object file(s) (-c)
  -emit-pcm               Emit a precompiled Clang module from a module map
  -emit-sibgen            Emit serialized AST + raw SIL file(s)
  -emit-sib               Emit serialized AST + canonical SIL file(s)
  -emit-silgen            Emit raw SIL file(s)
  -emit-sil               Emit canonical SIL file(s)
  -emit-supported-features
                          Emit a JSON file including all supported compiler features
  -index-file             Produce index data for a source file
  -parse                  Parse input file(s)
  -print-ast              Parse and type-check input file(s) and pretty print AST(s)
  -resolve-imports        Parse and resolve imports in input file(s)
  -scan-dependencies      Scan dependencies of the given Swift sources
  -typecheck              Parse and type-check input file(s)

🔗:

Swift的高级中间语言:SIL

Swift-Compiler 官方

Swift - 源码编译

编译优化之 - 常数传播入门

Apple - Swift Intermediate Language

深入浅出iOS编译