Devlog<br>Zig Programming Language
Download
Learn
News
Source<br>Join a Community
Zig Software Foundation
Devlog
Devlog
This page contains a curated list of recent changes to main branch Zig.
Also available as an<br>RSS feed.
This page contains entries for the year 2026. Other years are available in<br>the Devlog archive page.
June 25, 2026<br>New @bitCast Semantics and LLVM Backend Improvements
Author: Matthew Lugg<br>(Quite long devlog coming up, apologies—I got a little carried away with this one!)<br>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.<br>LLVM Backend Integer Lowering<br>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.<br>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)!<br>That change was actually fairly straightforward, but I hit one issue which led me down a bit of a rabbit-hole.<br>The Problem with @bitCast<br>@bitCast is an interesting builtin. In the past, it was defined as being equivalent to the following sequence of operations:<br>Take a pointer to the operand value<br>Cast it to a pointer to the destination type<br>Load from that pointer<br>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.<br>Up to now, the LLVM backend had implemented these underspecified semantics for the @bitCast builtin. However, because that definition involved reinterpreting memory, changing how we store integer types in memory ended up impacting the implementation of @bitCast, and introducing Illegal Behavior which led to crashes in the compiler test suite.<br>The easiest solution to this would probably have been to implement logic in the LLVM backend to approximately match the old behavior. I instead opted for a better solution—implement a new definition of @bitCast.<br>Redefining @bitCast<br>In 2024, Jacob Young wrote up language proposal #19755 which aimed to solve the problems with @bitCast by precisely specifying a new set of semantics for it. This proposal was accepted shortly after it was submitted, and in fact, the semantics it details are already implemented by the self-hosted x86_64 backend! So to solve the LLVM backend’s problems, I didn’t necessarily need to match the old @bitCast semantics—instead, this seemed like a good time to finally get the new semantics implemented everywhere.<br>As an aside, another advantage to doing this is that we could take advantage of the compiler’s Legalize pass, which takes difficult-to-lower operations and rewrites them in terms of simpler operations, so that compiler backends only need to support those simple operations. Legalize already had functionality, used by the self-hosted x86_64 backend, which converted complex @bitCast operations into simpler ones, and it could be easily adapted to aid the other compiler backends too (mainly the LLVM and C backends)—but only if they implemented the new semantics.<br>Regardless, the point is, I set out on a side quest (which ended up being harder than the original quest) to implement these new semantics throughout the compiler. This includes not only the LLVM and C backends, but also comptime execution—after all, Zig allows you to do almost any operation at comptime, @bitCast included! Because the new semantics are meaningfully different from the old (more on this later), I also had to audit a lot of uses of @bitCast across the standard library, compiler, and supporting libraries (e.g. compiler_rt). But after a few mostly-painless fixes for CI failures, I was able to finally get my PR green, and landed it in master yesterday (closing a good few issues in the process!).<br>The New @bitCast Semantics<br>Now that we’ve gotten through all of the background, it’s finally time for me to actually explain new @bitCast behavior. Instead of being based on reinterpreting bytes in...