You’ve correctly identified that this is to do with aliasing. @dimdin seems to have assumed it’s about val
pointing to fun
, but that’s not true: the concern is with val
pointing to val
, through a cast pointer, such as like this:
var ex: Example = .{
.val = undefined,
.fun = 123,
};
ex.val = @ptrCast(&ex.val);
In this case, the assignment ex.val[0] -= 1
would change the pointer ex.val
, so that the next comparison has to work differently. Thus, since for an export fn
the compiler has no useful provenance data for any parameters, the optimizer has no choice but to load the pointer again, in case val
aliased it.
The last piece of the puzzle, then: why does this not happen in C? The answer is a piece of UB in the C standard called Strict Aliasing. This is a form of TBAA (type-based alias analysis) – in short, it states that it is UB to use an A *
which “actually” points in memory to a different type B
(unless either A
or B
is a char type, which is an explicit exception). So, in your second Godbolt snippet, Clang notices that a pointer to val
has type int **
while the pointer we’re accessing (self->val[idx]
) has type int *
; since the pointee types int *
and int
are different and neither is a character type, the optimizer is allowed to assume they won’t alias! Clang and GCC allow using the compiler flag -fno-strict-aliasing
to disable this class of optimizations; if you pass that, you’ll see you get the same code as in the Zig case.