Questing for Swift Source Code 01 - 布尔值

96
作者 Thermod
2015.12.29 15:26* 字数 2826

"Questing for Swift Source Code" 系列是我学习 Swift 源码的心得和记录,内容主要是 Swift 源代码的相关分析和探究,如果您对 Swift 源代码也很感兴趣的话,欢迎阅读这个系列的文章。

Swift 源码极其庞大,里面所使用的语言囊括了 Swift、 Python、C++等,因此我觉得这是一个巨大的坑,我不知道能不能把它填完,不过我会尽力而为的~^_^

我们将一起通过分析和学习 Swift 源码,来体会 Swift 这个最前沿编程语言的设计思想,并且从中找出一些鲜为人知的用例!

本文是该系列的第一篇文章,将介绍关于 Swift 中整数类型的定义和实现。


Swift 的基本类型包含了「布尔类型 (boolean)」,表示为 Bool。布尔值通常情况下是作为逻辑值来使用的,因为它的值要么为真 (true),要么为假 (false),除此之外就没有其他值了。

定义

首先我们可以看到 Swift 中对 Bool 的定义:

@_fixed_layout public struct Bool

在 Swift 中,这些基本类型大多都是由结构体 (struct) 定义的,这也准确地表述了基本类型的「值类型 (value-type)」特征。

@_fixed_layout 则是一个属性标识 (attribute),由此属性标识修饰的类型将在 SIL (Swift Intermediate Language) 生成阶段进行处理。它的主要作用是将这个类型确定为固定布局,也就是说在内存当中,这个类型的空间占用大小总是确定的,而且也是无法改变的。

随后我们可以看到 Bool 中唯一的实际值:

internal var _value: Builtin.Int1

Builtin 是 Swift 内置库之一,由 C++ 写成,位于 Builtins.cpp 文件当中。我们暂时不去了解 Builtin 的具体调用方式,在之后的文章中我们会进行探讨。只需知道,这个操作会调用 Builtin 会调用此文件中的 swift::getBuiltinType(ASTContext &Context, StringRef Name) 函数,这个函数将会给 LLVM 返回一个用以数据处理的类型。Name 参数就是要获取的类型名称。具体该函数的实现和使用我们这里也暂时不介绍。

在这个函数中,有一个非常重要的片段:

// Handle 'int8' and friends.
if (Name.substr(0, 3) == "Int") {
  unsigned BitWidth;
  if (!Name.substr(3).getAsInteger(10, BitWidth) &&
      BitWidth <= 2048 && BitWidth != 0)  // Cap to prevent insane things.
    return BuiltinIntegerType::get(BitWidth, Context);
}

这个函数将会截取 Name 参数的前 3 个字符,这里也就是截取 Int1 的前 3 个字符,从而判断其是不是 "Int" 一族。随后,开始读取 "Int" 之后的内容,这里用到了定义在 StringRef 中的函数 getAsInteger(unsigned Radix, T &Result)

getAsInteger(unsigned Radix, T &Result) 函数将当前字符串解析为基于指定基数 (radix) 的整数,例如,Radix 指定为 10 表明将以十进制来解析字符串,Radix 指定为 2 将以二进制来解析字符串。如果字符串无法解析的话,那么这个函数就会返回 true。如果字符串成功解析,那么结果就会写到 Result 当中。

这里用预定义的 BitWidth 来存储转换过的数字,通过这个函数的调用,我们将能得到所读取到的数字 1。随后,要判断读取的数字是否在可处理范围之内(也就是(0,2048])。如果成功的话,就返回一个内置的 BuiltinIntegerType 类型,其定义为:

class BuiltinIntegerType : public BuiltinType

这个内置的整数类型会直接与 LLVM 编译器的 IR 整数类型响应。当然,这其中最重要的值是 BuiltinIntegerWidth 类型的 Width

BuiltinIntegerWidth Width;

这也说明了,对于 Swift 中内置的整数类型来说,无论是 Int8 还是 Int32,都是属于同样的类型,只不过它们的 Width 属性,也就是“位长(占用空间)”不同而已。

因此,Builtin.Int1 就很好理解了:这代表了一个内置的整数类型,它的位长是 1 个字节,也就是只有 “0” 和 “1” 两个值,刚好符合传统上布尔值的定义:0 代表 false,1 代表 true。

初始化

我们下面来查看定义的布尔值初始化方法。

Swift 内部定义了三个初始化方法:

@_transparent
public init() {
  let zero: Int8 = 0
  self._value = Builtin.trunc_Int8_Int1(zero._value)
}

@_versioned
@_transparent
internal init(_ v: Builtin.Int1) { self._value = v }

public init(_ value: Bool) {
  self = value
}

设置为 public 的有两个,也就是我们使用的 let bool = Bool()let bool = Bool(false),前面那个初始化方法的默认值是 false,而后面的那个则根据我们的赋值来决定其中的实际值。

解析

  1. 从语义上来看,@_transparent 类似于「将此操作视为一种原始操作(primitive operation)」。该特性会导致编译器在管道(pipeline)中更早地将函数内联。
  2. @_versioned 的唯一作用是:确保被此属性标识的类型或者方法在 SIL 级别视作 public,换句话说,表明这个属性在 SIL 当中可以被全局访问,但是在外部则无法访问。

这三个初始化很简单,第一个是将布尔值初始化为 falseinit() 方法。它建立了一个值为 0 的 Int8 常量(因为 Swift 内部不支持 Int1 的直接建立),然后通过内置的转换函数,将 Builtin.Int8 转换为 Builtin.Int1。其他两个一个是对内部的 _value 进行直接赋值初始化,另一个是对结构体本身进行赋值从而完成初始化。

BooleanLiteral 协议

这里有两个协议:_ExpressibleByBuiltinBooleanLiteralExpressibleByBooleanLiteral,它们是布尔值的字面量协议扩展,实现这两个协议之后就可以使用 truefalse 字面量或者 10 字面量直接赋值了。

注意:只有使用 _ExpressibleByBuiltinBooleanLiteral 协议的方法才能使用 10 字面量进行赋值的,这里的 10 字面量都会被自动转换为 Builtin.Int1 类型。

_ExpressibleByBuiltinBooleanLiteral 需要实现的是 public init(_builtinBooleanLiteral value: Builtin.Int1) 方法,ExpressibleByBooleanLiteral 需要实现的是 public init(booleanLiteral value: Bool) 方法,这两个方法都被标注为了 @_transparent 作为原始操作。

在目前 Swift 的规则当中,前面加上"_"(下划线)的协议、方法、变量等等都是只在内部有效的,也就是说它们需要设定为 internal 的权限。

我们可以在外面实现 ExpressibleByBooleanLiteral 协议来实现此功能,您可以打开 Playground,写入以下代码来体验一下:

struct TestBoolean: ExpressibleByBooleanLiteral {
  var value = 0

  init() { }

  init(booleanLiteral value: Bool) {
    self.value = value ? 1 : 0
  }
}

var test: TestBoolean = true
print(test.value)

这样您就可以看到,我们可以用布尔字面量给我们自定义的 TestBoolean 结构体赋值了。

获取内部逻辑值

这个部分主要是用以获取内置的实际值的,这是一个内部方法,它返回一个 Builtin.Int1 值。

@_transparent
public // COMPILER_INTRINSIC
func _getBuiltinLogicValue() -> Builtin.Int1 {
  return _value
}

如果我们尝试使用 true._getBuiltinLogicValue() 的话,会获得一个 <<<opaque type>>> 的输出,这是因为其返回值 Builtin.Int1 是一个内部值,Swift 是不允许我们通过这个方法来获取内部值的实际值的。

此外,还有一个与之对应的一个全局函数:_getBool(_ v: Builtin.Int1) -> Bool,它使用之前的 Bool(_ v: Builtin.Int1) 的初始化构造器,从而完成了 Builtin.Int1Bool 之间的转换。

举个例子:

let builtinBool = true._getBuiltinLogicValue() // 输出 <<<opaque type>>>
let getBool = _getBool(builtinBool) // 输出 true

CustomStringConvertible 协议

这是一个 Swift 中绝大多数类型都实现的协议,我们将在探寻 CompilerProtocols 文件时统一进行介绍。

布尔值实现此协议后,你就会发现它能够显示出其字面量的字符串,是 "true" 还是 "false"。

Equatable、Hashable 协议

这是一个 Swift 中大多数类型都实现的协议,我们将在探寻 CompilerProtocols 文件时统一进行介绍。

布尔值实现此协议后,你就会发现它可以使用 == 来判断两个布尔值是否相等,并且也可以使用 hashValue 来获取它的哈希值。

这里要注意的是,在比较相等的时候,Swift 使用了内置的 Builtin.cmp_eq_Int1 函数,来比较两个布尔值对应的 Builtin.Int1 是否相等,这个函数返回一个判断的 Builtin.Int1 值,随后再使用 Bool(_ v: Builtin.Int1) 初始化构造器来构造判等的布尔值。

LosslessStringConvertible 协议

这是一个在 Swift 3 中才出现的协议,它可以以无损耗、明确的方式来将字符串转换为对应的类型。简而言之,通过实现此类型,就可以实现将字符串转换为相应的类型。

让我们来看一看 Bool 类型当中的具体实现:

extension Bool : LosslessStringConvertible {
  public init?(_ description: String) {
    if description == "true" {
      self = true
    } else if description == "false" {
      self = false
    } else {
      return nil
    }
  }
}

可以看到,这里定义了一个可失败构造器,当我们输入了正确的字符串进行初始化的话,那么就能成功输出对应的结果,如果不合法的话,那么就抛出为空。这样,我们就可以像下面这样构造布尔值了:

let boolFromString = Bool("false") // 输出为 false

逻辑运算符

除此之外,布尔值源文件中还定义了三个逻辑运算符:&&|| 以及 !,即与、或、非。

这三种逻辑运算是比较基础的了,这里我们就不介绍它们的作用了,但是让我们来看一下它们的实现:

与运算

@_transparent
@inline(__always)
public static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows
    -> Bool{
return lhs ? try rhs() : false
}

可以看到,如果 lhs 为真,那么其结果直接就可以取 rhs() 的值即可;如果 lhs 为假,那么直接输出 false

  1. @inline 属性标识用于实现内联函数,它的有效的值为 __alwaysnever。这里表明这个函数总是编译成内联函数的形式。
  2. @autoclosure 做的事情就是把一句表达式自动地封装成一个闭包 (closure)。

或运算

@_transparent
@inline(__always)
public static func || (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows
    -> Bool {
  return lhs ? true : try rhs()
}

和与运算类似,如果 lhs 为真,那么直接输出 true;如果 lhs 为假,那么直接取 rhs() 的值即可。

非运算

@_transparent
public static prefix func ! (a: Bool) -> Bool {
  return Bool(Builtin.xor_Int1(a._value, true._value))
}

这里它使用了内置的 Builtin.xor_Int1 函数,它会将内置的 _value 值进行取反,然后输出,最后我们将这个函数的返回值转换为对应的 Bool 值。由于这里我们的默认布尔值是 false,因此这个函数的默认值将是其相反值,也就是 true

总结

在本文当中,我们介绍了以下几点内容:

  • Swift 中布尔值的基本定义
  • Builtin 的介绍
  • swift::getBuiltinType(ASTContext &Context, StringRef Name) 函数的部分内容
  • getAsInteger(unsigned Radix, T &Result) 函数作用
  • @_transparent@_fixed_layout@_versioned@inline(__always)@autoclosure 关键字
  • BooleanLiteral 协议、ExpressibleByBooleanLiteral 协议
  • 与、或、非运算

至此,这就是我们所看到的 Swift 布尔值的自举部分内容了,这是一个非常简单的类型,但是我们在其中也能看到很多精彩的内容。