Change:
x } <del> // CHECK: @struct_return(ptr noalias nocapture noundef sret([32 x i8]) align 4 dereferenceable(32){{( %_0)?}}) </del> <ins> // CHECK: @struct_return(ptr{{( dead_on_unwind)?}} noalias nocapture noundef{{( writable)?}} sret([32 x i8]) align 4 dereferenceable(32){{( %_0)?}}) </ins> #[no_mangle] pub fn struct_return() -> S { S {

Discussion:
I tried this code: The generated assembly at -Oz on x86 is: Observe that Rust (or LLVM, as the case may be) fails to RVO when using , ironically having potentially worse performance than the one that calls . Complete godbolt example: Here is the pertinent optimized IR: Skimming the -O0 IR doesn't help much, since Rust needs to generate a whole mess of code in the MaybeUninit version. The thing that's very mysterious to me is that, somehow, the in is acting as a barrier for merging with the return slot ?? This seems like an LLVM bug but filing here first in case it's bad IR codegen on rustc's part.
via observes that the is a red herring. Maybe LLVM is having trouble GVN'ing and together because they are of different types...? There's literally no other reason I can think of.
Is this possibly the same as
Another similar, albeit not identical, issue with :
This optimization is illegal on the LLVM level because may unwind. It does work fine if you enable the feature:
Isn't unwinding in the foreign -Rust direction UB? At the very least, Rust's unwind tables are set up (last time I checked, I could be wrong) so that unwinding through Rust jumps to a handler that aborts. Seems like this is a pessimization accounting for a case that cannot happen. ( has the same effect, fwiw.) Edit: hang on, how is this even disallowed at the IR level in the first place? Not only does it optimize correctly in the non-MaybeUninit case, but it also does so when using (ignoring for a moment the fact that that's also UB; I don't think it's interfering with the optimizer). I thought the was the culprit, but apparently not??? This is the IR in question but where I've just swapped out MaybeUninit<Foofor Foo, and then passed through : I'm not sold on unwinding being the real culprit here, but it's not clear whether rustc is emitting poor IR or LLVM is doing something a posteriori dumb. (I spent a while fighting with Alive2 to decide whether or not I believed unwinding was the problem and decided to go to sleep instead.)
It is UB, but currently only behind the feature flag, for historical reasons.

If the function first writes to its argument and then unwinds, without the call slot optimization the write would not be visible. With the optimization the write becomes visible. Now, in this particular case the destination is an argument, and we could argue that arguments shouldn't be observed on the unwind path. That would allow the optimization to occur without nounwind. We currently don't specify such a requirement for , but I think it would be viable (and I'm pretty sure I've seen a discussion about it somewhere in the past...)
Ah! That makes sense. I figured that because it was marked it was assumed the caller would not read it if the function unwound. Still doesn't explain why the version without MaybeUninit doesn't have this problem, though. I didn't read the -O0 IR too closely, but perhaps rustc emits sufficiently different IR for that function that it winds up not mattering? Or is this just a matter that the operations can't be reordered a priori (for a suitable definition of "reorder").
Without MaybeUninit some MIR optimization takes care of this (try to disable). I'm not familiar with MIR optimizations though, so I don't know which one does it / which limitations it has.
I started an llvm-dev thread on the topic: Unfortunately, while looking into this I found a major bug in call slot optimization: The check introduced in does not look through pointer casts, and thus rarely triggers in practice. I'm afraid that once this is fixed (or once we have opaque pointers) this may commonly block the optimization for rust in practice.
There is a regression with nightly compared to 1.64, the memcpy is back with of or . It still optimized with of , or .
Goes away with , likely caused by MIR inlining. On the LLVM side, the issue appear to be a intrinsic blocking call slot optimization:
Upstream patch:
The aforementioned regression (for panic=abort) is fixed with the LLVM 16 upgrade on nightly.