Skip to content

Commit eb05884

Browse files
emit @llvm.lifetime.end for moved arguments passed indirectly
This results in code like: ```rust fn array_dead_store(mut x: [u8; 1234]) { x[0] = 42; } ``` Getting compiled to: ```ll define void @array_dead_store([1234 x i8]* %x) { %0 = getelementptr inbounds [1234 x i8], [1234 x i8]* %x, i64 0, i64 0 store i8 42, i8* %0 ; this store can be dropped tail call void @llvm.lifetime.end.p0i8(i64 1234, i8* nonnull %0) ret void } ``` ...which allows LLVM to drop the dead store. Without the lifetime marker, `%x` is just a pointer argument, so LLVM does not know that it is unused after the function returns.
1 parent ca122c7 commit eb05884

File tree

6 files changed

+123
-0
lines changed

6 files changed

+123
-0
lines changed

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,28 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
295295
}
296296

297297
fn codegen_return_terminator(&mut self, mut bx: Bx) {
298+
// Mark storage dead for arguments passed indirectly.
299+
for (arg_index, local) in self.mir.args_iter().enumerate() {
300+
if Some(local) == self.mir.spread_arg {
301+
// Bail out on spread args. This may result in reduced optimization in "rust-call" ABI functions.
302+
// FIXME: handle spread args. This is subtle, see `mir::arg_local_refs`.
303+
break;
304+
}
305+
if self.fn_abi.c_variadic && arg_index == self.fn_abi.args.len() {
306+
// Ignore C variadic args (always the last argument, if present).
307+
continue;
308+
}
309+
let abi = &self.fn_abi.args[arg_index];
310+
if !abi.is_indirect() {
311+
// Only indirect arguments need storage markers.
312+
continue;
313+
}
314+
match self.locals[local] {
315+
LocalRef::Place(place) => place.storage_dead(&mut bx),
316+
LocalRef::UnsizedPlace(place) => place.storage_dead(&mut bx),
317+
_ => bug!("Unexpected non-place argument local: {:?}", local),
318+
}
319+
}
298320
// Call `va_end` if this is the definition of a C-variadic function.
299321
if self.fn_abi.c_variadic {
300322
// The `VaList` "spoofed" argument is just after all the real arguments.

src/test/codegen/array-equality.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ pub fn array_eq_value_still_passed_by_pointer(a: [u16; 9], b: [u16; 9]) -> bool
2929
// CHECK-NEXT: start:
3030
// CHECK: %[[CMP:.+]] = tail call i32 @{{bcmp|memcmp}}({{i8\*|ptr}} {{.*}} dereferenceable(18) %{{.+}}, {{i8\*|ptr}} {{.*}} dereferenceable(18) %{{.+}}, i64 18)
3131
// CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[CMP]], 0
32+
// CHECK-NEXT: call void @llvm.lifetime.end
33+
// CHECK-NEXT: call void @llvm.lifetime.end
3234
// CHECK-NEXT: ret i1 %[[EQ]]
3335
a == b
3436
}
@@ -58,6 +60,8 @@ pub fn array_eq_zero_mid(x: [u16; 8]) -> bool {
5860
// CHECK-NEXT: start:
5961
// CHECK: %[[LOAD:.+]] = load i128,
6062
// CHECK-NEXT: %[[EQ:.+]] = icmp eq i128 %[[LOAD]], 0
63+
// CHECK-NEXT: bitcast
64+
// CHECK-NEXT: call void @llvm.lifetime.end
6165
// CHECK-NEXT: ret i1 %[[EQ]]
6266
x == [0; 8]
6367
}
@@ -69,6 +73,7 @@ pub fn array_eq_zero_long(x: [u16; 1234]) -> bool {
6973
// CHECK-NOT: alloca
7074
// CHECK: %[[CMP:.+]] = tail call i32 @{{bcmp|memcmp}}(
7175
// CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[CMP]], 0
76+
// CHECK-NEXT: call void @llvm.lifetime.end
7277
// CHECK-NEXT: ret i1 %[[EQ]]
7378
x == [0; 1234]
7479
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Check that LLVM can see that stores to a by-move array (passed indirectly) are dead.
2+
3+
// compile-flags: -O -Zmir-opt-level=0
4+
// min-llvm-version: 14.0
5+
6+
#![crate_type="lib"]
7+
8+
// CHECK-LABEL: @array_dead_store
9+
#[no_mangle]
10+
pub fn array_dead_store(mut x: [u8; 1234]) {
11+
// CHECK-NOT: store
12+
x[10] = 42;
13+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// This test checks that moved arguments passed indirectly get lifetime markers.
2+
3+
// compile-flags: -O -C no-prepopulate-passes -Zmir-opt-level=0
4+
5+
#![crate_type = "lib"]
6+
7+
// CHECK-LABEL: @arg_indirect
8+
#[no_mangle]
9+
pub fn arg_indirect(a: [u8; 1234]) {
10+
// Arguments passed indirectly should get lifetime markers.
11+
12+
// CHECK: [[A:%[0-9]+]] = bitcast{{.*}} %a
13+
// CHECK: call void @llvm.lifetime.end{{.*}} [[A]])
14+
}
15+
16+
// CHECK-LABEL: @arg_by_val
17+
#[no_mangle]
18+
pub fn arg_by_val(a: u8) {
19+
// Arguments passed by value should not get lifetime markers.
20+
21+
// CHECK-NOT: call void @llvm.lifetime.end
22+
}
23+
24+
// CHECK-LABEL: @arg_by_ref
25+
#[no_mangle]
26+
pub fn arg_by_ref(a: &[u8; 1234]) {
27+
// Arguments passed by ref should not get lifetime markers.
28+
29+
// CHECK-NOT: call void @llvm.lifetime.end
30+
}
31+
32+
// CHECK-LABEL: @with_other_args
33+
#[no_mangle]
34+
pub fn with_other_args(x: (), y: (), a: [u8; 1234], z: ()) {
35+
// Lifetime markers should be applied to the correct argument,
36+
// even in the presence of ignored ZST arguments.
37+
38+
// CHECK-NOT: call void @llvm.lifetime.end
39+
// CHECK: [[A:%[0-9]+]] = bitcast{{.*}} %a
40+
// CHECK: call void @llvm.lifetime.end{{.*}} [[A]])
41+
// CHECK-NOT: call void @llvm.lifetime.end
42+
}
43+
44+
// CHECK-LABEL: @forward_directly_to_ret
45+
#[no_mangle]
46+
pub fn forward_directly_to_ret(a: [u8; 1234]) -> [u8; 1234] {
47+
// Ensure that lifetime markers appear after the argument is copied to the return place.
48+
// (Since reading from `a` after `lifetime.end` would return poison.)
49+
50+
// CHECK: memcpy
51+
// CHECK: [[A:%[0-9]+]] = bitcast{{.*}} %a
52+
// CHECK: call void @llvm.lifetime.end{{.*}} [[A]])
53+
// CHECK-NEXT: ret void
54+
a
55+
}
56+
57+
pub struct LargeWithField {
58+
x: u8,
59+
_rest: [u8; 1234],
60+
}
61+
62+
// CHECK-LABEL: @read_from_arg
63+
#[no_mangle]
64+
pub fn read_from_arg(a: LargeWithField) -> u8 {
65+
// Ensure that lifetime markers appear after reading from the argument.
66+
// (Since reading from `a` after `lifetime.end` would return poison.)
67+
68+
// CHECK: [[LOAD:%[0-9]+]] = load i8
69+
// CHECK: [[A:%[0-9]+]] = bitcast{{.*}} %a
70+
// CHECK: call void @llvm.lifetime.end{{.*}} [[A]])
71+
// CHECK-NEXT: ret i8 [[LOAD]]
72+
a.x
73+
}

src/test/codegen/vec-in-place.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ pub struct Bar {
3131
pub fn vec_iterator_cast_primitive(vec: Vec<i8>) -> Vec<u8> {
3232
// CHECK-NOT: loop
3333
// CHECK-NOT: call
34+
// CHECK: call void @llvm.lifetime.end
35+
// CHECK-NEXT: ret
3436
vec.into_iter().map(|e| e as u8).collect()
3537
}
3638

@@ -39,6 +41,8 @@ pub fn vec_iterator_cast_primitive(vec: Vec<i8>) -> Vec<u8> {
3941
pub fn vec_iterator_cast_wrapper(vec: Vec<u8>) -> Vec<Wrapper<u8>> {
4042
// CHECK-NOT: loop
4143
// CHECK-NOT: call
44+
// CHECK: call void @llvm.lifetime.end
45+
// CHECK-NEXT: ret
4246
vec.into_iter().map(|e| Wrapper(e)).collect()
4347
}
4448

@@ -47,6 +51,8 @@ pub fn vec_iterator_cast_wrapper(vec: Vec<u8>) -> Vec<Wrapper<u8>> {
4751
pub fn vec_iterator_cast_unwrap(vec: Vec<Wrapper<u8>>) -> Vec<u8> {
4852
// CHECK-NOT: loop
4953
// CHECK-NOT: call
54+
// CHECK: call void @llvm.lifetime.end
55+
// CHECK-NEXT: ret
5056
vec.into_iter().map(|e| e.0).collect()
5157
}
5258

@@ -55,6 +61,8 @@ pub fn vec_iterator_cast_unwrap(vec: Vec<Wrapper<u8>>) -> Vec<u8> {
5561
pub fn vec_iterator_cast_aggregate(vec: Vec<[u64; 4]>) -> Vec<Foo> {
5662
// CHECK-NOT: loop
5763
// CHECK-NOT: call
64+
// CHECK: call void @llvm.lifetime.end
65+
// CHECK-NEXT: ret
5866
vec.into_iter().map(|e| unsafe { std::mem::transmute(e) }).collect()
5967
}
6068

@@ -63,6 +71,8 @@ pub fn vec_iterator_cast_aggregate(vec: Vec<[u64; 4]>) -> Vec<Foo> {
6371
pub fn vec_iterator_cast_deaggregate(vec: Vec<Bar>) -> Vec<[u64; 4]> {
6472
// CHECK-NOT: loop
6573
// CHECK-NOT: call
74+
// CHECK: call void @llvm.lifetime.end
75+
// CHECK-NEXT: ret
6676

6777
// Safety: For the purpose of this test we assume that Bar layout matches [u64; 4].
6878
// This currently is not guaranteed for repr(Rust) types, but it happens to work here and

0 commit comments

Comments
 (0)