Julia 推出统一中间表示 UnifiedIR
速览
UnifiedIR 是 Julia 语言引入的一种统一中间表示,用于替代原有多种 IR,简化编译流程并提升代码优化效率。该设计有望降低 Julia 在机器学习、科学计算等场景下的运行时开销,并增强跨平台兼容性。此更新对 Julia 生态的发展具有重要意义,尤其有助于高性能计算与 AI 模型的部署。
AI 深度解读
背景
Julia 语言的基础 IR(中间表示)数据结构已经使用了大约十年。虽然它们整体上运行良好,但存在三个主要问题:一是 IR 难以使用(既有通用 API 设计上的原因,也有与所有权和失效相关的语义原因);二是无法扩展以支持生态系统中涌现的各种其他用例,导致开发者要么忍受 hack 式的解决方案,要么自行构建自己的 IR 数据结构;三是它从一开始就不是为外部使用而设计的系统。
为了解决这些问题,社区提出了一个全新的顶层包——UnifiedIR,与 Compiler、JuliaSyntax 和 JuliaLowering 并列,并被这三个包所依赖。UnifiedIR 提供一套自包含、可扩展的 IR 数据结构与操作,既可用于 Julia 基础库,也可用于更广泛的生态系统。
核心内容
UnifiedIR 是一个方言感知的、基于区域(region-based)的 IR 表示,类似于 MLIR,但保留了原始 Julia IR(以及 JuliaLowering 表示)中许多设计良好的部分。它旨在替代以下现有数据结构:
- SyntaxTree(来自 JuliaSyntax)
- SyntaxGraph(来自 JuliaSyntax/Lowering)
- CodeInfo(用于推理目的;代码生成/解释器暂时不在范围之内,但未来应适配)
- IRCode
- 生态系统中的各种其他 IR
为了实现这一目标,UnifiedIR 提供了一个核心数据结构,以高效的方式表示 IR,同时附带核心工具。该核心数据结构的任何使用者都可以通过添加额外的列(每个语句一条记录)来扩展它,并且引入了“分层视图”(layered views)的概念,以便在特定应用中更轻松地操作 IR。
当前定义的分层视图包括:
- IncrementalCompact 风格扩展:允许快速单遍替换(one-pass replacement)的扩展。
- LLVM 风格视图:通过链表维护顺序,使得复杂的 CFG 变换变得容易得多。为了避免 LLVM 中常见的二次复杂度维护问题(这对于某些下游应用很重要),作者与 Claude 共同设计了一种奇特的有序维护算法(仍需验证)。
- 图嵌入视图:供数组编译器以及 lowering 阶段使用(图是没有控制流依赖的 IR)。
- 树状编码:供 lowering 阶段使用(树是 IR 中每个语句只有一个使用者,根节点除外,根节点有零个使用者)。
主要特性包括:
- 对区域的一流支持:使得像 #58532 这样的功能更容易实现,并能更好地与 MLIR 集成。
- 对方言扩展的一流支持:通过命名空间化的种类注册表(kind registry),为引导堆栈预留了静态方言 ID。
UnifiedIR 的设计规格在 UnifiedIR/docs/design.md 中有详细描述,核心思路是:一个扁平的语句表,带有位于共享存储核心上的混合区域(hybrid regions)。该存储核心称为 AttrGraph,包含一个种类列、打包的两模式操作数字段、一个带标签的操作数池以及开放的属性列宇宙。布局状态(builder/dense/editable/floating)恰好有两个重命名点。此外还提供了命名空间化的种类注册表、通用树状处理接口(Tree/NodeList 游标、构造、mapchildren/copy_ast、通过图限定的 :source 链进行来源遍历)、compact! 作为垃圾回收机制(compact_graph!/collect_syntax!)、验证器、打印/解析器以及一个参考解释器。UnifiedIR 零依赖。
与其他包的集成方式:
-
JuliaSyntax:SyntaxGraph 直接包装 UnifiedIR.AttrGraph。种类是真正的核心列,子节点是共享操作数池中标记为 STMT 的单词,由打包的范围字寻址。历史属性(
edge_ranges、edges、attributes)被精确保留(保持 identity 稳定的视图和is_compatible_graph语义)。compact_graph!为 lowering 提供了其一直想要的语法图 GC。 -
SyntaxTree:
SyntaxTree{Attrs} = UnifiedIR.Tree{SyntaxGraph{Attrs}},SyntaxList = UnifiedIR.NodeList。子节点索引、属性属性、newnode/newleaf/mknode/mkleaf/mktree、copy_attrs!/mapchildren/copy_ast、来源遍历、结构化的isapprox和打印都是 UnifiedIR 的通用函数。JuliaSyntax 仅保留真正语法相关的部分(种类注册表包装器、SourceRef/源码文本机制、叶节点载荷约定、解析器集成、prune/unalias/@stm)。 -
种类注册表:历史的模块 ID 机制通过
register_kinds!重新接入共享的 UnifiedIR 注册表。种类模块成为方言,声明连续的 opcode 块(保留范围谓词和BEGIN_/END_标记)。引导栈声明了静态预留的方言 ID:JuliaSyntax=1,JuliaLowering=2,formatter=3,core=0。这样K"..."字面量保持编译时常量。语法K"call"和核心K"call"是不同的种类,共享同一编号空间。种类在__init__中重新注册(注册表是 UnifiedIR 会话状态);编号是确定性的,保持烘焙常量有效。双模式:引导时作为 Base 的一部分绑定Base.UnifiedIR;作为包时依赖UnifiedIR包。 -
JuliaLowering.UnifiedBackend(纯增量,零改动现有源码):重用前端不变的部分——宏展开、脱糖、作用域分析、闭包转换——然后直接生成结构化的 UnifiedIR 区域形式(if/loop/try 区域操作、用于帧变量的 cells、密封出口终结符),而不是 goto 线性 IR。同时带有图限定的来源信息:每个发射的语句携带一个
:source列条目,指向原始语法树游标,因此通用的来源遍历可以从优化后的 IR 语句回溯到源码文本(支持高亮诊断)。collect_syntax!可以针对活 IR 对 lowering 图进行 GC。该后端是纯定义,在引导期间不会被调用,在可选的 sys-JL 系统映像阶段可以干净地烘焙。绑定通过外层 JuliaLowering 解析(在 Base 烘焙模式和包模式下使用相同的 JuliaSyntax 和相同的 UnifiedIR 实例)。
关于编译管道(仅包模式,系统映像引导永远不会看到它):
- CodeInfo <-> UnifiedIR 边界转换器:包括 CFG 包装入口(含异常处理)、goto/typed 出口与 phi 合成。
- 推理端口:原生运行在 UnifiedIR 上,通过差异验证 100% 相等或更好的返回类型,与标准管道在会话的 MethodInstances 上比较。
- 优化器 passes:SROA、内联(通过
splice_body!)、ADCE、结构化、单元提升。 - Queries API:通过普通的编译器替换机制激活(
Core.OptimizedGenerics.CompilerPlugins.typeinfowner hook +with_unified_compiler)。每个缓存结果都会通过已验证的 UnifiedIR 影子对象往返。 - 加载机制:
Compiler.load_unified!()按需将端口载入到以 Main 为根的载体模块中,并绑定为Compiler。
当前 PR 已经将推理、优化器和 JuliaLowering 移植到了新数据结构上,但尚未启用 bootstrap。作者运行了大约一半的测试套件,一切正常。这应被视为预览 PR 和讨论基础,还有许多工作待完成。
关键要点
- UnifiedIR 是一个统一的、方言感知的、基于区域的 IR 表示,旨在替代 Julia 基础库和生态系统中多个分散的 IR 数据结构。
- 核心数据结构是一个扁平语句表,带有一个共享存储核心(AttrGraph),支持通过
