所有包管理功能已从编译器移至构建系统
摘要
Zig将所有包管理功能从编译器移至构建系统,从而减小二进制文件大小,并简化补丁和安全检查。这一架构变化改进了构建服务器协议,并解除了ZLS集成的阻塞。
<p><a href="https://lobste.rs/s/4liqdw/all_package_management_functionality">评论</a></p>
查看缓存全文
缓存时间: 2026/07/01 14:01
# 开发日志 ⚡ Zig 编程语言
来源:https://ziglang.org/devlog/2026/?2026-06-30
本页面收录了 main 分支 Zig 近期变更的精选列表。
本页面收录了 2026 年的条目。其他年份的开发日志可在开发日志存档页面(https://ziglang.org/devlog/)中查看。
2026年6月30日
## 所有包管理功能从编译器移至构建系统 (https://ziglang.org/devlog/2026/?2026-06-30#2026-06-30)
作者:Andrew Kelley
既然用户的 build.zig 脚本与构建系统本身现在有独立的进程,那么将包管理逻辑放在构建系统这边就显得合理了。
我将以下子命令移至 maker 进程:
- `zig build`
- `zig fetch`
- `zig init`
- `zig libc`
这意味着编译器可执行文件中原先包含的很大一部分内容现在改以源码形式分发,包括:
- 包获取逻辑
- HTTP 客户端与网络
- TLS(传输层安全)及相关的加密算法
- Git 协议
- xz、gzip、zstd、flate、zip
- 解析、验证和处理 `build.zig.zon` 文件
因此,现在无需重新编译编译器即可修补这些功能,从而方便用户和贡献者进行尝试。
此外,这意味着 Zig 的包管理在进行网络操作时启用了安全检查,因为 maker 可执行文件是以 `ReleaseSafe` 模式编译的。而且,用于网络和文件哈希的所有加密算法现在都可以利用宿主机上可用的特殊 CPU 指令,即使是那些在分发软件时通常因过于罕见而无法依赖的指令也可以利用。*我们既能拥有 AOT 蛋糕,也能吃掉 JIT !*
我这样做的初衷是,为了在 maker/configurer 进程分离(https://ziglang.org/devlog/2026/?2026-06-30#2026-05-26)对 `--build-runner` 覆盖标志做出破坏性变更后,能够暴露构建服务器协议(https://codeberg.org/ziglang/zig/issues/35538)以解除对 ZLS(https://zigtools.org/zls/)的阻塞。
原先的进程树是这样的:
```
zig build (Zig 编译器 + 包管理器)
└─ builder (用户 build.zig 逻辑 + 构建系统实现)
```
进程分离变更集使其变成了这样:
```
zig build (Zig 编译器 + 包管理器)
├─ configurer (用户 build.zig 逻辑)
└─ maker (构建系统)
```
此时,考虑一个长期运行的 `zig build --watch` 进程,它监视文件并在源代码发生变化时重新构建。如果检测到 `build.zig` 有任何变化,或者执行该逻辑期间观察到的任何文件发生变化,则意味着需要重新运行 configurer,也就是 maker 进程必须退出,以便让 zig build 有机会重复执行包管理逻辑。
现在,经过本开发日志条目所述变更之后,进程树变成了这样:
```
zig build (Zig 编译器)
└─ maker (构建系统 + 包管理器)
└─ configurer (用户 build.zig 逻辑)
```
因此,当需要重新运行配置时,maker 进程可以继续存活,因为它是父进程而非同级进程。就即将推出的构建服务器而言,这意味着避免了服务器必须退出而客户端必须重新连接的尴尬局面,取而代之的是直接通知客户端配置发生了变化。
这几乎是一个非破坏性变更,但仍有一些可观察到的差异:
- Zig 可执行文件大小:从 14.1 MiB 缩小 4% 至 13.5 MiB(无 LLVM,ReleaseSmall)
- `--maker-opt` 标志被 `ZIG_DEBUG_MAKER` 环境变量取代
- `--zig-lib-dir` 标志被 `ZIG_LIB_DIR` 环境变量取代
此变更集后续待解决的问题是标记 Zig 0.17.0 之前的主要阻塞项:
- 构建服务器协议 MVP(https://codeberg.org/ziglang/zig/issues/35538)(需要解除 ZLS 的阻塞)
- 引入向构建脚本自身添加路径依赖的概念(https://codeberg.org/ziglang/zig/issues/35473)
- 让 `zig build --watch` 检测构建脚本的修改并自动重新运行(https://github.com/ziglang/zig/issues/20602)
- 不同的 cwd 导致构建脚本缓存未命中(https://codeberg.org/ziglang/zig/issues/35500)
我七月有两个会议要参加,需要准备演讲,所以实事求是地说,我认为直到八月初我才有时间完成这些工作。当然,欢迎贡献。
特别感谢 ZLS 团队的 Techatrix 联系我并一起合作构建服务器协议。顺便一提,他们正在寻求资助。
2026年6月26日
## SPIR-V 后端进展 (https://ziglang.org/devlog/2026/?2026-06-30#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)以追溯背景。
```zig
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`,用于网格着色管线。
```zig
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 链接器将它们拼合为单个模块。
在此过程中还修复了数十个 bug,`spirv64-vulkan` 目标上总通过的 behavior 测试增加了近 10%(现在达到 49%)。`std.gpu` 已重命名为 `std.spirv`,SPIR-V 后端比一个月前更有用了,但仍有很长的路要走。大量 behavior 测试在 SPIR-V 上仍被跳过。不过,如果你一直犹豫是否要用 Zig 编写着色器或计算内核,现在是个好时机试试看。欢迎在 Codeberg(https://codeberg.org/ziglang/zig/issues)提交 bug 报告。快乐编码!
2026年6月25日
## 新的 @bitCast 语义与 LLVM 后端改进 (https://ziglang.org/devlog/2026/?2026-06-30#2026-06-25)
作者:Matthew Lugg
(开发日志比较长,抱歉——这个我有点写嗨了!)
几周前,我开始在一个分支上工作,旨在实现 LLVM 后端的一个长期计划的改进。这最终滚雪球般变成了一项更大的变更,实现了你可能感兴趣的几个语言提议。
## LLVM 后端整数降低
Zig 一直将任意位宽整数类型(例如 `u4`、`i13`、`u40`)直接降低为 LLVM IR 的 bit-int 类型(`i4`、`i13`、`i40`)。然而,我们早就知道这种降低方式并非最优,因为 LLVM 对于内存中表示这些类型的文档化语义不必要地限制了优化器。也许更重要的是,由于 Clang 从不发出这样的 LLVM IR,这些代码路径在 LLVM 中从未被充分测试,因此在实际中支持很差——过去几年里,我们观察到许多次遗漏了简单优化(https://github.com/ziglang/zig/issues/17768)甚至直接错误编译(https://codeberg.org/ziglang/zig/issues/35560)的情况。
因此,PR 的原始目标是在 SSA 形式中操作值时仅使用这些 bit-int 类型,并在存储到内存时将其零扩展或符号扩展至 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 和后端)——但前提是它们实现了新语义。
无论如何,关键是,我开始了一场支线任务(结果比*原始*任务还要难),即在整个编译器中实现这些新语义。这不仅包括 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`)。这个操作完全符合预期——位不变,我们只是将最高有效位重新解释为符号位。同样,整数类型与 `packed struct`/`packed union` 类型之间的 `@bitCast` 语义也未改变。
新语义与旧语义不同的地方在于涉及聚合类型(数组和向量)时。
例如,考虑将 `[2]u8` 位转换为 `u16`。在旧语义下,此操作的结果取决于目标端序:在大端目标上,第一个数组元素成为 8 个*最高*有效位,而在小端目标上,第一个数组元素成为 8 个*最低*有效位。在新语义下,由于我们只关心*逻辑*位表示(与端序无关),该操作在所有目标上表现相同:第一个数组元素成为 8 个*最低*有效位。一般规则是,新语义倾向于匹配旧语义在小端目标上的行为。
这个定义也允许一些更奇怪的操作,例如将 `[2]u3` 转换为 `@Vector(3, u2)`:
```zig
test "bitcast [2]u3 to @Vector(3, u2)" {
const arr: [2]u3 = .{ 0b001, 0b011 };
const vec: @Vector(3, u2) = @bitCast(arr);
// 从 `arr[0]` 的最低有效位开始,连接 `arr` 的所有位以找到逻辑位序列,
// 然后从中读取 2 位块以得到结果向量的元素 `vec`。
//
// arr[0] arr[1]
// 0b001 0b011
// ------------- -------------
// 1 0 0 1 1 0
// -------- -------- --------
// 0b01 0b10 0b01
// vec[0] vec[1] vec[2]
t
相似文章
构建系统重构
Zig 构建系统已经重构,将配置器和制造器进程分离,支持缓存、发布模式编译,并且'zig build'命令速度提升高达90%。这一变化提高了性能,并允许构建系统在不减速的情况下增加功能。
Zig 构建速度正在提升
Zig 0.15 相比 0.14 在编译时性能有显著提升,构建脚本编译时间从约 7 秒降至约 1.7 秒,完整构建时间从 41 秒降至 32 秒,且仍使用 LLVM。本文重点介绍了自托管后端和增量编译方面的进展。
Zig ELF 链接器改进开发日志
新的 Zig ELF 链接器现在支持外部库和 C 源码的快速增量编译,在 x86_64 Linux 上能够实现毫秒级重建。
Zig 0.16.0 发布说明:"Juicy Main"
Zig 0.16.0 发布了一个名为 'Juicy Main' 的新特性,它为 main() 函数提供了依赖注入功能,方便地访问分配器、IO、环境变量和命令行参数。
以Zig风格构建你的项目
作者详细介绍了构建一个名为bygge-zig的工具,该工具使用Zig构建系统来编译Rust项目,用更少的代码行复制了Cargo的功能,并突出了其中的差异和挑战。