Skip to content

Stack slots are not being reused due to missing LLVM IR lifetime annotations. #157676

Open

Description

@VinceQu

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

  1. added
    C-bugCategory: This is a bug.
    regression-untriagedUntriaged performance or correctness regression.
    on Jun 9, 2026
  2. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Jun 9, 2026
  3. added
    A-mir-optArea: MIR optimizations
    C-optimizationCategory: An issue highlighting optimization opportunities or PRs implementing such
    and removed
    C-bugCategory: This is a bug.
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Jun 9, 2026
  4. scottmcm commented on Jun 9, 2026

    @scottmcm
    Member

    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/YPc4MKnnj

  5. Amanieu commented on Jun 9, 2026

    @Amanieu
    Member

    I suspect this is caused by #142915 which enabled DestProp by default in 1.92. While this allows multiple LargeStruct locals 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 2 LargeStruct, which in turn means that the array's lifetime overlaps with the lifetime of a, requiring 2 separate stack slots.

  6. Amanieu commented on Jun 10, 2026

    @Amanieu
    Member

    I tested this on #156046 and it fixes this issue: only 1KB of stack is allocated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-mir-optArea: MIR optimizationsC-optimizationCategory: An issue highlighting optimization opportunities or PRs implementing suchI-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}regression-untriagedUntriaged performance or correctness regression.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions