Use captures(address) instead of captures(none) for indirect args

While provenance cannot be captured through these arguments, the
address / object identity can.
This commit is contained in:
Nikita Popov 2025-08-26 11:13:03 +02:00
parent ace9a74442
commit c3ab409b4f
15 changed files with 47 additions and 35 deletions

View File

@ -44,7 +44,7 @@ const ABI_AFFECTING_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 1] =
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 6] = [
(ArgAttribute::NoAlias, llvm::AttributeKind::NoAlias),
(ArgAttribute::NoCapture, llvm::AttributeKind::NoCapture),
(ArgAttribute::CapturesAddress, llvm::AttributeKind::CapturesAddress),
(ArgAttribute::NonNull, llvm::AttributeKind::NonNull),
(ArgAttribute::ReadOnly, llvm::AttributeKind::ReadOnly),
(ArgAttribute::NoUndef, llvm::AttributeKind::NoUndef),
@ -84,8 +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) {
// captures(...) is only available since LLVM 21.
if (attr == ArgAttribute::CapturesReadOnly || attr == ArgAttribute::CapturesAddress)
&& llvm_util::get_version() < (21, 0, 0)
{
continue;
}
attrs.push(llattr.create_attr(cx.llcx));

View File

@ -263,7 +263,7 @@ pub(crate) enum AttributeKind {
MinSize = 4,
Naked = 5,
NoAlias = 6,
NoCapture = 7,
CapturesAddress = 7,
NoInline = 8,
NonNull = 9,
NoRedZone = 10,

View File

@ -242,7 +242,7 @@ enum class LLVMRustAttributeKind {
MinSize = 4,
Naked = 5,
NoAlias = 6,
NoCapture = 7,
CapturesAddress = 7,
NoInline = 8,
NonNull = 9,
NoRedZone = 10,
@ -297,12 +297,6 @@ static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
return Attribute::Naked;
case LLVMRustAttributeKind::NoAlias:
return Attribute::NoAlias;
case LLVMRustAttributeKind::NoCapture:
#if LLVM_VERSION_GE(21, 0)
report_fatal_error("NoCapture doesn't exist in LLVM 21");
#else
return Attribute::NoCapture;
#endif
case LLVMRustAttributeKind::NoCfCheck:
return Attribute::NoCfCheck;
case LLVMRustAttributeKind::NoInline:
@ -377,6 +371,7 @@ static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
#else
report_fatal_error("DeadOnReturn attribute requires LLVM 21 or later");
#endif
case LLVMRustAttributeKind::CapturesAddress:
case LLVMRustAttributeKind::CapturesReadOnly:
report_fatal_error("Should be handled separately");
}
@ -429,9 +424,9 @@ extern "C" void LLVMRustEraseInstFromParent(LLVMValueRef Instr) {
extern "C" LLVMAttributeRef
LLVMRustCreateAttrNoValue(LLVMContextRef C, LLVMRustAttributeKind RustAttr) {
#if LLVM_VERSION_GE(21, 0)
// LLVM 21 replaced the NoCapture attribute with Captures(none).
if (RustAttr == LLVMRustAttributeKind::NoCapture) {
return wrap(Attribute::getWithCaptureInfo(*unwrap(C), CaptureInfo::none()));
if (RustAttr == LLVMRustAttributeKind::CapturesAddress) {
return wrap(Attribute::getWithCaptureInfo(
*unwrap(C), CaptureInfo(CaptureComponents::Address)));
}
if (RustAttr == LLVMRustAttributeKind::CapturesReadOnly) {
return wrap(Attribute::getWithCaptureInfo(

View File

@ -114,7 +114,7 @@ mod attr_impl {
bitflags::bitflags! {
impl ArgAttribute: u8 {
const NoAlias = 1 << 1;
const NoCapture = 1 << 2;
const CapturesAddress = 1 << 2;
const NonNull = 1 << 3;
const ReadOnly = 1 << 4;
const InReg = 1 << 5;
@ -400,11 +400,11 @@ impl<'a, Ty> ArgAbi<'a, Ty> {
let mut attrs = ArgAttributes::new();
// For non-immediate arguments the callee gets its own copy of
// the value on the stack, so there are no aliases. It's also
// program-invisible so can't possibly capture
// the value on the stack, so there are no aliases. The function
// can capture the address of the argument, but not the provenance.
attrs
.set(ArgAttribute::NoAlias)
.set(ArgAttribute::NoCapture)
.set(ArgAttribute::CapturesAddress)
.set(ArgAttribute::NonNull)
.set(ArgAttribute::NoUndef);
attrs.pointee_size = layout.size;

View File

@ -5,7 +5,7 @@
// Test for the absence of `readonly` on the argument when it is mutated via `&raw const`.
// See <https://github.com/rust-lang/rust/issues/111502>.
// CHECK: i8 @foo(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef align 1{{( captures\(none\))?}} dereferenceable(128) %x)
// CHECK: i8 @foo(ptr{{( dead_on_return)?}} noalias noundef align 1{{( captures\(address\))?}} dereferenceable(128) %x)
#[no_mangle]
pub fn foo(x: [u8; 128]) -> u8 {
let ptr = core::ptr::addr_of!(x).cast_mut();
@ -15,7 +15,7 @@ pub fn foo(x: [u8; 128]) -> u8 {
x[0]
}
// CHECK: i1 @second(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef align {{[0-9]+}}{{( captures\(none\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
// CHECK: i1 @second(ptr{{( dead_on_return)?}} noalias noundef align {{[0-9]+}}{{( captures\(address\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
#[no_mangle]
pub unsafe fn second(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
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 {
}
// If going through a deref (and there are no other mutating accesses), then `readonly` is fine.
// CHECK: i1 @third(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef readonly align {{[0-9]+}}{{( captures\(none\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
// CHECK: i1 @third(ptr{{( dead_on_return)?}} noalias noundef readonly align {{[0-9]+}}{{( captures\(address\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
#[no_mangle]
pub unsafe fn third(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
let b_bool_ptr = core::ptr::addr_of!((*a_ptr_and_b.0).1).cast_mut();

View File

@ -134,7 +134,7 @@ pub fn mutable_notunpin_borrow(_: &mut NotUnpin) {}
#[no_mangle]
pub fn notunpin_borrow(_: &NotUnpin) {}
// CHECK: @indirect_struct(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef readonly align 4{{( captures\(none\))?}} dereferenceable(32) %_1)
// CHECK: @indirect_struct(ptr{{( dead_on_return)?}} noalias noundef readonly align 4{{( captures\(address\))?}} dereferenceable(32) %_1)
#[no_mangle]
pub fn indirect_struct(_: S) {}
@ -197,7 +197,7 @@ pub fn notunpin_box(x: Box<NotUnpin>) -> Box<NotUnpin> {
x
}
// CHECK: @struct_return(ptr{{( dead_on_unwind)?}} noalias{{( nocapture)?}} noundef{{( writable)?}} sret([32 x i8]) align 4{{( captures\(none\))?}} dereferenceable(32){{( %_0)?}})
// CHECK: @struct_return(ptr{{( dead_on_unwind)?}} noalias noundef{{( writable)?}} sret([32 x i8]) align 4{{( captures\(address\))?}} dereferenceable(32){{( %_0)?}})
#[no_mangle]
pub fn struct_return() -> S {
S { _field: [0, 0, 0, 0, 0, 0, 0, 0] }

View File

@ -256,11 +256,11 @@ pub struct IntDoubleInt {
c: i32,
}
// CHECK: define void @f_int_double_int_s_arg(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef align 8{{( captures\(none\))?}} dereferenceable(24) %a)
// CHECK: define void @f_int_double_int_s_arg(ptr{{( dead_on_return)?}} noalias noundef align 8{{( captures\(address\))?}} dereferenceable(24) %a)
#[no_mangle]
pub extern "C" fn f_int_double_int_s_arg(a: IntDoubleInt) {}
// CHECK: define void @f_ret_int_double_int_s(ptr{{( dead_on_unwind)?}} noalias{{( nocapture)?}} noundef{{( writable)?}} sret([24 x i8]) align 8{{( captures\(none\))?}} dereferenceable(24) %_0)
// CHECK: define void @f_ret_int_double_int_s(ptr{{( dead_on_unwind)?}} noalias noundef{{( writable)?}} sret([24 x i8]) align 8{{( captures\(address\))?}} dereferenceable(24) %_0)
#[no_mangle]
pub extern "C" fn f_ret_int_double_int_s() -> IntDoubleInt {
IntDoubleInt { a: 1, b: 2., c: 3 }

View File

@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi {
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(0 bytes),
pointee_align: Some(

View File

@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi {
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(0 bytes),
pointee_align: Some(

View File

@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi {
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(0 bytes),
pointee_align: Some(

View File

@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi {
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(0 bytes),
pointee_align: Some(

View File

@ -454,7 +454,7 @@ error: ABIs are not compatible
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(32 bytes),
pointee_align: Some(
@ -527,7 +527,7 @@ error: ABIs are not compatible
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(128 bytes),
pointee_align: Some(

View File

@ -454,7 +454,7 @@ error: ABIs are not compatible
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(32 bytes),
pointee_align: Some(
@ -527,7 +527,7 @@ error: ABIs are not compatible
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(128 bytes),
pointee_align: Some(

View File

@ -454,7 +454,7 @@ error: ABIs are not compatible
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(32 bytes),
pointee_align: Some(
@ -527,7 +527,7 @@ error: ABIs are not compatible
},
mode: Indirect {
attrs: ArgAttributes {
regular: NoAlias | NoCapture | NonNull | NoUndef,
regular: NoAlias | CapturesAddress | NonNull | NoUndef,
arg_ext: None,
pointee_size: Size(128 bytes),
pointee_align: Some(

View File

@ -0,0 +1,15 @@
// Regression test for issue #137668 where an indirect argument have been marked as nocapture
// despite the fact that callee did in fact capture the address.
//
//@ run-pass
//@ compile-flags: -Copt-level=2
#[inline(never)]
pub fn f(a: [u32; 64], b: [u32; 64]) -> bool {
&a as *const _ as usize != &b as *const _ as usize
}
fn main() {
static S: [u32; 64] = [0; 64];
assert!(f(S, S));
}