EuroLLVM 2024技术报告
EuroLLVM是一个专注于LLVM基金会旗下项目的开发者会议,这些项目存在于LLVM GitHub单仓库中,如Clang以及最近因机器学习研究而兴起的MLIR框架。Trail of Bits在编译器工程和LLVM相关领域有着悠久历史,我们派出一批编译器专家参会,并展示了两个项目:基于MLIR的C/C++编译器VAST,以及针对MLIR的新型指针分析方法PoTATo。本篇博客分享我们在这场为期两天(包含一天会前研讨会)的开发者会议中的收获与体验。
安全意识的提升
与往年相比,一个显著变化是对安全性的新兴关注。LLVM社区内似乎越来越重视增强整个软件生态系统的安全性。这代表了编译器社区中相对较新的发展动态,LLVM领导层正在积极寻求该主题的专业知识。
开幕主题演讲引入了安全主题,断言其已成为编译器优化和转换之外的第三大支柱。ARM的Kristof Beyls发表了主题演讲,简要回顾了编译器关注点和角色的演变历程。他强调安全性现在已成为与正确性和性能并列的主要关注点。
主题演讲的技术部分提出了一个有趣的问题:是否有人验证安全缓解措施是否正确应用,或者是否被应用?为回答这个问题,Kristof使用BOLT实现了一个静态二进制分析工具。他选择验证的缓解措施是-fstack-clash-protection和-mbranch-protection=standard,特别是其pac-ret机制。
基于BOLT的扫描器评估在Fedora 39 AArch64-linux发行版的库上进行,包含约3,000个已安装软件包。对于pac-ret,分析显示有250万条返回指令,其中4.6万条缺乏适当保护。扫描使用-fstack-clash-protection的1,920个库,发现39个可能存在漏洞,尽管其中可能存在误报。
关于选择BOLT而非逆向工程领域的IDA、Ghidra或Angr等工具,引发了有趣的讨论。区别在于BOLT适用于批量处理二进制文件,而IDA或Ghidra更注重用户交互性。此外,BOLT的优势在于它支持最新的目标架构变更,因为它是编译流水线的一部分,而逆向工程工具往往滞后,特别是对于更小众的指令。
更多细节可参考Kristof在LLVM论坛上的RFC。
对于对编译器强化感兴趣的读者,OpenSSF指南提供了全面概述。此外,对于编译器工程师的安全深入讨论,我们建议阅读《低级软件安全》在线书籍。该项目仍在进行中,欢迎对指南做出贡献。
关于程序分析和调试的一个值得关注的演讲是《Clang静态分析器的增量符号执行》,讨论了Clang静态分析器现在如何缓存结果。这一创新有助于在代码库变更时保持诊断信息的相关性,并最小化调用分析器的需求。另一个亮点是《Mojo调试:扩展MLIR和LLDB》,探讨了LLDB的新发展,使其能够在Clang环境外使用。该演讲还涵盖了从Modular仓库上游化调试方言的潜力。
MLIR不仅关乎机器学习
MLIR是一个编译器基础设施项目,因机器学习(ML)热潮而获得关注。然而,MLIR中的ML代表多层级(Multi-Level),该项目远不止于张量处理。以RISC-V工作闻名的SiFive在电路设计等应用中采用它。使用MLIR的通用语言编译器也在涌现,如用于JavaScript的JSIR方言、作为Python超集的Mojo、ClangIR,以及我们自己的C/C++编译器VAST。
本次开发者会议的MLIR主题可总结为“弄清楚如何在共享流水线中充分利用LLVM和MLIR”。多位演讲者展示的工作以不同方式得出结论:由于其更好的抽象,许多性能优化在MLIR中完成得更好。LLVM则主要负责生成目标机器代码。
在回顾了MLIR相比LLVM的所有慢速方面后,Jeff Niu(Modular)指出,在Mojo编译器中,大部分运行时仍然消耗在LLVM中。原因很简单:当代码编译到LLVM时,需要处理的输入更多。
慕尼黑工业大学的一个团队甚至选择完全跳过LLVM IR,直接生成机器IR(MIR),在即时(JIT)编译工作负载中实现了约20%的性能提升。
对MLIR内部机制感兴趣的人一定要观看第二次会议主题演讲《MLIR中的高效惯用法》。该演讲深入探讨了不同MLIR原语和模式的性能比较。它为开发者提供了关于执行操作(如获取属性、迭代或变异IR)成本的直观认识。类似主题的演讲《接口实现深度剖析》更深入地洞察了MLIR通用性的基石。这些接口使方言能够表达常见概念,如副作用、符号和控制流交互。该演讲阐明了它们的实现细节以及在追求通用性时产生的相关开销。
基于区域的分析
我们注意到的另一个有趣趋势是,几个独立团队发现,传统上使用基于基本块的控制流图定义的分析,在使用基于区域的控制流表示时可能获得更好的运行时性能。这一改进主要是因为分析不需要重建循环信息,且整体表示更小,因此分析更快。展示的主要例子是在Mojo编译器内部完成的数据流分析。
对于像Mojo这样的情况,从源代码开始并编译基于MLIR的流水线,切换到基于区域的控制流进行分析只需在流水线更早阶段进行分析。其他用户就没这么幸运,需要从传统控制流图构建区域。如果你属于后者,你并不孤单。高性能计算行业的团队一直在寻找从循环中挤出更多性能的方法,将循环明确表示为区域而不是在图中寻找它们,使很多事情变得更容易。这就是为什么MLIR现在有一个传递(pass)来将控制流图提升为基于区域的控制流。听起来熟悉吗?在我们的LLVM到C反编译器Rellic底层,做了非常类似的事情。
然而,使用区域进行控制流并非一帆风顺。区域需要具有单入口和单出口。但许多编程语言允许在循环体内使用break和continue等结构。这些被视为异常入口或出口。幸运的是,随着关于区域的讨论增多,核心MLIR开发者已经注意到并正在开发一个主要新功能来解决这个问题。如在MLIR研讨会上所展示,新设计的基于区域的控制流将允许指定continue或break等结构的语义。想法很简单:这些操作将产生终止信号,并将控制流转发到捕获该信号的某个父区域。不幸的是,这仍然不允许我们在高级表示中表示goto,因为信令机制只允许用户将控制流传递给父区域。
C/C++后继语言
会议上最后一个主要话题,鉴于最近的发展,是C/C++的后继语言。其中一个努力是Carbon,它有一个专门的小组讨论。小组讨论的问题范围从技术问题(如如何支持重构工具)到更管理性的问题(如Carbon如何避免过度受Google需求的影响,Google目前是该项目的主要支持者)。关于小组讨论的更全面总结,请查看Alex Bradbury的这篇优秀博客文章。
其他C++的替代者也得到了提及——特别是Rust和Swift。这两种语言都认识到C++在软件生态系统中的权威,并有自己的C++互操作故事。在Carbon小组讨论中提到了Google的Crubit(用于Rust),而Apple的Egor Zhdan就Swift的互操作性进行了单独演讲。
我们的贡献
我们自己的Henrich Lauko就VAST(我们基于MLIR的C/C++编译器)即将推出的新功能进行了演讲:IR塔(Tower of IRs)。总体思路是,VAST是一个基于MLIR的C/C++编译器IR项目,提供多层抽象。VAST用户可以为他们的分析或转换用例选择合适的抽象。然而,有许多有价值的基于LLVM的工具,如果我们不能将它们与我们的高级MLIR表示一起使用,那将非常遗憾。这正是我们开发IR塔的原因。它使用户能够将低级分析与高级抽象桥接起来。
IR塔引入了一种机制,允许用户在转换之间和之后拍摄IR快照,并将它们链接在一起,创建来源链。这样,当一段代码发生变化时,总有一条引用链回到原始输入。敏锐的读者已经会心一笑。
Henrich展示的演示用例是通过使用IR塔将输入C源代码一直向下传递到LLVM,执行依赖分析,并通过塔中的来源链接将分析结果一直翻译回C,从而在MLIR中重新利用LLVM分析。
与Henrich一起,Robert Konicar展示了他学生工作的丰硕成果PoTATo。该项目实现了一个简单的MLIR方言,专门用于实现指针分析。想法是将内存操作从源方言翻译到PoTATo方言,进行一些基本优化,然后运行你选择的指针分析,产生别名集。当然,为了将相关信息返回原始代码,可以使用VAST的IR塔。Robert在海报上展示的结果很有前景:在指针分析之前应用基本复制传播显著减少了问题规模。
AI走廊谈话
除了参加官方演讲和研讨会外,Trail of Bits代表在休息时间和宴会上花了很多时间与人交谈。许多对话的潜流是各种形式的AI和机器学习。由于EuroLLVM专注于语言、编译器和硬件运行时,对话通常以“我们如何最好地服务这种新计算范式?”的形式进行。硬件人员对如何为专用加速器生成代码感兴趣;编译器人群以各种可想象的方式优化线性代数;而语言则尽力满足数据科学家的需求。
关于反向项目的讨论——即“机器学习如何帮助LLVM人群?”——寥寥无几。这些项目通常研究在LLVM领域收集的各种数据,以便使用机器学习方法理解它们。从我们所见,像LLM和GAN这样的东西几乎没有被提及。似乎是新想法的机会!