前语

本文翻译自 Swift’s mysterious Builtin module
翻译的不对的当地还请多多包容纠正,谢谢~

神秘的 Swift 内置模块

… 好吧,自从 swift 开源后或许不那么神秘。但不管怎样,假如你在playground 按住 Command 并单击Int类型,你或许已经留意到相似这种代码:

/// A 64-bit signed integer value
/// type.
public struct Int : SignedIntegerType, Comparable, Equatable {
    public var value: Builtin.Int64
...
}

或许假如你已经阅读过 Swift 的 stdlib 库,那大概率留意到了有许多 Builtin.* 类的函数,比方:

  • Builtin.Int1
  • Builtin.RawPointer
  • Builtin.NativeObject
  • Builtin.allocRaw(size._builtinWordValue, Builtin.alignof(Memory.self)))

因此,神秘的 Builtin 究竟是什么呢?

Clang, Swift Compiler, SIL, IR, LLVM

为了解Builtin真正的效果,让咱们快速简要地看看 Objective-CSwift 编译器是怎么工作的。

Objective-C

神秘的 Swift 内置模块

(隐藏了许多细节,但用于解说本篇文章完全ok)

Objective-C 代码进入 Clang 前端处理后会产出叫 LLVM 中心表明言语(Intermediate Representation = IR),IR 随后会被送入 LLVM,经过处理后二进制机器码就出来了。

LLVM 中心表明言语是一种相似高档的汇编言语,它是独立于架构的(如i368,ARM 等)。为了给运用 LLVM 的新言语发明编译器,咱们只需要实现一个前端,它能够将新言语的代码转化成 LLVM 中心表明言语(IR),并将 IR 传递给 LLVM 生成给任何它所支撑渠道的汇编或许二进制代码。

Swift

神秘的 Swift 内置模块

Swift 首要生成 Swift 中心表明言语 SIL(Swift Intermediate Representation),它能够被转化成 LLVM IR 中心表明言语,接着 LLVM IRLLVM 编辑器编译。

如你所见 SILLLVM IR 的快速化封装,它被创建出来是有许多原因的,比方:

  1. 确保变量在运用前被初始化;
  2. 检测不可达(未运用)的代码;
  3. 在代码发送给 LLVM 前进行优化;

你能够看这个 Youtube 视频找到更多 SIL 存在的原因及它做的工作。

这里的主要内容就是 LLVM IR。关于像以下简略的 Swift 程序:

let a = 5
let b = 6
let c = a + b

转化成 LLVM IR 后如下:(可经过 swiftc -emit-ir addswift.swift 指令生成)

...
  //^ 将 5 数值存入 a 变量
  store i64 5, i64* getelementptr inbounds (%Si* @_Tv8addswift1aSi, i32 0, i32 0), align 8
  //^ 将 6 数值存入 b 变量
  store i64 6, i64* getelementptr inbounds (%Si* @_Tv8addswift1bSi, i32 0, i32 0), align 8
  //^ 将 a 加载到虚拟寄存器 %5 内
  %5 = load i64* getelementptr inbounds (%Si* @_Tv8addswift1aSi, i32 0, i32 0), align 8
  //^ 将 b 加载到虚拟寄存器 %6 内
  %6 = load i64* getelementptr inbounds (%Si* @_Tv8addswift1bSi, i32 0, i32 0), align 8
  //^ 调用 llvm 有符号可溢出相加办法(回来值是两者之和及一个是否溢出标识)
  %7 = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %5, i64 %6)
  //^ 提取第一个值放入 %8
  %8 = extractvalue { i64, i1 } %7, 0 
  //^ 提取第二个值放入 %9
  %9 = extractvalue { i64, i1 } %7, 1
  //^ 假如溢出跳转trap分支(label 11),不然跳到 label10(相似汇编的 goto)
  br i1 %9, label %11, label %10
; <label>:10                                      ; preds = %once_done
  //^ 将成果存入变量 c
  store i64 %8, i64* getelementptr inbounds (%Si* @_Tv8addswift1cSi, i32 0, i32 0), align 8
  ret i32 0
...

在上述代码中,能够经过 //^ 符号找到我关于 LLVM IR 代码注释。

尽管上述代码看起来像一坨垃圾,但你只需知道这两件事:

  1. LLVM 里有个数据类型叫做 i64,它是64位整数;
  2. LLVM 里有个函数叫做 llvm.sadd.with.overflow.i64,它能将两个 i64 整数相加并回来两个值,一个是和,另一个是1bit的溢出标识(假如相加失利);

能够解说 Bulitin

ok,回到 Swift,咱们知道 SwiftInt 类型实际上是 Swift struct 类型,并且 + 操作符实际是个大局函数,是作为 Int 关于 lhsrhs 的重载。Int 句子不是言语的一部分,从某种意义上来说被言语直接了解的符号是比方这 struct,class,if,guard,它们才是言语的一部分。

Int+Swift stdlib 库的一部分,意味着它们也就不是原生构造,那就能够说这样耗费很大 or Swift 很慢?并不是。

这就是 Builtin 发挥效果的当地。BuiltinLLVM IR 类型直接露出给 stdlib,因此没有运行时查找的耗费,也能够让 Int 作为 struct 来做相似的工作:extension Int { func times(otherInt: Int) -> Int { return self * otherInt } }; 5.times(6)

Swift struct Int 类型只包含了一个类型为 Builtin.Int64 名叫 value 存储特点,因为咱们可运用 unsafeBitCast 对它来回转化,并且 stdlib 也供给了将 Builtin.Int64 转化为 Swift struct Intinit 初始化的重载办法。

相似的,UnsafePointer 及相关类是对 Builtin 直接内存访问办法的封装。例如:alloc 函数的界说是这样:

public static func alloc(num: Int) -> UnsafeMutablePointer {
  let size = strideof(Memory.self) * num
  return UnsafeMutablePointer(
    Builtin.allocRaw(size._builtinWordValue, Builtin.alignof(Memory.self)))
}

现在咱知道了 Swift Int 不会引起功能问题,但 + 操作符呢。它还是一个函数,界说如下:

@_transparent
public func + (lhs: Int, rhs: Int) -> Int {
  let (result, error) = Builtin.sadd_with_overflow_Int64(
    lhs._value, rhs._value, true._value)
  // return overflowChecked((Int(result), Bool(error)))
  Builtin.condfail(error)
  return Int(result)
}
  • @_transparent 表明函数是以内联方法被调用;
  • Builtin.sadd_with_overflow_Int64 对应咱们之前在 LLVM IR 看到的会回来元组(Builtin.Int64 类型的成果, Builtin.Int1 类型的过错)的 llvm.sadd.with.overflow.i64 办法;
  • 成果经过 Int(result) 办法转化回 Swift struct Int 型,并且回来;

因此,这些都是内联调用的话,意味着将会生成非常好的能生成又快又好的二进制 LLVM IR 代码 ^_^

我能够玩玩 Builtin

因为清楚明了的原因,Swift 内的 Builtin 只在 stdlib 库及特殊 Swift 程序内可见。咱们能够经过swiftc-parse-stdlib 标识试试 Builtin

例:

import Swift //Import swift stdlib
let result = Builtin.sadd_with_overflow_Int64(5.value, 6.value, true._getBuiltinLogicValue())
print(Int(result.0))
let result2 = Builtin.sadd_with_overflow_Int64(unsafeBitCast(5, Builtin.Int64), unsafeBitCast(6, Builtin.Int64), true._getBuiltinLogicValue())
print(unsafeBitCast(result2.0, Int.self))
swiftc -parse-stdlib add.swift && ./add

翻译结束~

个人总结

本文主要解说了 Builtin 存在的原因:

  1. 加快编译速度。Swift 许多 struct 值类型,终究内部都封装了 IILV IR 基础类型,不需要过多转化;
  2. 进步运行功能。因为不需要做过多转化,直接运用的 IILV IR 的函数,相当于运用许多相似底层函数在开发,功能更高;

其次,文章给咱们对比 Objective-CSwift 言语大致的编译过程。LLVM 后端流程相同:LLVM 经过将 LLVM IR 转化成二进制代码。那么两者言语差异点在于 LLVM IR 代码的生成。 Objective-C 经过 Clang,而 Swift 经过自身的编译器先生成 SIL,再经过 IRGen 生成 LLVM IRSwift 在这个过程能够做许多优化(类型校验,初始化查验,不可达代码优化等)。

小结语

不像 Objective-C 部分代码只能靠猜,Swift 开源给了程序员更多的可探索性,开发信心及趣味性~ 我们一同学起来吧^_^

(PS:后面 swiftc -parse-stdlib add.swift && ./add 一直报错,知道的老铁能够告知下哟,ths~)