Skip to content

Commit

Permalink
add const_eval_select macro to reduce redundancy
Browse files Browse the repository at this point in the history
  • Loading branch information
RalfJung committed Nov 3, 2024
1 parent e3a918e commit 9456d5c
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 176 deletions.
99 changes: 48 additions & 51 deletions library/core/src/ffi/c_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
use crate::cmp::Ordering;
use crate::error::Error;
use crate::ffi::c_char;
use crate::intrinsics::const_eval_select;
use crate::iter::FusedIterator;
use crate::marker::PhantomData;
use crate::ptr::NonNull;
use crate::slice::memchr;
use crate::{fmt, intrinsics, ops, slice, str};
use crate::{fmt, ops, slice, str};

// FIXME: because this is doc(inline)d, we *have* to use intra-doc links because the actual link
// depends on where the item is being documented. however, since this is libcore, we can't
Expand Down Expand Up @@ -411,37 +412,35 @@ impl CStr {
#[rustc_const_stable(feature = "const_cstr_unchecked", since = "1.59.0")]
#[rustc_allow_const_fn_unstable(const_eval_select)]
pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr {
#[inline]
fn rt_impl(bytes: &[u8]) -> &CStr {
// Chance at catching some UB at runtime with debug builds.
debug_assert!(!bytes.is_empty() && bytes[bytes.len() - 1] == 0);

// SAFETY: Casting to CStr is safe because its internal representation
// is a [u8] too (safe only inside std).
// Dereferencing the obtained pointer is safe because it comes from a
// reference. Making a reference is then safe because its lifetime
// is bound by the lifetime of the given `bytes`.
unsafe { &*(bytes as *const [u8] as *const CStr) }
}

const fn const_impl(bytes: &[u8]) -> &CStr {
// Saturating so that an empty slice panics in the assert with a good
// message, not here due to underflow.
let mut i = bytes.len().saturating_sub(1);
assert!(!bytes.is_empty() && bytes[i] == 0, "input was not nul-terminated");

// Ending nul byte exists, skip to the rest.
while i != 0 {
i -= 1;
let byte = bytes[i];
assert!(byte != 0, "input contained interior nul");
const_eval_select!(
(bytes: &[u8]) -> &CStr:
if const {
// Saturating so that an empty slice panics in the assert with a good
// message, not here due to underflow.
let mut i = bytes.len().saturating_sub(1);
assert!(!bytes.is_empty() && bytes[i] == 0, "input was not nul-terminated");

// Ending nul byte exists, skip to the rest.
while i != 0 {
i -= 1;
let byte = bytes[i];
assert!(byte != 0, "input contained interior nul");
}

// SAFETY: See runtime cast comment below.
unsafe { &*(bytes as *const [u8] as *const CStr) }
} else #[inline] {
// Chance at catching some UB at runtime with debug builds.
debug_assert!(!bytes.is_empty() && bytes[bytes.len() - 1] == 0);

// SAFETY: Casting to CStr is safe because its internal representation
// is a [u8] too (safe only inside std).
// Dereferencing the obtained pointer is safe because it comes from a
// reference. Making a reference is then safe because its lifetime
// is bound by the lifetime of the given `bytes`.
unsafe { &*(bytes as *const [u8] as *const CStr) }
}

// SAFETY: See `rt_impl` cast.
unsafe { &*(bytes as *const [u8] as *const CStr) }
}

intrinsics::const_eval_select((bytes,), const_impl, rt_impl)
)
}

/// Returns the inner pointer to this C string.
Expand Down Expand Up @@ -735,29 +734,27 @@ impl AsRef<CStr> for CStr {
#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_cstr_from_ptr", since = "1.81.0"))]
#[rustc_allow_const_fn_unstable(const_eval_select)]
const unsafe fn strlen(ptr: *const c_char) -> usize {
const fn strlen_ct(s: *const c_char) -> usize {
let mut len = 0;

// SAFETY: Outer caller has provided a pointer to a valid C string.
while unsafe { *s.add(len) } != 0 {
len += 1;
}
const_eval_select!(
(s: *const c_char = ptr) -> usize:
if const {
let mut len = 0;

// SAFETY: Outer caller has provided a pointer to a valid C string.
while unsafe { *s.add(len) } != 0 {
len += 1;
}

len
}
len
} else #[inline] {
extern "C" {
/// Provided by libc or compiler_builtins.
fn strlen(s: *const c_char) -> usize;
}

#[inline]
fn strlen_rt(s: *const c_char) -> usize {
extern "C" {
/// Provided by libc or compiler_builtins.
fn strlen(s: *const c_char) -> usize;
// SAFETY: Outer caller has provided a pointer to a valid C string.
unsafe { strlen(s) }
}

// SAFETY: Outer caller has provided a pointer to a valid C string.
unsafe { strlen(s) }
}

intrinsics::const_eval_select((ptr,), strlen_ct, strlen_rt)
)
}

/// An iterator over the bytes of a [`CStr`], without the nul terminator.
Expand Down
80 changes: 70 additions & 10 deletions library/core/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2788,6 +2788,65 @@ where
unreachable!()
}

/// A macro to make it easier to invoke const_eval_select. Use as follows:
/// ```rust,ignore (just a macro example)
/// const_eval_select!(
/// #[inline]
/// (arg1: i32 = some_expr, arg2: T = other_expr) -> U:
/// if const {
/// // Compile-time code goes here.
/// } else {
/// // Run-time code goes here.
/// }
/// )
/// ```
pub(crate) macro const_eval_select {
(
$(#[$attr:meta])*
($($arg:ident : $ty:ty = $val:expr),* $(,)?) $( -> $ret:ty )?:
if const
$(#[$compiletime_attr:meta])* $compiletime:block
else
$(#[$runtime_attr:meta])* $runtime:block
) => {{
$(#[$attr])*
$(#[$runtime_attr])*
fn runtime($($arg: $ty),*) $( -> $ret )? {
$runtime
}

$(#[$attr])*
$(#[$compiletime_attr])*
const fn compiletime($($arg: $ty),*) $( -> $ret )? {
// Don't warn if one of the arguments is unused.
$(let _ = $arg;)*

$compiletime
}

const_eval_select(($($val,)*), compiletime, runtime)
}},
// We support leaving away the `val` expressions for *all* arguments
// (but not for *some* arguments, that's too tricky).
(
$(#[$attr:meta])*
($($arg:ident : $ty:ty),* $(,)?) -> $ret:ty:
if const
$(#[$compiletime_attr:meta])* $compiletime:block
else
$(#[$runtime_attr:meta])* $runtime:block
) => {
$crate::intrinsics::const_eval_select!(
$(#[$attr])*
($($arg : $ty = $arg),*) -> $ret:
if const
$(#[$compiletime_attr])* $compiletime
else
$(#[$runtime_attr])* $runtime
)
},
}

/// Returns whether the argument's value is statically known at
/// compile-time.
///
Expand Down Expand Up @@ -2830,7 +2889,7 @@ where
/// # Stability concerns
///
/// While it is safe to call, this intrinsic may behave differently in
/// a `const` context than otherwise. See the [`const_eval_select`]
/// a `const` context than otherwise. See the [`const_eval_select()`]
/// documentation for an explanation of the issues this can cause. Unlike
/// `const_eval_select`, this intrinsic isn't guaranteed to behave
/// deterministically even in a `const` context.
Expand Down Expand Up @@ -3734,14 +3793,15 @@ pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize
fn miri_promise_symbolic_alignment(ptr: *const (), align: usize);
}

fn runtime(ptr: *const (), align: usize) {
// SAFETY: this call is always safe.
unsafe {
miri_promise_symbolic_alignment(ptr, align);
const_eval_select!(
(ptr: *const (), align: usize):
if const {
// Do nothing.
} else {
// SAFETY: this call is always safe.
unsafe {
miri_promise_symbolic_alignment(ptr, align);
}
}
}

const fn compiletime(_ptr: *const (), _align: usize) {}

const_eval_select((ptr, align), compiletime, runtime);
)
}
23 changes: 10 additions & 13 deletions library/core/src/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,6 @@ macro_rules! panic {
#[doc(hidden)]
pub macro const_panic {
($const_msg:literal, $runtime_msg:literal, $($arg:ident : $ty:ty = $val:expr),* $(,)?) => {{
#[inline]
#[track_caller]
fn runtime($($arg: $ty),*) -> ! {
$crate::panic!($runtime_msg);
}

#[inline]
#[track_caller]
const fn compiletime($(_: $ty),*) -> ! {
$crate::panic!($const_msg);
}

// Wrap call to `const_eval_select` in a function so that we can
// add the `rustc_allow_const_fn_unstable`. This is okay to do
// because both variants will panic, just with different messages.
Expand All @@ -44,7 +32,16 @@ pub macro const_panic {
#[track_caller]
#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_panic", since = "CURRENT_RUSTC_VERSION"))]
const fn do_panic($($arg: $ty),*) -> ! {
$crate::intrinsics::const_eval_select(($($arg),* ,), compiletime, runtime)
$crate::intrinsics::const_eval_select!(
#[inline]
#[track_caller]
($($arg: $ty),*) -> !:
if const {
$crate::panic!($const_msg)
} else {
$crate::panic!($runtime_msg)
}
)
}

do_panic($($val),*)
Expand Down
Loading

0 comments on commit 9456d5c

Please sign in to comment.