Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lib] Prefer core::hint::unreachable_unchecked over core::unreachable macro #88606

Closed
BastiDood opened this issue Sep 3, 2021 · 3 comments
Closed

Comments

@BastiDood
Copy link

At the moment, many of the core assertions in the standard library make use of the unreachable macro, which is simply an indirection for panic. This is totally fine, but I would like to begin an initiative on upgrading some of these invocations as core::hint::unreachable_unchecked so that we may (at least) give the compiler a chance to optimize the code better.

Below is a non-exhaustive list of possible candidates. I will be editing the issue as more candidates are introduced and as certain examples are marked 100% safe or not.

Merged

None so far.

Definitely Safe

This is the perfect candidate for core::hint::unreachable_unchecked since it is apparent that self is now the Cow::Owned variant.

#[stable(feature = "rust1", since = "1.0.0")]
pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned {
match *self {
Borrowed(borrowed) => {
*self = Owned(borrowed.to_owned());
match *self {
Borrowed(..) => unreachable!(),
Owned(ref mut owned) => owned,
}
}
Owned(ref mut owned) => owned,
}
}

Possible Candidates

#[unstable(feature = "once_cell", issue = "74465")]
impl<T: Clone> Clone for OnceCell<T> {
fn clone(&self) -> OnceCell<T> {
let res = OnceCell::new();
if let Some(value) = self.get() {
match res.set(value.clone()) {
Ok(()) => (),
Err(_) => unreachable!(),
}
}
res
}
}

fn clone(&self) -> SyncOnceCell<T> {
let cell = Self::new();
if let Some(value) = self.get() {
match cell.set(value.clone()) {
Ok(()) => (),
Err(_) => unreachable!(),
}
}
cell
}

fn from(value: T) -> Self {
let cell = Self::new();
match cell.set(value) {
Ok(()) => cell,
Err(_) => unreachable!(),
}
}

// Initialization is done.
Err(DONE) => {}

// Not possible, these are one-use channels
DATA => unreachable!(),

match (&mut *self.data.get()).take() {
Some(data) => Ok(data),
None => unreachable!(),
}

// We are the sole receiver; there cannot be a blocking
// receiver already.
_ => unreachable!(),

// We're the only ones that can block on this port
_ => unreachable!(),

// Now that we've got ownership of our state, figure out what to do
// about it.
match state {
EMPTY => unreachable!(),

// There was a thread waiting, just pass the lock
if let NotifiedTcs::Single(tcs) = guard.notified_tcs() {
guard.lock_var_mut().owner = Some(tcs)
} else {
unreachable!() // called notify_one
}

unsafe fn __iterator_get_unchecked(&mut self, _idx: usize) -> Self::Item
where
Self: TrustedRandomAccessNoCoerce,
{
unreachable!("Always specialized");
}

// One or more readers were waiting, pass the lock to them
if let NotifiedTcs::All { count } = rguard.notified_tcs() {
*rguard.lock_var_mut() = Some(count)
} else {
unreachable!() // called notify_all
}

match (self.a.next_back(), self.b.next_back()) {
(Some(x), Some(y)) => Some((x, y)),
(None, None) => None,
_ => unreachable!(),
}

default unsafe fn get_unchecked(&mut self, _idx: usize) -> <Self as Iterator>::Item
where
Self: TrustedRandomAccessNoCoerce,
{
unreachable!("Always specialized");
}

// Now that we've determined that this queue "has data", we peek at the
// queue to see if the data is an upgrade or not. If it's an upgrade,
// then we need to destroy this port and abort selection on the
// upgraded port.
if has_data {
match self.queue.peek() {
Some(&mut GoUp(..)) => match self.queue.pop() {
Some(GoUp(port)) => Err(port),
_ => unreachable!(),
},
_ => Ok(true),
}
} else {
Ok(false)
}
}

match self.queue.pop() {
mpsc::Data(t) => Ok(t),
mpsc::Empty => Err(Disconnected),
// with no senders, an inconsistency is impossible.
mpsc::Inconsistent => unreachable!(),
}

match result {
CopyResult::Ended(bytes_copied) => return Ok(bytes_copied + written),
CopyResult::Error(e, _) => return Err(e),
CopyResult::Fallback(0) => { /* use the fallback below */ }
CopyResult::Fallback(_) => {
unreachable!("splice should not return > 0 bytes on the fallback path")
}

State::Done => unreachable!(),

rust/library/std/src/net/ip.rs

Lines 1679 to 1684 in a49e38e

match segments[5] {
// IPv4 Compatible address
0 => write!(f, "::{}", ipv4),
// IPv4 Mapped address
0xffff => write!(f, "::ffff:{}", ipv4),
_ => unreachable!(),

match (split_edge.force(), right_node.force()) {
(Internal(edge), Internal(node)) => {
left_node = edge.descend();
right_node = node.first_edge().descend();
}
(Leaf(_), Leaf(_)) => break,
_ => unreachable!(),
}

let mut out_node = match root.borrow_mut().force() {
Leaf(leaf) => leaf,
Internal(_) => unreachable!(),
};

Err(_) => unreachable!("empty internal node"),

match (left_node.reborrow_mut().force(), right_node.reborrow_mut().force()) {
(ForceResult::Internal(mut left), ForceResult::Internal(mut right)) => {
// Make room for stolen edges.
slice_shr(right.edge_area_mut(..new_right_len + 1), count);
// Steal edges.
move_to_slice(
left.edge_area_mut(new_left_len + 1..old_left_len + 1),
right.edge_area_mut(..count),
);
right.correct_childrens_parent_links(0..new_right_len + 1);
}
(ForceResult::Leaf(_), ForceResult::Leaf(_)) => {}
_ => unreachable!(),
}

match (left_node.reborrow_mut().force(), right_node.reborrow_mut().force()) {
(ForceResult::Internal(mut left), ForceResult::Internal(mut right)) => {
// Steal edges.
move_to_slice(
right.edge_area_mut(..count),
left.edge_area_mut(old_left_len + 1..new_left_len + 1),
);
// Fill gap where stolen edges used to be.
slice_shl(right.edge_area_mut(..old_right_len + 1), count);
left.correct_childrens_parent_links(old_left_len + 1..new_left_len + 1);
right.correct_childrens_parent_links(0..new_right_len + 1);
}
(ForceResult::Leaf(_), ForceResult::Leaf(_)) => {}
_ => unreachable!(),
}

match (left_node.force(), right_node.force()) {
(ForceResult::Internal(mut left), ForceResult::Internal(mut right)) => {
move_to_slice(
left.edge_area_mut(new_left_len + 1..old_left_len + 1),
right.edge_area_mut(1..new_right_len + 1),
);
right.correct_childrens_parent_links(1..new_right_len + 1);
}
(ForceResult::Leaf(_), ForceResult::Leaf(_)) => {}
_ => unreachable!(),
}

match mem::replace(&mut guard.blocker, f(signal_token)) {
NoneBlocked => {}
_ => unreachable!(),
}

Flavor::Sync(..) => unreachable!(),

BlockedSender(..) => unreachable!(),

// If this is a no-buffer channel (cap == 0), then if we didn't wait we
// need to ACK the sender. If we waited, then the sender waking us up
// was already the ACK.
let pending_sender2 = if guard.cap == 0 && !waited {
match mem::replace(&mut guard.blocker, NoneBlocked) {
NoneBlocked => None,
BlockedReceiver(..) => unreachable!(),
BlockedSender(token) => {
guard.canceled.take();
Some(token)
}
}
} else {
None
};

match mem::replace(&mut guard.blocker, NoneBlocked) {
NoneBlocked => {}
BlockedSender(..) => unreachable!(),
BlockedReceiver(token) => wakeup(token, guard),
}

let waiter = match mem::replace(&mut guard.blocker, NoneBlocked) {
NoneBlocked => None,
BlockedSender(token) => {
*guard.canceled.take().unwrap() = true;
Some(token)
}
BlockedReceiver(..) => unreachable!(),
};

let new_port = match *unsafe { self.inner() } {
Flavor::Oneshot(ref p) => match p.recv(None) {
Ok(t) => return Ok(t),
Err(oneshot::Disconnected) => return Err(RecvError),
Err(oneshot::Upgraded(rx)) => rx,
Err(oneshot::Empty) => unreachable!(),
},
Flavor::Stream(ref p) => match p.recv(None) {
Ok(t) => return Ok(t),
Err(stream::Disconnected) => return Err(RecvError),
Err(stream::Upgraded(rx)) => rx,
Err(stream::Empty) => unreachable!(),
},
Flavor::Shared(ref p) => match p.recv(None) {
Ok(t) => return Ok(t),
Err(shared::Disconnected) => return Err(RecvError),
Err(shared::Empty) => unreachable!(),
},
Flavor::Sync(ref p) => return p.recv(None).map_err(|_| RecvError),
};

Must Remain unreachable

None so far.

Action

I would love to send in a PR that resolves this issue! However, I do need some help and guidance from the library maintainers to ensure that undefined behavior is 110% avoided. Feedback on which parts are safe regardless of platform/architecture/hardware is much appreciated.

As mentioned earlier, I will be editing this issue as more people chime in. Thanks!

@hellow554
Copy link
Contributor

hellow554 commented Sep 3, 2021

6 issues before this (#88600) is a very good reason to not do it and remain unreachable!(). In fact, a lot of ICEs will hit either a unreachable or an unwrap (or similar).

If it is really unreachable, the compiler will figure it out and eleminate the branch, see https://rust.godbolt.org/z/5Kez3MPGW

I don't think that this is reasonable but I'd like to hear others people opinion.

@the8472
Copy link
Member

the8472 commented Sep 3, 2021

#86520 went in the opposite direction. Have you checked the performanee on any of these, e.g. by running the benches included in the library?

@BastiDood
Copy link
Author

Have you checked the performance on any of these?

Honestly, this shouldn't be necessary anymore. I sense that there is already a strong consensus that the compiler is (more than) smart enough to detect truly unreachable branches. The panic infrastructure as a safety mechanism is hard to argue against, especially since the compiler already optimizes it away in most cases.

This is great! Glad that I can certainly say that I can close this issue with much greater faith in the compiler's abilities. Thanks for the valuable input, everyone! I believe this issue does not need to linger any longer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants