Skip to content

Commit 4e5fec2

Browse files
committed
Auto merge of #134757 - RalfJung:const_swap, r=scottmcm
stabilize const_swap libs-api FCP passed in #83163. However, I only just realized that this actually involves an intrinsic. The intrinsic could be implemented entirely with existing stable const functionality, but we choose to make it a primitive to be able to detect more UB. So nominating for `@rust-lang/lang` to make sure they are aware; I leave it up to them whether they want to FCP this. While at it I also renamed the intrinsic to make the "nonoverlapping" constraint more clear. Fixes #83163
2 parents 7f75bfa + 3c0c138 commit 4e5fec2

28 files changed

+139
-95
lines changed

compiler/rustc_codegen_ssa/src/mir/intrinsic.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
7575
// If we're swapping something that's *not* an `OperandValue::Ref`,
7676
// then we can do it directly and avoid the alloca.
7777
// Otherwise, we'll let the fallback MIR body take care of it.
78-
if let sym::typed_swap = name {
78+
if let sym::typed_swap_nonoverlapping = name {
7979
let pointee_ty = fn_args.type_at(0);
8080
let pointee_layout = bx.layout_of(pointee_ty);
8181
if !bx.is_backend_ref(pointee_layout)

compiler/rustc_codegen_ssa/src/traits/builder.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ pub trait BuilderMethods<'a, 'tcx>:
382382
/// Avoids `alloca`s for Immediates and ScalarPairs.
383383
///
384384
/// FIXME: Maybe do something smarter for Ref types too?
385-
/// For now, the `typed_swap` intrinsic just doesn't call this for those
385+
/// For now, the `typed_swap_nonoverlapping` intrinsic just doesn't call this for those
386386
/// cases (in non-debug), preferring the fallback body instead.
387387
fn typed_place_swap(
388388
&mut self,

compiler/rustc_const_eval/src/interpret/call.rs

+1-13
Original file line numberDiff line numberDiff line change
@@ -883,19 +883,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
883883
.local_to_op(mir::RETURN_PLACE, None)
884884
.expect("return place should always be live");
885885
let dest = self.frame().return_place.clone();
886-
let res = if self.stack().len() == 1 {
887-
// The initializer of constants and statics will get validated separately
888-
// after the constant has been fully evaluated. While we could fall back to the default
889-
// code path, that will cause -Zenforce-validity to cycle on static initializers.
890-
// Reading from a static's memory is not allowed during its evaluation, and will always
891-
// trigger a cycle error. Validation must read from the memory of the current item.
892-
// For Miri this means we do not validate the root frame return value,
893-
// but Miri anyway calls `read_target_isize` on that so separate validation
894-
// is not needed.
895-
self.copy_op_no_dest_validation(&op, &dest)
896-
} else {
897-
self.copy_op_allow_transmute(&op, &dest)
898-
};
886+
let res = self.copy_op_allow_transmute(&op, &dest);
899887
trace!("return value: {:?}", self.dump_place(&dest.into()));
900888
// We delay actually short-circuiting on this error until *after* the stack frame is
901889
// popped, since we want this error to be attributed to the caller, whose type defines

compiler/rustc_const_eval/src/interpret/intrinsics.rs

+23-7
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
424424
let result = self.raw_eq_intrinsic(&args[0], &args[1])?;
425425
self.write_scalar(result, dest)?;
426426
}
427-
sym::typed_swap => {
428-
self.typed_swap_intrinsic(&args[0], &args[1])?;
427+
sym::typed_swap_nonoverlapping => {
428+
self.typed_swap_nonoverlapping_intrinsic(&args[0], &args[1])?;
429429
}
430430

431431
sym::vtable_size => {
@@ -638,19 +638,35 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
638638
}
639639

640640
/// Does a *typed* swap of `*left` and `*right`.
641-
fn typed_swap_intrinsic(
641+
fn typed_swap_nonoverlapping_intrinsic(
642642
&mut self,
643643
left: &OpTy<'tcx, <M as Machine<'tcx>>::Provenance>,
644644
right: &OpTy<'tcx, <M as Machine<'tcx>>::Provenance>,
645645
) -> InterpResult<'tcx> {
646646
let left = self.deref_pointer(left)?;
647647
let right = self.deref_pointer(right)?;
648-
debug_assert_eq!(left.layout, right.layout);
648+
assert_eq!(left.layout, right.layout);
649+
assert!(left.layout.is_sized());
649650
let kind = MemoryKind::Stack;
650651
let temp = self.allocate(left.layout, kind)?;
651-
self.copy_op(&left, &temp)?;
652-
self.copy_op(&right, &left)?;
653-
self.copy_op(&temp, &right)?;
652+
self.copy_op(&left, &temp)?; // checks alignment of `left`
653+
654+
// We want to always enforce non-overlapping, even if this is a scalar type.
655+
// Therefore we directly use the underlying `mem_copy` here.
656+
self.mem_copy(right.ptr(), left.ptr(), left.layout.size, /*nonoverlapping*/ true)?;
657+
// This means we also need to do the validation of the value that used to be in `right`
658+
// ourselves. This value is now in `left.` The one that started out in `left` already got
659+
// validated by the copy above.
660+
if M::enforce_validity(self, left.layout) {
661+
self.validate_operand(
662+
&left.clone().into(),
663+
M::enforce_validity_recursively(self, left.layout),
664+
/*reset_provenance_and_padding*/ true,
665+
)?;
666+
}
667+
668+
self.copy_op(&temp, &right)?; // checks alignment of `right`
669+
654670
self.deallocate_ptr(temp.ptr(), None, kind)?;
655671
interp_ok(())
656672
}

compiler/rustc_const_eval/src/interpret/place.rs

+5-26
Original file line numberDiff line numberDiff line change
@@ -773,22 +773,6 @@ where
773773
interp_ok(())
774774
}
775775

776-
/// Copies the data from an operand to a place.
777-
/// The layouts of the `src` and `dest` may disagree.
778-
/// Does not perform validation of the destination.
779-
/// The only known use case for this function is checking the return
780-
/// value of a static during stack frame popping.
781-
#[inline(always)]
782-
pub(super) fn copy_op_no_dest_validation(
783-
&mut self,
784-
src: &impl Projectable<'tcx, M::Provenance>,
785-
dest: &impl Writeable<'tcx, M::Provenance>,
786-
) -> InterpResult<'tcx> {
787-
self.copy_op_inner(
788-
src, dest, /* allow_transmute */ true, /* validate_dest */ false,
789-
)
790-
}
791-
792776
/// Copies the data from an operand to a place.
793777
/// The layouts of the `src` and `dest` may disagree.
794778
#[inline(always)]
@@ -797,9 +781,7 @@ where
797781
src: &impl Projectable<'tcx, M::Provenance>,
798782
dest: &impl Writeable<'tcx, M::Provenance>,
799783
) -> InterpResult<'tcx> {
800-
self.copy_op_inner(
801-
src, dest, /* allow_transmute */ true, /* validate_dest */ true,
802-
)
784+
self.copy_op_inner(src, dest, /* allow_transmute */ true)
803785
}
804786

805787
/// Copies the data from an operand to a place.
@@ -810,9 +792,7 @@ where
810792
src: &impl Projectable<'tcx, M::Provenance>,
811793
dest: &impl Writeable<'tcx, M::Provenance>,
812794
) -> InterpResult<'tcx> {
813-
self.copy_op_inner(
814-
src, dest, /* allow_transmute */ false, /* validate_dest */ true,
815-
)
795+
self.copy_op_inner(src, dest, /* allow_transmute */ false)
816796
}
817797

818798
/// Copies the data from an operand to a place.
@@ -824,22 +804,21 @@ where
824804
src: &impl Projectable<'tcx, M::Provenance>,
825805
dest: &impl Writeable<'tcx, M::Provenance>,
826806
allow_transmute: bool,
827-
validate_dest: bool,
828807
) -> InterpResult<'tcx> {
829808
// These are technically *two* typed copies: `src` is a not-yet-loaded value,
830-
// so we're going a typed copy at `src` type from there to some intermediate storage.
809+
// so we're doing a typed copy at `src` type from there to some intermediate storage.
831810
// And then we're doing a second typed copy from that intermediate storage to `dest`.
832811
// But as an optimization, we only make a single direct copy here.
833812

834813
// Do the actual copy.
835814
self.copy_op_no_validate(src, dest, allow_transmute)?;
836815

837-
if validate_dest && M::enforce_validity(self, dest.layout()) {
816+
if M::enforce_validity(self, dest.layout()) {
838817
let dest = dest.to_place();
839818
// Given that there were two typed copies, we have to ensure this is valid at both types,
840819
// and we have to ensure this loses provenance and padding according to both types.
841820
// But if the types are identical, we only do one pass.
842-
if allow_transmute && src.layout().ty != dest.layout().ty {
821+
if src.layout().ty != dest.layout().ty {
843822
self.validate_operand(
844823
&dest.transmute(src.layout(), self)?,
845824
M::enforce_validity_recursively(self, src.layout()),

compiler/rustc_hir_analysis/src/check/intrinsic.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,9 @@ pub fn check_intrinsic_type(
501501
(1, 0, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], tcx.types.unit)
502502
}
503503

504-
sym::typed_swap => (1, 0, vec![Ty::new_mut_ptr(tcx, param(0)); 2], tcx.types.unit),
504+
sym::typed_swap_nonoverlapping => {
505+
(1, 0, vec![Ty::new_mut_ptr(tcx, param(0)); 2], tcx.types.unit)
506+
}
505507

506508
sym::discriminant_value => {
507509
let assoc_items = tcx.associated_item_def_ids(

compiler/rustc_span/src/symbol.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2059,7 +2059,7 @@ symbols! {
20592059
type_macros,
20602060
type_name,
20612061
type_privacy_lints,
2062-
typed_swap,
2062+
typed_swap_nonoverlapping,
20632063
u128,
20642064
u128_legacy_const_max,
20652065
u128_legacy_const_min,

library/core/src/intrinsics/mod.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -3969,6 +3969,21 @@ pub const fn is_val_statically_known<T: Copy>(_arg: T) -> bool {
39693969
false
39703970
}
39713971

3972+
#[rustc_nounwind]
3973+
#[inline]
3974+
#[rustc_intrinsic]
3975+
#[rustc_intrinsic_const_stable_indirect]
3976+
#[rustc_allow_const_fn_unstable(const_swap_nonoverlapping)] // this is anyway not called since CTFE implements the intrinsic
3977+
#[cfg(bootstrap)]
3978+
pub const unsafe fn typed_swap<T>(x: *mut T, y: *mut T) {
3979+
// SAFETY: The caller provided single non-overlapping items behind
3980+
// pointers, so swapping them with `count: 1` is fine.
3981+
unsafe { ptr::swap_nonoverlapping(x, y, 1) };
3982+
}
3983+
3984+
#[cfg(bootstrap)]
3985+
pub use typed_swap as typed_swap_nonoverlapping;
3986+
39723987
/// Non-overlapping *typed* swap of a single value.
39733988
///
39743989
/// The codegen backends will replace this with a better implementation when
@@ -3982,9 +3997,10 @@ pub const fn is_val_statically_known<T: Copy>(_arg: T) -> bool {
39823997
#[rustc_nounwind]
39833998
#[inline]
39843999
#[rustc_intrinsic]
3985-
// Const-unstable because `swap_nonoverlapping` is const-unstable.
3986-
#[rustc_const_unstable(feature = "const_typed_swap", issue = "none")]
3987-
pub const unsafe fn typed_swap<T>(x: *mut T, y: *mut T) {
4000+
#[rustc_intrinsic_const_stable_indirect]
4001+
#[rustc_allow_const_fn_unstable(const_swap_nonoverlapping)] // this is anyway not called since CTFE implements the intrinsic
4002+
#[cfg(not(bootstrap))]
4003+
pub const unsafe fn typed_swap_nonoverlapping<T>(x: *mut T, y: *mut T) {
39884004
// SAFETY: The caller provided single non-overlapping items behind
39894005
// pointers, so swapping them with `count: 1` is fine.
39904006
unsafe { ptr::swap_nonoverlapping(x, y, 1) };

library/core/src/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@
112112
#![feature(asm_experimental_arch)]
113113
#![feature(const_carrying_mul_add)]
114114
#![feature(const_eval_select)]
115-
#![feature(const_typed_swap)]
116115
#![feature(core_intrinsics)]
117116
#![feature(coverage_attribute)]
118117
#![feature(internal_impls_macro)]

library/core/src/mem/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -725,12 +725,12 @@ pub unsafe fn uninitialized<T>() -> T {
725725
/// ```
726726
#[inline]
727727
#[stable(feature = "rust1", since = "1.0.0")]
728-
#[rustc_const_unstable(feature = "const_swap", issue = "83163")]
728+
#[rustc_const_stable(feature = "const_swap", since = "CURRENT_RUSTC_VERSION")]
729729
#[rustc_diagnostic_item = "mem_swap"]
730730
pub const fn swap<T>(x: &mut T, y: &mut T) {
731731
// SAFETY: `&mut` guarantees these are typed readable and writable
732732
// as well as non-overlapping.
733-
unsafe { intrinsics::typed_swap(x, y) }
733+
unsafe { intrinsics::typed_swap_nonoverlapping(x, y) }
734734
}
735735

736736
/// Replaces `dest` with the default value of `T`, returning the previous `dest` value.

library/core/src/ptr/mod.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1009,9 +1009,8 @@ pub const fn slice_from_raw_parts_mut<T>(data: *mut T, len: usize) -> *mut [T] {
10091009
/// ```
10101010
#[inline]
10111011
#[stable(feature = "rust1", since = "1.0.0")]
1012-
#[rustc_const_unstable(feature = "const_swap", issue = "83163")]
1012+
#[rustc_const_stable(feature = "const_swap", since = "CURRENT_RUSTC_VERSION")]
10131013
#[rustc_diagnostic_item = "ptr_swap"]
1014-
#[rustc_const_stable_indirect]
10151014
pub const unsafe fn swap<T>(x: *mut T, y: *mut T) {
10161015
// Give ourselves some scratch space to work with.
10171016
// We do not have to worry about drops: `MaybeUninit` does nothing when dropped.

library/core/src/ptr/mut_ptr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1594,7 +1594,7 @@ impl<T: ?Sized> *mut T {
15941594
///
15951595
/// [`ptr::swap`]: crate::ptr::swap()
15961596
#[stable(feature = "pointer_methods", since = "1.26.0")]
1597-
#[rustc_const_unstable(feature = "const_swap", issue = "83163")]
1597+
#[rustc_const_stable(feature = "const_swap", since = "CURRENT_RUSTC_VERSION")]
15981598
#[inline(always)]
15991599
pub const unsafe fn swap(self, with: *mut T)
16001600
where

library/core/src/ptr/non_null.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1146,7 +1146,7 @@ impl<T: ?Sized> NonNull<T> {
11461146
/// [`ptr::swap`]: crate::ptr::swap()
11471147
#[inline(always)]
11481148
#[stable(feature = "non_null_convenience", since = "1.80.0")]
1149-
#[rustc_const_unstable(feature = "const_swap", issue = "83163")]
1149+
#[rustc_const_stable(feature = "const_swap", since = "CURRENT_RUSTC_VERSION")]
11501150
pub const unsafe fn swap(self, with: NonNull<T>)
11511151
where
11521152
T: Sized,

library/core/src/slice/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@ impl<T> [T] {
913913
/// assert!(v == ["a", "b", "e", "d", "c"]);
914914
/// ```
915915
#[stable(feature = "rust1", since = "1.0.0")]
916-
#[rustc_const_unstable(feature = "const_swap", issue = "83163")]
916+
#[rustc_const_stable(feature = "const_swap", since = "CURRENT_RUSTC_VERSION")]
917917
#[inline]
918918
#[track_caller]
919919
pub const fn swap(&mut self, a: usize, b: usize) {

library/core/tests/lib.rs

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

src/tools/miri/tests/fail/intrinsics/typed-swap-invalid-array.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![feature(core_intrinsics)]
22
#![feature(rustc_attrs)]
33

4-
use std::intrinsics::typed_swap;
4+
use std::intrinsics::typed_swap_nonoverlapping;
55
use std::ptr::addr_of_mut;
66

77
fn invalid_array() {
@@ -10,7 +10,7 @@ fn invalid_array() {
1010
unsafe {
1111
let a = addr_of_mut!(a).cast::<[bool; 100]>();
1212
let b = addr_of_mut!(b).cast::<[bool; 100]>();
13-
typed_swap(a, b); //~ERROR: constructing invalid value
13+
typed_swap_nonoverlapping(a, b); //~ERROR: constructing invalid value
1414
}
1515
}
1616

src/tools/miri/tests/fail/intrinsics/typed-swap-invalid-array.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error: Undefined Behavior: constructing invalid value at [0]: encountered 0x02, but expected a boolean
22
--> tests/fail/intrinsics/typed-swap-invalid-array.rs:LL:CC
33
|
4-
LL | typed_swap(a, b);
5-
| ^^^^^^^^^^^^^^^^ constructing invalid value at [0]: encountered 0x02, but expected a boolean
4+
LL | typed_swap_nonoverlapping(a, b);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at [0]: encountered 0x02, but expected a boolean
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

src/tools/miri/tests/fail/intrinsics/typed-swap-invalid-scalar.stderr src/tools/miri/tests/fail/intrinsics/typed-swap-invalid-scalar.left.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error: Undefined Behavior: constructing invalid value: encountered 0x02, but expected a boolean
22
--> tests/fail/intrinsics/typed-swap-invalid-scalar.rs:LL:CC
33
|
4-
LL | typed_swap(a, b);
5-
| ^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0x02, but expected a boolean
4+
LL | typed_swap_nonoverlapping(a, b);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0x02, but expected a boolean
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: constructing invalid value: encountered 0x03, but expected a boolean
2+
--> tests/fail/intrinsics/typed-swap-invalid-scalar.rs:LL:CC
3+
|
4+
LL | typed_swap_nonoverlapping(a, b);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0x03, but expected a boolean
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `invalid_scalar` at tests/fail/intrinsics/typed-swap-invalid-scalar.rs:LL:CC
11+
note: inside `main`
12+
--> tests/fail/intrinsics/typed-swap-invalid-scalar.rs:LL:CC
13+
|
14+
LL | invalid_scalar();
15+
| ^^^^^^^^^^^^^^^^
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+

src/tools/miri/tests/fail/intrinsics/typed-swap-invalid-scalar.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1+
//@revisions: left right
12
#![feature(core_intrinsics)]
23
#![feature(rustc_attrs)]
34

4-
use std::intrinsics::typed_swap;
5+
use std::intrinsics::typed_swap_nonoverlapping;
56
use std::ptr::addr_of_mut;
67

78
fn invalid_scalar() {
8-
let mut a = 1_u8;
9-
let mut b = 2_u8;
9+
// We run the test twice, with either the left or the right side being invalid.
10+
let mut a = if cfg!(left) { 2_u8 } else { 1_u8 };
11+
let mut b = if cfg!(right) { 3_u8 } else { 1_u8 };
1012
unsafe {
1113
let a = addr_of_mut!(a).cast::<bool>();
1214
let b = addr_of_mut!(b).cast::<bool>();
13-
typed_swap(a, b); //~ERROR: constructing invalid value
15+
typed_swap_nonoverlapping(a, b); //~ERROR: constructing invalid value
1416
}
1517
}
1618

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![feature(core_intrinsics)]
2+
#![feature(rustc_attrs)]
3+
4+
use std::intrinsics::typed_swap_nonoverlapping;
5+
use std::ptr::addr_of_mut;
6+
7+
fn main() {
8+
let mut a = 0_u8;
9+
unsafe {
10+
let a = addr_of_mut!(a);
11+
typed_swap_nonoverlapping(a, a); //~ERROR: called on overlapping ranges
12+
}
13+
}

0 commit comments

Comments
 (0)