const_eval_select: make it safe but be careful with what we expose on stable for now

This commit is contained in:
Ralf Jung 2024-03-02 12:53:28 +01:00
parent 4cdd20584c
commit 374607d6b9
18 changed files with 84 additions and 43 deletions

View File

@ -128,7 +128,8 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -
| sym::fsub_algebraic | sym::fsub_algebraic
| sym::fmul_algebraic | sym::fmul_algebraic
| sym::fdiv_algebraic | sym::fdiv_algebraic
| sym::frem_algebraic => hir::Unsafety::Normal, | sym::frem_algebraic
| sym::const_eval_select => hir::Unsafety::Normal,
_ => hir::Unsafety::Unsafe, _ => hir::Unsafety::Unsafe,
}; };

View File

@ -385,6 +385,7 @@ fn rt_error(layout: Layout) -> ! {
} }
#[cfg(not(feature = "panic_immediate_abort"))] #[cfg(not(feature = "panic_immediate_abort"))]
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
unsafe { unsafe {
core::intrinsics::const_eval_select((layout,), ct_error, rt_error) core::intrinsics::const_eval_select((layout,), ct_error, rt_error)
} }

View File

@ -428,10 +428,13 @@ const fn const_impl(bytes: &[u8]) -> &CStr {
unsafe { &*(bytes as *const [u8] as *const CStr) } unsafe { &*(bytes as *const [u8] as *const CStr) }
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: The const and runtime versions have identical behavior // SAFETY: The const and runtime versions have identical behavior
// unless the safety contract of `from_bytes_with_nul_unchecked` is // unless the safety contract of `from_bytes_with_nul_unchecked` is
// violated, which is UB. // violated, which is UB.
unsafe { intrinsics::const_eval_select((bytes,), const_impl, rt_impl) } unsafe {
intrinsics::const_eval_select((bytes,), const_impl, rt_impl)
}
} }
/// Returns the inner pointer to this C string. /// Returns the inner pointer to this C string.
@ -719,6 +722,9 @@ fn strlen_rt(s: *const c_char) -> usize {
unsafe { strlen(s) } unsafe { strlen(s) }
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: the two functions always provide equivalent functionality // SAFETY: the two functions always provide equivalent functionality
unsafe { intrinsics::const_eval_select((ptr,), strlen_ct, strlen_rt) } unsafe {
intrinsics::const_eval_select((ptr,), strlen_ct, strlen_rt)
}
} }

View File

@ -2515,6 +2515,8 @@ pub const fn unlikely(b: bool) -> bool {
/// intrinsic will be replaced with a call to `called_in_const`. It gets /// intrinsic will be replaced with a call to `called_in_const`. It gets
/// replaced with a call to `called_at_rt` otherwise. /// replaced with a call to `called_at_rt` otherwise.
/// ///
/// This function is safe to call, but note the stability concerns below.
///
/// # Type Requirements /// # Type Requirements
/// ///
/// The two functions must be both function items. They cannot be function /// The two functions must be both function items. They cannot be function
@ -2524,45 +2526,47 @@ pub const fn unlikely(b: bool) -> bool {
/// the two functions, therefore, both functions must accept the same type of /// the two functions, therefore, both functions must accept the same type of
/// arguments. Both functions must return RET. /// arguments. Both functions must return RET.
/// ///
/// # Safety /// # Stability concerns
/// ///
/// The two functions must behave observably equivalent. Safe code in other /// Rust has not yet decided that `const fn` are allowed to tell whether
/// crates may assume that calling a `const fn` at compile-time and at run-time /// they run at compile-time or at runtime. Therefore, when using this
/// produces the same result. A function that produces a different result when /// intrinsic anywhere that can be reached from stable, it is crucial that
/// evaluated at run-time, or has any other observable side-effects, is /// the end-to-end behavior of the stable `const fn` is the same for both
/// *unsound*. /// modes of execution. (Here, Undefined Behavior is considerd "the same"
/// as any other behavior, so if the function exhibits UB at runtime then
/// it may do whatever it wants at compile-time.)
/// ///
/// Here is an example of how this could cause a problem: /// Here is an example of how this could cause a problem:
/// ```no_run /// ```no_run
/// #![feature(const_eval_select)] /// #![feature(const_eval_select)]
/// #![feature(core_intrinsics)] /// #![feature(core_intrinsics)]
/// # #![allow(internal_features)] /// # #![allow(internal_features)]
/// use std::hint::unreachable_unchecked; /// # #![cfg_attr(bootstrap, allow(unused))]
/// use std::intrinsics::const_eval_select; /// use std::intrinsics::const_eval_select;
/// ///
/// // Crate A /// // Standard library
/// # #[cfg(not(bootstrap))]
/// pub const fn inconsistent() -> i32 { /// pub const fn inconsistent() -> i32 {
/// fn runtime() -> i32 { 1 } /// fn runtime() -> i32 { 1 }
/// const fn compiletime() -> i32 { 2 } /// const fn compiletime() -> i32 { 2 }
/// ///
/// unsafe {
// // ⚠ This code violates the required equivalence of `compiletime` // // ⚠ This code violates the required equivalence of `compiletime`
/// // and `runtime`. /// // and `runtime`.
/// const_eval_select((), compiletime, runtime) /// const_eval_select((), compiletime, runtime)
/// } /// }
/// } /// # #[cfg(bootstrap)]
/// # pub const fn inconsistent() -> i32 { 0 }
/// ///
/// // Crate B /// // User Crate
/// const X: i32 = inconsistent(); /// const X: i32 = inconsistent();
/// let x = inconsistent(); /// let x = inconsistent();
/// if x != X { unsafe { unreachable_unchecked(); }} /// assert_eq!(x, X);
/// ``` /// ```
/// ///
/// This code causes Undefined Behavior when being run, since the /// Currently such an assertion would always succeed; until Rust decides
/// `unreachable_unchecked` is actually being reached. The bug is in *crate A*, /// otherwise, that principle should not be violated.
/// which violates the principle that a `const fn` must behave the same at
/// compile-time and at run-time. The unsafe code in crate B is fine.
#[rustc_const_unstable(feature = "const_eval_select", issue = "none")] #[rustc_const_unstable(feature = "const_eval_select", issue = "none")]
#[cfg_attr(not(bootstrap), rustc_safe_intrinsic)]
pub fn const_eval_select<ARG: Tuple, F, G, RET>( pub fn const_eval_select<ARG: Tuple, F, G, RET>(
arg: ARG, arg: ARG,
called_in_const: F, called_in_const: F,

View File

@ -1153,8 +1153,11 @@ fn rt_f32_to_u32(x: f32) -> u32 {
// Stability concerns. // Stability concerns.
unsafe { mem::transmute(x) } unsafe { mem::transmute(x) }
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: We use internal implementations that either always work or fail at compile time. // SAFETY: We use internal implementations that either always work or fail at compile time.
unsafe { intrinsics::const_eval_select((self,), ct_f32_to_u32, rt_f32_to_u32) } unsafe {
intrinsics::const_eval_select((self,), ct_f32_to_u32, rt_f32_to_u32)
}
} }
/// Raw transmutation from `u32`. /// Raw transmutation from `u32`.
@ -1245,8 +1248,11 @@ fn rt_u32_to_f32(x: u32) -> f32 {
// Stability concerns. // Stability concerns.
unsafe { mem::transmute(x) } unsafe { mem::transmute(x) }
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: We use internal implementations that either always work or fail at compile time. // SAFETY: We use internal implementations that either always work or fail at compile time.
unsafe { intrinsics::const_eval_select((v,), ct_u32_to_f32, rt_u32_to_f32) } unsafe {
intrinsics::const_eval_select((v,), ct_u32_to_f32, rt_u32_to_f32)
}
} }
/// Return the memory representation of this floating point number as a byte array in /// Return the memory representation of this floating point number as a byte array in

View File

@ -1146,8 +1146,11 @@ fn rt_f64_to_u64(rt: f64) -> u64 {
// Stability concerns. // Stability concerns.
unsafe { mem::transmute::<f64, u64>(rt) } unsafe { mem::transmute::<f64, u64>(rt) }
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: We use internal implementations that either always work or fail at compile time. // SAFETY: We use internal implementations that either always work or fail at compile time.
unsafe { intrinsics::const_eval_select((self,), ct_f64_to_u64, rt_f64_to_u64) } unsafe {
intrinsics::const_eval_select((self,), ct_f64_to_u64, rt_f64_to_u64)
}
} }
/// Raw transmutation from `u64`. /// Raw transmutation from `u64`.
@ -1243,8 +1246,11 @@ fn rt_u64_to_f64(rt: u64) -> f64 {
// Stability concerns. // Stability concerns.
unsafe { mem::transmute::<u64, f64>(rt) } unsafe { mem::transmute::<u64, f64>(rt) }
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: We use internal implementations that either always work or fail at compile time. // SAFETY: We use internal implementations that either always work or fail at compile time.
unsafe { intrinsics::const_eval_select((v,), ct_u64_to_f64, rt_u64_to_f64) } unsafe {
intrinsics::const_eval_select((v,), ct_u64_to_f64, rt_u64_to_f64)
}
} }
/// Return the memory representation of this floating point number as a byte array in /// Return the memory representation of this floating point number as a byte array in

View File

@ -117,6 +117,7 @@ const fn comptime(fmt: fmt::Arguments<'_>, _force_no_backtrace: bool) -> ! {
panic_fmt(fmt); panic_fmt(fmt);
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: const panic does not care about unwinding // SAFETY: const panic does not care about unwinding
unsafe { unsafe {
super::intrinsics::const_eval_select((fmt, force_no_backtrace), comptime, runtime); super::intrinsics::const_eval_select((fmt, force_no_backtrace), comptime, runtime);

View File

@ -48,8 +48,11 @@ const fn const_impl(ptr: *const u8) -> bool {
} }
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: The two versions are equivalent at runtime. // SAFETY: The two versions are equivalent at runtime.
unsafe { const_eval_select((self as *const u8,), const_impl, runtime_impl) } unsafe {
const_eval_select((self as *const u8,), const_impl, runtime_impl)
}
} }
/// Casts to a pointer of another type. /// Casts to a pointer of another type.
@ -806,6 +809,7 @@ pub fn mask(self, mask: usize) -> *const T {
where where
T: Sized, T: Sized,
{ {
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: The comparison has no side-effects, and the intrinsic // SAFETY: The comparison has no side-effects, and the intrinsic
// does this check internally in the CTFE implementation. // does this check internally in the CTFE implementation.
unsafe { unsafe {
@ -1623,8 +1627,11 @@ const fn const_impl(ptr: *const (), align: usize) -> bool {
ptr.align_offset(align) == 0 ptr.align_offset(align) == 0
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: The two versions are equivalent at runtime. // SAFETY: The two versions are equivalent at runtime.
unsafe { const_eval_select((self.cast::<()>(), align), const_impl, runtime_impl) } unsafe {
const_eval_select((self.cast::<()>(), align), const_impl, runtime_impl)
}
} }
} }

View File

@ -991,6 +991,7 @@ macro_rules! attempt_swap_as_chunks {
}; };
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: the caller must guarantee that `x` and `y` are // SAFETY: the caller must guarantee that `x` and `y` are
// valid for writes and properly aligned. // valid for writes and properly aligned.
unsafe { unsafe {

View File

@ -48,8 +48,11 @@ const fn const_impl(ptr: *mut u8) -> bool {
} }
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: The two versions are equivalent at runtime. // SAFETY: The two versions are equivalent at runtime.
unsafe { const_eval_select((self as *mut u8,), const_impl, runtime_impl) } unsafe {
const_eval_select((self as *mut u8,), const_impl, runtime_impl)
}
} }
/// Casts to a pointer of another type. /// Casts to a pointer of another type.
@ -1896,8 +1899,11 @@ const fn const_impl(ptr: *mut (), align: usize) -> bool {
ptr.align_offset(align) == 0 ptr.align_offset(align) == 0
} }
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: The two versions are equivalent at runtime. // SAFETY: The two versions are equivalent at runtime.
unsafe { const_eval_select((self.cast::<()>(), align), const_impl, runtime_impl) } unsafe {
const_eval_select((self.cast::<()>(), align), const_impl, runtime_impl)
}
} }
} }

View File

@ -35,6 +35,7 @@ fn index_mut(&mut self, index: I) -> &mut I::Output {
#[track_caller] #[track_caller]
#[rustc_const_unstable(feature = "const_slice_index", issue = "none")] #[rustc_const_unstable(feature = "const_slice_index", issue = "none")]
const fn slice_start_index_len_fail(index: usize, len: usize) -> ! { const fn slice_start_index_len_fail(index: usize, len: usize) -> ! {
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: we are just panicking here // SAFETY: we are just panicking here
unsafe { unsafe {
const_eval_select( const_eval_select(
@ -63,6 +64,7 @@ const fn slice_start_index_len_fail_ct(_: usize, _: usize) -> ! {
#[track_caller] #[track_caller]
#[rustc_const_unstable(feature = "const_slice_index", issue = "none")] #[rustc_const_unstable(feature = "const_slice_index", issue = "none")]
const fn slice_end_index_len_fail(index: usize, len: usize) -> ! { const fn slice_end_index_len_fail(index: usize, len: usize) -> ! {
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: we are just panicking here // SAFETY: we are just panicking here
unsafe { unsafe {
const_eval_select((index, len), slice_end_index_len_fail_ct, slice_end_index_len_fail_rt) const_eval_select((index, len), slice_end_index_len_fail_ct, slice_end_index_len_fail_rt)
@ -87,8 +89,11 @@ const fn slice_end_index_len_fail_ct(_: usize, _: usize) -> ! {
#[track_caller] #[track_caller]
#[rustc_const_unstable(feature = "const_slice_index", issue = "none")] #[rustc_const_unstable(feature = "const_slice_index", issue = "none")]
const fn slice_index_order_fail(index: usize, end: usize) -> ! { const fn slice_index_order_fail(index: usize, end: usize) -> ! {
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: we are just panicking here // SAFETY: we are just panicking here
unsafe { const_eval_select((index, end), slice_index_order_fail_ct, slice_index_order_fail_rt) } unsafe {
const_eval_select((index, end), slice_index_order_fail_ct, slice_index_order_fail_rt)
}
} }
// FIXME const-hack // FIXME const-hack

View File

@ -86,6 +86,7 @@
#[rustc_allow_const_fn_unstable(const_eval_select)] #[rustc_allow_const_fn_unstable(const_eval_select)]
#[cfg(not(feature = "panic_immediate_abort"))] #[cfg(not(feature = "panic_immediate_abort"))]
const fn slice_error_fail(s: &str, begin: usize, end: usize) -> ! { const fn slice_error_fail(s: &str, begin: usize, end: usize) -> ! {
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
// SAFETY: panics for both branches // SAFETY: panics for both branches
unsafe { unsafe {
crate::intrinsics::const_eval_select( crate::intrinsics::const_eval_select(

View File

@ -12,8 +12,5 @@ fn uhoh() {
const fn c() {} const fn c() {}
fn main() { fn main() {
// safety: this is unsound and just used to test
unsafe {
std::intrinsics::const_eval_select((), c, uhoh); std::intrinsics::const_eval_select((), c, uhoh);
}
} }

View File

@ -1,3 +1,3 @@
thread 'main' panicked at $DIR/const-eval-select-backtrace.rs:17:9: thread 'main' panicked at $DIR/const-eval-select-backtrace.rs:15:5:
Aaah! Aaah!
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

View File

@ -13,7 +13,7 @@ const fn nothing(){}
#[stable(since = "1.0", feature = "hey")] #[stable(since = "1.0", feature = "hey")]
#[rustc_const_stable(since = "1.0", feature = "const_hey")] #[rustc_const_stable(since = "1.0", feature = "const_hey")]
pub const unsafe fn hey() { pub const fn hey() {
const_eval_select((), nothing, log); const_eval_select((), nothing, log);
//~^ ERROR `const_eval_select` is not yet stable as a const fn //~^ ERROR `const_eval_select` is not yet stable as a const fn
} }

View File

@ -22,9 +22,7 @@
} }
const fn eq(x: [i32; 4], y: [i32; 4]) -> bool { const fn eq(x: [i32; 4], y: [i32; 4]) -> bool {
unsafe {
const_eval_select((x, y), eq_ct, eq_rt) const_eval_select((x, y), eq_ct, eq_rt)
}
} }
fn main() { fn main() {

View File

@ -13,9 +13,9 @@ fn no() -> bool {
false false
} }
// not a sound use case; testing only // not allowed on stable; testing only
const fn is_const_eval() -> bool { const fn is_const_eval() -> bool {
unsafe { const_eval_select((), yes, no) } const_eval_select((), yes, no)
} }
fn main() { fn main() {

View File

@ -511,6 +511,7 @@ const fn drop<T: ~const Destruct>(_: T) {}
extern "rust-intrinsic" { extern "rust-intrinsic" {
#[rustc_const_stable(feature = "const_eval_select", since = "1.0.0")] #[rustc_const_stable(feature = "const_eval_select", since = "1.0.0")]
#[rustc_safe_intrinsic]
fn const_eval_select<ARG: Tuple, F, G, RET>( fn const_eval_select<ARG: Tuple, F, G, RET>(
arg: ARG, arg: ARG,
called_in_const: F, called_in_const: F,
@ -525,5 +526,5 @@ fn test_const_eval_select() {
const fn const_fn() {} const fn const_fn() {}
fn rt_fn() {} fn rt_fn() {}
unsafe { const_eval_select((), const_fn, rt_fn); } const_eval_select((), const_fn, rt_fn);
} }