首页 > 解决方案 > Swift 如何实现机器码级别的扩展?

问题描述

我现在正在搞乱 LLVM,我想到了一个问题:Swift 如何能够在二进制级别实现扩展?

在 Swift 中,可以提供一个简单地添加方法的基本扩展,但它们也可以提供符合另一种协议的扩展。然后将此类识别为具有所述协议作为父级,并且它们可以相互转换。这是如何实现的?

我知道 vtables,它们可以定义函数定义的位置等等,但据我所知,它们是固定长度的,对吗?Swift 是否能够通过其运行时库来实现此功能,或者是否将类型映射到较低级别的 LLVM,并且在定义新扩展时以某种方式操纵 vtables?

标签: swiftllvmextension-methods

解决方案


您正在寻找的答案在Swift ABI 文档中,特别是在TypeMetadata.rstTypeLayout.rst中。

Swift 使用称为见证表的 vtable来处理协议一致性。每种类型的每个协议都有一个见证表,并且见证表针对该协议所需的每个功能都有一个条目。

当我们有一个类型为存在的变量(即不是静态已知的)时,Swift 将该变量存储在一个称为存在容器的运行时结构中。TypeLayout.rst 描述了存在容器的格式:

不透明的存在容器

如果对协议或协议组合类型没有类约束,则存在容器必须容纳任意大小和对齐的值。它使用一个固定大小的缓冲区来执行此操作,该缓冲区大小为三个指针并且指针对齐。如果它的大小和对齐方式都小于或等于固定大小的缓冲区,则它直接包含该值,或者包含指向存在容器拥有的侧分配的指针。包含值的类型由其类型元数据记录标识,并且包括所有必需协议一致性的见证表。布局就像在以下 C 结构中声明的一样:

struct OpaqueExistentialContainer {
  void *fixedSizeBuffer[3];
  Metadata *type;
  WitnessTable *witnessTables[NUM_WITNESS_TABLES];
};

witnessTables数组包含每个我们的存在静态已知符合的协议的条目 - 因此,如果我们的变量具有 type P1 & P2,则存在将恰好包含两个见证表指针(存在容器特定于其协议约束,因此任何额外的协议具体类型符合被忽略)。只要我们有一个描述一个类型对协议的一致性的见证表,我们就可以创建一个存在容器,传递它,并使用见证表调用协议方法。

那么我们如何通过扩展为类型添加一致性呢?好吧,我们实际上不必修改类型本身的任何属性;我们只需要创建一个新的见证表。为了实现存在类型之间的动态转换,我们需要一些方法来注册与 Swift 运行时的一致性;这是通过在运行时知道查找它的二进制文件的指定部分中放置协议一致性记录来完成的:

协议一致性记录

协议一致性记录表明给定类型符合特定协议。协议一致性记录被发送到它们自己的部分,在需要时由 Swift 运行时扫描(例如,响应 swift_conformsToProtocol() 查询)。每个协议一致性记录包含:

  • 描述一致性协议的协议描述符,表示为相对于该字段的(可能是间接的)32 位偏移量。低位表示是否为间接偏移;第二个最低位保留供将来使用。

  • 对符合类型的引用,表示为相对于字段的 32 位偏移量。低两位表示符合类型的表示方式:

    • 0:对标称类型描述符的直接引用。
    • 1:对名义类型描述符的间接引用。
    • 2:保留供将来使用。
    • 3:对指向 Objective-C 类对象的指针的引用。
  • 提供对描述一致性本身的见证表的访问的见证表字段,表示为直接的 32 位相对偏移量。低两位表示见证表的表示方式:

    • 0:见证表字段是对见证表的引用。
    • 1:见证表字段是对见证表访问器函数的引用,以实现无条件一致性。
    • 2:见证表字段是对条件一致性的见证表访问器函数的引用。
    • 3:保留以备将来使用。
  • 保留供将来使用的 32 位值。


推荐阅读