Tell LLVM about read-only captures

`&Freeze` parameters are not only `readonly` within the function,
but any captures of the pointer can also only be used for reads.
This can now be encoded using the `captures(address, read_provenance)`
attribute.
This commit is contained in:
Nikita Popov 2025-08-11 17:18:23 +02:00
parent 8365fcb2b8
commit d71ed8d19b
10 changed files with 48 additions and 14 deletions

View file

@ -42,12 +42,13 @@ trait ArgAttributesExt {
const ABI_AFFECTING_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 1] =
[(ArgAttribute::InReg, llvm::AttributeKind::InReg)];
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 5] = [
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 6] = [
(ArgAttribute::NoAlias, llvm::AttributeKind::NoAlias),
(ArgAttribute::NoCapture, llvm::AttributeKind::NoCapture),
(ArgAttribute::NonNull, llvm::AttributeKind::NonNull),
(ArgAttribute::ReadOnly, llvm::AttributeKind::ReadOnly),
(ArgAttribute::NoUndef, llvm::AttributeKind::NoUndef),
(ArgAttribute::CapturesReadOnly, llvm::AttributeKind::CapturesReadOnly),
];
fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attribute; 8]> {
@ -83,6 +84,10 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'
}
for (attr, llattr) in OPTIMIZATION_ATTRIBUTES {
if regular.contains(attr) {
// captures(address, read_provenance) is only available since LLVM 21.
if attr == ArgAttribute::CapturesReadOnly && llvm_util::get_version() < (21, 0, 0) {
continue;
}
attrs.push(llattr.create_attr(cx.llcx));
}
}

View file

@ -251,6 +251,7 @@ pub(crate) enum AttributeKind {
Writable = 42,
DeadOnUnwind = 43,
DeadOnReturn = 44,
CapturesReadOnly = 45,
}
/// LLVMIntPredicate

View file

@ -278,6 +278,7 @@ enum class LLVMRustAttributeKind {
Writable = 42,
DeadOnUnwind = 43,
DeadOnReturn = 44,
CapturesReadOnly = 45,
};
static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
@ -376,6 +377,8 @@ static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
#else
report_fatal_error("DeadOnReturn attribute requires LLVM 21 or later");
#endif
case LLVMRustAttributeKind::CapturesReadOnly:
report_fatal_error("Should be handled separately");
}
report_fatal_error("bad LLVMRustAttributeKind");
}
@ -430,6 +433,11 @@ LLVMRustCreateAttrNoValue(LLVMContextRef C, LLVMRustAttributeKind RustAttr) {
if (RustAttr == LLVMRustAttributeKind::NoCapture) {
return wrap(Attribute::getWithCaptureInfo(*unwrap(C), CaptureInfo::none()));
}
if (RustAttr == LLVMRustAttributeKind::CapturesReadOnly) {
return wrap(Attribute::getWithCaptureInfo(
*unwrap(C), CaptureInfo(CaptureComponents::Address |
CaptureComponents::ReadProvenance)));
}
#endif
return wrap(Attribute::get(*unwrap(C), fromRust(RustAttr)));
}

View file

@ -119,6 +119,7 @@ mod attr_impl {
const ReadOnly = 1 << 4;
const InReg = 1 << 5;
const NoUndef = 1 << 6;
const CapturesReadOnly = 1 << 7;
}
}
rustc_data_structures::external_bitflags_debug! { ArgAttribute }

View file

@ -356,6 +356,7 @@ fn arg_attrs_for_rust_scalar<'tcx>(
if matches!(kind, PointerKind::SharedRef { frozen: true }) && !is_return {
attrs.set(ArgAttribute::ReadOnly);
attrs.set(ArgAttribute::CapturesReadOnly);
}
}
}

View file

@ -80,7 +80,7 @@ pub fn option_nonzero_int(x: Option<NonZero<u64>>) -> Option<NonZero<u64>> {
x
}
// CHECK: @readonly_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
// CHECK: @readonly_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn readonly_borrow(_: &i32) {}
@ -91,12 +91,12 @@ pub fn readonly_borrow_ret() -> &'static i32 {
loop {}
}
// CHECK: @static_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
// CHECK: @static_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1)
// static borrow may be captured
#[no_mangle]
pub fn static_borrow(_: &'static i32) {}
// CHECK: @named_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
// CHECK: @named_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1)
// borrow with named lifetime may be captured
#[no_mangle]
pub fn named_borrow<'r>(_: &'r i32) {}
@ -129,7 +129,7 @@ pub fn mutable_borrow_ret() -> &'static mut i32 {
// <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>.
pub fn mutable_notunpin_borrow(_: &mut NotUnpin) {}
// CHECK: @notunpin_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
// CHECK: @notunpin_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1)
// But `&NotUnpin` behaves perfectly normal.
#[no_mangle]
pub fn notunpin_borrow(_: &NotUnpin) {}
@ -138,12 +138,12 @@ pub fn notunpin_borrow(_: &NotUnpin) {}
#[no_mangle]
pub fn indirect_struct(_: S) {}
// CHECK: @borrowed_struct(ptr noalias noundef readonly align 4 dereferenceable(32) %_1)
// CHECK: @borrowed_struct(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(32) %_1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn borrowed_struct(_: &S) {}
// CHECK: @option_borrow(ptr noalias noundef readonly align 4 dereferenceable_or_null(4) %_x)
// CHECK: @option_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable_or_null(4) %_x)
#[no_mangle]
pub fn option_borrow(_x: Option<&i32>) {}
@ -185,7 +185,7 @@ pub fn _box(x: Box<i32>) -> Box<i32> {
// With a custom allocator, it should *not* have `noalias`. (See
// <https://github.com/rust-lang/miri/issues/3341> for why.) The second argument is the allocator,
// which is a reference here that still carries `noalias` as usual.
// CHECK: @_box_custom(ptr noundef nonnull align 4 %x.0, ptr noalias noundef nonnull readonly align 1 %x.1)
// CHECK: @_box_custom(ptr noundef nonnull align 4 %x.0, ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))?}} %x.1)
#[no_mangle]
pub fn _box_custom(x: Box<i32, &std::alloc::Global>) {
drop(x)
@ -208,7 +208,7 @@ pub fn struct_return() -> S {
#[no_mangle]
pub fn helper(_: usize) {}
// CHECK: @slice(ptr noalias noundef nonnull readonly align 1 %_1.0, [[USIZE]] noundef %_1.1)
// CHECK: @slice(ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))?}} %_1.0, [[USIZE]] noundef %_1.1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn slice(_: &[u8]) {}
@ -227,7 +227,7 @@ pub fn unsafe_slice(_: &[UnsafeInner]) {}
#[no_mangle]
pub fn raw_slice(_: *const [u8]) {}
// CHECK: @str(ptr noalias noundef nonnull readonly align 1 %_1.0, [[USIZE]] noundef %_1.1)
// CHECK: @str(ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))?}} %_1.0, [[USIZE]] noundef %_1.1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn str(_: &[u8]) {}
@ -259,7 +259,7 @@ pub fn trait_option(x: Option<Box<dyn Drop + Unpin>>) -> Option<Box<dyn Drop + U
x
}
// CHECK: { ptr, [[USIZE]] } @return_slice(ptr noalias noundef nonnull readonly align 2 %x.0, [[USIZE]] noundef %x.1)
// CHECK: { ptr, [[USIZE]] } @return_slice(ptr noalias noundef nonnull readonly align 2{{( captures\(address, read_provenance\))?}} %x.0, [[USIZE]] noundef %x.1)
#[no_mangle]
pub fn return_slice(x: &[u16]) -> &[u16] {
x

View file

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

View file

@ -0,0 +1,18 @@
//@ compile-flags: -C opt-level=3 -Z mir-opt-level=0
//@ min-llvm-version: 21
#![crate_type = "lib"]
unsafe extern "C" {
safe fn do_something(p: &i32);
}
#[unsafe(no_mangle)]
pub fn test() -> i32 {
// CHECK-LABEL: @test(
// CHECK: ret i32 0
let i = 0;
do_something(&i);
do_something(&i);
i
}

View file

@ -939,7 +939,7 @@ error: fn_abi_of(assoc_test) = FnAbi {
},
mode: Direct(
ArgAttributes {
regular: NoAlias | NonNull | ReadOnly | NoUndef,
regular: NoAlias | NonNull | ReadOnly | NoUndef | CapturesReadOnly,
arg_ext: None,
pointee_size: Size(2 bytes),
pointee_align: Some(

View file

@ -939,7 +939,7 @@ error: fn_abi_of(assoc_test) = FnAbi {
},
mode: Direct(
ArgAttributes {
regular: NoAlias | NonNull | ReadOnly | NoUndef,
regular: NoAlias | NonNull | ReadOnly | NoUndef | CapturesReadOnly,
arg_ext: None,
pointee_size: Size(2 bytes),
pointee_align: Some(