SPIR-V 后端进展
摘要
Zig 的 SPIR-V 后端经过重构,新增了内置类型、改进了执行模式处理、从 CPU 特性中提取能力和扩展、支持多线程代码生成和对象文件链接,使其对着色器和 GPU 编程更加实用。
<p><a href="https://lobste.rs/s/ymhp52/spir_v_backend_progress">评论</a></p>
查看缓存全文
缓存时间: 2026/06/26 06:05
# 开发日志 ⚡ Zig 编程语言
来源:https://ziglang.org/devlog/2026/
此页面包含主线分支 Zig 近期变更的精选列表。
此页面收录的是2026年的条目。其他年份的条目请见开发日志归档页面 (https://ziglang.org/devlog/)。
2026年6月26日
## SPIR-V 后端进展 (https://ziglang.org/devlog/2026/#2026-06-26)
作者:Ali Cheraghi
内容不少。最近编译器变更后,SPIR-V 后端在多处出现了位腐烂问题,因此我花了过去几周的时间将其拖回更好的状态。
## @SpirvType
SPIR-V 有一些类型无法用 Zig 的类型系统表达。新增的 `@SpirvType` 内置函数旨在解决编写着色器时长期存在的阻碍。参见 #20550 (https://github.com/ziglang/zig/issues/20550)、#23326 (https://github.com/ziglang/zig/pull/23326) 和 #35461 (https://codeberg.org/ziglang/zig/pulls/35461) 以了解背景。
```
const Sampler = @SpirvType(.sampler);
const Image = @SpirvType(.{ .image = .{
.usage = .{ .sampled = u32 },
.format = .unknown,
.dim = .@"2d",
.depth = .unknown,
.arrayed = false,
.multisampled = false,
.access = .unknown,
} });
const SampledImage = @SpirvType(.{ .sampled_image = Image });
const RuntimeArray = @SpirvType(.{ .runtime_array = u32 });
const sampled_image = @extern(*addrspace(.constant) const SampledImage, .{
.name = "sampled_image",
.decoration = .{ .descriptor = .{ .set = 0, .binding = 1 } },
});
```
## 调用约定上的执行模式
执行模式信息(工作组大小、片元原点等)现在通过调用约定来携带,而不是通过内联汇编发出 `OpExecutionMode`。旧的 `std.gpu.executionMode()` 辅助函数已移除,SPIR-V 汇编器现在拒绝手动使用 `OpExecutionMode` 指令。还新增了两个调用约定 `spirv_task` 和 `spirv_mesh`,用于网格着色管线。
```
export fn vert() callconv(.spirv_vertex) void {}
export fn frag() callconv(.{ .spirv_fragment = .{ .depth_assumption = .greater } }) void {}
export fn comp() callconv(.{ .spirv_kernel = .{ .x = 8, .y = 8, .z = 1 } }) void {}
export fn task() callconv(.{ .spirv_task = .{ .x = 1, .y = 1, .z = 1 } }) void {}
export fn mesh() callconv(.{ .spirv_mesh = .{ .stage_output = .output_lines, .max_primitives = 1, .max_vertices = 2 } }) void {}
```
## 从 CPU 特性派生的能力和扩展
以前,能力和扩展是通过代码生成或内联汇编临时发出的。现在它们完全由 CPU 特性集驱动,与其他目标一样,依赖链从 SPIRV-Headers (https://github.com/KhronosGroup/SPIRV-Headers) 中提取(暂不包括外部供应商),汇编器现在拒绝任何直接发出 `OpCapability` 或 `OpExtension` 的尝试。
## 多线程代码生成
从一开始,SPIR-V 后端就在链接器线程内单线程运行代码生成。现在每个代码生成任务都会生成一个 `Mir` 值,就像所有其他自托管后端一样,并调度到编译器的线程池上。
同样的变更还带回了两个在早期重构中被移除的 ISel 过程:`dedup_types`(合并等效的类型指令)和 `prune_unused`(从最终模块中移除死代码)。这些过程最初在代码生成单线程时被删除。
## 目标文件链接
`.spv` 文件现在被识别为目标文件。你可以编译多个 `.zig` 文件(或外部的 `.spv` 目标文件),并由 SPIR-V 链接器将它们整合成一个模块。
在此过程中还修复了数十个错误,在 `spirv64-vulkan` 目标上,总通过的行为测试增加了近 10%(目前为 49%)。`std.gpu` 已重命名为 `std.spirv`,SPIR-V 后端比一个月前更有实用价值,但仍有很长的路要走。许多行为测试在 SPIR-V 上仍被跳过。不过,如果你一直在犹豫是否尝试使用 Zig 编写着色器或计算内核,现在是个好时机。非常欢迎在 Codeberg (https://codeberg.org/ziglang/zig/issues) 上提交错误报告。祝编程愉快!
2026年6月25日
## 新的 @bitCast 语义与 LLVM 后端改进 (https://ziglang.org/devlog/2026/#2026-06-25)
作者:Matthew Lugg
(这篇开发日志有点长,抱歉——我写得有点投入了!)
几周前,我开始在一个分支上实现 LLVM 后端的一项改进,这个计划已经酝酿很久了。结果这演变成了一次更大的变更,实现了几个你可能感兴趣的语言提案。
## LLVM 后端整数降级
Zig 一直将任意位宽的整数类型(例如 `u4`、`i13`、`u40`)直接降级为 LLVM IR 的位整数类型(`i4`、`i13`、`i40`)。然而,我们很早就知道这种降级并非最优,因为 LLVM 对这些类型在内存中表示的文档化语义对优化器施加了不必要的限制。或许更重要的是,由于 Clang 从未发出过这样的 LLVM IR,LLVM 中的这些代码路径从未得到充分测试,因此在实践中支持不佳——过去几年中,我们观察到许多丢失简单优化 (https://github.com/ziglang/zig/issues/17768) 甚至直接出现编译错误 (https://codeberg.org/ziglang/zig/issues/35560) 的情况。
因此,该 PR 的原始目标是在 SSA 形式中处理值时仅使用这些位整数类型,并在存储到内存时将其零扩展或符号扩展为 ABI 大小的类型(`i8`、`i16`、`i32` 等)。这应该能得到良好支持,尤其是因为它与 Clang 降级 C 的 `_BitInt(N)` 的方式一致!
那个变更实际上相当直接,但我遇到了一个问题,让我深入探究了一番。
## `@bitCast` 的问题
`@bitCast` 是一个有趣的内置函数。在过去,它被定义为等价于以下操作序列:
- 取操作数值的指针
- 将其转换为目标类型的指针
- 从该指针加载
换句话说,它本质上是重新解释内存字节的语法糖。然而,随着时间的推移,我们偏离了这个定义——例如,允许使用 `@bitCast` 将 `[3]u8` 重新解释为 `u24`,尽管在大多数目标上 `@sizeOf(u24)` 大于 `@sizeOf([3]u8)`,因此上述定义会引发非法行为。
到目前为止,LLVM 后端一直为 `@bitCast` 内置函数实现这些未明确指定的语义。但是,由于该定义涉及重新解释内存,改变我们在内存中存储整数类型的方式最终影响了 `@bitCast` 的实现,并引入了非法行为,导致编译器测试套件崩溃。
这个问题最简单的解决方案可能是在 LLVM 后端实现逻辑来近似匹配旧行为。我选择了更好的解决方案——实现 `@bitCast` 的新定义。
## 重新定义 `@bitCast`
2024 年,Jacob Young 撰写了语言提案 #19755 (https://github.com/ziglang/zig/issues/19755),旨在通过精确指定一组新语义来解决 `@bitCast` 的问题。该提案提交后不久就被接受了,事实上,它所详述的语义已经由自托管的 x86_64 后端实现!因此,为了解决 LLVM 后端的问题,我不一定需要匹配旧的 `@bitCast` 语义——相反,这似乎是最终在所有地方实现新语义的好时机。
顺便提一下,这样做还有一个好处,我们可以利用编译器的 `Legalize` 过程,该过程将难以降级的操作重写为更简单的操作,这样编译器后端只需要支持那些简单的操作。`Legalize` 已经具有自托管 x86_64 后端使用的功能,将复杂的 `@bitCast` 操作转换为简单的操作,并且可以很容易地适配以帮助其他编译器后端(主要是 LLVM 和 C 后端)——但前提是它们实现新的语义。
无论如何,关键是我开始了一个支线任务(结果比原始任务还要困难),在整个编译器中实现这些新语义。这不仅包括 LLVM 和 C 后端,还包括 `comptime` 执行——毕竟,Zig 允许你在编译时执行几乎所有操作,包括 `@bitCast`!由于新语义与旧语义有显著不同(稍后会详细介绍),我还必须审计标准库、编译器和支持库(例如 `compiler_rt`)中大量使用 `@bitCast` 的地方。但在经过几次基本无痛的 CI 失败修复后,我终于使我的 PR (https://codeberg.org/ziglang/zig/pulls/35711) 变绿,并在昨天将其合并到 master(同时关闭了好几个问题!)。
## 新的 `@bitCast` 语义
在讲完所有背景之后,终于可以真正解释新的 `@bitCast` 行为了。与以前基于重新解释内存字节不同,这个内置函数现在根据逻辑表示类型的位来定义。
每个支持 `@bitCast` 的类型都有一个“逻辑位布局”——该类型作为有序位序列的表示。例如,`u5` 由 5 个逻辑位组成,我们按从最低有效位到最高有效位的顺序排列。`[2]u5` 由 10 个逻辑位组成——第一个元素的 5 位,后跟第二个元素的 5 位。`@bitCast` 的新定义是它将一种类型的逻辑位重新解释为另一种类型的逻辑位。
最简单的例子是取一个无符号整数,比如 `u8`,并将其转换为相同大小的有符号整数,这里为 `i8`。这个操作完全符合你的预期——位不变,我们只是将最高有效位重新解释为符号位。`@bitCast` 在整数类型和 `packed struct`/`packed union` 类型之间的语义也保持不变。
新语义与旧语义不同之处在于涉及聚合类型(数组和向量)时。
例如,考虑将 `[2]u8` 位转换为 `u16`。在旧语义下,此操作的结果取决于目标端序:在大端目标上,第一个数组元素成为 8 个最高有效位;而在小端目标上,第一个数组元素成为 8 个最低有效位。在新语义下,因为我们只关心逻辑位表示(与端序无关),该操作在每个目标上的行为相同:第一个数组元素成为 8 个最低有效位。作为一般规则,新语义倾向于匹配小端目标上的旧语义行为。
这个定义还允许一些更奇怪的操作,例如将 `[2]u3` 转换为 `@Vector(3, u2)`:
```
test "bitcast [2]u3 to @Vector(3, u2)" {
const arr: [2]u3 = .{ 0b001, 0b011 };
const vec: @Vector(3, u2) = @bitCast(arr);
// 连接 `arr` 的所有位,从 `arr[0]` 的最低有效位开始,得到
// 逻辑位序列,然后从中读取 2 位块,得到结果向量值 `vec` 的元素。
//
// arr[0] arr[1]
// 0b001 0b011
// ------------- -------------
// 1 0 0 1 1 0
// -------- -------- --------
// 0b01 0b10 0b01
// vec[0] vec[1] vec[2]
try expect(vec[0] == 0b01);
try expect(vec[1] == 0b10);
try expect(vec[2] == 0b01);
}
const expect = @import("std").testing.expect;
```
这种操作在大多数时候并不是很有用,但如果你需要,它就在那里!例如,也许你想将一个整数分解成单个位的向量以便操作——现在可以通过 `@bitCast` 到 `@Vector(n, u1)` 来实现。
在做所有这些事情的同时,我还实现了一些较小的已接受提案——这里不详细说明,但如果你感兴趣,可以看看这些 issue:
- 禁止 `@bitCast` 到/从指针向量(#18936 (https://github.com/ziglang/zig/issues/18936))
- 允许对枚举进行 `@bitCast`(#35602 (https://codeberg.org/ziglang/zig/issues/35602) 的一部分)
当然,所有这些语义变更将在 0.17.0 发布说明中解释(希望能比我在这里写得更简洁!),并会给出建议的迁移步骤。
## LLVM 后端性能
最后,我想提一下,这个分支的原始动机——改变 LLVM 后端降低非 ABI 整数类型的方式——在恢复丢失的优化方面取得了明显成功 (https://github.com/ziglang/zig/issues/17768#issuecomment-4787726124)。事实上,Zig 编译器本身——尽管内部没有大量使用任意位宽整数!——由于更好的优化,性能提升了大约 5%。这意味着在 0.17.0 中,你可能会期待一些小的运行时性能提升。
感谢阅读,希望这对你们中的一些人来说很有趣。祝编程愉快!
2026年5月30日
## ELF 链接器改进 (https://ziglang.org/devlog/2026/#2026-05-30)
作者:Matthew Lugg
过去几周我一直在研究我们新的 ELF 链接器,它在 Zig 0.16.0 中首次亮相。在 0.16.0 发布时,这个链接器实现还处于早期阶段,只真正支持链接纯 Zig 代码,不包含任何外部库(甚至 libc)——这就是为什么它当时(现在仍然)默认禁用(可以通过 `-fnew-linker` 启用)。不过,自那次初始发布以来,已经取得了相当大的进展!
这里有一个不错的里程碑——根据我最近的 PR (https://codeberg.org/ziglang/zig/pulls/35533),新的 ELF 链接器能够构建启用 LLVM 和 LLD 库的自托管 Zig 编译器,这需要底层相当多的功能。
```
[mlugg@nebula master]$ # 使用新链接器构建 Zig 编译器:
[mlugg@nebula master]$ zig build -Dno-lib -Dnew-linker -Denable-llvm
[mlugg@nebula master]$ # 使用该编译器构建带有 LLVM 和 LLD 的程序:
[mlugg@nebula master]$ ./zig-out/bin/zig build-exe ~/hello.zig -fllvm -flld
[mlugg@nebula master]$ ./hello
Hello, World!
[mlugg@nebula master]$
```
当然,ELF 链接器不一定是最令人兴奋的东西,这就是为什么这个新链接器的主打功能是支持快速增量编译。经过最近的增强,现在(在 x86_64 Linux 上)可以在链接外部库、C 源码等时进行增量重建——而没有任何额外的性能开销!以下是我在 Andrew 的 Tetris 克隆 (https://github.com/andrewrk/tetris) 上尝试的片段:
对 Andrew 的 Tetris 克隆进行一些简单的更改,每次构建大约需要 30ms。哦,快速增量重建在 Zig 编译器本身也能很好地工作:
```
[mlugg@nebula master]$ zig build -Dno-lib -Denable-llvm -fincremental --watch
Build Summary: 4/4 steps succeeded
install success
└─ install zig success
└─ compile exe zig Debug native success 36s
Build Summary: 4/4 steps succeeded
install success
└─ install zig success
└─ compile exe zig Debug native success 244ms
Build Summary: 4/4 steps succeeded
install success
└─ install zig success
└─ compile exe zig Debug native success 228ms
Build Summary: 4/4 steps succeeded
install success
└─ install zig success
└─ compile exe zig Debug native success 288ms
Build Summary: 4/4 steps succeeded
install success
└─ install zig success
└─ compile exe zig Debug native success 283ms
```
目前这个链接器实现最大的缺失功能是它仍然不支持生成
相似文章
Zig 构建速度正在提升
Zig 0.15 相比 0.14 在编译时性能有显著提升,构建脚本编译时间从约 7 秒降至约 1.7 秒,完整构建时间从 41 秒降至 32 秒,且仍使用 LLVM。本文重点介绍了自托管后端和增量编译方面的进展。
构建系统重构
Zig 构建系统已经重构,将配置器和制造器进程分离,支持缓存、发布模式编译,并且'zig build'命令速度提升高达90%。这一变化提高了性能,并允许构建系统在不减速的情况下增加功能。
Zig ELF 链接器改进开发日志
新的 Zig ELF 链接器现在支持外部库和 C 源码的快速增量编译,在 x86_64 Linux 上能够实现毫秒级重建。
@bitCast 新语义与 LLVM 后端改进
Zig 语言引入了新的 @bitCast 语义,并通过更改整数降低(integer lowering)来避免编译错误,并更好地与编译器优化对齐,从而改进了其 LLVM 后端。
@vllm_project: Rust 前端现已正式合并至 vLLM!随着 GPU 性能不断提升,前端已占据相当比例的 CPU 时间。…
vLLM 的 Rust 前端现已正式合并,可作为 Python API 服务器的直接替代方案,在预处理密集型工作负载上吞吐量提升高达 5 倍。