SPIR-V后端稳定性的推进工作报告总结
0 信息来源
https://llvm.org/devmtg/2024-10/slides/techtalk/Paszkowski-Levytskyy-AdvancingSPIR-V-BackendStability.pdf
1. 一段话总结
在2024年LLVM开发者会议上,Vyacheslav Levytskyy和Michal Paszkowski介绍了SPIR-V后端稳定性的推进工作,当前SPIR-V后端已实现显著提升,包括支持约500个LIT测试用例、达到OpenCL 3.0兼容性、SYCL兼容性达93%-99%(依优化级别而定),还完成26个SPIR-V扩展的实现并提升了与Khronos LLVM/SPIR-V Translator的兼容性;同时分析了LLVM IR映射到SPIR-V的关键挑战,如类型不匹配、控制流差异、不透明指针问题等,提出了类型推断、聚合体降低机制、多通行证协同解决不透明指针等技术方案,通过LIT测试、spirv-val验证、spirv-sim工具保障正确性,未来计划推进SPIR-V消费者在LLVM代码库的集成及与后端共享代码,以助力SYCL/DPC++项目。
2. 思维导图(mindmap)
## 会议基础信息
- 会议名称:LLVM Developers' Meeting 2024
- 演讲者:Vyacheslav Levytskyy、Michal Paszkowski
- 主题:Advancing SPIR-V Backend Stability: Navigating GlobalISel Compromises
## SPIR-V后端现状与价值
- 核心价值- SPIR-V:IR+可移植二进制格式,异构加速器编程接口- 丰富生态:支持OpenCL、SYCL、GLSL、HLSL等语言/API- 跨厂商统一IR:Khronos定义核心规范,厂商扩展环境
- 后端成果- 测试与兼容性:500个LIT、OpenCL 3.0兼容、SYCL兼容93%-99%- 扩展与兼容:26个SPIR-V扩展,提升与Khronos LLVM/SPIR-V Translator兼容性- 其他:支持Vulkan/HLSL(进行中),集成外部工具库
## 关键挑战:LLVM IR映射到SPIR-V
- 语义与层级问题- SPIR-V语义丰富,与LLVM IR层级相当(甚至更高)- SPIR-V概念难用Machine IR表示,难循标准GlobalISel翻译模式
- 硬件与类型问题- 无实际加速器硬件,需反向翻译SPIR-V到硬件指令集- LLT类型不足以表达SPIR-V类型
- 具体技术问题- 类型与作用域:虚拟寄存器vs SPIRV标识符,LLVM 17前指针含元素类型而SPIR-V指针必带类型- 控制流:SPIR-V需结构化控制流(OpLoopMerge/OpSelectionMerge),无单独if逻辑,难优化分支- 指令语义:LLVM IR与SPIR-V、GISel’s MIR与SPIR-V存在细微差异(如phi、bitcast)- 不透明指针:LLVM 17过渡后,SPIR-V后端需重新推导指针类型
## 技术解决方案
- 类型推断- 模块级推断:识别指令模式(GEP、alloca等),推导复合类型、指针操作数类型,利用内置函数信息- 处理未知类型:记录未知类型,模块所有函数处理后重新检查
- 聚合体降低机制- 初始方案:替换函数签名中聚合体为i32,元数据记录类型以便后续恢复(存缺陷)- 升级方案:注册类型变更,访问原始函数类型;借助@llvm.fake.use/@llvm.spv.value.md沟通目标
- 不透明指针解决- 三通行证协同:SPIRVEmitIntrinsics(推断指针类型)→ SPIRVCallLowering(创建函数声明/降参数)→ SPIRVPreLegalizer(移除 intrinsic,存储类型映射)
- 控制流与示例问题解决- AsmPrinter标签问题:添加SPIR-V格式判断,避免函数体后生成标签- Machine Verifier问题:即时生成OpBitcast,复用通用操作码表示SPIR-V phi-node至最终编码
- TargetExtType应用- 替代不透明结构体表示SPIR-V特殊类型(如OpTypeImage)- 替代TypedPointerType表示推导的嵌套类型(LLVM无法创建TypedPointerType实例)
## 正确性维护
- 测试工具- LIT测试:核心回归测试手段,扩展后结合spirv-val验证SPIR-V二进制合规性- spirv-sim工具(Google贡献):测试SPIR-V结构化器,灵活且抗脆弱
- 避免测试问题:spirv-sim减少“连锁反应”,无需因后端变更频繁修改测试
## 未来工作
- 定位SPIR-V:作为目标更优,替代Khronos LLVM/SPIR-V Translator
- 代码共享与集成:在LLVM代码库集成SPIR-V消费者,共享后端与消费者代码
- 支持项目:助力上游SYCL/DPC++项目,作为测试与生产解决方案
3. 详细总结
一、会议与主题概述
本次内容来自2024年LLVM开发者会议(LLVM Developers’ Meeting 2024),由Vyacheslav Levytskyy和Michal Paszkowski主讲,核心主题为“推进SPIR-V后端稳定性:应对GlobalISel的妥协(Advancing SPIR-V Backend Stability: Navigating GlobalISel Compromises)”,围绕SPIR-V后端的现状、LLVM IR映射到SPIR-V的挑战、技术解决方案、正确性维护及未来规划展开。
二、SPIR-V与后端的核心价值及当前成果
1. SPIR-V的核心价值
- 定位:既是中间表示(IR),也是可移植二进制格式,作为异构加速器(如GPU、FPGA、NPU)的编程接口。
- 生态:支持丰富的高级语言与API,包括OpenCL、SYCL、GLSL、HLSL等。
- 统一性:核心规范由Khronos Group定义,厂商可基于此扩展客户端API环境,成为跨厂商统一IR,由Intel、Microsoft、Google等Khronos成员公司共同开发。
2. SPIR-V后端的当前成果
| 成果类别 | 具体内容 | 关键数据/指标 |
|---|---|---|
| 测试与兼容性 | LIT测试用例支持、OpenCL兼容性、SYCL兼容性 | 约500个LIT测试用例、OpenCL 3.0完全兼容、SYCL兼容性93%-99%(依优化级别变化) |
| 扩展与协同 | SPIR-V扩展实现、与Khronos工具兼容性 | 已实现26个SPIR-V扩展,提升与Khronos LLVM/SPIR-V Translator的兼容性 |
| 其他进展 | 新API支持、工具集成 | 正在推进Vulkan和HLSL支持,实现与外部工具和库的集成 |
三、LLVM IR映射到SPIR-V的关键挑战
1. 语义与层级差异挑战
- SPIR-V语义丰富,其抽象层级与LLVM IR相当,甚至在部分场景下更高,导致难以通过标准GlobalISel翻译流程将其映射到Machine IR,且无法满足Machine Verifier的要求。
2. 硬件与类型基础挑战
- 无实际硬件依赖:SPIR-V是硬件和厂商无关的格式,需通过反向翻译(如重新编码为LLVM IR)转换为具体加速器(FPGA、GPU、NPU)的指令集,且需进行硬件相关优化。
- 类型表达不足:LLVM的LLT(Low-Level Type)类型无法完整表达SPIR-V的复杂类型需求。
3. 具体技术问题
| 问题类别 | 具体表现 |
|---|---|
| 类型与作用域 | 1. 虚拟寄存器与SPIRV标识符不匹配,GISel虚拟寄存器无法关联原始LLVM IR值,而SPIR-V后端需追踪完整类型(非LLT类型); 2. LLVM 17之前,IR指针包含元素类型,而SPIR-V指针必须带类型,存在“薛定谔的TypedPointerType”问题 |
| 控制流差异 | 1. SPIR-V要求结构化控制流,需通过OpLoopMerge(循环)、OpSelectionMerge(选择)声明,而LLVM IR无此强制要求; 2. SPIR-V中标签是启动逻辑基本块的指令,无条件分支后不能删除指令(需为块内最后一条指令); 3. 无单独“if (Cond) then Stmt”逻辑,OpBranchConditional仅支持完整if-then-else,且无法进行分支折叠、If转换等优化 |
| 指令语义差异 | LLVM IR与SPIR-V、GISel’s MIR与SPIR-V在指令语义上存在细微差异,例如phi节点(SPIR-V OpPhi要求每个父块一个条目,LLVM phi可多个条目对应同一前驱)、bitcast指令的处理逻辑不同 |
| 不透明指针问题 | LLVM 17从“带类型指针”过渡到“不透明指针”,简化了LLVM IR,但SPIR-V后端需指针类型生成代码,导致需重新推导指针类型,影响类型声明、嵌套类型降低、内置函数调用解析等流程 |
四、核心技术解决方案
1. 类型推断方案
- 推断范围与逻辑:在模块级(Module pass)进行类型推断,通过识别指令模式(如GEP的结果元素类型、alloca的分配类型、addrspacecast的指针操作数)推导类型;利用CallInst中内置函数(如OpGroupAsyncCopy、OpAtomic*)的已知信息,对齐ReturnInst、phi、ICmpInst的类型;分析函数调用点推导函数参数类型。
- 未知类型处理:记录模块中的未知类型,待所有函数处理完成后重新访问并解决。
2. 聚合体降低机制
- 初始方案(存在缺陷):移除函数调用中的聚合体以避免崩溃,将函数签名中的聚合体替换为i32,通过元数据记录类型变更以便后续恢复,但存在无法提前推导正确类型、spirv-val报无效SPIR-V、OpStore中类型不匹配等问题。
- 升级方案与优化:
- 注册类型变更,直接访问原始函数类型,避免类型丢失;
- 与IRTranslator协同:通过
void @llvm.fake.use(...)映射虚拟寄存器到原始值,通过void @llvm.spv.value.md(metadata valAttrs)保留名称和数据类型,复用通用逻辑减少维护负担。
3. 不透明指针问题解决方案(三通行证协同)
| 通行证名称 | 核心功能 | 处理逻辑 |
|---|---|---|
| SPIRVEmitIntrinsics | 推断并分配指针类型 | 1. 从常见指令(GEP、Load等)推导类型,发射类型分配intrinsic; 2. 调整操作数类型,发射指针转换intrinsic; 3. 处理phi节点(不同入值类型取最频繁者),递归解决类型不一致; 4. 用assign_ptr_type为SSA值分配类型,或替换为ptrcast intrinsic |
| SPIRVCallLowering | 创建函数声明,降低形式参数 | 1. 生成含类型的SPIR-V函数/参数声明(如OpFunction、OpFunctionParameter); 2. 优先从byval/byref属性、assign_type(SPIR-V内置类型)、assign_ptr_type获取指针类型; 3. 硬编码/通过TableGen(SPIRVBuiltins.td)解析OpenCL/GLSL内置函数类型(解决Itanium mangling无返回类型信息问题) |
| SPIRVPreLegalizer | 清理与类型存储 | 1. 移除所有assign_ptr_type/assign_type intrinsic调用; 2. 为每个MIR寄存器分配相关类型,通过GlobalRegistry存储类型映射,确保模块内类型声明唯一 |
4. TargetExtType的应用
- 用途:LLVM 16新增TargetExtType,用于保存目标相关且目标无关优化无法处理的类型,SPIR-V后端用其:
- 替代原不透明结构体(如%opencl.event_t = type opaque)表示SPIR-V特殊类型(OpTypeImage、OpTypeEvent等),格式为
target("spirv.Event"); - 替代TypedPointerType表示SPIRVEmitIntrinsics中推导的嵌套类型(因LLVM无法创建TypedPointerType实例)。
- 替代原不透明结构体(如%opencl.event_t = type opaque)表示SPIR-V特殊类型(OpTypeImage、OpTypeEvent等),格式为
- 兼容性:导致SPIR-V后端与LLVM旧版本生成的IR不兼容(因不透明指针过渡)。
5. 典型问题解决案例
| 问题案例 | 问题描述 | 解决方案 |
|---|---|---|
| AsmPrinter标签问题(#107013) | 含有效调试信息时,AsmPrinter强制生成函数结束符号,而SPIR-V标签仅允许在块内,不能在函数体后 | 修改AsmPrinter代码,添加getObjectFormat() != Triple::SPIR判断,避免SPIR-V格式下函数体后生成标签 |
| Machine Verifier与G_BITCAST/G_PHI(#110270) | phi节点入值类型不同(如%r1为_ptr_Function_uchar,%r2为_ptr_Function_uint),G_BITCAST复用导致验证失败 | 即时生成OpBitcast(非复用G_BITCAST),因OpBitcast非无操作bitcast;复用通用操作码表示SPIR-V phi-node,直至最终编码转为OpPhi |
| 自定义OpPhi问题(#110019、#110507) | SPIR-V OpPhi要求每个父块一个条目,LLVM phi可多个条目对应同一前驱;Machine Verifier不识别OpPhi | 指令选择阶段按SPIR-V规则生成OpPhi,遵循“复用GlobalISel共享代码”原则;用通用操作码标记OpPhi直至最终编码 |
五、正确性维护措施
1. 测试工具与流程
- LIT测试:核心回归测试手段,优势是快速定位回归问题、测试用例简洁;扩展后结合Khronos的SPIR-V Tools(spirv-val)验证输出SPIR-V二进制是否符合规范,测试在ubuntu-latest环境中45分钟内可完成。
- spirv-sim工具:由Google贡献,用于测试SPIR-V结构化器(控制流、跨通道交互),相比FileCheck更灵活、抗脆弱(无需匹配输出CFG顺序,避免后端变更导致测试“连锁修改”)。
六、未来工作规划
- SPIR-V目标定位:过去两年SPIR-V作为LLVM目标已成熟,常优于双向的Khronos LLVM/SPIR-V Translator,计划进一步强化其目标地位。
- 集成SPIR-V消费者:LLVM及依赖项目将受益于在代码库中集成SPIR-V消费者,实现后端与消费者的代码共享(如类型处理、控制流逻辑)。
- 支持SYCL/DPC++项目:将集成后的SPIR-V后端与消费者作为上游SYCL/DPC++项目的测试和生产解决方案,提升项目兼容性与稳定性。
4. 关键问题
问题1:SPIR-V后端当前已达成的核心成果有哪些?这些成果对实际开发有何意义?
答案
- 核心成果:① 测试与兼容性方面,支持约500个LIT测试用例,达到OpenCL 3.0完全兼容,SYCL兼容性依优化级别达93%-99%;② 扩展与协同方面,已实现26个SPIR-V扩展,提升了与Khronos LLVM/SPIR-V Translator的兼容性;③ 功能扩展方面,正在推进Vulkan和HLSL的支持,且实现了与外部工具和库的集成。
- 实际开发意义:① OpenCL 3.0和高比例SYCL兼容,确保基于OpenCL/SYCL的异构计算项目(如GPU加速的AI推理、科学计算)可无缝适配SPIR-V后端,减少兼容性调试成本;② 26个SPIR-V扩展覆盖更多硬件特性(如特殊指令、资源类型),满足复杂场景需求;③ 外部工具集成与Vulkan/HLSL支持,拓宽了SPIR-V后端的应用场景,可适配图形渲染(Vulkan)、游戏开发(HLSL)等领域。
问题2:LLVM 17引入的“不透明指针”给SPIR-V后端带来了哪些核心挑战?后端通过何种技术方案解决了这些挑战?
答案
- 核心挑战:① 类型推导断层,LLVM 17前指针含元素类型,SPIR-V后端依赖该信息进行类型声明、嵌套类型(结构体/数组)降低、OpenCL内置函数调用解析等,不透明指针移除元素类型后,这些流程无法正常进行;② 类型一致性维护,函数调用(尤其是函数指针、间接调用)和phi节点入值可能存在不同类型,需确保推导后类型一致,避免SPIR-V二进制无效。
- 解决方案:采用三通行证协同机制:① 第一阶段(SPIRVEmitIntrinsics):从常见指令(GEP、Load等)推导指针类型,发射类型分配/转换intrinsic,处理phi节点类型冲突(取最频繁类型),递归解决类型不一致;② 第二阶段(SPIRVCallLowering):生成含类型的SPIR-V函数/参数声明,优先从属性、内置类型映射获取指针类型,通过TableGen解析内置函数类型(解决mangling无返回类型问题);③ 第三阶段(SPIRVPreLegalizer):移除intrinsic,通过GlobalRegistry存储类型映射,确保模块内类型声明唯一,最终实现不透明指针到SPIR-V类型的正确映射。
问题3:SPIR-V后端在控制流处理上与LLVM IR存在哪些关键差异?针对这些差异,后端采取了哪些措施保障正确性?
答案
- 关键差异:① 控制流结构要求不同,SPIR-V强制结构化控制流,需通过OpLoopMerge(循环)、OpSelectionMerge(选择)显式声明,而LLVM IR无此要求;② 分支逻辑表达不同,SPIR-V无单独“if (Cond) then Stmt”指令,仅通过OpBranchConditional实现完整if-then-else,且无法进行分支折叠、If转换等LLVM常见分支优化;③ 标签规则不同,SPIR-V中标签是启动基本块的指令,无条件分支后不能删除指令(需为块内最后一条),而LLVM IR标签无此严格位置限制。
- 正确性保障措施:① 针对结构化控制流,开发spirv-sim工具(Google贡献)测试SPIR-V结构化器,验证控制流生成是否符合规范,避免非结构化控制流导致的SPIR-V无效;② 针对分支逻辑差异,在指令选择阶段严格按SPIR-V规则生成分支指令,放弃LLVM原生分支优化(如分支折叠),确保语义正确性;③ 针对标签规则,修改AsmPrinter代码(添加SPIR-V格式判断),避免在函数体后生成标签,同时在Machine Verifier阶段验证标签位置,确保符合SPIR-V规范;④ 复用GlobalISel共享代码时,通过通用操作码标记SPIR-V特有控制流指令(如OpPhi),直至最终编码阶段再转为标准SPIR-V指令,避免中间流程误判。
核心基础术语
- SPIR-V:兼具中间表示(IR)和可移植二进制格式的特性,是异构加速器(如GPU、FPGA、NPU)的编程接口,由Khronos Group制定核心规范,支持跨厂商统一适配。
- LLVM IR:LLVM编译器框架的中间表示,是连接高级语言与目标硬件指令集的关键环节,SPIR-V后端需将其映射为SPIR-V格式。
- GlobalISel:LLVM的全局指令选择框架,负责将LLVM IR转换为Machine IR,SPIR-V后端在适配该框架时需解决多类兼容性问题。
- SYCL/DPC++:SYCL是面向异构计算的编程模型,DPC++是基于SYCL的实现,SPIR-V后端需提供高兼容性支持(当前达93%-99%)。
- OpenCL:开放计算语言,用于异构平台并行编程,SPIR-V后端已实现OpenCL 3.0完全兼容。
- Khronos Group:制定SPIR-V、OpenCL、Vulkan等标准的行业联盟,主导跨厂商异构计算技术规范。
技术架构与工具术语
- Machine IR(MIR):LLVM编译流程中介于LLVM IR和目标硬件指令集之间的中间表示,SPIR-V概念难以直接在其中表达。
- SPIR-V Backend:LLVM中负责将LLVM IR转换为SPIR-V格式的模块,由Intel、Microsoft、Google等Khronos成员公司共同开发。
- Khronos LLVM/SPIR-V Translator:双向转换LLVM IR与SPIR-V的工具,SPIR-V后端已提升与其的兼容性。
- SPIR-V Tools:Khronos提供的SPIR-V工具集,含spirv-val(验证SPIR-V二进制合规性)等核心工具。
- LIT:LLVM的集成测试框架,是SPIR-V后端的核心回归测试工具,支持快速定位代码回归问题。
- spirv-sim:Google贡献的测试工具,用于验证SPIR-V结构化器的控制流和跨通道交互逻辑,抗脆弱性强。
- AsmPrinter:LLVM中负责生成汇编代码(含SPIR-V二进制)的组件,需适配SPIR-V的标签指令规则。
- Machine Verifier:LLVM的机器码验证工具,用于检查Machine IR的合法性,SPIR-V后端需适配其对类型、控制流的校验规则。
类型与指令相关术语
- LLT(Low-Level Type):LLVM的底层类型系统,无法完整表达SPIR-V的复杂类型需求。
- TypedPointerType:LLVM 17之前的带类型指针类型,包含指针指向的元素类型,SPIR-V指针强制要求带类型。
- Opaque Pointers:LLVM 17引入的不透明指针类型,移除元素类型信息,给SPIR-V后端的类型推导带来挑战。
- TargetExtType:LLVM 16新增的目标扩展类型,用于表示SPIR-V特殊类型(如OpTypeImage)和推导的嵌套类型。
- OpLabel:SPIR-V中启动逻辑基本块的指令,是基本块的首个指令,位置有严格限制。
- OpPhi:SPIR-V中的phi节点指令,要求为当前块的每个父块提供一个输入条目,与LLVM IR的phi节点语义存在差异。
- OpBitcast:SPIR-V中的类型转换指令,非无操作转换,需在指令选择阶段即时生成。
- OpLoopMerge:SPIR-V中声明结构化循环的指令,是结构化控制流的核心指令之一。
- OpSelectionMerge:SPIR-V中声明结构化选择(if-then-else)的指令,确保控制流符合结构化要求。
- OpBranchConditional:SPIR-V中的条件分支指令,仅支持完整的if-then-else逻辑,无单独的if逻辑表达。
- Aggregate Types:聚合类型(如结构体、数组),SPIR-V中需显式保留在模块作用域,不能被GlobalISel拆解为底层类型。
编译流程与优化术语
- IRTranslator:GlobalISel中负责将LLVM IR转换为Machine IR的组件,SPIR-V后端需与其协同处理聚合体、类型映射问题。
- Call Lowering:函数调用的降低过程,将LLVM IR中的函数调用转换为Machine IR,SPIR-V后端需适配SPIR-V的函数声明和参数类型规则。
- PreLegalizer:GlobalISel的预处理阶段,SPIR-V后端通过SPIRVPreLegalizer移除类型相关intrinsic并存储类型映射。
- Intrinsic:LLVM的内置函数,SPIR-V后端通过自定义intrinsic(如assign_ptr_type)实现类型推导和映射。
- SSA(Static Single Assignment):静态单赋值形式,LLVM IR和SPIR-V均基于SSA,要求每个变量仅赋值一次。
- CFG(Control Flow Graph):控制流图,描述程序中基本块的跳转关系,SPIR-V要求CFG为结构化控制流图。
- Branch Folding:分支折叠优化,通过合并冗余分支简化控制流,SPIR-V因结构化控制流要求不支持该优化。
- If Conversion:if转换优化,将条件执行的代码转换为无分支代码,SPIR-V不支持该优化。
其他关键术语
- SPIR-V Extensions:SPIR-V的扩展功能,当前SPIR-V后端已实现26个扩展,扩展核心规范的功能边界。
- GlobalRegistry:SPIR-V后端的全局注册表,用于存储寄存器与类型的映射关系,确保模块内类型声明唯一。
- TableGen:LLVM的代码生成工具,SPIR-V后端通过SPIRVBuiltins.td定义OpenCL/GLSL内置函数的类型规则。
- Itanium Mangling:C++名字修饰规则,不包含函数返回类型信息,给SPIR-V后端解析内置函数类型带来挑战。
- Byval/Byref Attributes:LLVM IR中的函数参数属性,用于标识参数传递方式,SPIR-V后端在类型推导时优先使用这些属性的类型信息。
