Skip to content

Commit 6bc3793

Browse files
Auto merge of #145259 - nikic:read-only-capture, r=<try>
Tell LLVM about read-only captures
2 parents 5771665 + 2f266b1 commit 6bc3793

File tree

15 files changed

+91
-26
lines changed

15 files changed

+91
-26
lines changed

compiler/rustc_codegen_llvm/src/abi.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::attributes::{self, llfn_attrs_from_instance};
2424
use crate::builder::Builder;
2525
use crate::context::CodegenCx;
2626
use crate::llvm::{self, Attribute, AttributePlace};
27+
use crate::llvm_util;
2728
use crate::type_::Type;
2829
use crate::type_of::LayoutLlvmExt;
2930
use crate::value::Value;
@@ -41,12 +42,13 @@ trait ArgAttributesExt {
4142
const ABI_AFFECTING_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 1] =
4243
[(ArgAttribute::InReg, llvm::AttributeKind::InReg)];
4344

44-
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 5] = [
45+
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 6] = [
4546
(ArgAttribute::NoAlias, llvm::AttributeKind::NoAlias),
4647
(ArgAttribute::NoCapture, llvm::AttributeKind::NoCapture),
4748
(ArgAttribute::NonNull, llvm::AttributeKind::NonNull),
4849
(ArgAttribute::ReadOnly, llvm::AttributeKind::ReadOnly),
4950
(ArgAttribute::NoUndef, llvm::AttributeKind::NoUndef),
51+
(ArgAttribute::CapturesReadOnly, llvm::AttributeKind::CapturesReadOnly),
5052
];
5153

5254
fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attribute; 8]> {
@@ -82,6 +84,10 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'
8284
}
8385
for (attr, llattr) in OPTIMIZATION_ATTRIBUTES {
8486
if regular.contains(attr) {
87+
// captures(address, read_provenance) is only available since LLVM 21.
88+
if attr == ArgAttribute::CapturesReadOnly && llvm_util::get_version() < (21, 0, 0) {
89+
continue;
90+
}
8591
attrs.push(llattr.create_attr(cx.llcx));
8692
}
8793
}
@@ -500,7 +506,16 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
500506
}
501507
}
502508
PassMode::Indirect { attrs, meta_attrs: None, on_stack: false } => {
503-
apply(attrs);
509+
let i = apply(attrs);
510+
if cx.sess().opts.optimize != config::OptLevel::No
511+
&& llvm_util::get_version() >= (21, 0, 0)
512+
{
513+
attributes::apply_to_llfn(
514+
llfn,
515+
llvm::AttributePlace::Argument(i),
516+
&[llvm::AttributeKind::DeadOnReturn.create_attr(cx.llcx)],
517+
);
518+
}
504519
}
505520
PassMode::Indirect { attrs, meta_attrs: Some(meta_attrs), on_stack } => {
506521
assert!(!on_stack);

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ pub(crate) enum AttributeKind {
249249
FnRetThunkExtern = 41,
250250
Writable = 42,
251251
DeadOnUnwind = 43,
252+
DeadOnReturn = 44,
253+
CapturesReadOnly = 45,
252254
}
253255

254256
/// LLVMIntPredicate

compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ enum class LLVMRustAttributeKind {
277277
FnRetThunkExtern = 41,
278278
Writable = 42,
279279
DeadOnUnwind = 43,
280+
DeadOnReturn = 44,
281+
CapturesReadOnly = 45,
280282
};
281283

282284
static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
@@ -369,6 +371,14 @@ static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
369371
return Attribute::Writable;
370372
case LLVMRustAttributeKind::DeadOnUnwind:
371373
return Attribute::DeadOnUnwind;
374+
case LLVMRustAttributeKind::DeadOnReturn:
375+
#if LLVM_VERSION_GE(21, 0)
376+
return Attribute::DeadOnReturn;
377+
#else
378+
report_fatal_error("DeadOnReturn attribute requires LLVM 21 or later");
379+
#endif
380+
case LLVMRustAttributeKind::CapturesReadOnly:
381+
report_fatal_error("Should be handled separately");
372382
}
373383
report_fatal_error("bad LLVMRustAttributeKind");
374384
}
@@ -423,6 +433,11 @@ LLVMRustCreateAttrNoValue(LLVMContextRef C, LLVMRustAttributeKind RustAttr) {
423433
if (RustAttr == LLVMRustAttributeKind::NoCapture) {
424434
return wrap(Attribute::getWithCaptureInfo(*unwrap(C), CaptureInfo::none()));
425435
}
436+
if (RustAttr == LLVMRustAttributeKind::CapturesReadOnly) {
437+
return wrap(Attribute::getWithCaptureInfo(*unwrap(C),
438+
CaptureInfo(CaptureComponents::Address |
439+
CaptureComponents::ReadProvenance)));
440+
}
426441
#endif
427442
return wrap(Attribute::get(*unwrap(C), fromRust(RustAttr)));
428443
}

compiler/rustc_target/src/callconv/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ mod attr_impl {
119119
const ReadOnly = 1 << 4;
120120
const InReg = 1 << 5;
121121
const NoUndef = 1 << 6;
122+
const CapturesReadOnly = 1 << 7;
122123
}
123124
}
124125
rustc_data_structures::external_bitflags_debug! { ArgAttribute }

compiler/rustc_ty_utils/src/abi.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ fn arg_attrs_for_rust_scalar<'tcx>(
356356

357357
if matches!(kind, PointerKind::SharedRef { frozen: true }) && !is_return {
358358
attrs.set(ArgAttribute::ReadOnly);
359+
attrs.set(ArgAttribute::CapturesReadOnly);
359360
}
360361
}
361362
}

tests/codegen-llvm/addr-of-mutate.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Test for the absence of `readonly` on the argument when it is mutated via `&raw const`.
66
// See <https://github.com/rust-lang/rust/issues/111502>.
77

8-
// CHECK: i8 @foo(ptr noalias{{( nocapture)?}} noundef align 1{{( captures\(none\))?}} dereferenceable(128) %x)
8+
// CHECK: i8 @foo(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef align 1{{( captures\(none\))?}} dereferenceable(128) %x)
99
#[no_mangle]
1010
pub fn foo(x: [u8; 128]) -> u8 {
1111
let ptr = core::ptr::addr_of!(x).cast_mut();
@@ -15,7 +15,7 @@ pub fn foo(x: [u8; 128]) -> u8 {
1515
x[0]
1616
}
1717

18-
// CHECK: i1 @second(ptr noalias{{( nocapture)?}} noundef align {{[0-9]+}}{{( captures\(none\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
18+
// CHECK: i1 @second(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef align {{[0-9]+}}{{( captures\(none\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
1919
#[no_mangle]
2020
pub unsafe fn second(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
2121
let b_bool_ptr = core::ptr::addr_of!(a_ptr_and_b.1.1).cast_mut();
@@ -24,7 +24,7 @@ pub unsafe fn second(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
2424
}
2525

2626
// If going through a deref (and there are no other mutating accesses), then `readonly` is fine.
27-
// CHECK: i1 @third(ptr noalias{{( nocapture)?}} noundef readonly align {{[0-9]+}}{{( captures\(none\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
27+
// CHECK: i1 @third(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef readonly align {{[0-9]+}}{{( captures\(none\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
2828
#[no_mangle]
2929
pub unsafe fn third(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
3030
let b_bool_ptr = core::ptr::addr_of!((*a_ptr_and_b.0).1).cast_mut();

tests/codegen-llvm/dead_on_return.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//@ compile-flags: -C opt-level=3
2+
//@ min-llvm-version: 21
3+
4+
#![crate_type = "lib"]
5+
#![allow(unused_assignments, unused_variables)]
6+
7+
// Check that the old string is deallocated, but a new one is not initialized.
8+
#[unsafe(no_mangle)]
9+
pub fn test_str_new(mut s: String) {
10+
// CHECK-LABEL: @test_str_new
11+
// CHECK: __rust_dealloc
12+
// CHECK-NOT: store
13+
s = String::new();
14+
}
15+
16+
#[unsafe(no_mangle)]
17+
pub fn test_str_take(mut x: String) -> String {
18+
// CHECK-LABEL: @test_str_take
19+
// CHECK-NEXT: {{.*}}:
20+
// CHECK-NEXT: call void @llvm.memcpy
21+
// CHECK-NEXT: ret
22+
core::mem::take(&mut x)
23+
}
24+
25+
#[unsafe(no_mangle)]
26+
pub fn test_array_store(mut x: [u32; 100]) {
27+
// CHECK-LABEL: @test_array_store
28+
// CHECK-NEXT: {{.*}}:
29+
// CHECK-NEXT: ret
30+
x[0] = 1;
31+
}

tests/codegen-llvm/function-arguments.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ pub fn option_nonzero_int(x: Option<NonZero<u64>>) -> Option<NonZero<u64>> {
8080
x
8181
}
8282

83-
// CHECK: @readonly_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
83+
// CHECK: @readonly_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable(4) %_1)
8484
// FIXME #25759 This should also have `nocapture`
8585
#[no_mangle]
8686
pub fn readonly_borrow(_: &i32) {}
@@ -91,12 +91,12 @@ pub fn readonly_borrow_ret() -> &'static i32 {
9191
loop {}
9292
}
9393

94-
// CHECK: @static_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
94+
// CHECK: @static_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable(4) %_1)
9595
// static borrow may be captured
9696
#[no_mangle]
9797
pub fn static_borrow(_: &'static i32) {}
9898

99-
// CHECK: @named_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
99+
// CHECK: @named_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable(4) %_1)
100100
// borrow with named lifetime may be captured
101101
#[no_mangle]
102102
pub fn named_borrow<'r>(_: &'r i32) {}
@@ -129,21 +129,21 @@ pub fn mutable_borrow_ret() -> &'static mut i32 {
129129
// <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>.
130130
pub fn mutable_notunpin_borrow(_: &mut NotUnpin) {}
131131

132-
// CHECK: @notunpin_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
132+
// CHECK: @notunpin_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable(4) %_1)
133133
// But `&NotUnpin` behaves perfectly normal.
134134
#[no_mangle]
135135
pub fn notunpin_borrow(_: &NotUnpin) {}
136136

137-
// CHECK: @indirect_struct(ptr noalias{{( nocapture)?}} noundef readonly align 4{{( captures\(none\))?}} dereferenceable(32) %_1)
137+
// CHECK: @indirect_struct(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef readonly align 4{{( captures\(none\))?}} dereferenceable(32) %_1)
138138
#[no_mangle]
139139
pub fn indirect_struct(_: S) {}
140140

141-
// CHECK: @borrowed_struct(ptr noalias noundef readonly align 4 dereferenceable(32) %_1)
141+
// CHECK: @borrowed_struct(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable(32) %_1)
142142
// FIXME #25759 This should also have `nocapture`
143143
#[no_mangle]
144144
pub fn borrowed_struct(_: &S) {}
145145

146-
// CHECK: @option_borrow(ptr noalias noundef readonly align 4 dereferenceable_or_null(4) %_x)
146+
// CHECK: @option_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable_or_null(4) %_x)
147147
#[no_mangle]
148148
pub fn option_borrow(_x: Option<&i32>) {}
149149

@@ -185,7 +185,7 @@ pub fn _box(x: Box<i32>) -> Box<i32> {
185185
// With a custom allocator, it should *not* have `noalias`. (See
186186
// <https://github.com/rust-lang/miri/issues/3341> for why.) The second argument is the allocator,
187187
// which is a reference here that still carries `noalias` as usual.
188-
// CHECK: @_box_custom(ptr noundef nonnull align 4 %x.0, ptr noalias noundef nonnull readonly align 1 %x.1)
188+
// CHECK: @_box_custom(ptr noundef nonnull align 4 %x.0, ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))}} %x.1)
189189
#[no_mangle]
190190
pub fn _box_custom(x: Box<i32, &std::alloc::Global>) {
191191
drop(x)
@@ -208,7 +208,7 @@ pub fn struct_return() -> S {
208208
#[no_mangle]
209209
pub fn helper(_: usize) {}
210210

211-
// CHECK: @slice(ptr noalias noundef nonnull readonly align 1 %_1.0, [[USIZE]] noundef %_1.1)
211+
// CHECK: @slice(ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))}} %_1.0, [[USIZE]] noundef %_1.1)
212212
// FIXME #25759 This should also have `nocapture`
213213
#[no_mangle]
214214
pub fn slice(_: &[u8]) {}
@@ -227,7 +227,7 @@ pub fn unsafe_slice(_: &[UnsafeInner]) {}
227227
#[no_mangle]
228228
pub fn raw_slice(_: *const [u8]) {}
229229

230-
// CHECK: @str(ptr noalias noundef nonnull readonly align 1 %_1.0, [[USIZE]] noundef %_1.1)
230+
// CHECK: @str(ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))}} %_1.0, [[USIZE]] noundef %_1.1)
231231
// FIXME #25759 This should also have `nocapture`
232232
#[no_mangle]
233233
pub fn str(_: &[u8]) {}
@@ -259,7 +259,7 @@ pub fn trait_option(x: Option<Box<dyn Drop + Unpin>>) -> Option<Box<dyn Drop + U
259259
x
260260
}
261261

262-
// CHECK: { ptr, [[USIZE]] } @return_slice(ptr noalias noundef nonnull readonly align 2 %x.0, [[USIZE]] noundef %x.1)
262+
// CHECK: { ptr, [[USIZE]] } @return_slice(ptr noalias noundef nonnull readonly align 2{{( captures\(address, read_provenance\))}} %x.0, [[USIZE]] noundef %x.1)
263263
#[no_mangle]
264264
pub fn return_slice(x: &[u16]) -> &[u16] {
265265
x

tests/codegen-llvm/loongarch-abi/loongarch64-lp64d-abi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ pub struct IntDoubleInt {
256256
c: i32,
257257
}
258258

259-
// CHECK: define void @f_int_double_int_s_arg(ptr noalias{{( nocapture)?}} noundef align 8{{( captures\(none\))?}} dereferenceable(24) %a)
259+
// CHECK: define void @f_int_double_int_s_arg(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef align 8{{( captures\(none\))?}} dereferenceable(24) %a)
260260
#[no_mangle]
261261
pub extern "C" fn f_int_double_int_s_arg(a: IntDoubleInt) {}
262262

tests/codegen-llvm/range-attribute.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ pub fn enum2_value(x: Enum2) -> Enum2 {
6767
x
6868
}
6969

70-
// CHECK: noundef [[USIZE]] @takes_slice(ptr noalias noundef nonnull readonly align 4 %x.0, [[USIZE]] noundef %x.1)
70+
// CHECK: noundef [[USIZE]] @takes_slice(ptr {{.*}} %x.0, [[USIZE]] noundef %x.1)
7171
#[no_mangle]
7272
pub fn takes_slice(x: &[i32]) -> usize {
7373
x.len()

0 commit comments

Comments
 (0)