From cb09a42d7ecc84d6791c74c6283fd7b8c6006cad Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 7 May 2026 16:01:48 +0200 Subject: [PATCH] Emit nofree attribute Treat the semantics of pointee.size as "dereferenceable-at-point" and always specify the size. Instead, use a separate NoFree attribute to determine whether dereferenceability extends to the whole function. Then in the LLVM backend, only actually emit dereferenceable if nofree is also set, as dereferenceable currently implies nofree. In addition, explicitly emit the nofree attribute, which will help when LLVM switches dereferenceable to be at-point. --- compiler/rustc_codegen_llvm/src/abi.rs | 10 +++- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 1 + .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 3 + compiler/rustc_middle/src/ty/layout.rs | 29 ++-------- compiler/rustc_target/src/callconv/mod.rs | 13 +++-- compiler/rustc_ty_utils/src/abi.rs | 55 ++++++++++++++----- tests/codegen-llvm/addr-of-mutate.rs | 6 +- tests/codegen-llvm/drop-in-place-noalias.rs | 2 +- tests/codegen-llvm/function-arguments.rs | 48 ++++++++-------- tests/codegen-llvm/llvm-writable.rs | 8 +-- .../loongarch-abi/loongarch64-lp64d-abi.rs | 4 +- tests/codegen-llvm/packed.rs | 4 +- tests/codegen-llvm/slice-iter-nonnull.rs | 4 +- tests/ui/abi/c-zst.powerpc-linux.stderr | 2 +- tests/ui/abi/c-zst.s390x-linux.stderr | 2 +- tests/ui/abi/c-zst.sparc64-linux.stderr | 2 +- .../ui/abi/c-zst.x86_64-pc-windows-gnu.stderr | 2 +- tests/ui/abi/debug.generic.stderr | 6 +- tests/ui/abi/debug.loongarch64.stderr | 6 +- tests/ui/abi/debug.riscv64.stderr | 6 +- tests/ui/abi/pass-indirectly-attr.stderr | 2 +- .../pass-by-value-abi.aarch64.stderr | 2 +- .../pass-by-value-abi.x86_64.stderr | 6 +- 23 files changed, 124 insertions(+), 99 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/abi.rs b/compiler/rustc_codegen_llvm/src/abi.rs index dcde960258a51..66c6cd1ecbfe9 100644 --- a/compiler/rustc_codegen_llvm/src/abi.rs +++ b/compiler/rustc_codegen_llvm/src/abi.rs @@ -38,12 +38,16 @@ 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::NonNull, llvm::AttributeKind::NonNull), (ArgAttribute::ReadOnly, llvm::AttributeKind::ReadOnly), (ArgAttribute::NoUndef, llvm::AttributeKind::NoUndef), (ArgAttribute::Writable, llvm::AttributeKind::Writable), + // Our internal NoFree attribute still allows deallocation of zero-size allocations. However, + // these don't render any bytes non-dereferenceable, so it's still fine to apply LLVM NoFree + // for them. + (ArgAttribute::NoFree, llvm::AttributeKind::NoFree), ]; const CAPTURES_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 3] = [ @@ -75,7 +79,9 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&' // Only apply remaining attributes when optimizing if cx.sess().opts.optimize != config::OptLevel::No { let deref = this.pointee_size.bytes(); - if deref != 0 { + // dereferenceable in LLVM currently implies nofree, so only emit dereferenceable if nofree + // is also set. + if deref != 0 && regular.contains(ArgAttribute::NoFree) { if regular.contains(ArgAttribute::NonNull) { attrs.push(llvm::CreateDereferenceableAttr(cx.llcx, deref)); } else { diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 9ecf5afcbcb35..36d5bf8362fa0 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -294,6 +294,7 @@ pub(crate) enum AttributeKind { SanitizeRealtimeNonblocking = 47, SanitizeRealtimeBlocking = 48, Convergent = 49, + NoFree = 50, } /// LLVMIntPredicate diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 91bb1c9733630..bfaa00dc0ebaa 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -383,6 +383,7 @@ enum class LLVMRustAttributeKind { SanitizeRealtimeNonblocking = 47, SanitizeRealtimeBlocking = 48, Convergent = 49, + NoFree = 50, }; static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) { @@ -481,6 +482,8 @@ static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) { return Attribute::SanitizeRealtimeBlocking; case LLVMRustAttributeKind::Convergent: return Attribute::Convergent; + case LLVMRustAttributeKind::NoFree: + return Attribute::NoFree; } report_fatal_error("bad LLVMRustAttributeKind"); } diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index e38e5393d967d..4efd06a9326d0 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -979,33 +979,19 @@ where } ty::Ref(_, ty, mt) if offset.bytes() == 0 => { tcx.layout_of(typing_env.as_query_input(ty)).ok().map(|layout| { - let (size, kind); - match mt { + let kind = match mt { hir::Mutability::Not => { let frozen = optimize && ty.is_freeze(tcx, typing_env); - - // Non-frozen shared references are not necessarily dereferenceable for the entire duration of the function - // (see ) - // (if we had "dereferenceable on entry", we could support this) - size = if frozen { layout.size } else { Size::ZERO }; - - kind = PointerKind::SharedRef { frozen }; + PointerKind::SharedRef { frozen } } hir::Mutability::Mut => { let unpin = optimize && ty.is_unpin(tcx, typing_env) && ty.is_unsafe_unpin(tcx, typing_env); - - // Mutable references to potentially self-referential types are not - // necessarily dereferenceable for the entire duration of the function - // (see ) - // (if we had "dereferenceable on entry", we could support this) - size = if unpin { layout.size } else { Size::ZERO }; - - kind = PointerKind::MutableRef { unpin }; + PointerKind::MutableRef { unpin } } }; - PointeeInfo { safe: Some(kind), size, align: layout.align.abi } + PointeeInfo { safe: Some(kind), size: layout.size, align: layout.align.abi } }) } @@ -1021,12 +1007,7 @@ where && pointee.is_unsafe_unpin(tcx, typing_env), global: this.ty.is_box_global(tcx), }), - - // `Box` are not necessarily dereferenceable for the entire duration of the function as - // they can be deallocated at any time. - // (if we had "dereferenceable on entry", we could support this) - size: Size::ZERO, - + size: layout.size, align: layout.align.abi, }) } diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index e6eb68c1fe10d..0e7e180854d28 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -122,6 +122,11 @@ mod attr_impl { const InReg = 1 << 6; const NoUndef = 1 << 7; const Writable = 1 << 8; + /// It is UB for this pointer or any pointer derived from it to be used for + /// deallocation (except for zero-sized deallocation) while the function is + /// executing. Only valid on arguments (including return values that are passed + /// indirectly as arguments). + const NoFree = 1 << 9; } } rustc_data_structures::external_bitflags_debug! { ArgAttribute } @@ -143,9 +148,8 @@ pub enum ArgExtension { pub struct ArgAttributes { pub regular: ArgAttribute, pub arg_ext: ArgExtension, - /// The minimum size of the pointee, guaranteed to be valid for the duration of the whole call - /// (corresponding to LLVM's dereferenceable_or_null attributes, i.e., it is okay for this to be - /// set on a null pointer, but all non-null pointers must be dereferenceable). + /// If the pointer is not null, the minimum dereferenceable size of the pointee, at the time of + /// function entry (for arguments) or function return (for return values). pub pointee_size: Size, /// The minimum alignment of the pointee, if any. pub pointee_align: Option, @@ -408,7 +412,8 @@ impl<'a, Ty> ArgAbi<'a, Ty> { .set(ArgAttribute::NoAlias) .set(ArgAttribute::CapturesAddress) .set(ArgAttribute::NonNull) - .set(ArgAttribute::NoUndef); + .set(ArgAttribute::NoUndef) + .set(ArgAttribute::NoFree); attrs.pointee_size = layout.size; attrs.pointee_align = Some(layout.align.abi); diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs index 02739ef6a4135..e782557d126bf 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -356,13 +356,7 @@ fn arg_attrs_for_rust_scalar<'tcx>( Some(pointee.align.min(cx.tcx().sess.target.max_reliable_alignment())); } - // LLVM dereferenceable attribute has unclear semantics on the return type, - // they seem to be "dereferenceable until the end of the program", which is - // generally, not valid for references. See - // - if !is_return { - attrs.pointee_size = pointee.size; - }; + attrs.pointee_size = pointee.size; if let Some(kind) = pointee.safe { // The aliasing rules for `Box` are still not decided, but currently we emit @@ -407,6 +401,26 @@ fn arg_attrs_for_rust_scalar<'tcx>( } } + // NoFree is not valid on return values. If it were, it would mean something like + // "will not be freed until the end of the program", which is generally not valid for + // references. + let no_free = !is_return + && match kind { + // Non-frozen shared references are not necessarily dereferenceable for the + // entire duration of the function + // (see ). + PointerKind::SharedRef { frozen } => frozen, + // Mutable references to potentially self-referential types are not necessarily + // dereferenceable for the entire duration of the function + // (see ). + PointerKind::MutableRef { unpin } => unpin, + // Box may be deallocated during execution of the function. + PointerKind::Box { .. } => false, + }; + if no_free { + attrs.set(ArgAttribute::NoFree); + } + if matches!(kind, PointerKind::SharedRef { frozen: true }) && !is_return { attrs.set(ArgAttribute::ReadOnly); attrs.set(ArgAttribute::CapturesReadOnly); @@ -423,11 +437,18 @@ fn fn_abi_sanity_check<'tcx>( fn_abi: &FnAbi<'tcx, Ty<'tcx>>, spec_abi: ExternAbi, ) { + fn fn_arg_attrs_sanity_check(attrs: &ArgAttributes, is_ret: bool) { + if attrs.regular.contains(ArgAttribute::NoFree) { + assert!(!is_ret, "NoFree not valid on return values"); + } + } + fn fn_arg_sanity_check<'tcx>( cx: &LayoutCx<'tcx>, fn_abi: &FnAbi<'tcx, Ty<'tcx>>, spec_abi: ExternAbi, arg: &ArgAbi<'tcx, Ty<'tcx>>, + is_ret: bool, ) { let tcx = cx.tcx(); @@ -452,7 +473,7 @@ fn fn_abi_sanity_check<'tcx>( PassMode::Ignore => { assert!(arg.layout.is_zst()); } - PassMode::Direct(_) => { + PassMode::Direct(attrs) => { // Here the Rust type is used to determine the actual ABI, so we have to be very // careful. Scalar/Vector is fine, since backends will generally use // `layout.backend_repr` and ignore everything else. We should just reject @@ -482,8 +503,9 @@ fn fn_abi_sanity_check<'tcx>( ); } } + fn_arg_attrs_sanity_check(attrs, is_ret); } - PassMode::Pair(_, _) => { + PassMode::Pair(attrs1, attrs2) => { // Similar to `Direct`, we need to make sure that backends use `layout.backend_repr` // and ignore the rest of the layout. assert!( @@ -491,19 +513,23 @@ fn fn_abi_sanity_check<'tcx>( "PassMode::Pair for type {}", arg.layout.ty ); + fn_arg_attrs_sanity_check(attrs1, is_ret); + fn_arg_attrs_sanity_check(attrs2, is_ret); } PassMode::Cast { .. } => { // `Cast` means "transmute to `CastType`"; that only makes sense for sized types. assert!(arg.layout.is_sized()); } - PassMode::Indirect { meta_attrs: None, .. } => { + PassMode::Indirect { meta_attrs: None, attrs, .. } => { // No metadata, must be sized. // Conceptually, unsized arguments must be copied around, which requires dynamically // determining their size, which we cannot do without metadata. Consult // t-opsem before removing this check. assert!(arg.layout.is_sized()); + // Indirect returns are arguments from an ABI perspective. + fn_arg_attrs_sanity_check(attrs, false); } - PassMode::Indirect { meta_attrs: Some(_), on_stack, .. } => { + PassMode::Indirect { meta_attrs: Some(meta_attrs), attrs, on_stack } => { // With metadata. Must be unsized and not on the stack. assert!(arg.layout.is_unsized() && !on_stack); // Also, must not be `extern` type. @@ -515,14 +541,17 @@ fn fn_abi_sanity_check<'tcx>( // t-opsem before removing this check. panic!("unsized arguments must not be `extern` types"); } + // Indirect returns are arguments from an ABI perspective. + fn_arg_attrs_sanity_check(attrs, false); + fn_arg_attrs_sanity_check(meta_attrs, false); } } } for arg in fn_abi.args.iter() { - fn_arg_sanity_check(cx, fn_abi, spec_abi, arg); + fn_arg_sanity_check(cx, fn_abi, spec_abi, arg, false); } - fn_arg_sanity_check(cx, fn_abi, spec_abi, &fn_abi.ret); + fn_arg_sanity_check(cx, fn_abi, spec_abi, &fn_abi.ret, true); } #[tracing::instrument( diff --git a/tests/codegen-llvm/addr-of-mutate.rs b/tests/codegen-llvm/addr-of-mutate.rs index d1939391b25de..def73bf8190d7 100644 --- a/tests/codegen-llvm/addr-of-mutate.rs +++ b/tests/codegen-llvm/addr-of-mutate.rs @@ -5,7 +5,7 @@ // Test for the absence of `readonly` on the argument when it is mutated via `&raw const`. // See . -// CHECK: i8 @foo(ptr{{( dead_on_return)?}} noalias noundef align 1{{( captures\(address\))?}}{{( dead_on_return)?}} dereferenceable(128) %x) +// CHECK: i8 @foo(ptr{{( dead_on_return)?}} noalias nofree noundef align 1{{( captures\(address\))?}}{{( dead_on_return)?}} 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 noundef align {{[0-9]+}}{{( captures\(address\))?}}{{( dead_on_return)?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b) +// CHECK: i1 @second(ptr{{( dead_on_return)?}} noalias nofree noundef align {{[0-9]+}}{{( captures\(address\))?}}{{( dead_on_return)?}} 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 noundef readonly align {{[0-9]+}}{{( captures\(none\))?}}{{( dead_on_return)?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b) +// CHECK: i1 @third(ptr{{( dead_on_return)?}} noalias nofree noundef readonly align {{[0-9]+}}{{( captures\(none\))?}}{{( dead_on_return)?}} 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(); diff --git a/tests/codegen-llvm/drop-in-place-noalias.rs b/tests/codegen-llvm/drop-in-place-noalias.rs index bff2f52781f23..86e0caa1b287d 100644 --- a/tests/codegen-llvm/drop-in-place-noalias.rs +++ b/tests/codegen-llvm/drop-in-place-noalias.rs @@ -7,7 +7,7 @@ use std::marker::PhantomPinned; -// CHECK: define internal void @{{.*}}core{{.*}}ptr{{.*}}drop_in_place{{.*}}StructUnpin{{.*}}(ptr noalias noundef align 4 dereferenceable(12) %{{.+}}) +// CHECK: define internal void @{{.*}}core{{.*}}ptr{{.*}}drop_in_place{{.*}}StructUnpin{{.*}}(ptr noalias nofree noundef align 4 dereferenceable(12) %{{.+}}) // CHECK: define internal void @{{.*}}core{{.*}}ptr{{.*}}drop_in_place{{.*}}StructNotUnpin{{.*}}(ptr noundef nonnull align 4 %{{.+}}) diff --git a/tests/codegen-llvm/function-arguments.rs b/tests/codegen-llvm/function-arguments.rs index 6eab9a58b47ad..0832778a6ed93 100644 --- a/tests/codegen-llvm/function-arguments.rs +++ b/tests/codegen-llvm/function-arguments.rs @@ -80,7 +80,7 @@ pub fn option_nonzero_int(x: Option>) -> Option> { x } -// CHECK: @readonly_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) +// CHECK: @readonly_borrow(ptr noalias nofree 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{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) +// CHECK: @static_borrow(ptr noalias nofree 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{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) +// CHECK: @named_borrow(ptr noalias nofree 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) {} @@ -106,12 +106,12 @@ pub fn named_borrow<'r>(_: &'r i32) {} #[no_mangle] pub fn unsafe_borrow(_: &UnsafeInner) {} -// CHECK: @mutable_unsafe_borrow(ptr noalias noundef align 2 dereferenceable(2) %_1) +// CHECK: @mutable_unsafe_borrow(ptr noalias nofree noundef align 2 dereferenceable(2) %_1) // ... unless this is a mutable borrow, those never alias #[no_mangle] pub fn mutable_unsafe_borrow(_: &mut UnsafeInner) {} -// CHECK: @mutable_borrow(ptr noalias noundef align 4 dereferenceable(4) %_1) +// CHECK: @mutable_borrow(ptr noalias nofree noundef align 4 dereferenceable(4) %_1) // FIXME #25759 This should also have `nocapture` #[no_mangle] pub fn mutable_borrow(_: &mut i32) {} @@ -129,25 +129,25 @@ pub fn mutable_borrow_ret() -> &'static mut i32 { // . pub fn mutable_notunpin_borrow(_: &mut NotUnpin) {} -// CHECK: @notunpin_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) +// CHECK: @notunpin_borrow(ptr noalias nofree noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(4) %_1) // But `&NotUnpin` behaves perfectly normal. #[no_mangle] pub fn notunpin_borrow(_: &NotUnpin) {} -// CHECK: @indirect_struct(ptr{{( dead_on_return)?}} noalias noundef readonly align 4{{( captures\(none\))?}}{{( dead_on_return)?}} dereferenceable(32) %_1) +// CHECK: @indirect_struct(ptr{{( dead_on_return)?}} noalias nofree noundef readonly align 4{{( captures\(none\))?}}{{( dead_on_return)?}} dereferenceable(32) %_1) #[no_mangle] pub fn indirect_struct(_: S) {} -// CHECK: @borrowed_struct(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable(32) %_1) +// CHECK: @borrowed_struct(ptr noalias nofree 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{{( captures\(address, read_provenance\))?}} dereferenceable_or_null(4) %_x) +// CHECK: @option_borrow(ptr noalias nofree noundef readonly align 4{{( captures\(address, read_provenance\))?}} dereferenceable_or_null(4) %_x) #[no_mangle] pub fn option_borrow(_x: Option<&i32>) {} -// CHECK: @option_borrow_mut(ptr noalias noundef align 4 dereferenceable_or_null(4) %_x) +// CHECK: @option_borrow_mut(ptr noalias nofree noundef align 4 dereferenceable_or_null(4) %_x) #[no_mangle] pub fn option_borrow_mut(_x: Option<&mut i32>) {} @@ -170,7 +170,7 @@ pub fn _box(x: Box) -> Box { // With a custom allocator, it should *not* have `noalias`. (See // 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{{( captures\(address, read_provenance\))?}} %x.1) +// CHECK: @_box_custom(ptr noundef nonnull align 4 %x.0, ptr noalias nofree noundef nonnull readonly{{( captures\(address, read_provenance\))?}} %x.1) #[no_mangle] pub fn _box_custom(x: Box) { drop(x) @@ -182,7 +182,7 @@ pub fn notunpin_box(x: Box) -> Box { x } -// CHECK: @struct_return(ptr{{( dead_on_unwind)?}} noalias noundef{{( writable)?}} sret([32 x i8]) align 4{{( captures\(none\))?}} dereferenceable(32){{( %_0)?}}) +// CHECK: @struct_return(ptr{{( dead_on_unwind)?}} noalias nofree noundef{{( writable)?}} sret([32 x i8]) align 4{{( captures\(none\))?}} dereferenceable(32){{( %_0)?}}) #[no_mangle] pub fn struct_return() -> S { S { _field: [0, 0, 0, 0, 0, 0, 0, 0] } @@ -194,14 +194,14 @@ pub fn struct_return() -> S { pub fn helper(_: usize) {} // CHECK: @slice( -// CHECK-SAME: ptr noalias noundef nonnull readonly{{( captures\(address, read_provenance\))?}} %_1.0, +// CHECK-SAME: ptr noalias nofree noundef nonnull readonly{{( captures\(address, read_provenance\))?}} %_1.0, // CHECK-SAME: [[USIZE]] noundef range({{i32 0, -2147483648|i64 0, -9223372036854775808}}) %_1.1) // FIXME #25759 This should also have `nocapture` #[no_mangle] pub fn slice(_: &[u8]) {} // CHECK: @mutable_slice( -// CHECK-SAME: ptr noalias noundef nonnull %_1.0, +// CHECK-SAME: ptr noalias nofree noundef nonnull %_1.0, // CHECK-SAME: [[USIZE]] noundef range({{i32 0, -2147483648|i64 0, -9223372036854775808}}) %_1.1) // FIXME #25759 This should also have `nocapture` #[no_mangle] @@ -219,13 +219,13 @@ pub fn unsafe_slice(_: &[UnsafeInner]) {} pub fn raw_slice(_: *const [u8]) {} // CHECK: @str( -// CHECK-SAME: ptr noalias noundef nonnull readonly{{( captures\(address, read_provenance\))?}} %_1.0, +// CHECK-SAME: ptr noalias nofree noundef nonnull readonly{{( captures\(address, read_provenance\))?}} %_1.0, // CHECK-SAME: [[USIZE]] noundef range({{i32 0, -2147483648|i64 0, -9223372036854775808}}) %_1.1) // FIXME #25759 This should also have `nocapture` #[no_mangle] pub fn str(_: &[u8]) {} -// CHECK: @trait_borrow(ptr noundef nonnull %_1.0, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}) %_1.1) +// CHECK: @trait_borrow(ptr noundef nonnull %_1.0, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}) %_1.1) // FIXME #25759 This should also have `nocapture` #[no_mangle] pub fn trait_borrow(_: &dyn Drop) {} @@ -238,31 +238,31 @@ pub fn option_trait_borrow(x: Option<&dyn Drop>) {} #[no_mangle] pub fn option_trait_borrow_mut(x: Option<&mut dyn Drop>) {} -// CHECK: @trait_raw(ptr noundef %_1.0, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}) %_1.1) +// CHECK: @trait_raw(ptr noundef %_1.0, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}) %_1.1) #[no_mangle] pub fn trait_raw(_: *const dyn Drop) {} // Ensure that `Box` gets `noalias` when the right traits are present, but removing *either* `Unpin` // or `UnsafeUnpin` is enough to lose the attribute. -// CHECK: @trait_box(ptr noalias noundef nonnull{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) +// CHECK: @trait_box(ptr noalias noundef nonnull{{( %0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) #[no_mangle] pub fn trait_box(_: Box) {} -// CHECK: @trait_box_pin1(ptr noundef nonnull{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) +// CHECK: @trait_box_pin1(ptr noundef nonnull{{( %0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) #[no_mangle] pub fn trait_box_pin1(_: Box) {} -// CHECK: @trait_box_pin2(ptr noundef nonnull{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) +// CHECK: @trait_box_pin2(ptr noundef nonnull{{( %0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}}) #[no_mangle] pub fn trait_box_pin2(_: Box) {} // Same for mutable references (with a non-zero minimal size so that we also see the // `dereferenceable` disappear). -// CHECK: @trait_mutref(ptr noalias noundef align 4 dereferenceable(4){{( %_1.0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) +// CHECK: @trait_mutref(ptr noalias nofree noundef align 4 dereferenceable(4){{( %_1.0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) #[no_mangle] pub fn trait_mutref(_: &mut (i32, dyn Drop + Unpin + UnsafeUnpin)) {} -// CHECK: @trait_mutref_pin1(ptr noundef nonnull align 4{{( %_1.0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) +// CHECK: @trait_mutref_pin1(ptr noundef nonnull align 4{{( %_1.0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) #[no_mangle] pub fn trait_mutref_pin1(_: &mut (i32, dyn Drop + Unpin)) {} -// CHECK: @trait_mutref_pin2(ptr noundef nonnull align 4{{( %_1.0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) +// CHECK: @trait_mutref_pin2(ptr noundef nonnull align 4{{( %_1.0)?}}, {{.+}} noalias nofree noundef readonly align {{.*}} dereferenceable({{.*}}){{( %_1.1)?}}) #[no_mangle] pub fn trait_mutref_pin2(_: &mut (i32, dyn Drop + UnsafeUnpin)) {} @@ -275,7 +275,7 @@ pub fn trait_option( } // CHECK: { ptr, [[USIZE]] } @return_slice( -// CHECK-SAME: ptr noalias noundef nonnull readonly align 2{{( captures\(address, read_provenance\))?}} %x.0, +// CHECK-SAME: ptr noalias nofree noundef nonnull readonly align 2{{( captures\(address, read_provenance\))?}} %x.0, // CHECK-SAME: [[USIZE]] noundef range({{i32 0, 1073741824|i64 0, 4611686018427387904}}) %x.1) #[no_mangle] pub fn return_slice(x: &[u16]) -> &[u16] { diff --git a/tests/codegen-llvm/llvm-writable.rs b/tests/codegen-llvm/llvm-writable.rs index ea245fc3a6e7a..0c72fb8a1dd9f 100644 --- a/tests/codegen-llvm/llvm-writable.rs +++ b/tests/codegen-llvm/llvm-writable.rs @@ -4,15 +4,15 @@ #![crate_type = "lib"] #![feature(rustc_attrs, unsafe_pinned)] -// CHECK: @mutable_borrow(ptr noalias noundef writable align 4 dereferenceable(4) %_1) +// CHECK: @mutable_borrow(ptr noalias nofree noundef writable align 4 dereferenceable(4) %_1) #[no_mangle] pub fn mutable_borrow(_: &mut i32) {} -// CHECK: @mutable_unsafe_borrow(ptr noalias noundef writable align 2 dereferenceable(2) %_1) +// CHECK: @mutable_unsafe_borrow(ptr noalias nofree noundef writable align 2 dereferenceable(2) %_1) #[no_mangle] pub fn mutable_unsafe_borrow(_: &mut std::cell::UnsafeCell) {} -// CHECK: @option_borrow_mut(ptr noalias noundef writable align 4 dereferenceable_or_null(4) %_1) +// CHECK: @option_borrow_mut(ptr noalias nofree noundef writable align 4 dereferenceable_or_null(4) %_1) #[no_mangle] pub fn option_borrow_mut(_: Option<&mut i32>) {} @@ -24,7 +24,7 @@ pub fn box_moved(_: Box) {} #[no_mangle] pub fn unsafe_pinned_borrow_mut(_: &mut std::pin::UnsafePinned) {} -// CHECK: @mutable_borrow_no_writable(ptr noalias noundef align 4 dereferenceable(4) %_1) +// CHECK: @mutable_borrow_no_writable(ptr noalias nofree noundef align 4 dereferenceable(4) %_1) #[no_mangle] #[rustc_no_writable] pub fn mutable_borrow_no_writable(_: &mut i32) {} diff --git a/tests/codegen-llvm/loongarch-abi/loongarch64-lp64d-abi.rs b/tests/codegen-llvm/loongarch-abi/loongarch64-lp64d-abi.rs index 4c61224ac6d8f..68f1267c2a444 100644 --- a/tests/codegen-llvm/loongarch-abi/loongarch64-lp64d-abi.rs +++ b/tests/codegen-llvm/loongarch-abi/loongarch64-lp64d-abi.rs @@ -256,11 +256,11 @@ pub struct IntDoubleInt { c: i32, } -// CHECK: define void @f_int_double_int_s_arg(ptr{{( dead_on_return)?}} noalias noundef align 8{{( captures\(address\))?}}{{( dead_on_return)?}} dereferenceable(24) %a) +// CHECK: define void @f_int_double_int_s_arg(ptr{{( dead_on_return)?}} noalias nofree noundef align 8{{( captures\(address\))?}}{{( dead_on_return)?}} 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 noundef{{( writable)?}} sret([24 x i8]) align 8{{( captures\(address\))?}} dereferenceable(24) %_0) +// CHECK: define void @f_ret_int_double_int_s(ptr{{( dead_on_unwind)?}} noalias nofree 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 } diff --git a/tests/codegen-llvm/packed.rs b/tests/codegen-llvm/packed.rs index 6f62719282eac..84e2b0897c09c 100644 --- a/tests/codegen-llvm/packed.rs +++ b/tests/codegen-llvm/packed.rs @@ -52,7 +52,7 @@ pub struct BigPacked2 { #[no_mangle] pub fn call_pkd1(f: fn() -> Array) -> BigPacked1 { // CHECK: [[ALLOCA:%[_a-z0-9]+]] = alloca [32 x i8] - // CHECK: call void %{{.*}}(ptr{{( captures(none))?}} noalias{{( nocapture)?}} noundef sret{{.*}} dereferenceable(32) [[ALLOCA]]) + // CHECK: call void %{{.*}}(ptr{{( captures(none))?}} noalias{{( nocapture)?}} nofree noundef sret{{.*}} dereferenceable(32) [[ALLOCA]]) // CHECK: call void @llvm.memcpy.{{.*}}(ptr align 1 %{{.*}}, ptr align 4 %{{.*}}, i{{[0-9]+}} 32, i1 false) // check that calls whose destination is a field of a packed struct // go through an alloca rather than calling the function with an @@ -64,7 +64,7 @@ pub fn call_pkd1(f: fn() -> Array) -> BigPacked1 { #[no_mangle] pub fn call_pkd2(f: fn() -> Array) -> BigPacked2 { // CHECK: [[ALLOCA:%[_a-z0-9]+]] = alloca [32 x i8] - // CHECK: call void %{{.*}}(ptr{{( captures(none))?}} noalias{{( nocapture)?}} noundef sret{{.*}} dereferenceable(32) [[ALLOCA]]) + // CHECK: call void %{{.*}}(ptr{{( captures(none))?}} noalias{{( nocapture)?}} nofree noundef sret{{.*}} dereferenceable(32) [[ALLOCA]]) // CHECK: call void @llvm.memcpy.{{.*}}(ptr align 2 %{{.*}}, ptr align 4 %{{.*}}, i{{[0-9]+}} 32, i1 false) // check that calls whose destination is a field of a packed struct // go through an alloca rather than calling the function with an diff --git a/tests/codegen-llvm/slice-iter-nonnull.rs b/tests/codegen-llvm/slice-iter-nonnull.rs index 6b8416662314f..8ef313f2d6715 100644 --- a/tests/codegen-llvm/slice-iter-nonnull.rs +++ b/tests/codegen-llvm/slice-iter-nonnull.rs @@ -51,7 +51,7 @@ pub fn slice_iter_next_back<'a>(it: &mut std::slice::Iter<'a, u32>) -> Option<&' // attribute is there, and confirms adding the assume back doesn't do anything. // CHECK-LABEL: @slice_iter_new -// CHECK-SAME: (ptr noalias noundef nonnull {{.+}} %slice.0, {{.+}} noundef range({{.+}}) %slice.1) +// CHECK-SAME: (ptr noalias nofree noundef nonnull {{.+}} %slice.0, {{.+}} noundef range({{.+}}) %slice.1) #[no_mangle] pub fn slice_iter_new(slice: &[u32]) -> std::slice::Iter<'_, u32> { // CHECK-NOT: slice @@ -66,7 +66,7 @@ pub fn slice_iter_new(slice: &[u32]) -> std::slice::Iter<'_, u32> { } // CHECK-LABEL: @slice_iter_mut_new -// CHECK-SAME: (ptr noalias noundef nonnull {{.+}} %slice.0, {{.+}} noundef range({{.+}}) %slice.1) +// CHECK-SAME: (ptr noalias nofree noundef nonnull {{.+}} %slice.0, {{.+}} noundef range({{.+}}) %slice.1) #[no_mangle] pub fn slice_iter_mut_new(slice: &mut [u32]) -> std::slice::IterMut<'_, u32> { // CHECK-NOT: slice diff --git a/tests/ui/abi/c-zst.powerpc-linux.stderr b/tests/ui/abi/c-zst.powerpc-linux.stderr index f297aa984dd2e..edea2d5772280 100644 --- a/tests/ui/abi/c-zst.powerpc-linux.stderr +++ b/tests/ui/abi/c-zst.powerpc-linux.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(0 bytes), pointee_align: Some( diff --git a/tests/ui/abi/c-zst.s390x-linux.stderr b/tests/ui/abi/c-zst.s390x-linux.stderr index f297aa984dd2e..edea2d5772280 100644 --- a/tests/ui/abi/c-zst.s390x-linux.stderr +++ b/tests/ui/abi/c-zst.s390x-linux.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(0 bytes), pointee_align: Some( diff --git a/tests/ui/abi/c-zst.sparc64-linux.stderr b/tests/ui/abi/c-zst.sparc64-linux.stderr index f297aa984dd2e..edea2d5772280 100644 --- a/tests/ui/abi/c-zst.sparc64-linux.stderr +++ b/tests/ui/abi/c-zst.sparc64-linux.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(0 bytes), pointee_align: Some( diff --git a/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr index f297aa984dd2e..edea2d5772280 100644 --- a/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr +++ b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(pass_zst) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(0 bytes), pointee_align: Some( diff --git a/tests/ui/abi/debug.generic.stderr b/tests/ui/abi/debug.generic.stderr index b154c3fa201e5..125150f2c2e3f 100644 --- a/tests/ui/abi/debug.generic.stderr +++ b/tests/ui/abi/debug.generic.stderr @@ -487,7 +487,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(32 bytes), pointee_align: Some( @@ -560,7 +560,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(128 bytes), pointee_align: Some( @@ -966,7 +966,7 @@ error: fn_abi_of(assoc_test) = FnAbi { }, mode: Direct( ArgAttributes { - regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef, + regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef | NoFree, arg_ext: None, pointee_size: Size(2 bytes), pointee_align: Some( diff --git a/tests/ui/abi/debug.loongarch64.stderr b/tests/ui/abi/debug.loongarch64.stderr index 68bcd736e47ce..5f05174c12760 100644 --- a/tests/ui/abi/debug.loongarch64.stderr +++ b/tests/ui/abi/debug.loongarch64.stderr @@ -487,7 +487,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(32 bytes), pointee_align: Some( @@ -560,7 +560,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(128 bytes), pointee_align: Some( @@ -966,7 +966,7 @@ error: fn_abi_of(assoc_test) = FnAbi { }, mode: Direct( ArgAttributes { - regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef, + regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef | NoFree, arg_ext: None, pointee_size: Size(2 bytes), pointee_align: Some( diff --git a/tests/ui/abi/debug.riscv64.stderr b/tests/ui/abi/debug.riscv64.stderr index 68bcd736e47ce..5f05174c12760 100644 --- a/tests/ui/abi/debug.riscv64.stderr +++ b/tests/ui/abi/debug.riscv64.stderr @@ -487,7 +487,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(32 bytes), pointee_align: Some( @@ -560,7 +560,7 @@ error: ABIs are not compatible }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(128 bytes), pointee_align: Some( @@ -966,7 +966,7 @@ error: fn_abi_of(assoc_test) = FnAbi { }, mode: Direct( ArgAttributes { - regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef, + regular: CapturesReadOnly | NoAlias | NonNull | ReadOnly | NoUndef | NoFree, arg_ext: None, pointee_size: Size(2 bytes), pointee_align: Some( diff --git a/tests/ui/abi/pass-indirectly-attr.stderr b/tests/ui/abi/pass-indirectly-attr.stderr index d8cc39cb2e4e3..7c4b0240e9f31 100644 --- a/tests/ui/abi/pass-indirectly-attr.stderr +++ b/tests/ui/abi/pass-indirectly-attr.stderr @@ -40,7 +40,7 @@ error: fn_abi_of(extern_c) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(1 bytes), pointee_align: Some( diff --git a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr index 584416f58f861..45edd7bc0e0ee 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(take_va_list) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(32 bytes), pointee_align: Some( diff --git a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr index b5e0e8589af16..1e203b93e66b3 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr @@ -27,7 +27,7 @@ error: fn_abi_of(take_va_list) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(24 bytes), pointee_align: Some( @@ -105,7 +105,7 @@ error: fn_abi_of(take_va_list_sysv64) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(24 bytes), pointee_align: Some( @@ -185,7 +185,7 @@ error: fn_abi_of(take_va_list_win64) = FnAbi { }, mode: Indirect { attrs: ArgAttributes { - regular: CapturesAddress | NoAlias | NonNull | NoUndef, + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, arg_ext: None, pointee_size: Size(24 bytes), pointee_align: Some(