Clang 现已用于构建 Windows 版 Chrome
从 Chrome 64 开始,Windows 版 Chrome 使用 Clang 编译。我们现在使用 Clang 来构建所有平台上的 Chrome:macOS、iOS、Linux、Chrome OS、Android 和 Windows。根据 statcounter 的数据,Windows 是仅次于 Android 的 Chrome 用户最多的平台,这使得此次切换变得尤为令人兴奋。
Clang 是第一个与 Microsoft Visual C++ (MSVC) ABI 兼容的开源 C++ 编译器,这意味着您可以使用 MSVC 编译器 (“cl.exe”) 构建程序的某些部分(例如,系统库),使用 Clang 构建其他部分,并在将它们链接在一起时(通过 MSVC 的链接器 “link.exe” 或 LLVM 项目的链接器 LLD,见下文),这些部分将形成一个可运行的程序。
请注意,Clang 不是 Visual Studio 的替代品,而是它的补充。我们仍然使用 Microsoft 的头文件和库来构建 Chrome,我们仍然使用一些 SDK 二进制文件,如 midl.exe 和 mc.exe,并且许多 Chrome/Win 开发人员仍然使用 Visual Studio IDE(用于开发和调试)。
这篇文章讨论了使用 Clang 代替 MSVC 的原因、好处、弊端、如何自行尝试使用 Windows 版 Clang、项目历史和下一步计划。有关技术方面的更多信息,您可以查看 我们 2015 年 LLVM 大会演讲的幻灯片,以及从那里链接的幻灯片。
数据
这是大多数人首先询问的问题,所以让我们先谈谈这个问题。不过,我们认为其他部分更有趣。构建时间
使用 Clang 在本地构建 Chrome 比使用 MSVC 慢约 15%。(我们听说在某些机器上,Windows Defender 会使 Clang 构建速度大大减慢,因此,如果您看到更严重的减速,请确保在 Windows Defender 中将 Clang 列入白名单。)但是,Clang 发出调试信息的方式更便于并行化,并且使用分布式构建服务(例如 Goma)构建的速度更快。二进制文件大小
使用 Clang 构建的 Chrome 安装程序大小在 64 位版本中更小,在 32 位版本中略大。对于常规构建,未压缩代码大小也显示出相同的差异(请参阅 跟踪 Clang 二进制文件大小的错误,以获取更多数据)。但是,与使用链接时代码生成 (LTCG) 和 配置文件引导优化 (PGO) 的 MSVC 构建相比,Clang 在 64 位版本中为使用 /O2 的目标生成更大的代码,但在 32 位版本中为使用 /Os 的目标生成更小的代码。安装程序大小比较表明,Clang 的输出压缩效果更好。64.0.3278.2 版本 (MSVC PGO) 和 64.0.3278.0 版本 (Clang) 的一些原始数据。mini_installer.exe 是用户下载的 Chrome 安装程序,其中包含 LZMA 压缩的代码。chrome_child.dll 是两个主要 dll 之一;它包含 Blink 和 V8,通常包含许多使用 /O2 构建的目标。chrome.dll 是另一个主要 dll,包含浏览器进程代码,大部分使用 /Os 构建。
mini_installer.exe | chrome.dll | chrome_child.dll | chrome.exe | |
32 位 win-pgo | 45.46 MB | 36.47 MB | 53.76 MB | 1.38 MB |
32 位 win-clang | 45.65 MB (+0.04%) | 42.56 MB (+16.7%) | 62.38 MB (+16%) | 1.45 MB (+5.1%) |
64 位 win-pgo | 49.4 MB | 53.3 MB | 65.6 MB | 1.6 MB |
64 位 win-clang | 46.27 MB (-6.33%) | 50.6 MB (-5.1%) | 72.71 MB (+10.8%) | 1.57 MB (-1.2%) |
性能
我们对性能进行了广泛的 A/B 测试。性能遥测数据在使用 MSVC 构建的 Chrome 和使用 clang 构建的 Chrome 中大致相同——有些指标有所改善,有些指标有所下降,但所有指标都在彼此的 5% 之内。官方 MSVC 构建使用了 LTCG 和 PGO,而 Clang 构建目前都没有使用这些。这是我们期待探索的潜在改进方向。由于需要收集配置文件然后再次构建,PGO 构建的构建时间非常长,因此该配置没有在我们的性能测量构建机器上启用。现在我们使用 Clang,性能构建机器再次跟踪我们发布的配置。在开始使用 链接顺序文件(一种“简化版 PGO”)之前,使用 Clang 构建的 Chrome 的启动性能较差。
稳定性
我们还对稳定性进行了 A/B 测试,发现两种构建配置之间没有区别。原因
该项目有许多促使我们进行它的原因,最主要的原因是使用所有 Chrome 平台上的相同编译器带来的好处,以及能够快速更改编译器并将这些更改部署到我们所有开发人员和构建机器的能力。以下是一些不完整的示例列表。- Chrome 大量使用基于编译器检测的技术(ASan、CFI、ClusterFuzz——使用 ASan)。Clang 已经支持这种检测,但我们无法将其添加到 MSVC 中。我们之前使用 事后二进制检测 来缓解这个问题,但让工具链在第一时间写入正确的位更简洁、更快。
- Clang 使我们能够编写编译器 插件,以添加特定于 Chromium 的警告,并编写用于 大规模重构 的工具。现在,Chromium 的代码搜索 可以学习索引 Windows 代码。
- Chromium 是开源的,因此使用开源工具链构建它很不错。
- Chrome 运行在 6 个以上的平台上,而大多数开发人员只熟悉 1 到 3 个平台。如果您在不熟悉的平台上编译补丁时遇到无法在本地开发机器上重现的编译器错误,那么您将需要花费一些时间来修复它。另一方面,如果所有平台都使用相同的编译器,那么如果它可以在您的机器上构建,那么它很有可能可以在所有平台上构建。
- 使用相同的编译器还意味着,编译器特定的微优化将有助于所有平台(假设所有平台都使用相同的 -O 标志,而 Chrome 目前还没有这样做,并且仅限于相同的 ISA——x86 与 ARM 将保持不同)。
- 使用相同的编译器可以实现 交叉编译——那些最习惯使用 Linux 机器进行开发的开发人员现在可以在他们的 Linux 机器上(无需运行 Wine)处理特定于 Windows 的代码。
- 我们可以 持续使用 Clang trunk 构建 Chrome trunk,以便快速发现编译器回归。这使我们能够每隔一两周更新一次 Clang。在 Chrome 中发布主要 MSVC 更新通常需要一年或更长时间,并需要经过几轮内部编译器错误和误编译报告。问题不在于 MSVC 比 Clang 更容易出现错误——它并没有,所有软件都会出现错误——而是我们能够通过 Clang 开源来持续改进 Clang。
- C++ 每隔几年就会进行重大更新。C++11 发布时,我们仍在使用六种不同的编译器,而且 启用 C++11 非常困难。使用更少的编译器,这个问题就容易多了。
- 我们可以优先考虑对我们重要的编译器功能。例如:
- 在 确定性构建 变得对 MSVC 团队重要之前,它对我们来说很重要。例如,link.exe /incremental 依赖于每个目标文件中不断增加的 mtime 时间戳。
- 在 MSVC 添加对系统头文件概念的支持 之前很久,我们就可以启用在系统头文件中触发的警告。
- cl.exe 始终打印输入文件的名称,因此 构建系统必须在静默构建中过滤掉它。
当然,并非所有(甚至大多数)这些原因都适用于其他项目。
使用 Clang 代替 Visual C++ 的好处和弊端
如果您想在您的项目中尝试使用 Clang,那么使用 Clang 的好处
- Clang 支持 64 位内联汇编。例如,在 Chrome 中,我们构建 libyuv(一个视频格式转换库)使用的是 Clang,比我们使用 Clang 构建整个 Chrome 早得多。libyuv 具有高度优化的 64 位内联汇编,其性能无法通过内在函数实现,我们只需在 Windows 上使用这段代码即可。
- 如果您的项目运行在多个平台上,那么您可以在所有平台上使用一种编译器。使用多种编译器构建项目通常被认为有利于代码健康,但在 Chrome 中,我们发现 Clang 的诊断发现了大多数问题,而我们主要是在与编译器错误作斗争(如果另一个编译器拥有很棒的新诊断,我们也可以将其添加到 Clang 中)。
- 同样,如果您的项目仅限于 Windows,那么您可以获得第二种编译器对您的代码的意见,而 Clang 的警告可能会发现错误。
- 您可以使用 Address Sanitizer 来查找内存错误。
- 如果您不使用 LTCG 和 PGO,那么 Clang 可能会生成更快的代码。
- Clang 的 诊断和修复提示。
- Clang 不支持 C++/CX 或 #import “foo.dll”。
- MSVC 提供付费支持,Clang 只为您提供代码以及自行编写补丁的能力(虽然社区非常活跃且乐于助人!)。
- MSVC 的文档更完善。
- 使用 Clang 时,高级调试功能(如编辑并继续)将无法使用。
使用方法
如果您想尝试使用 Windows 版 Clang,有两种方法- 您可以使用 clang-cl,它是一个编译器驱动程序,试图在命令行标志方面与 cl.exe 兼容(就像 Clang 试图在命令行标志方面与 gcc 兼容一样)。Clang 用户手册 描述了如何告诉流行的 Windows 构建系统如何调用 clang-cl 而不是 cl.exe。我们在 Chrome 中使用这种方法,使 Clang/Win 构建与 MSVC 构建多年并行运行,维护成本很低。您可以继续使用 link.exe,所有当前的编译标志、MSVC 调试器或 windbg、ETW 等。clang-cl 甚至以与 cl.exe 兼容的格式写入警告消息,这样您就可以单击 Visual Studio 中的构建错误消息,跳转到正确的文件和行。所有功能都应该正常工作。
- 或者,如果您有一个跨平台项目,并且希望为 Windows 构建使用 gcc 样式的标志,您可以将 Windows 三元组(例如,--target=x86_64-windows-msvc)传递给常规 Clang,它将生成与 MSVC ABI 兼容的输出。从 Clang 7.0.0(预计在 2018 年秋季发布)开始,使用这种三元组时,Clang 还将默认使用 CodeView 调试信息。
clang-cl 接受解析系统头文件所需的 Microsoft 语言扩展,但在执行此操作时会尝试发出 -Wmicrosoft-foo 警告(对于系统头文件,会忽略警告)。您可以选择修复代码,或者将 -Wno-microsoft-foo 传递给 Clang。
link.exe 可以从 Clang 写入的 CodeView 信息生成常规 PDB 文件。
项目历史
我们几个月前将 chrome/mac 和 chrome/linux 切换到 Clang。但在 Windows 上,Clang 仍然缺少解析许多 Microsoft 语言扩展的支持,并且完全没有 Microsoft C++ ABI 兼容的代码生成。2013 年,我们 组建了一个团队 来改进 Clang 的 Windows 支持,其中一半是拥有编译器背景的 Chrome 工程师,另一半是其他工具链人员。在 2014 年年中,Clang 可以 在 Windows 上进行自托管。2015 年 2 月,我们有了第一个没有回退的 64 位 Chrome 版本,2015 年 7 月有了第一个没有回退的 32 位 Chrome 版本(32 位 SEH 很困难)。在 2015 年 10 月,我们向 Canary 频道发布了第一个由 clang 构建的 Chrome。从那时起,我们一直在努力 改进 Clang 输出的大小、改进 Clang 的调试信息(其中一部分目前在 -instcombine-lower-dbg-declare=0 后面),并通过 A/B 测试了稳定性和遥测性能指标。我们使用固定到最新上游版本的 Clang 版本,我们每 1 到 3 周更新一次,没有任何本地补丁。我们所有的工作都在上游 LLVM 中完成。
2015 年年中,微软宣布他们正在我们工作的基础上,让 Clang 能够解析所有 Microsoft SDK 头文件,并使用 clang/c2,该工具使用 Clang 前端解析代码,但使用 cl.exe 的代码生成来生成代码。clang/c2 的开发在 2017 年年中再次停止;这很可能是由于我们对兼容 MSVC-ABI 的 Clang 代码生成质量的改进。我们感谢微软发布有关 PDB 文件格式的文档、回答我们的许多问题、修复 SDK 中的 Clang 兼容性问题,并在他们的博客上为我们提供宣传!再次强调,Clang 并非 MSVC 的替代品,而是对它的补充。
Opera for Windows 也从版本 51 开始 使用 Clang 编译。
Firefox 也在考虑 使用 clang-cl 构建 Windows 版 Firefox。
下一步
正如 clang-cl 是 Clang 的 cl.exe 兼容接口一样,lld-link 是 lld 的 link.exe 兼容接口,lld 是 LLVM 链接器。我们的下一步是将 lld-link 用作 link.exe 的替代方案,用于链接 Windows 版 Chrome。这与 clang-cl 具有许多相同的优势(开源、易于更新等)。此外,将 clang-cl 与 lld-link 一起使用允许使用 基于 LLVM 位代码的 LTO(反过来又可以启用使用 CFI)和 使用 PE/COFF 扩展来加快链接速度。使用 lld-link 的先决条件是 它能够写入 PDB 文件。我们也考虑使用 libc++ 来代替 MSVC STL - 这使我们能够对标准库进行检测,这对 CFI 和 Address Sanitizer 来说非常有用。