LLVM 项目博客

LLVM 项目新闻和来自战壕的细节

LLILC : 基于 LLVM 的 dotnet CoreCLR 编译器。

LLILC 项目(我们称之为“丁香”)是微软启动的一项新计划,旨在基于 LLVM 生成 MSIL 代码,并将其目标设置为开源的 dotnet CoreCLR。 我们设想将 LLVM 基础设施用于多种场景,但我们的第一个工具是 CoreCLR 的即时(JIT)编译器。 该新项目正在 GitHub 上开发,您可以 在这里查看它。 本帖的其余部分概述了项目的理由和目标,以及我们使用 LLVM 的经验。

为什么 CoreCLR 需要一个新的 JIT?

虽然 CoreCLR 已经拥有 JIT,但我们看到了提供一个新的代码生成器的机会,它有潜力运行在 LLVM 支持的所有目标和平台上。 为此,作为我们项目的一部分,我们正在开放一个 MSIL 读取器,它直接针对与生产 JIT(RyuJIT)相同的通用 JIT 接口。 这个新的 JIT 将允许为 .NET Core 类库编写的任何 C# 程序运行在 CoreCLR 可移植到的任何平台上,以及 LLVM 所支持的任何平台上。

LLVM 社区中有一些正在进行的努力来编译 MSIL,SharpLang 就映入脑海。 为什么还要构建另一个?

当我们开始思考最快的 LLVM 基于代码生成的方法时,我们查看了当前的开源项目以及我们内部的代码。 虽然许多 OSS 项目已经针对 LLVM BitCode,但还没有一个项目与 CoreCLR 接口完全匹配。 查看我们的选项,最简单的方法是将一个工作 MSIL 读取器重构为面向 BitCode,然后教一个现有项目支持 CoreCLR 用于 JITing MSIL 的契约和 API。 使用现有的 MSIL 读取器,我们可以快速开始使用许多我们认为社区可以利用的构建块组件。 跨多个平台快速引导 C# 是这个项目的起源,也是启动一项新计划的令人信服的原因。 我们希望 LLILC 能为社区提供一个有用的示例——以及可重用组件——并使其他项目更容易与 CoreCLR 运行时交互。

为什么选择 LLVM?

基本上,我们认为 LLVM 太棒了。 它已经在许多平台和芯片组上获得了广泛的支持,而且社区非常活跃。 当我们开始参与时,仅仅是试图跟上开发人员邮件列表的更新就是一个启示! LLVM 既可以作为 JIT 运行,也可以作为 AOT 编译器运行的能力特别吸引人。 通过将 MSIL 语义引入 LLVM,我们计划构建一系列工具,这些工具可以针对 CoreCLR 或其某些子集组件。 我们还希望社区能够开发出我们尚未想到的工具。

工具路线图

  • CoreCLR JIT
    • 即时 - 传统的 JIT。 预计它在吞吐量方面会面临挑战,但它将是正确的,并且可以用于启动。 也可以使用它,并在启用更多优化的情况下作为更高层级的 JIT
    • 安装时 JIT - .NET 所谓的 NGen。 这将适合安装时 JITing(LLVM 在运行时配置中仍然很慢)
  • 提前编译器。 一个构建实验室编译器,它使用 CoreCLR 中的一些共享组件生成独立的可执行文件。 AOT 编译器将用于改进重要命令行应用程序(如 Roslyn 编译器)的启动时间。
LLIC JIT 将是 CoreCLR 运行时的功能正确且完整的 JIT。 它可能没有足够的吞吐量来成为首层 jit,但预计会生成高质量的代码,因此可能会成为非常有趣的二层或更高级的 JIT,或者成为为 RyuJIT 提供原型代码生成更改的良好工具。

实际有效的功能

今天,在 Windows 上,我们已经实现了 MSIL 读取器和 LLVM JIT,足以在 CoreCLR 中包含的 JIT 启动测试中编译大量方法。 在这些测试中,我们编译了大约 90% 的方法,然后在无法处理的情况下回退到 RyuJIT。 测试体验对于开发人员来说相当不错。 我们运行的测试可以在 CoreCLR 测试仓库中看到。
我们在 Linux 和 Mac OSX 上建立了构建,并且正在从 CoreFx中提取 mscorlib(.NET Core 基础库)以及测试资产依赖项,以便为这些平台启动测试。
所有测试都针对 CoreCLR GC 在保守模式下运行——它扫描帧以查找根——而不是精确模式。 我们目前还不支持异常处理。

架构

从哲学角度来说,LLILC 旨在提供 CoreCLR 和 LLVM 之间的精简接口。 只要有可能,我们都依赖于现有的技术。

对于 JIT,当我们按需编译时,我们将运行时类型和 MSIL 映射到 LLVM BitCode。 从那里开始,编译使用 LLVM MCJIT 基础设施生成编译后的代码,这些代码输出到 CoreCLR 提供的缓冲区。

我们的 AOT 图表更具推测性,但基本概述是,代码生成器使用与 JIT 相同的接口驱动,但它背后有一个静态类型系统,我们使用 LLVM 构建一个完整的程序模块,并在类似 LTO 的模式下运行。 必需的运行时组件会与输出 obj 一起发出,然后平台链接器生成目标可执行文件。 围绕泛型等问题,仍然存在许多悬而未决的问题需要解决,但这只是我们对未来发展的初步设想。

使用 LLVM 的经验

在使用 LLVM 的几个月里,我们获得了非常好的体验,但也遇到了一些问题。 将代码翻译为 BitCode 的入门体验非常直观,对于有编译器经验的人来说,学习曲线非常快。 我们从 MCJIT 开始,用于我们的 JIT,它很容易配置,并且能够编译代码并将其返回给运行时。 除了下面讨论的 COFF 问题之外,我们只需要对配置进行调整或对类(如 EEMemoryManager)进行简单的覆盖,就能使代码正常工作。 在这些问题中,第一个问题很简单,但另外两个问题需要持续努力才能达到我们期望的水平。 第一个问题是 MCJIT 基础设施中的 DynamicRuntime 对 Windows 支持的问题。 后两个问题,精确垃圾回收和异常处理,是由于托管语言的语义不同而产生的。 对我们来说幸运的是,社区中的人们已经开始在这方面工作,因此我们不必从头开始。

COFF 动态加载器支持

我们对 LLVM 的一个补充是实现了一个 COFF 动态加载器,这让我们能够继续工作。(添加 RuntimeDyldCoff.{h,cpp} 的补丁已经过审查并已提交)。 这是我们为了使代码生成器启动而必须直接对 LLVM 进行的唯一补充。 一旦完成,我们将在数据库中看到许多关于 Windows JIT 支持的错误,这些错误应该更容易解决。

精确垃圾回收

精确 GC 是 CoreCLR 内存管理方法的核心。 它的目的是尽可能降低托管内存的开销。 运行时假设 JIT 将生成关于 GC ref 生命周期的精确信息,并将其与编译后的代码一起提供以供执行。 为此,我们开始使用 StatePoint 方法,并在其中添加了将标准输出格式转换为 CoreCLR 预期的自定义格式的功能。 我们对 Philip Reames 在 StatePoints 初始设计中提到的某些问题也有相同的担忧。 例如,必须在优化器中保留“GC 特性”,但不能阻止优化器转换。 考虑到这一点,我们其中一个悬而未决的问题是如何启用测试以查找潜入的 GC 洞,同时还要启用额外的检查,以便在传入的 IR 包含 GC 指针时可以选择加入这些检查。
我们的存储库中包含一份更详细的文档,其中概述了我们更具体的 GC 计划,您可以在 这里查看。

异常处理

MSIL EH 模型是针对 CLR 的,正如您所预期的那样,但它在一定程度上从 Windows 结构化异常处理 (SEH) 中继承了概念。 特别是,从内存访问到实现空检查的隐式异常流,以及在处理异常时使用过滤器和函数,都反映了 SEH(这里概述了 C# EH)。 我们目前计划将 MSIL 所需的所有检查添加为显式比较/分支/抛出序列,以更好地匹配 C++ EH,并在此基础上构建当前正在 Clang 中添加的 SEH 支持。 然后,一旦我们确保了正确性,我们将看看是否有一个合理的方法可以提高性能。
与 GC 一样,存储库中有一份详细的文档,概述了我们的具体问题和计划,您可以在 这里查看。

未来工作

  • 更多平台。 今天,我们正在 Windows 上运行,并且开始为 Linux 和 Mac OSX 构建。 我们希望更多。
  • 完成 JIT 实现
    • 支持更多 MSIL 操作码
    • 精确 GC 支持
    • EH 支持
  • 针对托管解决方案的专门内存分配器。 CoreCLR 已经用作托管解决方案(由其他程序在进程中运行),但为了支持这一点,我们需要更好的内存分配方案。 运行时应该能够提供一个内存分配器,该分配器用于所有编译。
  • AOT - 全面完善 AOT 方案。
LLILC
CoreCLR
CoreFx
.NET 基金会