Skip to content

Commit fcb9da5

Browse files
authored
Rollup merge of #137280 - RalfJung:const_swap_nonoverlapping, r=lcnr
stabilize ptr::swap_nonoverlapping in const Closes #133668 The blocking issue mentioned there is resolved by documentation. We may in the future actually support such code, but that is blocked on rust-lang/const-eval#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.)
2 parents ab62d56 + 9f4abd3 commit fcb9da5

File tree

6 files changed

+46
-11
lines changed

6 files changed

+46
-11
lines changed

library/core/src/intrinsics/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -3320,7 +3320,6 @@ pub const fn is_val_statically_known<T: Copy>(_arg: T) -> bool {
33203320
#[inline]
33213321
#[rustc_intrinsic]
33223322
#[rustc_intrinsic_const_stable_indirect]
3323-
#[rustc_allow_const_fn_unstable(const_swap_nonoverlapping)] // this is anyway not called since CTFE implements the intrinsic
33243323
pub const unsafe fn typed_swap_nonoverlapping<T>(x: *mut T, y: *mut T) {
33253324
// SAFETY: The caller provided single non-overlapping items behind
33263325
// pointers, so swapping them with `count: 1` is fine.

library/core/src/ptr/mod.rs

+36-1
Original file line numberDiff line numberDiff line change
@@ -1065,10 +1065,45 @@ pub const unsafe fn swap<T>(x: *mut T, y: *mut T) {
10651065
/// assert_eq!(x, [7, 8, 3, 4]);
10661066
/// assert_eq!(y, [1, 2, 9]);
10671067
/// ```
1068+
///
1069+
/// # Const evaluation limitations
1070+
///
1071+
/// If this function is invoked during const-evaluation, the current implementation has a small (and
1072+
/// rarely relevant) limitation: if `count` is at least 2 and the data pointed to by `x` or `y`
1073+
/// contains a pointer that crosses the boundary of two `T`-sized chunks of memory, the function may
1074+
/// fail to evaluate (similar to a panic during const-evaluation). This behavior may change in the
1075+
/// future.
1076+
///
1077+
/// The limitation is illustrated by the following example:
1078+
///
1079+
/// ```
1080+
/// use std::mem::size_of;
1081+
/// use std::ptr;
1082+
///
1083+
/// const { unsafe {
1084+
/// const PTR_SIZE: usize = size_of::<*const i32>();
1085+
/// let mut data1 = [0u8; PTR_SIZE];
1086+
/// let mut data2 = [0u8; PTR_SIZE];
1087+
/// // Store a pointer in `data1`.
1088+
/// data1.as_mut_ptr().cast::<*const i32>().write_unaligned(&42);
1089+
/// // Swap the contents of `data1` and `data2` by swapping `PTR_SIZE` many `u8`-sized chunks.
1090+
/// // This call will fail, because the pointer in `data1` crosses the boundary
1091+
/// // between several of the 1-byte chunks that are being swapped here.
1092+
/// //ptr::swap_nonoverlapping(data1.as_mut_ptr(), data2.as_mut_ptr(), PTR_SIZE);
1093+
/// // Swap the contents of `data1` and `data2` by swapping a single chunk of size
1094+
/// // `[u8; PTR_SIZE]`. That works, as there is no pointer crossing the boundary between
1095+
/// // two chunks.
1096+
/// ptr::swap_nonoverlapping(&mut data1, &mut data2, 1);
1097+
/// // Read the pointer from `data2` and dereference it.
1098+
/// let ptr = data2.as_ptr().cast::<*const i32>().read_unaligned();
1099+
/// assert!(*ptr == 42);
1100+
/// } }
1101+
/// ```
10681102
#[inline]
10691103
#[stable(feature = "swap_nonoverlapping", since = "1.27.0")]
1070-
#[rustc_const_unstable(feature = "const_swap_nonoverlapping", issue = "133668")]
1104+
#[rustc_const_stable(feature = "const_swap_nonoverlapping", since = "CURRENT_RUSTC_VERSION")]
10711105
#[rustc_diagnostic_item = "ptr_swap_nonoverlapping"]
1106+
#[rustc_allow_const_fn_unstable(const_eval_select)] // both implementations behave the same
10721107
pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize) {
10731108
ub_checks::assert_unsafe_precondition!(
10741109
check_library_ub,

library/coretests/tests/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
#![feature(char_max_len)]
1616
#![feature(clone_to_uninit)]
1717
#![feature(const_eval_select)]
18-
#![feature(const_swap_nonoverlapping)]
1918
#![feature(const_trait_impl)]
2019
#![feature(core_intrinsics)]
2120
#![feature(core_intrinsics_fallbacks)]

library/coretests/tests/ptr.rs

+4
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,10 @@ fn test_const_swap_ptr() {
949949
// Make sure they still work.
950950
assert!(*s1.0.ptr == 1);
951951
assert!(*s2.0.ptr == 666);
952+
953+
// This is where we'd swap again using a `u8` type and a `count` of `size_of::<T>()` if it
954+
// were not for the limitation of `swap_nonoverlapping` around pointers crossing multiple
955+
// elements.
952956
};
953957
}
954958

tests/ui/consts/missing_span_in_backtrace.rs

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
//@ compile-flags: -Z ui-testing=no
22

3-
4-
#![feature(const_swap_nonoverlapping)]
53
use std::{
64
mem::{self, MaybeUninit},
75
ptr,

tests/ui/consts/missing_span_in_backtrace.stderr

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
error[E0080]: evaluation of constant value failed
2-
--> $DIR/missing_span_in_backtrace.rs:16:9
2+
--> $DIR/missing_span_in_backtrace.rs:14:9
33
|
4-
16 | / ptr::swap_nonoverlapping(
5-
17 | | &mut ptr1 as *mut _ as *mut MaybeUninit<u8>,
6-
18 | | &mut ptr2 as *mut _ as *mut MaybeUninit<u8>,
7-
19 | | mem::size_of::<&i32>(),
8-
20 | | );
4+
14 | / ptr::swap_nonoverlapping(
5+
15 | | &mut ptr1 as *mut _ as *mut MaybeUninit<u8>,
6+
16 | | &mut ptr2 as *mut _ as *mut MaybeUninit<u8>,
7+
17 | | mem::size_of::<&i32>(),
8+
18 | | );
99
| |_________^ unable to copy parts of a pointer from memory at ALLOC0
1010
|
1111
note: inside `swap_nonoverlapping::<MaybeUninit<u8>>`

0 commit comments

Comments
 (0)