New @bitCast Semantics and LLVM Backend Improvements

New @bitCast Semantics and LLVM Backend Improvements

June 26, 2026: SPIR-V Backend Progress

Author: Ali Cheraghi

There’s quite a bit to cover. The SPIR-V backend had bitrotted in a number of places after the recent compiler changes, so I spent the past several weeks dragging it into a better state.

内容非常丰富。由于近期编译器的一系列变动,SPIR-V 后端在多处出现了“代码腐烂”(bitrotted),因此我花了过去几周的时间对其进行了修复和改进。

@SpirvType

SPIR-V has a handful of types that couldn’t be expressed in Zig’s type system. The new @SpirvType builtin has been introduced to address the longest-standing blocker for writing shaders. See #20550, #23326 and #35461 to trace the background.

SPIR-V 拥有一些无法在 Zig 类型系统中直接表达的类型。为了解决编写着色器时长期存在的障碍,我们引入了新的内置函数 @SpirvType。相关背景请参考 #20550、#23326 和 #35461。

Execution Mode on the Calling Convention

Execution mode info (workgroup size, fragment origin, etc.) is now carried by the calling convention instead of being emitted via inline assembly OpExecutionMode. The old std.gpu.executionMode() helper is gone, and the SPIR-V assembler now rejects manual OpExecutionMode instructions. Two new calling conventions, spirv_task and spirv_mesh, were also added for mesh shading pipelines.

执行模式信息(如工作组大小、片段原点等)现在由调用约定(calling convention)携带,而不是通过内联汇编 OpExecutionMode 发出。旧的 std.gpu.executionMode() 辅助函数已被移除,SPIR-V 汇编器现在会拒绝手动的 OpExecutionMode 指令。此外,针对网格着色(mesh shading)管线,新增了 spirv_taskspirv_mesh 两种调用约定。

Capabilities and Extensions from CPU Features

Capabilities and extensions used to be emitted ad hoc by codegen or via inline assembly. They’re now driven entirely by the CPU feature set like other targets, with dependency chains extracted from SPIRV-Headers (excluding external vendors for now), and the assembler now rejects any attempt to emit OpCapability or OpExtension directly.

功能(Capabilities)和扩展(Extensions)过去是由代码生成器临时发出或通过内联汇编发出的。现在,它们像其他目标一样完全由 CPU 特性集驱动,依赖链从 SPIRV-Headers 中提取(目前排除了外部供应商),汇编器现在拒绝任何直接发出 OpCapabilityOpExtension 的尝试。

Multi-Threaded Codegen

From day one, the SPIR-V backend ran codegen single-threaded inside the linker thread. Each codegen job now produces an Mir value just like every other self-hosted backend, and gets scheduled on the compiler’s thread pool. The same change brought back two ISel passes that had been removed during earlier refactors: dedup_types (which merges equivalent type instructions) and prune_unused (which strips dead code from the final module). These had originally been deleted back when codegen was single-threaded.

从第一天起,SPIR-V 后端就在链接器线程内以单线程方式运行代码生成。现在,每个代码生成任务都像其他自托管后端一样产生一个 Mir 值,并被调度到编译器的线程池中。这一改动还恢复了在早期重构中被移除的两个指令选择(ISel)过程:dedup_types(合并等效类型指令)和 prune_unused(从最终模块中剔除死代码)。这些过程最初是在代码生成为单线程时被删除的。

Object File Linking

.spv files are now recognised as object files. You can compile multiple .zig files (or external .spv objects) and have the SPIR-V linker stitch them into a single module.

.spv 文件现在被识别为目标文件。你可以编译多个 .zig 文件(或外部 .spv 对象),并让 SPIR-V 链接器将它们拼接成一个单一的模块。


June 25, 2026: New @bitCast Semantics and LLVM Backend Improvements

Author: Matthew Lugg

(Quite long devlog coming up, apologies—I got a little carried away with this one!) A few weeks ago, I began working on a branch implementing an improvement to the LLVM backend which had been planned for a long time. This ended up snowballing into a bigger change which implemented a few language proposals you might be interested to hear about.

(接下来的开发日志会比较长,抱歉——我这次写得有点收不住了!)几周前,我开始在一个分支上实现一项针对 LLVM 后端的改进,这项改进已经计划了很久。最终,这演变成了一个更大的改动,实现了一些你可能感兴趣的语言提案。

LLVM Backend Integer Lowering

Zig has always lowered arbitrary bit-width integer types (e.g. u4, i13, u40) directly to LLVM IR’s bit-int types (i4, i13, i40). However, we’ve known for a long time that this lowering is not optimal, because LLVM’s documented semantics for representing these types in memory are unnecessarily restrictive to the optimizer. Perhaps more importantly, because Clang never emits LLVM IR like this, these code paths in LLVM have never been properly tested, and so are poorly supported in practice—over the past few years, we have observed many instances of trivial optimizations being missed and even straight-up miscompilations.

Zig 一直将任意位宽的整数类型(如 u4i13u40)直接降低(lower)为 LLVM IR 的位整数类型(i4i13i40)。然而,我们早就知道这种降低方式并非最优,因为 LLVM 文档中关于在内存中表示这些类型的语义对优化器来说过于严格。更重要的是,由于 Clang 从不发出这样的 LLVM IR,LLVM 中的这些代码路径从未得到充分测试,因此在实践中支持较差——在过去几年中,我们观察到许多本该进行的简单优化被遗漏,甚至直接出现了编译错误。

So, the original goal of the PR was to only use these bit-int types when manipulating values in SSA form, and to zero- or sign-extend them to ABI-sized types (i8, i16, i32, etc) when storing them in memory. This should be well-supported, not least because it matches how Clang lowers C’s _BitInt(N)! That change was actually fairly straightforward, but I hit one issue which led me down a bit of a rabbit-hole.

因此,该 PR 的最初目标是仅在操作 SSA 形式的值时使用这些位整数类型,并在将它们存储到内存中时将其零扩展或符号扩展为 ABI 大小的类型(i8、i16、i32 等)。这应该会得到很好的支持,因为它与 Clang 降低 C 语言 _BitInt(N) 的方式一致!这个改动实际上相当直接,但我遇到了一个问题,这让我陷入了一个“兔子洞”(指陷入了复杂且耗时的深究中)。

The Problem with @bitCast

@bitCast is an interesting builtin. In the past, it was defined as being equivalent to the following sequence of operations: Take a pointer to the operand value, cast it to a pointer to the destination type, load from that pointer. In other words, it was essentially syntax sugar for reinterpreting bytes of memory. However, over time, we diverged from this definition—for instance, it became allowed to use @bitCast to reinterpret a [3]u8 as a u24, even though on most targets @sizeOf(u24) is greater than @sizeOf([3]u8) so the above definition would invoke Illegal Behavior. Up to now, the LLVM backend had implemented these underspecified semantics for the @bitCast builtin.

@bitCast 是一个有趣的内置函数。过去,它被定义为等同于以下操作序列:获取操作数值的指针,将其转换为目标类型的指针,然后从该指针加载数据。换句话说,它本质上是重新解释内存字节的语法糖。然而,随着时间的推移,我们偏离了这个定义——例如,现在允许使用 @bitCast[3]u8 重新解释为 u24,尽管在大多数目标平台上 @sizeOf(u24) 大于 @sizeOf([3]u8),因此上述定义会导致非法行为。到目前为止,LLVM 后端一直为 @bitCast 内置函数实现这些定义不明确的语义。