From 3df0dc880376ef16076851046c40f2ad7374b63c Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 10 Jan 2026 14:21:33 +0100 Subject: [PATCH 1/2] mark rust_dealloc as captures(address) Co-authored-by: Ralf Jung --- compiler/rustc_codegen_llvm/src/attributes.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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); From 2b8f4a562f24afcb45f4f12b109c13beb5b45f75 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sun, 29 Sep 2024 00:27:50 +0200 Subject: [PATCH 2/2] avoid phi node for pointers flowing into Vec appends --- library/alloc/src/slice.rs | 11 ++++--- library/alloc/src/vec/mod.rs | 6 +++- .../lib-optimizations/append-elements.rs | 33 +++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 tests/codegen-llvm/lib-optimizations/append-elements.rs diff --git a/library/alloc/src/slice.rs b/library/alloc/src/slice.rs index e7d0fc3454eeb..634747ca3f846 100644 --- a/library/alloc/src/slice.rs +++ b/library/alloc/src/slice.rs @@ -444,13 +444,16 @@ impl [T] { impl ConvertVec for T { #[inline] fn to_vec(s: &[Self], alloc: A) -> Vec { - let mut v = Vec::with_capacity_in(s.len(), alloc); + let len = s.len(); + let mut v = Vec::with_capacity_in(len, alloc); // 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 len > 0 { + unsafe { + s.as_ptr().copy_to_nonoverlapping(v.as_mut_ptr(), len); + v.set_len(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..b8657104d6653 --- /dev/null +++ b/tests/codegen-llvm/lib-optimizations/append-elements.rs @@ -0,0 +1,33 @@ +//@ compile-flags: -O -Zmerge-functions=disabled +//@ needs-deterministic-layouts +//@ 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 +// CHECK-SAME: ptr{{.*}}[[DST:%[a-z]+]]{{.*}}ptr{{.*}}[[SRC:%[a-z]+]] +#[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]] + // CHECK-NOT: call void @llvm.memcpy + let temp = src.to_vec(); + dst.extend(&temp); + // CHECK: ret +} + +// CHECK-LABEL: @string_append_with_temp_alloc +// CHECK-SAME: ptr{{.*}}[[DST:%[a-z]+]]{{.*}}ptr{{.*}}[[SRC:%[a-z]+]] +#[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]] + // CHECK-NOT: call void @llvm.memcpy + let temp = src.to_string(); + dst.push_str(&temp); + // CHECK: ret +}