RyuJIT的历史和架构,你能详细介绍一下吗?
摘要:本系列为 RyuJIT 教程,将分为多篇进行更新发布,旨在给对 .NET 编译器有兴趣、以及希望参与 .NET JIT 编译器开发工作的人提供一些参考资料。
索引
上一篇:无
下一篇:待更新
正文开始
RyuJIT - 即 .NET 的 JIT 编译器,负责将 IL 代码编译为最终用于执行的机器代码。
本系列为 RyuJIT 教程,将分为多篇进行更新发布,旨在给对 .NET 编译器有兴趣、以及希望参与 .NET JIT 编译器开发工作的人提供一些参考资料。
说是教程其实也只是我在社区中从事 RyuJIT 相关开发工作的一些经验和见解,抛砖引玉。
如有疏漏请见谅。
写在前面
RyuJIT,最初也叫做 JITBlue,是微软在大概 2014 年前后为 .NET 发布的新一代 JIT 编译器。“Ryu”则取自日语中龙(竜)的发音,也算是致敬编译原理的经典书籍——“龙书”。
而 .NET 作为上世纪末就诞生的平台,原有的 JIT 在生产中用了将近 20 年,为什么需要一个新的 JIT 呢?这就需要了解一些历史。
一些历史
.NET 最初的 JIT 编译器也叫做 JIT32,顾名思义这是给 32 位架构设计的编译器,最早可以追溯到 1996 年。在当时 32 位是主流架构,并且人们也只关心 32 位架构。JIT32 设计上是一个非常轻量的编译器,并且拥有良好的代码生成质量。
然而进入 21 世纪之后 IA64 架构的诞生,.NET 需要运行在 64 位的服务器上。这个时候代码生成质量成了关键,而服务器并不在乎编译时间,并且内存也很大,.NET 选择了采用 C++ 编译器后端(UTC,Universal Tuple Compiler)作为优化器,带来的结果就是支持了 IA64 架构,但同时编译器也用到了大量的 O(N^X) 复杂度的优化算法,并且很吃计算机资源。
然后 AMD64 诞生了,这个时候简单的把 IA64 移植到 AMD64 架构上,就带来了此后一直沿用了十年的 JIT64。然而此时 64 位架构不再是服务器的专属,个人电脑也开始用 64 位架构了。
2010 年的时候由于 Windows RT 在 ARM 设备上的尝试,.NET 需要支持 ARM32 架构。由于个人终端设备的资源很有限,.NET 选择了将 JIT32 移植到 ARM32 上。然而 ARM32 和 x86 虽然都叫做 32 位,实际上几乎没有任何相同之处。虽然花费了大量的精力做了移植,此时的 JIT32 for ARM 的代码质量实际上相当的糟糕,主打一个能用就行。
而到了 2012 年的时候,ARM64 要来了。此时我们回顾一下历史,就会发现在这个时候:
用于 x86 的 JIT32 现在跟不上时代
用于 x64 的 JIT64 编译速度很慢而且资源消耗大
用于 ARM32 的 JIT32 代码质量很差且难以解决
那 ARM64 应该用哪个实现呢?很显然无论哪个都难以满足现代 JIT 的需求。
于是此时 RyuJIT(JITBlue)项目启动了,既然要做一个新的玩意,那自然要跟上最新的架构。于是 RyuJIT 的目标自然就是:
生成的代码质量要高(性能好)
吞吐量要高(编译快)
在所有架构上都有一致的可预测的性能
要做到这些,自然要用上现代的编译器架构:
采用基于 SSA(Static Single Assignment)的优化算法
能够充分利用类型信息的 VN(Value Numbering)
单一代码库支持各种新特性,比如 SIMD 等
架构相关的部分(lowering、codegen)相互隔离
等等...
RyuJIT
RyuJIT 复用了 JIT32 的树状 IR 结构,重写大部分前端,然后在 Rationalization 这个步骤将树形 IR 转换为线性 IR 后交给新写的后端。后端的 register allocator 这次用上了 LSRA 而不是 JIT64 那样的图着色来提升编译速度。
这里有一个有趣的小插曲,RyuJIT 最初是打算只重写大部分前端,然后 Rationalization 后就直接扔给 JIT32 去做代码生成的,结果做着做着发现这完全就是个错误的决定,于是最后把后端也重写了。
老的 JIT64 的 IR 结构是线性的,毕竟 UTC 顾名思义就是主要在线性 IR 上做文章的编译器,而把 IL 导入成线性 IR 的开销非常大。由于 RyuJIT 是将 IL 导入成树形的 IR,这相比导入到线性 IR 要容易得多,并且速度也更快。
另外,换上了现代编译器架构,将基于 lexical 的算法更换为基于 semantic 的算法,用上了基于 SSA 和 VN 的优化,RyuJIT 能编译出质量相当优秀的代码。
多亏了 SSA,让 RyuJIT 能用上各种线性或者近线性的算法,最终编译速度也比 JIT64 快得多,开销也要更小。
