rust-lang / rust-lang/rust

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

Open
#157,676 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

A-mir-opt C-optimization I-prioritize regression-untriaged
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the provided reproducer and compare the generated LLVM IR from rustc 1.91 against 1.92 or newer, focusing on the missing llvm.lifetime.start and llvm.lifetime.end calls. Confirm whether the change explains the increased stack allocation, then identify the compiler regression and verify that the lifetime annotations and stack-slot reuse are restored without changing the reported behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.