Repository navigation
Description
Code
I tried this code:
static STRUCT_SIZE : usize = 1024;
struct LargeStruct {
data: [u8; STRUCT_SIZE],
}
#[inline(never)]
fn consume_large_struct(val: LargeStruct) -> usize {
println!("consumed val[0] = {}", val.data[0]);
val.data[0] as usize
}
#[unsafe(no_mangle)]
pub fn main(decision : i64) {
let mut a = LargeStruct { data: [0u8; STRUCT_SIZE] };
a.data[0] = 1;
consume_large_struct(a); // `a` is moved here
if decision > 0 { // Branch to prevent initialization reordering
let mut b = LargeStruct { data: [0u8; STRUCT_SIZE] };
b.data[0] = 2;
consume_large_struct(b); // `b` is moved here
}
}I expected to see this happen: In rustc versions < 1.92 the stack slot occupied by a gets reused for b because a's lifetime ended.
Instead, this happened: In rustc versions >= 1.92 this behavior is no longer observed and the function reserves stack space for both a and b.
See https://godbolt.org/z/o4Ye3aKP3 for the expected behavior: sub rsp, 1064
See https://godbolt.org/z/erPebqsdP for the unexpected behavior: sub rsp, 2088
It appears that the lifetime annotations (call void @llvm.lifetime.start.p0(i64 1024, ptr nonnull %_8)) are missing from the LLVM IR for the newer rustc versions. I am not sure if this is intended behavior due to a newly introduced optimization pass, just wanted to report it in case it was an unwanted side effect.
LLVM IR for 1.92:
define void @example::main::hbaa888d9139c15b4() unnamed_addr {
start:
%b = alloca [1024 x i8], align 1
%a = alloca [1024 x i8], align 1
%0 = getelementptr inbounds nuw i8, ptr %a, i64 1
call void @llvm.memset.p0.i64(ptr noundef nonnull align 1 dereferenceable(1023) %0, i8 0, i64 1023, i1 false)
store i8 1, ptr %a, align 1
%res = call fastcc noundef i64 @example::consume_large_struct::h22f578e03999bc66(ptr noalias noundef readonly align 1 captures(address) dereferenceable(1024) %a) #4
%_4.not = icmp eq i64 %res, 0
br i1 %_4.not, label %bb4, label %bb2
bb4:
ret void
bb2:
%1 = getelementptr inbounds nuw i8, ptr %b, i64 1
call void @llvm.memset.p0.i64(ptr noundef nonnull align 1 dereferenceable(1023) %1, i8 0, i64 1023, i1 false)
store i8 2, ptr %b, align 1
%_6 = call fastcc noundef i64 @example::consume_large_struct::h22f578e03999bc66(ptr noalias noundef readonly align 1 captures(address) dereferenceable(1024) %b) #4
br label %bb4
}
LLVM IR for 1.91
define void @example::main::hf6eacbf6b45cf567() unnamed_addr {
start:
%_8 = alloca [1024 x i8], align 1
%_4 = alloca [1024 x i8], align 1
%a.sroa.5.0._4.sroa_idx = getelementptr inbounds nuw i8, ptr %_4, i64 1
call void @llvm.lifetime.start.p0(i64 1024, ptr nonnull %_4)
call void @llvm.memset.p0.i64(ptr noundef nonnull align 1 dereferenceable(1023) %a.sroa.5.0._4.sroa_idx, i8 0, i64 1023, i1 false)
store i8 1, ptr %_4, align 1
%res = call fastcc noundef i64 @example::consume_large_struct::h7e10161d3a176006(ptr noalias noundef readonly align 1 captures(address) dereferenceable(1024) %_4) #4
call void @llvm.lifetime.end.p0(i64 1024, ptr nonnull %_4)
%_5.not = icmp eq i64 %res, 0
br i1 %_5.not, label %bb4, label %bb2
bb4:
ret void
bb2:
%b.sroa.5.0._8.sroa_idx = getelementptr inbounds nuw i8, ptr %_8, i64 1
call void @llvm.lifetime.start.p0(i64 1024, ptr nonnull %_8)
call void @llvm.memset.p0.i64(ptr noundef nonnull align 1 dereferenceable(1023) %b.sroa.5.0._8.sroa_idx, i8 0, i64 1023, i1 false)
store i8 2, ptr %_8, align 1
%_7 = call fastcc noundef i64 @example::consume_large_struct::h7e10161d3a176006(ptr noalias noundef readonly align 1 captures(address) dereferenceable(1024) %_8) #4
call void @llvm.lifetime.end.p0(i64 1024, ptr nonnull %_8)
br label %bb4
}
Version it worked on
It most recently worked on: rustc 1.91
Version with regression
Everything from rustc 1.92 up to current beta/nightly
Activity
scottmcm commentedon Jun 9, 2026
In what rustc gives to LLVM, there's actually fewer 1024-byte
allocas in 1.92 (down from 5 to 3): https://rust.godbolt.org/z/YPc4MKnnjAmanieu commentedon Jun 9, 2026
I suspect this is caused by #142915 which enabled DestProp by default in 1.92. While this allows multiple
LargeStructlocals to be merged, it also removes all storage statements (which are used to generated LLVM lifetime annotations) for the merged locals.This is also partially caused by GVN since that pass causes the
[const 0_u8; 1024]to be shared by the initializers of the 2LargeStruct, which in turn means that the array's lifetime overlaps with the lifetime ofa, requiring 2 separate stack slots.Amanieu commentedon Jun 10, 2026
I tested this on #156046 and it fixes this issue: only 1KB of stack is allocated.