RFC-0061:可扩展联合

RFC-0061:可扩展联合体
状态已接受
领域
  • FIDL
说明

为了提供更多的表达方式来表达其形状可能需要随着时间不断演变的载荷,我们提议用可扩展的联合来代替当今存在的联合体。

作者
  • pascallouis@google.com
提交日期(年-月-日)2018-09-26
审核日期(年-月-日)2018-10-11

“供应夏威夷和阿拉斯加”

总结

为了提供更多方式来表达形状可能需要随着时间不断演变的载荷,我们提议用可扩展的联合替换现存的联合体

设计初衷

如今,联合无法随时间演进,我们甚至警告“通常,更改联合的定义会破坏二进制文件兼容性。”

目前有许多需要扩展的联合体,例如 fuchsia.modular/TriggerCondition(其中字段已弃用而不移除)或 fuchsia.modular/Interaction

稍后所述,还有许多联合体的当前表示法适合,因为它们在不久的将来不太可能演变。不过,同时保留 static unionsextensible unions 会导致不必要的复杂性,请参阅优缺点

设计

为了引入可扩展的联合,我们需要修改 FIDL 的多个部分:语言和 fidlc、JSON IR、有线格式以及所有语言绑定。我们还需要在不同的地方记录这项新功能。 我们会逐一讨论每项变更。

语言

从语法上讲,可扩展联合与静态联合看起来完全相同:

union MyExtensibleUnion {
    Type1 field1;
    Type2 field2;
     ...
    TypeN fieldN;
}

在后台,系统会为每个字段分配一个序数:这与每个字段的序数以及方法序数的自动分配方式相类似。

具体而言:

  • 序数使用与方法序数相同的算法进行计算详见此处),我们要将库名称“.”、可扩展并集名称“/”和成员名称串联起来,最后取 SHA256 值,最后用 0x7fffffff 串联掩码。
  • 序数为 uint32任何两个字段都不能声明同一序数,并且我们不允许使用 0。如果顺序冲突,应使用 [Selector] 属性提供备用名称(或成员重命名)。
  • 序数可能稀疏,也就是说,与表的工作原理不同,序数需要密集的序数。
  • 可扩展联合中不允许为 null 字段
  • 可扩展联合必须至少有一个成员

可扩展联合体可在当前可在该语言中使用的任何环境中使用。尤其是:

  • 结构体、表和可扩展联合体可以包含可扩展的联合体;
  • 可扩展联合体可以包含结构体、表和可扩展联合体;
  • 接口参数或返回可以是可扩展的联合;
  • 可扩展联合体可以为 null。

JSON IR

在下表中,我们将在每个联合字段声明“序数”中添加一个键。

电汇格式

在线上,可扩展并集由序数表示,用于区分选择(填充为 8 个字节),后跟提供方已知的各种成员的信封。具体而言,即:

  • uint32 标记,包含被编码成员的序数;
  • uint32 内边距,调整至 8 个字节;
  • 一个 uint32 num_bytes,用于存储信封中的字节数,始终为 8 的倍数,如果信封为 null,则必须为 0;
  • 用于存储信包中的句柄数量的 uint32 num_handles;如果信包为 null,则该值必须为 0;
  • 一个 uint64 数据指针,用于指示存在(或不存在)外行数据:
    • 当信包为 null 时,为 0
    • FIDL_ALLOC_PRESENT(或 UINTPTR_MAX):
  • 在解码以供使用时,如果信包为 null,则此 data 指针为 nullptr,否则为指向该信包的有效指针
  • 信封会为紧跟在内容后面的句柄预留存储空间。

可为 null 的可扩展联合组具有标记 0字节数设置为 0num_handles 设置为 0,以及数据指针为 FIDL_ALLOC_ABSENT,即0。实质上,一个 null 可扩展联合体是 24 个字节的 0 个字节。

语言绑定

可扩展联合与联合类似,只不过在读取联合时,还需要处理“未知”情况。理想情况下,大多数语言绑定将

union Name { Type1 field1; ...; TypeN fieldN; };

就像它们具有可扩展的联合一样,以便代码可以轻松地从一个切换到另一个,为未知情况提供模数支持,这仅在可扩展的联合情景中才有意义。

首先,我们建议不要在语言绑定中公开预留成员:虽然为了完整起见,这些成员存在于 JSON IR 中,但我们预计在语言绑定中公开这些成员并无用处。

实施策略

我们将分两步完成实施。

首先,我们将构建对可扩展联合体的支持:

  1. 使用不同的关键字 (xunion) 来区分静态联合和可扩展联合,从而以语言 (fidlc) 引入了该功能。
  2. 实现各种核心语言绑定(C、C++、Rust、Go、Dart)。 相应地扩展兼容性测试和其他测试。

其次,我们会将所有静态联合迁移到可扩展联合:

  1. 为静态联合生成序数,并将其放入 JSON IR 中。后端最初应忽略这些内容。

  2. 在读取路径上,具有两种读取联合模式的模式,就像它们是静态联合一样,以及它们是可扩展的联合(要做到这一点,需要序数)。根据事务消息标头中的标志进行选择。

  3. 更新写入路径以将联合编码为可扩展联合,并通过在事务消息标头中设置标记来指示尽可能多的联合体。

  4. 更新、部署和传播所有写入者后,移除软转换的静态联合处理和基架代码。

文档和示例

这至少需要提供以下位置的相关文档:

向后兼容性

可扩展联合体明确与“静态”联合体向后兼容。

性能

不使用时不会影响性能。 构建期间可忽略不计的性能影响。

安全性

对安全性没有任何影响。

测试

编译器中的单元测试、使用各种语言绑定进行编码/解码的单元测试,以及用于检查各种语言绑定的兼容性测试。

缺点、替代方案和未知情况

可扩展联合体的效率不及不可扩展联合体。此外,不可扩展的联合不能通过语言中的其他方式来表示。 因此,我们提议将这两项功能并排使用。

但是,我们可以决定只应存在可扩展的联合,而不再使用当前定义的联合。这有悖于 Fuchsia 中的多个位置:联合代表性能关键型消息,而对扩展的期望值几乎没有提升,例如 fuchsia.io/NodeInfofuchsia.net/IpAddress

保留静态联合的优缺点

优点

  • 与联合相比,可扩展联合会产生 8 字节的费用(信封大小和句柄数量)。此外,可扩展联合的数据始终在外存储(即,为数据指针额外存储 8 个字节),而对于可为 null 的联合体的数据,仅在外存储。
  • 由于联合的编码,因此无法使用 FIDL 中的其他基元来表达它们。因此,如果将它们从语言中移除,某些类别的消息将无法再紧凑而高效地表示。
  • 在某些情况下,联合可以高效表示,但具体取决于其用途;不过,这种例外情况并非常态。可以在不使用联合的情况下重写的一个示例是,fuchsia.net.stack/InterfaceAddressChangeEvent 仅用于 fuchsia.net.stack/InterfaceAddressChange,其中可以直接写入 InterfaceAddress,并使用 enum 来指示是添加或移除了该接口。

缺点

  • 同时保持静态联合和可扩展联合都会增加编译器、JSON IR、所有后端以及编码/解码的复杂性。优势微乎其微:在 FIDL 编码最初并不是特别高效的,大小差异微乎其微。 此外,还可根据需要对可扩展联合进行解码。
  • 作为增益最小的示例,以下是对 Fuchsia.io/NodeInfo 的分析:
    • 如今,NodeInfo 具有 6 个选项:服务(大小 1)、文件(大小 4)、目录(大小 1)、管道(大小 4)、vmofile(大小 24)、设备(大小 4)。
    • 因此,NodeInfo 的总大小始终为 32 个字节,即标记 + 选项大小的最大值 = 8 + 24 = 32。
    • 使用可扩展联合时,NodeInfo 大小将取决于编码的选项。“tax”始终为 16 个字节(而非 8),因此相应的大小为:service = 24、file = 24、directory = 24、pipe = 24、vmofile = 40、device = 24。
    • 因此,在所有情况下,我们都会缩减 8 个字节,但在 vmofile 中额外添加了 8 个字节的情况除外。
  • 同时拥有静态联合和可扩展联合的语言的复杂性也令人担忧。我们预计库创建者会切换使用一种与另一种,这是因为选择可扩展的联合是一种更安全的长期选择,而且费用非常低。

总之,我们决定用可扩展的联合取代静态联合。

标记与序数

我们使用序数来表示分配给字段的内部数值,即通过哈希处理计算的值。我们使用标记来表示绑定中变体的表示法:在 Go 中,这可能是类型为 alias 的常量,在 Dart 中可以是 enum

fidlc 编译器仅处理序数。开发者很可能只处理代码。绑定可提供从高级标记到低级内部序数的转换。

没有空的可扩展联合体

在设计阶段,我们曾考虑过创建可扩展的联合体为空。不过,我们最终选择不允许这样做:选择具有单个变体(例如空结构体)的可为 null 的可扩展联合可以清楚地对 intent 建模。这也可以避免为可扩展的联合使用两个“单位”值,即 null 值和空值。

早期技术和参考资料

  • 协议缓冲区具有 oneof
  • FlatBuffer 的联合不可扩展,除非在特殊情况下。