swift - Swift 如何实现机器码级别的扩展?
问题描述
我现在正在搞乱 LLVM,我想到了一个问题:Swift 如何能够在二进制级别实现扩展?
在 Swift 中,可以提供一个简单地添加方法的基本扩展,但它们也可以提供符合另一种协议的扩展。然后将此类识别为具有所述协议作为父级,并且它们可以相互转换。这是如何实现的?
我知道 vtables,它们可以定义函数定义的位置等等,但据我所知,它们是固定长度的,对吗?Swift 是否能够通过其运行时库来实现此功能,或者是否将类型映射到较低级别的 LLVM,并且在定义新扩展时以某种方式操纵 vtables?
解决方案
您正在寻找的答案在Swift ABI 文档中,特别是在TypeMetadata.rst和TypeLayout.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 位值。
推荐阅读
- postgresql - 如何在 LIKE 搜索中显式类型转换 ENUM 类型?
- excel - 如何在未安装 Excel 的情况下使用 Powershell 递归地将文件夹中的所有 .xls 转换为 .xlsx
- google-bigquery - 如何部分过滤子集字符串?
- node.js - 作为 API 调用的一部分,您如何在数据库查询中正确传递变量?
- php - 当结果不应该是数组时,PHP函数返回数组
- powerbi - 在 PowerBI 中逐月比较服务订单数量时无法显示条件格式图标
- sql-server - SSIS 目标表被阻止。如何防止这种情况
- java - 如何在java受保护方法中测试局部变量
- go - 不声明类型的嵌套结构
- search - 如何在我的 Solr schema.xml 中表示子文档?