Skip to content

Commit bc5faca

Browse files
committed
Auto merge of #100759 - fee1-dead-contrib:const_eval_select_real_intrinsic, r=oli-obk,RalfJung
Make `const_eval_select` a real intrinsic This fixes issues where `track_caller` functions do not have nice panic messages anymore when there is a call to the function, and uses the MIR system to replace the call instead of dispatching via lang items. Fixes #100696.
2 parents ddbf916 + de3772e commit bc5faca

File tree

11 files changed

+153
-97
lines changed

11 files changed

+153
-97
lines changed

core/src/intrinsics.rs

Lines changed: 82 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@
5454
)]
5555
#![allow(missing_docs)]
5656

57-
use crate::marker::{Destruct, DiscriminantKind};
57+
#[cfg(bootstrap)]
58+
use crate::marker::Destruct;
59+
use crate::marker::DiscriminantKind;
5860
use crate::mem;
5961

6062
// These imports are used for simplifying intra-doc links
@@ -2085,6 +2087,65 @@ extern "rust-intrinsic" {
20852087
/// `ptr` must point to a vtable.
20862088
/// The intrinsic will return the alignment stored in that vtable.
20872089
pub fn vtable_align(ptr: *const ()) -> usize;
2090+
2091+
/// Selects which function to call depending on the context.
2092+
///
2093+
/// If this function is evaluated at compile-time, then a call to this
2094+
/// intrinsic will be replaced with a call to `called_in_const`. It gets
2095+
/// replaced with a call to `called_at_rt` otherwise.
2096+
///
2097+
/// # Type Requirements
2098+
///
2099+
/// The two functions must be both function items. They cannot be function
2100+
/// pointers or closures. The first function must be a `const fn`.
2101+
///
2102+
/// `arg` will be the tupled arguments that will be passed to either one of
2103+
/// the two functions, therefore, both functions must accept the same type of
2104+
/// arguments. Both functions must return RET.
2105+
///
2106+
/// # Safety
2107+
///
2108+
/// The two functions must behave observably equivalent. Safe code in other
2109+
/// crates may assume that calling a `const fn` at compile-time and at run-time
2110+
/// produces the same result. A function that produces a different result when
2111+
/// evaluated at run-time, or has any other observable side-effects, is
2112+
/// *unsound*.
2113+
///
2114+
/// Here is an example of how this could cause a problem:
2115+
/// ```no_run
2116+
/// #![feature(const_eval_select)]
2117+
/// #![feature(core_intrinsics)]
2118+
/// use std::hint::unreachable_unchecked;
2119+
/// use std::intrinsics::const_eval_select;
2120+
///
2121+
/// // Crate A
2122+
/// pub const fn inconsistent() -> i32 {
2123+
/// fn runtime() -> i32 { 1 }
2124+
/// const fn compiletime() -> i32 { 2 }
2125+
///
2126+
/// unsafe {
2127+
// // ⚠ This code violates the required equivalence of `compiletime`
2128+
/// // and `runtime`.
2129+
/// const_eval_select((), compiletime, runtime)
2130+
/// }
2131+
/// }
2132+
///
2133+
/// // Crate B
2134+
/// const X: i32 = inconsistent();
2135+
/// let x = inconsistent();
2136+
/// if x != X { unsafe { unreachable_unchecked(); }}
2137+
/// ```
2138+
///
2139+
/// This code causes Undefined Behavior when being run, since the
2140+
/// `unreachable_unchecked` is actually being reached. The bug is in *crate A*,
2141+
/// which violates the principle that a `const fn` must behave the same at
2142+
/// compile-time and at run-time. The unsafe code in crate B is fine.
2143+
#[cfg(not(bootstrap))]
2144+
#[rustc_const_unstable(feature = "const_eval_select", issue = "none")]
2145+
pub fn const_eval_select<ARG, F, G, RET>(arg: ARG, called_in_const: F, called_at_rt: G) -> RET
2146+
where
2147+
G: FnOnce<ARG, Output = RET>,
2148+
F: FnOnce<ARG, Output = RET>;
20882149
}
20892150

20902151
// Some functions are defined here because they accidentally got made
@@ -2095,6 +2156,11 @@ extern "rust-intrinsic" {
20952156
/// Check that the preconditions of an unsafe function are followed, if debug_assertions are on,
20962157
/// and only at runtime.
20972158
///
2159+
/// This macro should be called as `assert_unsafe_precondition!([Generics](name: Type) => Expression)`
2160+
/// where the names specified will be moved into the macro as captured variables, and defines an item
2161+
/// to call `const_eval_select` on. The tokens inside the square brackets are used to denote generics
2162+
/// for the function declaractions and can be omitted if there is no generics.
2163+
///
20982164
/// # Safety
20992165
///
21002166
/// Invoking this macro is only sound if the following code is already UB when the passed
@@ -2109,18 +2175,21 @@ extern "rust-intrinsic" {
21092175
/// the occasional mistake, and this check should help them figure things out.
21102176
#[allow_internal_unstable(const_eval_select)] // permit this to be called in stably-const fn
21112177
macro_rules! assert_unsafe_precondition {
2112-
($e:expr) => {
2178+
($([$($tt:tt)*])?($($i:ident:$ty:ty),*$(,)?) => $e:expr) => {
21132179
if cfg!(debug_assertions) {
2114-
// Use a closure so that we can capture arbitrary expressions from the invocation
2115-
let runtime = || {
2180+
// allow non_snake_case to allow capturing const generics
2181+
#[allow(non_snake_case)]
2182+
#[inline(always)]
2183+
fn runtime$(<$($tt)*>)?($($i:$ty),*) {
21162184
if !$e {
21172185
// abort instead of panicking to reduce impact on code size
21182186
::core::intrinsics::abort();
21192187
}
2120-
};
2121-
const fn comptime() {}
2188+
}
2189+
#[allow(non_snake_case)]
2190+
const fn comptime$(<$($tt)*>)?($(_:$ty),*) {}
21222191

2123-
::core::intrinsics::const_eval_select((), comptime, runtime);
2192+
::core::intrinsics::const_eval_select(($($i,)*), comptime, runtime);
21242193
}
21252194
};
21262195
}
@@ -2243,7 +2312,7 @@ pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: us
22432312
// SAFETY: the safety contract for `copy_nonoverlapping` must be
22442313
// upheld by the caller.
22452314
unsafe {
2246-
assert_unsafe_precondition!(
2315+
assert_unsafe_precondition!([T](src: *const T, dst: *mut T, count: usize) =>
22472316
is_aligned_and_not_null(src)
22482317
&& is_aligned_and_not_null(dst)
22492318
&& is_nonoverlapping(src, dst, count)
@@ -2329,7 +2398,8 @@ pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
23292398

23302399
// SAFETY: the safety contract for `copy` must be upheld by the caller.
23312400
unsafe {
2332-
assert_unsafe_precondition!(is_aligned_and_not_null(src) && is_aligned_and_not_null(dst));
2401+
assert_unsafe_precondition!([T](src: *const T, dst: *mut T) =>
2402+
is_aligned_and_not_null(src) && is_aligned_and_not_null(dst));
23332403
copy(src, dst, count)
23342404
}
23352405
}
@@ -2397,63 +2467,12 @@ pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {
23972467

23982468
// SAFETY: the safety contract for `write_bytes` must be upheld by the caller.
23992469
unsafe {
2400-
assert_unsafe_precondition!(is_aligned_and_not_null(dst));
2470+
assert_unsafe_precondition!([T](dst: *mut T) => is_aligned_and_not_null(dst));
24012471
write_bytes(dst, val, count)
24022472
}
24032473
}
24042474

2405-
/// Selects which function to call depending on the context.
2406-
///
2407-
/// If this function is evaluated at compile-time, then a call to this
2408-
/// intrinsic will be replaced with a call to `called_in_const`. It gets
2409-
/// replaced with a call to `called_at_rt` otherwise.
2410-
///
2411-
/// # Type Requirements
2412-
///
2413-
/// The two functions must be both function items. They cannot be function
2414-
/// pointers or closures.
2415-
///
2416-
/// `arg` will be the arguments that will be passed to either one of the
2417-
/// two functions, therefore, both functions must accept the same type of
2418-
/// arguments. Both functions must return RET.
2419-
///
2420-
/// # Safety
2421-
///
2422-
/// The two functions must behave observably equivalent. Safe code in other
2423-
/// crates may assume that calling a `const fn` at compile-time and at run-time
2424-
/// produces the same result. A function that produces a different result when
2425-
/// evaluated at run-time, or has any other observable side-effects, is
2426-
/// *unsound*.
2427-
///
2428-
/// Here is an example of how this could cause a problem:
2429-
/// ```no_run
2430-
/// #![feature(const_eval_select)]
2431-
/// #![feature(core_intrinsics)]
2432-
/// use std::hint::unreachable_unchecked;
2433-
/// use std::intrinsics::const_eval_select;
2434-
///
2435-
/// // Crate A
2436-
/// pub const fn inconsistent() -> i32 {
2437-
/// fn runtime() -> i32 { 1 }
2438-
/// const fn compiletime() -> i32 { 2 }
2439-
///
2440-
/// unsafe {
2441-
// // ⚠ This code violates the required equivalence of `compiletime`
2442-
/// // and `runtime`.
2443-
/// const_eval_select((), compiletime, runtime)
2444-
/// }
2445-
/// }
2446-
///
2447-
/// // Crate B
2448-
/// const X: i32 = inconsistent();
2449-
/// let x = inconsistent();
2450-
/// if x != X { unsafe { unreachable_unchecked(); }}
2451-
/// ```
2452-
///
2453-
/// This code causes Undefined Behavior when being run, since the
2454-
/// `unreachable_unchecked` is actually being reached. The bug is in *crate A*,
2455-
/// which violates the principle that a `const fn` must behave the same at
2456-
/// compile-time and at run-time. The unsafe code in crate B is fine.
2475+
#[cfg(bootstrap)]
24572476
#[unstable(
24582477
feature = "const_eval_select",
24592478
issue = "none",
@@ -2475,6 +2494,7 @@ where
24752494
called_at_rt.call_once(arg)
24762495
}
24772496

2497+
#[cfg(bootstrap)]
24782498
#[unstable(
24792499
feature = "const_eval_select",
24802500
issue = "none",

core/src/mem/valid_align.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ impl ValidAlign {
2828
#[inline]
2929
pub(crate) const unsafe fn new_unchecked(align: usize) -> Self {
3030
// SAFETY: Precondition passed to the caller.
31-
unsafe { assert_unsafe_precondition!(align.is_power_of_two()) };
31+
unsafe { assert_unsafe_precondition!((align: usize) => align.is_power_of_two()) };
3232

3333
// SAFETY: By precondition, this must be a power of two, and
3434
// our variants encompass all possible powers of two.

core/src/num/f32.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,10 +1033,14 @@ impl f32 {
10331033
}
10341034
}
10351035
}
1036-
// SAFETY: `u32` is a plain old datatype so we can always... uh...
1037-
// ...look, just pretend you forgot what you just read.
1038-
// Stability concerns.
1039-
let rt_f32_to_u32 = |rt| unsafe { mem::transmute::<f32, u32>(rt) };
1036+
1037+
#[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491
1038+
fn rt_f32_to_u32(x: f32) -> u32 {
1039+
// SAFETY: `u32` is a plain old datatype so we can always... uh...
1040+
// ...look, just pretend you forgot what you just read.
1041+
// Stability concerns.
1042+
unsafe { mem::transmute(x) }
1043+
}
10401044
// SAFETY: We use internal implementations that either always work or fail at compile time.
10411045
unsafe { intrinsics::const_eval_select((self,), ct_f32_to_u32, rt_f32_to_u32) }
10421046
}
@@ -1121,10 +1125,14 @@ impl f32 {
11211125
}
11221126
}
11231127
}
1124-
// SAFETY: `u32` is a plain old datatype so we can always... uh...
1125-
// ...look, just pretend you forgot what you just read.
1126-
// Stability concerns.
1127-
let rt_u32_to_f32 = |rt| unsafe { mem::transmute::<u32, f32>(rt) };
1128+
1129+
#[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491
1130+
fn rt_u32_to_f32(x: u32) -> f32 {
1131+
// SAFETY: `u32` is a plain old datatype so we can always... uh...
1132+
// ...look, just pretend you forgot what you just read.
1133+
// Stability concerns.
1134+
unsafe { mem::transmute(x) }
1135+
}
11281136
// SAFETY: We use internal implementations that either always work or fail at compile time.
11291137
unsafe { intrinsics::const_eval_select((v,), ct_u32_to_f32, rt_u32_to_f32) }
11301138
}

core/src/num/f64.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,10 +1026,14 @@ impl f64 {
10261026
}
10271027
}
10281028
}
1029-
// SAFETY: `u64` is a plain old datatype so we can always... uh...
1030-
// ...look, just pretend you forgot what you just read.
1031-
// Stability concerns.
1032-
let rt_f64_to_u64 = |rt| unsafe { mem::transmute::<f64, u64>(rt) };
1029+
1030+
#[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491
1031+
fn rt_f64_to_u64(rt: f64) -> u64 {
1032+
// SAFETY: `u64` is a plain old datatype so we can always... uh...
1033+
// ...look, just pretend you forgot what you just read.
1034+
// Stability concerns.
1035+
unsafe { mem::transmute::<f64, u64>(rt) }
1036+
}
10331037
// SAFETY: We use internal implementations that either always work or fail at compile time.
10341038
unsafe { intrinsics::const_eval_select((self,), ct_f64_to_u64, rt_f64_to_u64) }
10351039
}
@@ -1119,10 +1123,14 @@ impl f64 {
11191123
}
11201124
}
11211125
}
1122-
// SAFETY: `u64` is a plain old datatype so we can always... uh...
1123-
// ...look, just pretend you forgot what you just read.
1124-
// Stability concerns.
1125-
let rt_u64_to_f64 = |rt| unsafe { mem::transmute::<u64, f64>(rt) };
1126+
1127+
#[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491
1128+
fn rt_u64_to_f64(rt: u64) -> f64 {
1129+
// SAFETY: `u64` is a plain old datatype so we can always... uh...
1130+
// ...look, just pretend you forgot what you just read.
1131+
// Stability concerns.
1132+
unsafe { mem::transmute::<u64, f64>(rt) }
1133+
}
11261134
// SAFETY: We use internal implementations that either always work or fail at compile time.
11271135
unsafe { intrinsics::const_eval_select((v,), ct_u64_to_f64, rt_u64_to_f64) }
11281136
}

core/src/num/nonzero.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ macro_rules! nonzero_integers {
5656
pub const unsafe fn new_unchecked(n: $Int) -> Self {
5757
// SAFETY: this is guaranteed to be safe by the caller.
5858
unsafe {
59-
core::intrinsics::assert_unsafe_precondition!(n != 0);
59+
core::intrinsics::assert_unsafe_precondition!((n: $Int) => n != 0);
6060
Self(n)
6161
}
6262
}

core/src/ptr/const_ptr.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,9 +755,12 @@ impl<T: ?Sized> *const T {
755755
where
756756
T: Sized,
757757
{
758+
let this = self;
758759
// SAFETY: The comparison has no side-effects, and the intrinsic
759760
// does this check internally in the CTFE implementation.
760-
unsafe { assert_unsafe_precondition!(self >= origin) };
761+
unsafe {
762+
assert_unsafe_precondition!([T](this: *const T, origin: *const T) => this >= origin)
763+
};
761764

762765
let pointee_size = mem::size_of::<T>();
763766
assert!(0 < pointee_size && pointee_size <= isize::MAX as usize);

core/src/ptr/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,7 @@ pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize) {
886886
// SAFETY: the caller must guarantee that `x` and `y` are
887887
// valid for writes and properly aligned.
888888
unsafe {
889-
assert_unsafe_precondition!(
889+
assert_unsafe_precondition!([T](x: *mut T, y: *mut T, count: usize) =>
890890
is_aligned_and_not_null(x)
891891
&& is_aligned_and_not_null(y)
892892
&& is_nonoverlapping(x, y, count)
@@ -983,7 +983,7 @@ pub const unsafe fn replace<T>(dst: *mut T, mut src: T) -> T {
983983
// and cannot overlap `src` since `dst` must point to a distinct
984984
// allocated object.
985985
unsafe {
986-
assert_unsafe_precondition!(is_aligned_and_not_null(dst));
986+
assert_unsafe_precondition!([T](dst: *mut T) => is_aligned_and_not_null(dst));
987987
mem::swap(&mut *dst, &mut src); // cannot overlap
988988
}
989989
src
@@ -1470,7 +1470,7 @@ pub const unsafe fn write_unaligned<T>(dst: *mut T, src: T) {
14701470
pub unsafe fn read_volatile<T>(src: *const T) -> T {
14711471
// SAFETY: the caller must uphold the safety contract for `volatile_load`.
14721472
unsafe {
1473-
assert_unsafe_precondition!(is_aligned_and_not_null(src));
1473+
assert_unsafe_precondition!([T](src: *const T) => is_aligned_and_not_null(src));
14741474
intrinsics::volatile_load(src)
14751475
}
14761476
}
@@ -1541,7 +1541,7 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T {
15411541
pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
15421542
// SAFETY: the caller must uphold the safety contract for `volatile_store`.
15431543
unsafe {
1544-
assert_unsafe_precondition!(is_aligned_and_not_null(dst));
1544+
assert_unsafe_precondition!([T](dst: *mut T) => is_aligned_and_not_null(dst));
15451545
intrinsics::volatile_store(dst, src);
15461546
}
15471547
}

0 commit comments

Comments
 (0)