OCaml 编译器 - 2021年11月至2022年2月
我很高兴发布第五期“OCaml 编译器开发新闻通讯”。您可以使用标签 compiler-newsletter 找到所有期数。
注意:新闻通讯的内容绝非详尽无遗,只有少数编译器维护者和贡献者有时间撰写内容,这完全可以理解。
当然,欢迎随时发表评论或提问!
如果您一直在从事 OCaml 编译器的工作并希望发表意见,请随时在此主题中发帖!如果您希望我在下次准备新闻通讯时与您联系(未来某个随机时间点),请通过 Discuss 消息或电子邮件 (gabriel.scherer at gmail) 告知我。
背景
上期(2021年10月)新闻通讯 对应于合并多核 OCaml 实现和“顺序冻结”(为方便多核合并而冻结非多核相关更改)之前的最后一个开发周期。
从那时起,多核团队当然做了大量工作(参见 2022 年 1 月的大型多核新闻通讯)。上游开发速度不同寻常:非多核活动比以前少(4.14 版本的最后一分钟更改,以及并行推进的长期项目),有相当数量的工作清理了多核合并破坏的内容,涌入了新的贡献者,也有一些非新贡献者,他们在多核运行时方面仍然是完全的初学者。
事情正在按合理的节奏进行,我们预计将在某个时间点发布 5.0 版本 :-)
个人报告
@gasche Gabriel Scherer
形状
在 #10718 中,@voodoos Ulysse Gérard、@trefis Thomas Refis 和 @lpw25 Leo White 提出了“形状”,这是一种关于 OCaml 模块的新静态信息形式,它将由 OCaml 编译器计算和存储,以帮助其他工具,特别是 Merlin,处理 OCaml 名称/定义。(这完全是他们的工作,不是我的!)这项工作已合并到 4.14 开发版本中。
合并后,@kit-ty-kate Kate Deplaix 的 opam 范围测试揭示了一些函数式代码(特别是 Irmin)的性能问题。编译器中的形状计算会在计算时间和/或生成的构建工件大小方面出现爆炸。
“形状”的核心机制是 lambda 表达式的求值器。在 #10825 中,我(在 @Edkohibs Nathanaëlle Courant 的帮助下)开发了一个更高效的求值器,使用了相对高级的机制(强大的按需规约),并且在实践中取得了良好的效果——不再有已知的计算时间或大小爆炸案例。在不同的设计和实现选择之间进行了大量的来回讨论,Ulysse 的额外测试提供了很大的帮助!最后,我们与 Ulysse、Thomas、@Octachron Florian Angeletti 和 Nathanaëlle 进行了面对面的审查会议,这非常有趣,尤其是在如今面对面活动较少的情况下。
GADT 和模式泛化
在 #1097 中,@dongyan 报告了由于多态性(泛化)和 GADT 存在类型在模式匹配类型推断中的交互作用导致的 OCaml 类型检查器中的一个健全性错误。我们分析了这个问题,我提出了一种对类型规则的限制,以拒绝不安全的示例;@lpw25 Leo White 进一步完善了该提议。我尝试自己实现修复/限制,但这在不熟悉 OCaml 代码库的情况下并不容易。Jacques Garrigue 在 #10907 中提出了完整的实现,该实现现已合并。(这仅实现了我的初始标准,而不是 Leo 的改进,后者在当前模式类型推断实现中更难实现。)
初学者级多核黑客攻击
如今,OCaml 维护人员被温和地鼓励参与多核相关的合并后任务,而不是懈怠地处理很酷的优化或类型系统错误。(上面这两个任务的借口是它们帮助准备了 4.14 版本的发布。)但大多数人在几个月前对多核运行时一无所知,所以每个人都是新手!
我在阅读代码时发现了小的重构或小的错误修复,并完成了三个较大的部分
-
#10887:我继续与 @xavierleroy 合作开发 Domain.DLS 接口,以允许 OCaml 库存储每个域的全局状态;现在可以创建每个域的状态,并在 Domain.spawn 中“继承”(子状态是从父状态计算的)。这是用可分割随机数生成器替换 Random 实现的必要构建块,Xavier 最终在 #10742 中按照计划使用 LXM 完成了此操作。
-
#10971:@sabine Sabine Schmaltz 提出的这个问题正在讨论当用户请求更大的次要堆或更多域时如何更改用于次要堆的保留内存地址空间的大小。(每个域都有自己的次要堆,但它们是连续的,以便快速进行
Is_young
检查。)Sabine 和我正在开发实现。我发送了各种准备 PR,例如 #10974(更改计算唯一域标识符的机制,该机制依赖于固定的 Max_domains 限制)。 -
gc 统计信息(#11008、#11047):计算 GC 统计信息的代码需要一些改进,它在多核运行时发生了重大变化,但也试图保留先前 GC 公开的接口,某些方面略有错误。我从 Max_domains 的角度开始阅读代码(它在一个固定大小的数组中存储每个域的统计信息),但最终在 @Engil Enguerrand Decorne 的帮助下完成了这项工作。
@xavierleroy Xavier Leroy
2022 年 1 月 10 日,我有幸在拉取请求 #10831 上按下“合并”按钮,从而将多核 OCaml 引入 OCaml“主干”并诞生了 OCaml 5。
在此光荣时刻之前和之后,多核 OCaml 开发团队、其他审阅者和我本人一直在花费大量时间研究和审查多核 OCaml 源代码,审查大型拉取请求,并修复合并后仍然存在的问题。我还花费了大量时间重新设计我们的 Jenkins CI 系统以处理 OCaml 5 并相应地调整测试套件。
向 OCaml 5 的过渡也是一个绝佳的机会,可以删除长期过时的功能并简化代码库。例如,在 #10898 中,我能够删除大量不再相关的信号处理代码,因为信号处理程序不再在收到信号时立即运行,并且栈溢出由 ocamlopt 生成的代码显式管理。另一个例子是 #10935,它弃用了 Thread.exit
函数,并提供了一种更简单、基于异常的提前线程终止机制。
最后但并非最不重要的是,我终于能够在我的基于可爱的 LXM 伪随机数生成器的 Random 模块重新实现(#10742)之上合并,这要感谢 @gasche 在域本地状态方面的工作。
@Octachron Florian Angeletti
为形状基准测试编译时间和编译工件大小
如 @gasche 所述,OCaml 4.14 引入了一种称为 shape
的新型元数据。形状元数据的计算在编译时间方面会产生一些成本。在最初的审查中,我完全低估了这种成本,这导致了 irmin 报告的编译时间激增。当需要修复此错误时,我希望更全面地了解形状计算对 opam 存储库的重要部分的影响。因此,我测量了使用和不使用形状计算的 1000 多个 opam 包(opam 存储库中具有最多反向依赖项的一半)的编译时间和编译工件大小。幸运的是,这项性能测量活动得出的结论是,对于 90% 的源文件,编译时间的增加小于 10%
| 百分位数 | 相对编译时间增加 | |---|------------------------| | 1% | -9% | | 10% | 0% | | 25% | 0% | | 50% | 0% | | 75% | +4% | | 90% | +10% | | 99% | +20% | | 99.9% | +32% | | 99.99% | +46% |
新时代文档标签
在多核 OCaml 中,OCaml 库需要记录它们在多核环境中使用的安全性,特别是如果它们使用某些全局状态。为了尽可能轻松地记录这一点,我已经开始在 #10983 中为多核安全性实现新的 ocamldoc 标签。但是,odoc 的开发者并不热衷于添加更多标签,并建议此信息应通过可以提升到文档中的属性来传达。@julow Jules Aguillon 在 #11024 中提出了一个实现方案,我已经审查过了。这个更改本身改善了文档中的警报状态,这已经是一个巨大的进步,并且应该允许我们在以后为多核安全文档设计出更好的方案。
初学者级多核编程
为了为 OCaml 5.0 的稳定化贡献一点力量,我花了一些时间以最简单的方式使 Dynlink
库线程安全:通过向库添加一个全局锁。这是为数不多的向现有库添加全局锁有意义的情况之一,因为我们同时不期望对 Dynlink
函数的调用成为性能瓶颈,并且确实不希望 Dynlink
中的竞争条件破坏整个程序的状态。
@garrigue Jacques Garrigue
清理方差计算
对于许多人来说,类型定义中方差的计算相当神秘。唯一可用的规范是 OCaml 会议 2013 上的一篇简短摘要,而且它实际上是不完整的。我和斋川隆文重新审视了这个问题,并提出了一个更清晰的格子和更明确的算法。这仍然是一个草稿 PR #11018。
type_pat
分离
将反例的类型检查与 自从引入 GADT 以来,type_pat
(用于类型化模式的函数)也用于在穷举性检查期间丢弃不可能的情况。此外,它后来被转换为延续传递风格以允许回溯,以便检查类型是否可居住。最初这似乎是一个好主意,允许分解大量代码,但随后向 type_pat
添加了新功能,这两个角色开始出现分歧。#11027 是另一个将它们分开的草稿 PR,它将 type_pat
恢复为直接风格,并添加了一个新的 retype_pat
函数,该函数以部分类型化的树作为输入。有趣的是,这种去分解实际上使代码大小减少了 100 多行。
Coqgen
实验性的 Gallina 生成后端仍在缓慢地发展。对于感兴趣的人,现在有一些幻灯片描述了它。http://www.math.nagoya-u.ac.jp/~garrigue/papers/coqgen-slides-tpp2021.pdf
@nojb Nicolas Ojeda Bär
我们正在利用 5.0 版本的机会摆脱随着时间推移积累的大量冗余代码。
- #10897:删除在 5.0 版本发布之前正式标记为已弃用的所有内容。
- #10863:删除
<caml/compatibiliy.h>
头文件,该文件包含一些运行时符号的定义,这些符号没有caml_
前缀以保持兼容性。 - #10896:从标准库中删除
Stream
、Genlex
和Pervasives
,以及独立的bigarray
库(由于Bigarray
模块已移至标准库,因此不再需要)。 - #11002:在运行时系统中不再使用
Begin_roots
/End_roots
宏。它们尚未删除,因为某些外部项目(例如camlidl
)仍在使用它们。 - #10922、#10923:向一些没有弃用属性的符号添加弃用属性,以便我们可以在将来的某个版本中删除它们。
除了所有这些春季大扫除之外,还有一个我很久以前就错过的对标准库的小小补充。
- #10986:添加返回选项的变体
Scanf.scanf_opt
、Scanf.sscanf_opt
和Scanf.bscanf_opt
。
@dra27 David Allsopp
适用于原生 Windows OCaml 5 的 mingw-w64 移植
我在 2018 年很久以前就进行了多核 OCaml 的最初移植(参见 讨论帖子);它被重新移植到 4.10,并在 2020 年夏季更新以包括原生代码支持,但测试故事并不完全到位。然而,它在秋季得到了更新,并在合并到 ocaml/ocaml 的主要 PR 中及时合并!目前,我们仅支持 Windows 上的 mingw-w64 移植:OCaml 4.x 在 systhreads 库中对所有必需的 pthreads 原语进行了手工实现,但对于 5.x,至少目前,我们使用的是 mingw-w64 项目中的 winpthreads 库。
同时,Cygwin64 移植由于一些稍微复杂的原因而基本损坏,并且 MSVC 移植由于缺乏 C11 原子操作支持和前面提到的 winpthreads 库(位于 mingw-w64 中)的组合而无法工作。MSVC 移植极不可能在 OCaml 5.0 中准备好,但我已经使用 C++ 原子操作和使用 Visual Studio 手动构建的 winpthreads 库让它几乎可以工作,因此对于 MSVC 移植来说,隧道尽头有光,希望在 OCaml 5.1 中实现!
FlexDLL 对 Visual Studio 的更新
Windows SDK 中的最新更改要求对 flexlink
链接器进行一些更改,该链接器用于 OCaml 的原生 Windows 移植和 Cygwin 移植。该项目获得了一些合并关注和发布,因此 Visual Studio 2017 和 2019 的最新更新以及 Visual Studio 2022 现在可以成功构建 OCaml 4.x,即使使用 Windows 11 SDK 也是如此。
4.14 中的原生顶层库
我帮助将其他人完成的一些工作上游,并且还缩小了 4.14 中 ocaml
和 ocamlnat
之间的差异。在 OCaml 4.14 中,原生顶层库现在始终安装,这为希望解释顶层语句的原生代码程序(例如 mdx
工具的原生版本)铺平了道路。这项工作的一部分还在顶层插入了一些钩子,允许用动态代码发射器替换外部链接器(所谓的“ocaml-jit”,但使用JIT似乎会导致很多混淆——重点是重复调用外部汇编器的开销被消除了,这在编译大量单个短语时是可以衡量的)。
glibc 2.34 反向移植
glibc 2.34 包括对信号处理程序的备用堆栈的分配方式的更改。该问题在 4.13 中已部分修复,但随着较新的发行版开始附带此较新的 glibc,旧版 OCaml 版本的行为变化变得越来越成问题——例如,您无法在最新版本的 Ubuntu 或 Fedora 上安装 OCaml 4.12 或更早版本。Xavier 已经在 4.13 中处理了修复的后半部分(在终止时释放备用堆栈),我管理了将其反向移植到 opam-repository 中所有 OCaml 版本(3.07+!)的过程。我们还决定将这些补丁推送到 GitHub 上的旧发布分支,部分原因是为了将其作为方便的存储位置,因为 opam-repository 从那里引用它们,部分原因是为了允许旧分支仍然可以直接从 git 克隆进行编译。