… When these types are passed as parameters, Zig may choose to copy and pass by value, or pass by reference, whichever way Zig decides will be faster. This is made possible, in part, by the fact that parameters are immutable.
LLVM can still decide to promote a pointer argument into a by-value argumemt. Big struct arguments/return values that don’t fit in register are technically passed by pointer even if you pass them by value
That being said i did see a measurable speedup in my chess engine when i changed a big struct being passed by value vs by *const… (the const pointer was faster) so i’m not sure what the best advice here is. I would have hoped that by value is always better and LLVM will do the right thing but I have been burned by this in the past
I used to rely on parameter reference optimization, but since learning about *const T , I’ve started choosing between T , *T , and *const T to make my intent explicit.
But what is the difference between T and *const T in terms of intent? IMO they are equivalent “in” parameters and the compiler should reliably do the right decision between them, whatever the programmer wrote. (especially because in generic code where you don’t know T you can’t make the right decision)
If there’s no inlining, technically it’s usually done like this: the caller copies the value into the ABI-defined stack argument area and the callee reads it directly from there (Linux x64), or: the caller copies it into memory and passes a pointer to that copy in a register (Windows).
Pass-by-value semantics require an independent object to avoid aliasing issues (which is also the aliasing issue that led to Zig’s PRO being removed). LLVM can sometimes prove that the copy is unobservable and eliminate it. The Windows ABI makes that optimization much easier because the argument is already passed indirectly as a pointer. The Linux x64 ABI can sometimes be optimized this way, but the required stack-layout constraints are fairly stringent, so this optimization is unlikely to apply in the general case.