前语
本文翻译自 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-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)
办法转化回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
存在的原因:
- 加快编译速度。
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~)