前语
本文翻译自 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.Int1Builtin.RawPointerBuiltin.NativeObjectBuiltin.allocRaw(size._builtinWordValue, Builtin.alignof(Memory.self)))
因此,神秘的 Builtin 究竟是什么呢?
Clang, Swift Compiler, SIL, IR, LLVM
为了解Builtin真正的效果,让咱们快速简要地看看 Objective-C 和 Swift 编译器是怎么工作的。
Objective-C
(隐藏了许多细节,但用于解说本篇文章完全ok)
Objective-C 代码进入 Clang 前端处理后会产出叫 LLVM 中心表明言语(Intermediate Representation = IR),IR 随后会被送入 LLVM,经过处理后二进制机器码就出来了。
LLVM 中心表明言语是一种相似高档的汇编言语,它是独立于架构的(如i368,ARM 等)。为了给运用 LLVM 的新言语发明编译器,咱们只需要实现一个前端,它能够将新言语的代码转化成 LLVM 中心表明言语(IR),并将 IR 传递给 LLVM 生成给任何它所支撑渠道的汇编或许二进制代码。
Swift
Swift 首要生成 Swift 中心表明言语 SIL(Swift Intermediate Representation),它能够被转化成 LLVM IR 中心表明言语,接着 LLVM IR 被 LLVM 编辑器编译。
如你所见 SIL 是 LLVM IR 的快速化封装,它被创建出来是有许多原因的,比方:
- 确保变量在运用前被初始化;
- 检测不可达(未运用)的代码;
- 在代码发送给
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 代码注释。
尽管上述代码看起来像一坨垃圾,但你只需知道这两件事:
- 在
LLVM里有个数据类型叫做i64,它是64位整数; - 在
LLVM里有个函数叫做llvm.sadd.with.overflow.i64,它能将两个i64整数相加并回来两个值,一个是和,另一个是1bit的溢出标识(假如相加失利);
能够解说 Bulitin 了
ok,回到 Swift,咱们知道 Swift 的 Int 类型实际上是 Swift struct 类型,并且 + 操作符实际是个大局函数,是作为 Int 关于 lhs 和 rhs 的重载。Int 句子不是言语的一部分,从某种意义上来说被言语直接了解的符号是比方这 struct,class,if,guard,它们才是言语的一部分。
Int 和 + 是 Swift stdlib 库的一部分,意味着它们也就不是原生构造,那就能够说这样耗费很大 or Swift 很慢?并不是。
这就是 Builtin 发挥效果的当地。Builtin 将 LLVM 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 Int 的 init 初始化的重载办法。
相似的,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)办法转化回SwiftstructInt型,并且回来;
因此,这些都是内联调用的话,意味着将会生成非常好的能生成又快又好的二进制 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 存在的原因:
- 加快编译速度。
Swift许多struct值类型,终究内部都封装了IILV IR基础类型,不需要过多转化; - 进步运行功能。因为不需要做过多转化,直接运用的
IILV IR的函数,相当于运用许多相似底层函数在开发,功能更高;
其次,文章给咱们对比 Objective-C 和 Swift 言语大致的编译过程。LLVM 后端流程相同:LLVM 经过将 LLVM IR 转化成二进制代码。那么两者言语差异点在于 LLVM IR 代码的生成。 Objective-C 经过 Clang,而 Swift 经过自身的编译器先生成 SIL,再经过 IRGen 生成 LLVM IR,Swift 在这个过程能够做许多优化(类型校验,初始化查验,不可达代码优化等)。
小结语
不像 Objective-C 部分代码只能靠猜,Swift 开源给了程序员更多的可探索性,开发信心及趣味性~ 我们一同学起来吧^_^
(PS:后面 swiftc -parse-stdlib add.swift && ./add 一直报错,知道的老铁能够告知下哟,ths~)


