RFC-0097:FIDL 工具链

RFC-0097:FIDL 工具链
状态已接受
领域
  • FIDL
说明

对规范 FIDL 工具链的说明。

Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-04-27
审核日期(年-月-日)2021-05-26

摘要

我们介绍了 FIDL 工具链需要满足的要求,并提供了 分解该问题的方向。

尽管具体的实施计划不在此 RFC 规范的讨论范围内, 工具(例如 fidlcfidlgen_gobanjo),以及 Fuchsia 源代码树(例如 fidl_library.gni) 不断改进,以满足此处列出的要求。

此外,Fucsia 源代码树之外的 FIDL 工具链应 符合此处列出的要求(此 RFC 除了 Fuchsia,因此我们无法强制要求您遵守政策,但肯定可以强有力地 。)

术语

在开始之前,我们先定义几个术语。FIDL 的简化视图 工具链可以总结如下:

简化的工具链

FIDL 语言体现在 fidlc,代表前端编译器(简称前端)。 所有语言验证操作都在此处进行。

前端生成中间表示法(称为 JSON IR) (针对编译的每个 FIDL 库)。 表示法不一定需要表示为 JSON 文件。

之后,一个或多个后端会处理 JSON IR,以便 生成输出。请注意,从 FIDL 工具链的角度来看, JSON IR 的使用方是一个后端。

生成的输出最常见的是采用目标语言(例如 C++、 或 Rust),这使得操作类型、与协议交互、 以及使用常量这类后端称为 FIDL 绑定1,而它们生成的代码应遵循绑定 规范。我们通常使用 简写 fidlgen(或 fidlgen_<suffix>,例如 fidlgen_rustfidlgen_dart)来指代生成 FIDL 绑定的后端。我们指的是 网域对象,即目标语言中使用的类和类型集合 表示 FIDL 类型。例如,FIDL 枚举 fuchsia.fonts/Slant 在 C++(如 enum class)或 Go(如 type Slant uint32)。

还有很多其他后端,每个后端都有自己的需求 特性。例如:fidldoc 会生成 FIDL 文档,例如 fuchsia.fonts 页面; fidl_api_summarize 会生成 FIDL 库;fidlcat 使用 JSON IR 来提供运行时 内省。从 FIDL 工具链的角度来看,fidlcat 工具 后端,尽管这只是此工具实际作用的很小一部分。

设计初衷

FIDL 一直在发展。其工具链的可表达性已经过测试。 它无法充分满足新的要求。新的展开式 工具链。

我们首先介绍其中一些新要求,然后遵循 同时又有方法支持所有这些技术。

完整节目视图

如今,FIDL 工具链假定后端按库运行 因此只需要该库的 JSON IR 即可运行。

后端越来越需要访问多个 JSON IR 来实现 满足自己的需求

例如,fidldoc 需要所有库的 JSON IR 以生成全局索引。fidlcat属于 但需要查看所有库才能正常运行。measure-tape 需要可通过目标服务以传递方式访问的库的 JSON IR 可为其生成测量纸带的类型。

渗透元数据

某些后端需要有关库的特殊元数据才能运行。 通常需要从子节点开始以迭代方式计算这些元数据 库依赖项树(“基础库”),其中包含元数据 渗透到根目录(正在编译的库)。

例如,[fidlgen_rust] 想知道 某个类型是否可能包含浮点数,以确定 trait 可以安全地派生。不包含任何浮动内容的 struct 点号可以包含 Eqstrict union,其中没有任何变体 包含任何浮点数,可以有 Eq,但有 flexible table 目前不含浮点字段的该字段不能有 Eq,因为 否则就会违反源代码兼容性规则。

另一个示例来自 fidlgen_cpp,它生成了非所有权网域 对象的操作。如果这些网域对象的内嵌部分是 在这里,再次计算 我们称之为“内嵌资源”的元数据需要以迭代方式计算 从叶到根的值。

最近,在讨论为库生成 ABI 指纹的新后端时, 你有 来回切换 该功能应位于何处。目前的思路是 出于实际原因,在 fidlc 编译器中配置 LLM 功能,但答案是 不满意。

我们观察到的是,需要元数据过滤的特征 通话暂停、解决(通常 黑客入侵), 或转换为新的编译器功能,通常会强制泛化 (例如上面讨论的 ABI 指纹)。

此外,由于 Fuchsia FIDL 团队能够更改编译器 因此我们在这方面相对于第三方更为有利因此, 可以说,工具的状态有损我们的开源原则, 力求让所有后端都位于关卡游戏领域。

每个目标语言和每个库后端选择

使用用于描述内核 API 的 FIDL 语言,以及 驱动程序 SDK 正在开发中 FIDL 则越来越普及。

然而,如今,“FIDL”之间的工具链 语言”和“后端”处理特定库所需的资源。当 target 需要库 fuchsia.fonts 的 Rust 代码,我们调用 fidlgen_rust

这种方法过于简单,并且无法描述特定库需要 专用后端例如,library zx;kazoo 处理。 这种针对每个目标、每个库的 fidlgen 选择会产生进一步的影响。乘坐 枚举 zx/clock,则我们希望 kazoo 有一天能生成 目前是手写体 zx_clock_t typedef 以及各种将枚举成员具体化的 #definefuchsia.fonts 库是否应依赖于 zx/clock,这又意味着 fidlgen_cpp 需要知道 API 协定,才能生成 绑定代码正确桥接2其代码生成和 kazoo 的代码生成。

每个平台一个库

如今,我们并不认为 FIDL 库的多种定义 使用相同名称您可以定义多个 库 fuchsia.confusing 位于源代码树的不同位置,并使用所有 不同的库

更明智的做法是利用平台标识符 概念, Fuchsia 源代码树默认为 fuchsia。然后,我们可以保证并执行 一个名称相似的库,不存在两个定义。

秉承这一限制,我们将“平台”称为 FIDL 集合 共享相同平台标识符的库

不延迟验证

目前,不允许后端对它们的 FIDL 库做出选择性选择 成功与否后端应处理任何有效的 JSON IR。此限制意味着我们不会在 后端。可以在后端添加验证, 尚未实现的 FIDL 功能;另一个验证示例是 fidldoc 中的文档注释的有效性以及拒绝生成引用 文档。(在这两个示例中,优雅降级是正常的。)

允许后期验证会造成远距离损坏,造成令人不快的中断 (例如 https://fxbug.dev/42144169):在 FIDL 的世界里, 库作为 SDK 工件提供,并集成到下游 那么运行后端的开发者可能不同于 FIDL 库作者。因此,向开发者提供警告或错误 如果 FIDL 可以更正内容,则使用 FIDL 库 库作者最好是令人失望的,最坏的是,使用 FIDL 库。

因此,禁止延迟验证的政策可以保持良好的运行状况 fidlc 编译器需要“验证所有内容”以及后端 “全面支持一切”这在很大程度上避免了 相距较远的破损失败,以位置为代价(“在 后端)。

限制对来源的访问权限

没有仔细考虑长期后果,于是就逐步允许 JSON IR,用于复制其来源的一部分 FIDL 源。例如, 添加了复杂表达式,我们公开了解析的 价值 以允许后端发出常量,同时保留表达式本身 (文本)。虽然使用表达式文本有助于生成 添加有意义的注释,这打开了降低隐私保护的大门 的 SDK 发布商(发布 FIDL 工件的平台), 可以选择是否提供来源。

不难想象未来,这条途径会带来更多 FIDL 来源 最终在 IR 中,这不是我们期望的结果:这两者都是重复的, 并可能导致违反隐私边界。

我们旨在设计 FIDL 工具链,使其包含的源文件数量不超过 。对于确实需要来源访问权限的罕见后端(例如 fidl-lsp), 我们需要依赖对 span 的引用。请参阅设计中的详细信息 部分

扩缩编译

为简单起见,fidlc 编译器最初设计为 仅限源文件(即 .fidl 文件)。当库有依赖项时 编译库需要编译其所有依赖项, 传递。

例如,在编译 fuchsia.fonts 库时,我们还必须编译 fuchsia.memfuchsia.intl 库,以及以传递方式。这个 则意味着目前的编译效率完全低下。核心库,例如 fuchsia.mem 会被多次重新编译。这种架构效率低下 从未出现过问题:SDK 中的 FIDL 源代码只有超过 64,000 个 LoC 如今,由于传递依赖项相对较浅,因此效率低下 没有强烈的感受

然而,在考虑我们的“理想”时,FIDL 工具链, 编译器设计方面的标准做法通常,编译器 和生成输出(例如 x86 汇编)。如 越来越多,编译器要满足额外的要求 对工作进行某种分区, 输入不需要重新编译整个代码库。

javac 编译器为例:如果您更改了 某个文件 SomeCode.java 中存在 for 循环,这样会出乎意料 重新编译数千个文件以便能够再次运行该程序。相反, 只重新编译这个文件,并且可以重复利用 源文件(作为 .class 文件)。

为了成功划分工作,一种标准方法是定义一个 编译单元(例如 FIDL 的库),并生成一个中间结果 (例如 JSON IR),以确保编译过程的输入都是源 以及直接依赖项的中间结果。这样就可以限制 到依赖项链的总编译时间(假设无限并行) 也就是用时最长的这也简化了构建规则, Jour

设计

我们将设计分为三个部分:

  1. 首先,提供为设计决策提供参考的指导原则; 并固定所采取的方法和路径;
  2. 作为规范 FIDL 工具链的说明, 示例:如何分解构建 FIDL 以满足所有要求 描述;
  3. 最后是 Fuchsia FIDL 中的一些特定清理 团队将使核心工具与此 RFC 的指导保持一致。

指导原则

IR 应能够适应常见后端

虽然应尽量使用复杂的后端(例如,整个程序) 视图),则必须确保 IR 的设计允许常见的后端 可以仅通过处理正在处理的库的单个 IR 来构建。

经验表明,大多数后端都比较简单。满足简单的餐饮 用例(而不是专家用例)确保我们能够 尽可能简化 IR,并在此过程中尽最大努力 确保后端生态系统蓬勃发展。

为了举例说明这一原则,我们来考虑“字体形状”在 fidlc。可以改为将其移至 过滤后端。但是,这会强制所有后端 生成目标代码 - 主要用例 - 依赖于 IR 和 “输入形状”后端。

IR 应尽可能小

力求做到极简是实现灵活适应环境的重要对策 因为“将所有内容”都很容易,也很有诱惑力。 并考虑所做的工作。

为了举例说明这一原则,我们来考虑计算 “声明顺序”在 fidlc 中。只有少数后端依赖于此订单 (C 家族,甚至更少每天), 。它还会把水浸泥,弄清楚为什么需要这样的命令。 往往会造成混淆这也缺乏灵活性,因为后端需要 独立于核心编译器 - 这一直是我们继续开发 支持递归类型

IR 不能包含来源

IR 不应该包含任何其他来源 容纳通用后端(例如名称)。IR 可以提供来源 span 相关参考信息。源 span 引用是一个三元组:

(filename, start position, end position)

其中位置是元组 (line number, character number)

后端不应依赖于对来源的访问权限。当后端必须具有 访问源代码才能运行(例如 fidl-lsp),还必须明确说明这一点 并在无法访问源代码的情况下正常失败。

在选择此分解时,我们明确选择提供 SDK, 发布商(发布 FIDL 工件的用户)可以选择添加源代码 是否 FIDL。目前,这一选择并非完全由他们自己选择,因为 来源最终在 IR 中。

除了名称之外,IR 中关于来源的一个重要部分是 文档注释。这些 注释是按照规范规定,旨在成为 API 的一部分,即 FIDL 库 作者明确选择公开这些评论。此外,大多数 这些文档注释(例如,在生成的 因此属于容易接受的常见限制, 后端原则。这些文档注释 不会作为原始源出现在注释中,而是会经过一定程度的预处理 (前导缩进、/// 和空格被去掉)。短期内 ,我们打算 以便将来进一步处理文档评论。

后端受到平等对待

FIDL 语言、其作为 fidlc 编译器的实现,以及 对中间表示法的定义, 具有包容性的后端生态系统,其中所有后端无论是构建于 无论是否有 Fuchsia 项目,我们都有相同的人等来源。

在选择这一分界线时,我们明确地做出了如下选择: 对 Fuchsia FIDL 团队拥有的后端的短期需求。 注重 FIDL 生态系统的长期可行性。

后端不易出错

处理有效的 IR 时,后端必须成功。否则,后端可能会发生故障 在其环境中遇到问题(例如文件系统访问错误)或 IR 无效。如果后端无法处理符合 IR 架构的 IR,就会 不得因错误而失败。

在选择这一分界线时,我们明确地强制所有验证 发生在前端,即验证必须提升为 FIDL 语言 限制。这一点很重要,原因有两个:

  1. 此规则的一个推论是,给定一个有效的 IR,所有后端都兼容 可与此 IR 搭配使用。这意味着,作为 SDK 发布商 成功编译 FIDL 库可确保使用这些库 适用于使用某个 FIDL 工具链版本的所有使用方的库 与发布商使用的代码兼容。
  2. 从语言设计的角度来看,这项非常严格的要求 有利的强制函数,使语言设计符合需求 多个后端。例如,谨慎的后端所有者要求对 出于某种原因会引发此问题(fidl-dev@fuchsia.dev 或通过 RFC) 发送给 Fuchsia FIDL 团队,以便将其纳入这一语言 规范这可能会改进语言, 或可能对后端进行重新设计,以更好地与 FIDL 工具链原则。

为了演示这一原则,请考虑 Rust 中的特征派生:Eq 特征 无法针对包含浮点数的类型进行派生。应该是 诱使向 FIDL 中的类型添加 @for_rust_has_floats 属性,其中包含 浮点数(float32float64),然后利用 fidlgen_rust 属性,以有条件地发出 Eq trait,以及 验证属性是否正确使用(与 价值资源 区分)。但是 这种诱惑违反了原则,因为它暗示 fidlgen_rust 是 易犯错误。您不可要在 fidlc 中验证此类小众属性 因为这导致 FIDL 因目标语言繁多而变得复杂 具体顾虑。3

规范 FIDL 工具链

规范的 FIDL 工具链以库分解为中心, 将有两种构建节点

渗透构建节点

系统为渗透节点提供了库和对象文件的来源, 将库的依赖项直接链接到该工具,并生成最终结果, 目标文件。

渗透节点

例如,目前大多数 fidlgen 后端都遵循此模式:它们的来源 是 JSON IR,其最终结果是生成的代码。他们没有被抚养人 对象文件 (DOF),也不会生成目标对象文件 (TOF)。

另一个例子是计划推出的ABI 数字“指纹”收集 一种工具,它需要计算类型的结构属性。此工具将 使用 JSON IR(源),并会同时生成 ABI 摘要(最终结果)、 和附带的目标对象文件 (TOF)。在对一个 具有依赖项,它将使用这些库的 TOF,即其 DOF, 以及 JSON IR 以生成下一个最终结果。很可能是 而 TOF 的区别只是格式不同,其中一个应当供 其他则由工具解析。

完整视图 build 节点

系统会提供整个视图节点的来源,包括所有可传递访问的节点 依赖于被调用的工具,并生成最终结果。

整个视图节点

例如,measure-tape 需要所有传递式的 IR 定义要编译的类型所需的可访问库,以及 可以自然地表示为整个视图节点。目前,fidlc 节点以 整个视图节点,因为它需要访问所有源才能运行(请参阅扩缩 编译)。fidlcatfidldoc 需要一个依赖于整个紫红色的完整视图 平台

虽然整个视图节点的效率不如渗透 节点,我们可能不希望重新调整所有工具的结构,使其以渗透方式运作, 而是选择将某种复杂性引入构建系统。

在 Fuchsia 源代码树 build 中,我们会生成一个 all_fidl_json.txt 文件。 了解对整个视图节点的更明确要求后,我们就可以 构建此汇总。例如,通过按 平台,为每个库记录其来源 JSON IR、 和直接依赖项,我们可以轻松利用这种聚合来快速 生成全视图工具所需的输入。该汇总值为 fidl-lspfidlbolt 等开发者工具。

工具选择

给定构建节点中的工具选择应取决于目标(例如“生成 低级 c++ 代码”)以及正在编译的库(例如“library zx”)。 我们定义一个总函数,它接受一个元组(目标生成、库)并返回 一种工具(例如 kazoofidlgen_cpp),作为 工具链。

例如,在 Fuchsia 源代码树中,我们预期会用到以下配置:

(*, library zx) → kazoo
(low_level_cpp, not library zx) → fidlgen_llcpp
(high_level_cpp, not library zx) → fidlgen_hlcpp
(rust, not library zx) → fidlgen_rust
(docs, *) → fidldoc

借助统一 C++ 绑定, 此配置将更改为:

(*, library zx) → kazoo
(cpp, not library zx) → fidlgen_cpp
(rust, not library zx) → fidlgen_rust
(docs, *) → fidldoc

对增量编译的影响

在考虑增量编译时,即追求尽可能减少 通过结合现有的已编译系统来应对源代码变更, 包含新编译的工件的工件, 大相径庭

一般而言,当一个或多个节点位于编译图中时, 其来源(也称为“源代码集”)的更改。

渗透节点的源代码集比整个视图节点小得多, 源集是直接“源”和目标对象文件 (TOF)。说到这里, 其渗透行为(如果利用)会将源更改传播到 TOF 更改,进而更改依赖性渗漏的源集 节点。例如,假设 fidlgen_rust 后端经过增强, 也会生成 TOF fuchsia.some.library.fidlgen_rust.tof。如果某个库 及其 TOF 变化,那么所有依赖库也需要 从而对 fidlgen_rust 后端执行更多调用(并且 依此类推)。

与渗透节点相比,整个视图节点的源代码集要广得多。 完整视图节点分为两大类,每个依据 传递依赖项(考虑 measure-tape)以及依赖于全部依赖项的依赖项 库(例如 fidldoc)。因此,任何变化都可能 会导致需要调用这些节点。

全视图节点的增量编译成本是双倍不便, 节点需要更频繁地运行,而且它们能完成更多工作, 来源。不经意间,有人说 编程”任何需要完整视图的后端都可以发展为以 作为一个渗透节点对考虑这种进化的健康压力, 通常都会带来很多复杂性和维护负担, 提升增量编译的速度优势。 无法承受。

除了工具链本身的增量编译开销之外, 考虑对下游的影响。由于大部分工具链都生成源代码 (例如 C++、Rust、Dart),它往往更接近整个 build 的根 以便指示对工具链输出所做的任何更改(例如,对 将对下游编译(例如 所有代码直接或间接依赖于生成的 C++ 头文件)。 因此,您应尽量减少对生成的源代码的更改。对于 对生成器的输出进行规范化(避免对 无意义的空白字符),或将输出与缓存的 以避免使用相同内容覆盖内容,从而避免 仅更改时间戳(有关示例,请参阅 GN 输出 详情)。

消除旧技术债务,避免债务增加

我们将按照此处所述的原则移动 C 绑定和 编码表生成(共 fidlc 代)。将这两代词嵌入 由于历史构建复杂问题,我们已完成核心编译器。

我们还计划删除“声明顺序”而不是推送任何 下至特定后端的特殊排序。

扩缩编译中所述,FIDL 编译器 fidlc 将演变,以仅要求直接输出 依赖项(可能是 JSON IR 本身),而不是 传递依赖项

最后,我们将避免积累更多技术债务,而是专注于 使我们的工作与此处所述的方向保持一致。例如,下一个 ABI 指纹识别的后端,将是一个渗透后端 而不是嵌入到核心编译器中。

前身

与 C++ 编译进行比较/对比:C++ 编译器通常会接受一个 C++ 源文件,并生成一个对象文件。在项目组建结束时 阶段叫做“linking”,链接器会将所有对象文件合并成一个二进制文件。 这种方法之所以有效,是因为在单独编译一个 C++ 源文件时, 编译器会在其他 C++ 源代码中看到有关外部函数的函数声明 通过使用文件头来指明当前文件所依赖的文件。同样, 我们当前的 JSON IR 提供了有关外部库类型的基本信息, 类似于函数声明

但是,在进行更深层优化时,此 C++ 编译模型 需要:当编译器只能查看声明时, 保守函数的实际行为(例如,它是否总是 终止?它会改变指针 X 吗?还是会保留指针 Y Esc?)。同样,在 FIDL 中,我们的代码生成后端或许能够 如果它能够更深入地了解 引用的外来类型。对于资源和源代码兼容性, 我们的要求导致了后端无法生成正确的 除非它们知道所有引用的外部类型的资源性。

为了解决这个问题,在 C++ 中, 向对象文件中注入越来越多的辅助数据。例如, GCC 和 Clang 开发了他们自己的可序列化 IR 格式, 这些 C++ 函数的行为方式,并将这些函数与相应的汇编函数打包在一起。 链接器会同时使用汇编和 IR,并生成更好的代码 (称为“链接时间优化”)。在 FIDL 中,因为各种后端可能需要 不同的知识,那么将外部模型与 “辅助数据”从“对象文件”提取,即生成特定于后端的文件 主 JSON IR 旁边的 Sidecar。事实上,资源问题 许多后端所需要的属性但在未来,例如有限责任公司 理想情况下,还希望了解某个类型是否以传递方式包含不符合规范的 对象(同样地, 解码);Rust 想要确定某个类型是否以传递方式包含 浮点数在更多情况下派生 Eq(尽管编译器保证 (避免源代码兼容性问题)。

文档

此 RFC 是改进 FIDL 工具链文档的基础, 并且建议工具链作者正确记录他们 提供。

实现

如上所述。

性能

对性能没有影响,此 RFC 描述了要求和问题 这是已经实现的,虽然没有那么彻底。

工效学设计

不适用。

向后兼容性

不适用。

安全注意事项

无安全注意事项。

隐私注意事项

由于从 IR 中更清晰地分离了来源,因此可以更好地保护隐私。

测试

对工具链进行标准测试。

缺点、替代方案和未知问题

如文中所述。


  1. 从技术上讲,我们将 FIDL 绑定称为代码生成工具, 支持使用生成的代码所需的运行时库,以及 生成的代码

  2. C 族 Fillgen 不希望生成自己的域对象 针对 zx/clock,改为选择 #include 生成的标头 kazoo。同样,Rust fidlgen 会导入 zx 绑定 由 kazoo 生成,而不是根据 使用 zx 库定义。

  3. 目前,我们无法证明添加 @has_floats 属性(或 has_float 修饰符)更改为 FIDL,因为唯一的用例是 fidlgen_rust,即使是在那里,也没什么大问题。如果这些 (例如,其他几个后端具有类似的 PartialEq/Eq 个问题),这可能是合理的。