描述 : PINC (PlugIN framework for Compiler) 是在 openEuler 社区发起的创新项目,帮助开发者无需深入修改编译器内部逻辑,可实现独立编译优化和编译工具的便捷开发。 推荐人 :李彦成 @li-yancheng openEuler Compiler SIG maintainer, 黄晓权 @huang-xiaoquan openEuler Compiler SIG committer, 丁光亚 @dguangya openEuler Compiler SIG committer ,伍明川 @wumingchuan openEuler Compiler SIG committer 社区地址 : https://gitee.com/openEuler/pin-gcc-client https://gitee.com/openeuler/pin-server
openEuler 优秀项目衡量标准
推荐奖项 openEuler 年度优秀项目 , openEuler 年度技术创新项目。 开源开放 项目代码托管在 openEuler 。 行业影响 在开源领域顶会 OSSEU23 上做主题演讲,致力于为开发者提供 个性化编译的便捷开发 。 技术创新 插件框架提供常用编译插件服务,让硬件厂商迅速使能和构建生态;插件开发基于 MLIR 技术进行,可处理不同编译器 IR 的转换,一次开发可多编译器使用;插件服务与编译进程隔离运行,插件逻辑可与编译器解耦,独立研发和部署。 高质量开发和运营 代码符合规范,代码质量高,对用户反馈问题响应及时 。
1. 软件介绍
现如今,主流的编译器框架都有提供自己的插件式开发能力,方便开发人员可以在不修改编译器的情况为编译器添加新的功能。例如 GCC Plugin ,它是 GCC 4.5.0 开始引入的一项插件功能,允许用户通过插件来干预 GCC 的编译过程并获取 GCC 编译过程中的各种数据,进而可以修改编译过程中生成的中间数据。 GCC Plugin 使开发人员可以通过回调函数,在编译器各个阶段添加新功能,并且不需要去修改编译器本身。这样的插件式开发提供了如下优势:
1) 缩短了构建和测试新功能所需的时间。只需要编译实现新功能所需的代码,不需要重新编译 GCC 。
2) 简化了开发成本。对那些需要修改 GCC 但没有时间或倾向去深入探讨编译器内部的开发人员,提供了入门难度低、更高效的开发方式。 但是,由于编译器插件是完全基于各自编译器框架进行开发实现的,与具体编译器版本紧耦合。由此引发了编译器插件的两大主要挑战。 挑战一:重复构建引发的开发维护成本 2021 年在 Linux 内核社区中有过对 GCC 插件的讨论。近年来,内核自我保护项目( Kernel Self Protection Project )将一些 grsecurity/PaX 的 patch 以 GCC 插件的形式给予 Kernel 诸多加固支持。但是,由于这些 GCC 插件与 GCC 版本联系非常紧密,一旦 Kernel 使用的 GCC 发生版本变动,这些加固插件均需要对每一次变动的 GCC 版本进行适配,由此给插件维护者带来了大量的维护工作量,如下图所示。
同时,类似完整性校验、兼容性检测等各个插件均会涉及的公共功能,相同的功能却需要在不同的插件里重复开发和维护(如下图)。这样给插件开发者带来了重复的开发维护成本。
挑战二:不同编译器生态引发的插件重复开发维护成本 当前存在两款主流的编译器框架, GCC 使用面广,稳健发展性能较好; LLVM 开拓创新,发展迅速。市场上大量的编译工具和编译扩展能力,都是基于这两类编译器完成的。这导致任何编译工具需要选择两种编译框架之一或者重复进行开发。 因为编译器框架的不同,导致即使是相同的工具设计逻辑,代码几乎没办法复用。这需要在不同的编译器框架上做重复的代码开发并分别进行维护,直接拉高了工具的开发和维护成本。
如上图所示, Randstruct 作为 Kernel 加固工具,于 2017 年基于 GCC 插件实现,然而在隔年,为了能在 LLVM 上使能此工具,基于 Clang 插件重复进行开发,但是基本功能和逻辑其实并没有太大变化。时至今日,仍然存在一个插件工具要为两个编译器生态维护两个不同代码版本的情况。 PINC :编译器插件框架 为了解决上诉编译器插件的两大挑战,我们设计实现了编译器插件框架( PINC : P lug IN Framework for C ompiler )。
如上图所示,我们的目标是让基于编译能力的相关工具开发者只需要做一次开发,即可在多个不同编译器框架落地;同时作为开发平台,提供对公共能力的支持与维护。 编译器插件框架的总体架构如下图所示:
编译器插件框架的最大架构特点就是设计插件服务端组件和编译插件客户端模块组成代理模式。插件服务端只承载插件逻辑,开发者在服务端使用相对中立的 MLIR 以及我们提供的一系列插件 API 进行开发,将关注点聚焦于工具的设计逻辑。服务端与不同编译器的客户端对接,并通过跨进程通信传递 IR 数据与操作,将插件逻辑操作转换映射,执行在客户端编译器上,实现一份代码,多编译器落地的目的。 插件使用者只需要下载所需插件的库文件和校验文件,通过框架的配置文件即可与编译器客户端联合使能。插件客户端将作为 GCC/LLVM Plugin 进行加载,拥有其完全不需要修改 GCC 编译器代码实现新功能的优势,能够灵活快捷地使用。同时享受框架所维护的各种公共能力。 下图是编译器插件框架的架构图。
编译器插件框架作为开发平台,提供了诸多公共能力的统一支持与维护。
・ 日志记录功能:在服务端和客户端,都支持日志记录功能。插件执行过程中可以打印内部结果到日志,记录插件执行过程关键的执行信息,供用户调试和查阅。日志记录提供 ERROR 、 WARN 、 INFO 和 DEBUG4 个级别的信息配置能力,可以通过配置管理能力由用户进行设置。
・ 运行监控器:在跨进程通信过程中,插件框架提供运行监控器对安全编译选项以及操作合法性进行校验。
・ 兼容性校验:在插件客户端,我们提供了对插件版本兼容性的校验功能。
・ 完整性校验:服务端插件工具提供二进制签名与完整性校验能力,防止对插件工具的恶意修改。 此时,我们能够发现,为了实现一次开发,多编译器落地的目标,工具开发所使用的 IR 应该足够中立,并且拥有高效翻译转换到其他编译器 IR 的能力。为此,编译器插件框架提供 PluginDialect 给插件开发者使用,并选择基于 MLIR 进行设计实现。 针对前面提到的挑战,重复构建引发的开发维护成本。如果基于编译器插件框架开发实现,由于框架插件逻辑与具体编译器解耦的设计理念,可以极大减少插件工具维护者所需要维护的版本数量,明显减少维护工作量。 案例分析 以查询元素扩展优化( Array Widen Compare )在编译器插件框架上的实现作为案例,介绍编译器插件框架的优势。
查询元素扩展优化的效果如上图所示。该优化来源于 GCC for openEuler 的优化特性,是用宽数据类型对原数组指针解引用,达到一次比较多个元素的效果。整个优化在 GCC 上实现的时候,大约是 1 人月的工作量。由于是专用于 GCC ,没有办法直接移植到 LLVM 等其他编译器。如果想要在 LLVM 上使能,需要重新开发,至少需要额外的 1 人月。 为此,选择基于 PINC 进行移植。从 GCC 上的查询元素扩展优化移植到编译器插件框架上,仅用了 7 人天。依托编译器插件框架实现了优化与 GCC 解耦,有快速使能 LLVM 的机会。并且编译器插件框架自身所维护的兼容性检测、完整性校验等公共能力,也能让插件开发者直接使用,降低了开发者的维护成本。 项目未来计划 编译器插件框架已在 openEuler 社区开源,插件服务端在 openEuler 社区的代码仓链接: https://gitee.com/openeuler/pin-server , GCC 客户端的链接: https://gitee.com/openEuler/pin-gcc-client , LLVM 客户端也即将在 openEuler 社区开源。欢迎大家使用、讨论、一起贡献。 在未来,我们专注于进一步提高插件 API 的功能和 IR 转换的覆盖率。针对插件易用性和调试便捷性,和社区贡献者一起讨论,提高编译器插件框架的客户体验。当前编译器插件框架的客户端均基于 GCC 等编译器自身的插件系统构建,因此只能处理中端优化。未来,编译器插件框架也将着力构建覆盖前端、后端、链接等阶段的插件体系,扩大作用范围。
2. 社区大事记
2022 openEuler Summit 技术分论坛 -- 编译器插件框架 2023 openEuler Developer Day -- 编译器插件框架亮相展台 2023 开放原子全球开源峰会 ?C 编译器插件框架亮相展台 2023 Open Source Summit Europe 主题演讲 -- The Compiler Plugin Framework to Facilitate Customized Compilation and Development