Swift如何实现机器码级别的扩展?

How does Swift achieve extensions at the machine code level?

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

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

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

您正在寻找的答案在 Swift ABI documentation, specifically in TypeMetadata.rst and TypeLayout.rst.

Swift 使用 vtables 称为 witness tables 来处理协议一致性。每种类型都有一个见证人 table 每个它符合的每个协议,一个见证人 table 每个协议要求的每个功能都有一个条目。

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

Opaque Existential Containers

If there is no class constraint on a protocol or protocol composition type, the existential container has to accommodate a value of arbitrary size and alignment. It does this using a fixed-size buffer, which is three pointers in size and pointer-aligned. This either directly contains the value, if its size and alignment are both less than or equal to the fixed-size buffer's, or contains a pointer to a side allocation owned by the existential container. The type of the contained value is identified by its type metadata record, and witness tables for all of the required protocol conformances are included. The layout is as if declared in the following C struct:

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

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

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

Protocol Conformance Records

A protocol conformance record states that a given type conforms to a particular protocol. Protocol conformance records are emitted into their own section, which is scanned by the Swift runtime when needed (e.g., in response to a swift_conformsToProtocol() query). Each protocol conformance record contains:

  • The protocol descriptor describing the protocol of the conformance, represented as an (possibly indirect) 32-bit offset relative to the field. The low bit indicates whether it is an indirect offset; the second lowest bit is reserved for future use.

  • A reference to the conforming type, represented as a 32-bit offset relative to the field. The lower two bits indicate how the conforming type is represented:

    • 0: A direct reference to a nominal type descriptor.
    • 1: An indirect reference to a nominal type descriptor.
    • 2: Reserved for future use.
    • 3: A reference to a pointer to an Objective-C class object.
  • The witness table field that provides access to the witness table describing the conformance itself, represented as a direct 32-bit relative offset. The lower two bits indicate how the witness table is represented:

    • 0: The witness table field is a reference to a witness table.
    • 1: The witness table field is a reference to a witness table accessor function for an unconditional conformance.
    • 2: The witness table field is a reference to a witness table accessor function for a conditional conformance.
    • 3: Reserved for future use.
  • A 32-bit value reserved for future use.