about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTrevor Gross <t.gross35@gmail.com>2025-05-04 18:11:47 -0400
committerGitHub <noreply@github.com>2025-05-04 18:11:47 -0400
commitfcb9da597abc6629bc645b14e79e16744830c994 (patch)
tree0a70bfeb3ee1ca4f42fe70c58946ddad6e5a9546
parentab62d56603293e9264b4d811d2d6f5b49a3264f9 (diff)
parent9f4abd313dfe3fbb0984d79a4a232009c582e9f5 (diff)
downloadrust-fcb9da597abc6629bc645b14e79e16744830c994.tar.gz
rust-fcb9da597abc6629bc645b14e79e16744830c994.zip
Rollup merge of #137280 - RalfJung:const_swap_nonoverlapping, r=lcnr
stabilize ptr::swap_nonoverlapping in const

Closes https://github.com/rust-lang/rust/issues/133668

The blocking issue mentioned there is resolved by documentation. We may in the future actually support such code, but that is blocked on https://github.com/rust-lang/const-eval/issues/72 which is non-trivial to implement. Meanwhile, this completes stabilization of all `const fn` in `ptr`. :)

Here's a version of the problematic example to play around with:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=6c390452379fb593e109b8f8ee854d2a

Should be FCP'd with both `@rust-lang/libs-api`  and `@rust-lang/lang`  since  `swap_nonoverlapping` is documented to work as an "untyped" operation but due to the limitation mentioned above, that's not entirely true during const evaluation. I expect this limitation will only be hit in niche corner cases, so the benefits of having this function work most of the time outweigh the downsides of users running into this problem. (Note that unsafe code could already hit this limitation before this PR by doing cursed pointer casts, but having it hidden inside `swap_nonoverlapping` feels a bit different.)
-rw-r--r--library/core/src/intrinsics/mod.rs1
-rw-r--r--library/core/src/ptr/mod.rs37
-rw-r--r--library/coretests/tests/lib.rs1
-rw-r--r--library/coretests/tests/ptr.rs4
-rw-r--r--tests/ui/consts/missing_span_in_backtrace.rs2
-rw-r--r--tests/ui/consts/missing_span_in_backtrace.stderr12
6 files changed, 46 insertions, 11 deletions
diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs
index c5d5bc000f1..5649736e404 100644
--- a/library/core/src/intrinsics/mod.rs
+++ b/library/core/src/intrinsics/mod.rs
@@ -3320,7 +3320,6 @@ pub const fn is_val_statically_known<T: Copy>(_arg: T) -> bool {
 #[inline]
 #[rustc_intrinsic]
 #[rustc_intrinsic_const_stable_indirect]
-#[rustc_allow_const_fn_unstable(const_swap_nonoverlapping)] // this is anyway not called since CTFE implements the intrinsic
 pub const unsafe fn typed_swap_nonoverlapping<T>(x: *mut T, y: *mut T) {
     // SAFETY: The caller provided single non-overlapping items behind
     // pointers, so swapping them with `count: 1` is fine.
diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs
index 445c789a7de..aa103af93ff 100644
--- a/library/core/src/ptr/mod.rs
+++ b/library/core/src/ptr/mod.rs
@@ -1065,10 +1065,45 @@ pub const unsafe fn swap<T>(x: *mut T, y: *mut T) {
 /// assert_eq!(x, [7, 8, 3, 4]);
 /// assert_eq!(y, [1, 2, 9]);
 /// ```
+///
+/// # Const evaluation limitations
+///
+/// If this function is invoked during const-evaluation, the current implementation has a small (and
+/// rarely relevant) limitation: if `count` is at least 2 and the data pointed to by `x` or `y`
+/// contains a pointer that crosses the boundary of two `T`-sized chunks of memory, the function may
+/// fail to evaluate (similar to a panic during const-evaluation). This behavior may change in the
+/// future.
+///
+/// The limitation is illustrated by the following example:
+///
+/// ```
+/// use std::mem::size_of;
+/// use std::ptr;
+///
+/// const { unsafe {
+///     const PTR_SIZE: usize = size_of::<*const i32>();
+///     let mut data1 = [0u8; PTR_SIZE];
+///     let mut data2 = [0u8; PTR_SIZE];
+///     // Store a pointer in `data1`.
+///     data1.as_mut_ptr().cast::<*const i32>().write_unaligned(&42);
+///     // Swap the contents of `data1` and `data2` by swapping `PTR_SIZE` many `u8`-sized chunks.
+///     // This call will fail, because the pointer in `data1` crosses the boundary
+///     // between several of the 1-byte chunks that are being swapped here.
+///     //ptr::swap_nonoverlapping(data1.as_mut_ptr(), data2.as_mut_ptr(), PTR_SIZE);
+///     // Swap the contents of `data1` and `data2` by swapping a single chunk of size
+///     // `[u8; PTR_SIZE]`. That works, as there is no pointer crossing the boundary between
+///     // two chunks.
+///     ptr::swap_nonoverlapping(&mut data1, &mut data2, 1);
+///     // Read the pointer from `data2` and dereference it.
+///     let ptr = data2.as_ptr().cast::<*const i32>().read_unaligned();
+///     assert!(*ptr == 42);
+/// } }
+/// ```
 #[inline]
 #[stable(feature = "swap_nonoverlapping", since = "1.27.0")]
-#[rustc_const_unstable(feature = "const_swap_nonoverlapping", issue = "133668")]
+#[rustc_const_stable(feature = "const_swap_nonoverlapping", since = "CURRENT_RUSTC_VERSION")]
 #[rustc_diagnostic_item = "ptr_swap_nonoverlapping"]
+#[rustc_allow_const_fn_unstable(const_eval_select)] // both implementations behave the same
 pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize) {
     ub_checks::assert_unsafe_precondition!(
         check_library_ub,
diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs
index f52e338a085..a71c4139308 100644
--- a/library/coretests/tests/lib.rs
+++ b/library/coretests/tests/lib.rs
@@ -15,7 +15,6 @@
 #![feature(char_max_len)]
 #![feature(clone_to_uninit)]
 #![feature(const_eval_select)]
-#![feature(const_swap_nonoverlapping)]
 #![feature(const_trait_impl)]
 #![feature(core_intrinsics)]
 #![feature(core_intrinsics_fallbacks)]
diff --git a/library/coretests/tests/ptr.rs b/library/coretests/tests/ptr.rs
index 7d6e4eac1e2..bb60fb07468 100644
--- a/library/coretests/tests/ptr.rs
+++ b/library/coretests/tests/ptr.rs
@@ -949,6 +949,10 @@ fn test_const_swap_ptr() {
         // Make sure they still work.
         assert!(*s1.0.ptr == 1);
         assert!(*s2.0.ptr == 666);
+
+        // This is where we'd swap again using a `u8` type and a `count` of `size_of::<T>()` if it
+        // were not for the limitation of `swap_nonoverlapping` around pointers crossing multiple
+        // elements.
     };
 }
 
diff --git a/tests/ui/consts/missing_span_in_backtrace.rs b/tests/ui/consts/missing_span_in_backtrace.rs
index c8c7453daa1..490eb57c24d 100644
--- a/tests/ui/consts/missing_span_in_backtrace.rs
+++ b/tests/ui/consts/missing_span_in_backtrace.rs
@@ -1,7 +1,5 @@
 //@ compile-flags: -Z ui-testing=no
 
-
-#![feature(const_swap_nonoverlapping)]
 use std::{
     mem::{self, MaybeUninit},
     ptr,
diff --git a/tests/ui/consts/missing_span_in_backtrace.stderr b/tests/ui/consts/missing_span_in_backtrace.stderr
index aad3d76dd26..88b3e37bb84 100644
--- a/tests/ui/consts/missing_span_in_backtrace.stderr
+++ b/tests/ui/consts/missing_span_in_backtrace.stderr
@@ -1,11 +1,11 @@
 error[E0080]: evaluation of constant value failed
-  --> $DIR/missing_span_in_backtrace.rs:16:9
+  --> $DIR/missing_span_in_backtrace.rs:14:9
    |
-16 | /         ptr::swap_nonoverlapping(
-17 | |             &mut ptr1 as *mut _ as *mut MaybeUninit<u8>,
-18 | |             &mut ptr2 as *mut _ as *mut MaybeUninit<u8>,
-19 | |             mem::size_of::<&i32>(),
-20 | |         );
+14 | /         ptr::swap_nonoverlapping(
+15 | |             &mut ptr1 as *mut _ as *mut MaybeUninit<u8>,
+16 | |             &mut ptr2 as *mut _ as *mut MaybeUninit<u8>,
+17 | |             mem::size_of::<&i32>(),
+18 | |         );
    | |_________^ unable to copy parts of a pointer from memory at ALLOC0
    |
 note: inside `swap_nonoverlapping::<MaybeUninit<u8>>`