diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index a25ce9e5a90ac..bf6bb81b53b06 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -517,7 +517,16 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>( to_add.push(llvm::CreateAllocKindAttr(cx.llcx, AllocKindFlags::Free)); // applies to argument place instead of function place let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx); - attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), &[allocated_pointer]); + let attrs: &[_] = if llvm_util::get_version() >= (21, 0, 0) { + // "Does not capture provenance" means "if the function call stashes the pointer somewhere, + // accessing that pointer after the function returns is UB". That is definitely the case here since + // freeing will destroy the provenance. + let captures_addr = AttributeKind::CapturesAddress.create_attr(cx.llcx); + &[allocated_pointer, captures_addr] + } else { + &[allocated_pointer] + }; + attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), attrs); } if let Some(align) = codegen_fn_attrs.alignment { llvm::set_alignment(llfn, align); diff --git a/library/alloc/src/slice.rs b/library/alloc/src/slice.rs index e7d0fc3454eeb..58abf4bd6571e 100644 --- a/library/alloc/src/slice.rs +++ b/library/alloc/src/slice.rs @@ -448,9 +448,11 @@ impl [T] { // SAFETY: // allocated above with the capacity of `s`, and initialize to `s.len()` in // ptr::copy_to_non_overlapping below. - unsafe { - s.as_ptr().copy_to_nonoverlapping(v.as_mut_ptr(), s.len()); - v.set_len(s.len()); + if s.len() > 0 { + unsafe { + s.as_ptr().copy_to_nonoverlapping(v.as_mut_ptr(), s.len()); + v.set_len(s.len()); + } } v } diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 379e964f0a0ca..ac86399df7ab8 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -2818,7 +2818,11 @@ impl Vec { let count = other.len(); self.reserve(count); let len = self.len(); - unsafe { ptr::copy_nonoverlapping(other as *const T, self.as_mut_ptr().add(len), count) }; + if count > 0 { + unsafe { + ptr::copy_nonoverlapping(other as *const T, self.as_mut_ptr().add(len), count) + }; + } self.len += count; } diff --git a/tests/codegen-llvm/lib-optimizations/append-elements.rs b/tests/codegen-llvm/lib-optimizations/append-elements.rs new file mode 100644 index 0000000000000..8ee520a131f05 --- /dev/null +++ b/tests/codegen-llvm/lib-optimizations/append-elements.rs @@ -0,0 +1,30 @@ +//@ compile-flags: -O -Zmerge-functions=disabled +//@ min-llvm-version: 21 +#![crate_type = "lib"] + +//! Check that a temporary intermediate allocations can eliminated and replaced +//! with memcpy forwarding. +//! This requires Vec code to be structured in a way that avoids phi nodes from the +//! zero-capacity length flowing into the memcpy arguments. + +// CHECK-LABEL: @vec_append_with_temp_alloc +#[no_mangle] +pub fn vec_append_with_temp_alloc(dst: &mut Vec, src: &[u8]) { + // CHECK-NOT: call void @llvm.memcpy + // CHECK: call void @llvm.memcpy.{{.*}}%dst.i{{.*}}%src.0 + // CHECK-NOT: call void @llvm.memcpy + let temp = src.to_vec(); + dst.extend(&temp); + // CHECK: ret +} + +// CHECK-LABEL: @string_append_with_temp_alloc +#[no_mangle] +pub fn string_append_with_temp_alloc(dst: &mut String, src: &str) { + // CHECK-NOT: call void @llvm.memcpy + // CHECK: call void @llvm.memcpy.{{.*}}%dst.i{{.*}}%src.0 + // CHECK-NOT: call void @llvm.memcpy + let temp = src.to_string(); + dst.push_str(&temp); + // CHECK: ret +}