Previously, I wrote about how the Zig self-hosted backend lowers a seq_cst @atomicRmw(.Xchg) with a discarded result to a plain mov, but this isn’t the only issue with the self-hosted backend and atomics.
A valid bit of Zig code is @atomicLoad(u128, p, .seq_cst) – an atomic load of an unsigned 128 bit value.
A trivial example in Zig:
export fn load128(p: *u128) u128 {
return @atomicLoad(u128, p, .seq_cst);
}
The equivalent trivial example in C:
unsigned __int128 load128(unsigned __int128 *p) { return __atomic_load_n(p, __ATOMIC_SEQ_CST); }
When building for a target that has the cmpxchg16b instruction (such as the x86-64v2 target, the base x86-64 processor level for several Linux distributions, or by specifying -mcx16 to enable just that instruction), this should compile down to the lock cmpxchg16b instruction. If using the LLVM backend for building Zig, then this works. However, if you try and build the same Zig with the self-hosted compiler (the default for x86-64 debug builds, or selected by passing -fno-llvm), then it doesn’t work:
/opt/zig-0.17.0/zig build-obj /repro/src/load128.zig -target x86_64-linux -mcpu=x86_64_v2 -O Debug -femit-bin=/tmp/load128/zig-0.17.0/load128.o
repro/src/load128.zig:2:8: error: genSetReg called with a value larger than dst_reg
export fn load128(p: *u128) u128 {
~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
exit status: 1
It’s possible that the Codeberg issue #37289 issue, “”x86_64 backend: compiler segfault on @cmpxchgStrong(u128, ...) when the new value is an expression”” is related (this was an open issue as of 2026-10-17).
I have not reported this upstream, as the Zig Code of Conduct is unambiguous about bugs found with the assistance of LLMs (see my previous post for details).