神秘的Builtin模块

原文:Swift's mysterious Builtin module


当你在Playground中cmd+click某个量,比如Int的时候,你可能会看到下面的代码:

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

或者当你阅读Swift标准库源代码的时候,你可能会看到许多形如Builtin.*的方法,就像下面这些:

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

那些反复出现的Builtin可能会让你感到困惑:那TM是什么玩意?

Clang, Swift Compiler, SIL, IR, LLVM

要弄明白Builtin是什么,先要明白Objective-C和Swift编译器是怎样工作的。

上图显示了Objective-C代码的编译过程:Objective-C代码经过Clang处理后,生成LLVM Intermediate Representation (IR),再经过LLVM处理,最后生成机器码。

可以把LLVM IR想象成某种高级的汇编语言,这种汇编语言和CPU架构(如i386或ARM)无关。任何一个编译器,只要能生成LLVM IR,就可以用LLVM生成和特定的CPU架构兼容的机器码。

明白了这点,我们再来看Swift编译器是如何工作的:

Swift代码首先被编译成SIL (Swift Intermediate Represention),然后再被编译成LLVM IR进入LLVM编译器,最后生成机器码。而SIL无非就是LLVM IR的一层Swift外壳(swifty wrapper),我们有很多理由需要SIL:比如确保变量在使用之前被初始化、检测不可执行的代码(unreachable code),优化代码等。如果你想知道SIL具体干了些啥,可以去看看这个视频

我们再来看看LLVM IR,对于下面的代码:

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

我们可以用这个命令

swiftc -emit-ir addswift.swift

将这三条语句编译成LLVM IR代码(以//^开头的语句为注释):

...
//^ store 5 in a
store i64 5, i64* getelementptr inbounds (%Si* @_Tv8addswift1aSi, i32 0, i32 0), align 8
//^ store 6 in b
store i64 6, i64* getelementptr inbounds (%Si* @_Tv8addswift1bSi, i32 0, i32 0), align 8
//^ load a to virtual register %5
%5 = load i64* getelementptr inbounds (%Si* @_Tv8addswift1aSi, i32 0, i32 0), align 8

//^ load b to virtual register %6
%6 = load i64* getelementptr inbounds (%Si* @_Tv8addswift1bSi, i32 0, i32 0), align 8

//^ call llvm's signed addition with overflow on %5 and %6
//^ returns two values: sum and a flag if overflowed
%7 = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %5, i64 %6)

//^ extract first value to %8
%8 = extractvalue { i64, i1 } %7, 0
//^ extract second value to %9
%9 = extractvalue { i64, i1 } %7, 1

//^ if overflowed jump to trap otherwise jump to label 10
br i1 %9, label %11, label %10

; <label>:10; preds = %once_done
//^ store result in c
store i64 %8, i64* getelementptr inbounds (%Si* @_Tv8addswift1cSi, i32 0, i32 0), align 8
ret i32 0

我知道你觉得上面的代码就像一坨屎,不过还是请注意:

  • i64是定义在LLVM中的一个类型,代表64位整数。
  • llvm.sadd.with.overflow.i64是个方法,这个方法将两个i64整数相加并返回两个值:一个表示相加的和,另一个标识操作是否成功。

Builtin模块

回到Swift,我们知道在Swift中,Int实际上一个struct,而+是一个针对Int重载的全局方法(global function)。严格说来,Int+不是Swift语言的一部分,它们是Swift标准库的一部分。既然不是原生态,是不是就意味着操作Int+的时候会有额外的负担,导致Swift跑得慢?当然不是,因为我们有Builtin

Builtin将LLVM IR的类型和方法直接暴露给Swift标准库,所以我们在操作Int+的时候,没有额外的运行时负担。

Int为例,Int在标准库中是一个struct,定义了一个属性value,类型是Builtin.Int64。我们可以用unsafeBitCastvalue属性在IntBuiltin.Int64之间相互转换。Int还重载了init方法,使得我们可以从Builtin.Int64直接构造一个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表示这个函数应该是个内联的(inline)。
  • Builtin.sadd_with_overflow_Int64就是llvm.sadd.with.overflow.i64
  • 相加的和被转换成Int类型,然后返回。

因为这是一个inline方法,我们有理由相信编译器会内联展开方法调用并生成高效的LLVM IR代码。

我可以使用Builtin吗?

Builtin模块只有在标准库内部才可以访问,一般的Swift程序是没有办法调用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))

只需要指定-parse-stdlib,上面的代码就可以编译。

swiftc -parse-stdlib add.swift && ./add
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容