blob: 62f71d47f27439858107d2ec66c6977fb0da517d [file] [log] [blame]
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Explicit stack slots, which can be used for stack emplacement.
//!
//! A [`Slot`] is uninitialized storage on the stack that can be manipulated
//! explicitly. Notionally, a [`Slot<T>`] represents a `let x: T;` in some
//! function's stack.
//!
//! [`Slot`]s mut be created with the [`slot!()`] macro:
//! ```
//! # use moveit::{slot};
//! slot!(storage);
//! let mut x = storage.put(42);
//! *x /= 2;
//! assert_eq!(*x, 21);
//! ```
//! Unfortunately, due to the constrains of Rust today, it is not possible to
//! produce a [`Slot`] as part of a larger expression; since it needs to expand
//! to a `let` to bind the stack location, [`slot!()`] must be a statement, not
//! an expression.
//!
//! [`Slot`]s can also be used to implement a sort of "guaranteed RVO":
//! ```
//! # use moveit::{slot, Slot, move_ref::MoveRef};
//! fn returns_on_the_stack(val: i32, storage: Slot<i32>) -> Option<MoveRef<i32>> {
//! if val == 0 {
//! return None
//! }
//! Some(storage.put(val))
//! }
//!
//! slot!(storage);
//! let val = returns_on_the_stack(42, storage);
//! assert_eq!(*val.unwrap(), 42);
//! ```
//!
//! [`Slot`]s provide a natural location for emplacing values on the stack.
//! The [`moveit!()`] macro is intended to make this operation
//! straight-forward.
//!
//! # [`DroppingSlot`]
//!
//! [`DroppingSlot`] is a support type similar to [`Slot`] that is used for
//! implementing [`DerefMove`], but which users should otherwise not construct
//! themselves (despite it being otherwise perfectly safe to do so).
use core::mem;
use core::mem::MaybeUninit;
use core::pin::Pin;
use core::ptr;
use crate::drop_flag::DropFlag;
use crate::move_ref::MoveRef;
use crate::new;
use crate::new::New;
use crate::new::TryNew;
#[cfg(doc)]
use {
crate::{move_ref::DerefMove, moveit, slot},
alloc::boxed::Box,
};
/// An empty slot on the stack into which a value could be emplaced.
///
/// The `'frame` lifetime refers to the lifetime of the stack frame this
/// `Slot`'s storage is allocated on.
///
/// See [`slot!()`] and [the module documentation][self].
pub struct Slot<'frame, T> {
ptr: &'frame mut MaybeUninit<T>,
drop_flag: DropFlag<'frame>,
}
impl<'frame, T> Slot<'frame, T> {
/// Creates a new `Slot` with the given pointer as its basis.
///
/// To safely construct a `Slot`, use [`slot!()`].
///
/// # Safety
///
/// `ptr` must not be outlived by any other pointers to its allocation.
///
/// `drop_flag`'s value must be dead, and must be a drop flag governing
/// the destruction of `ptr`'s storage in an appropriate manner as described
/// in [`moveit::drop_flag`][crate::drop_flag].
pub unsafe fn new_unchecked(
ptr: &'frame mut MaybeUninit<T>,
drop_flag: DropFlag<'frame>,
) -> Self {
Self { ptr, drop_flag }
}
/// Put `val` into this slot, returning a new [`MoveRef`].
pub fn put(self, val: T) -> MoveRef<'frame, T> {
unsafe {
// SAFETY: Pinning is conserved by this operation.
Pin::into_inner_unchecked(self.pin(val))
}
}
/// Pin `val` into this slot, returning a new, pinned [`MoveRef`].
pub fn pin(self, val: T) -> Pin<MoveRef<'frame, T>> {
self.emplace(new::of(val))
}
/// Emplace `new` into this slot, returning a new, pinned [`MoveRef`].
pub fn emplace<N: New<Output = T>>(self, new: N) -> Pin<MoveRef<'frame, T>> {
match self.try_emplace(new) {
Ok(x) => x,
Err(e) => match e {},
}
}
/// Try to emplace `new` into this slot, returning a new, pinned [`MoveRef`].
pub fn try_emplace<N: TryNew<Output = T>>(
self,
new: N,
) -> Result<Pin<MoveRef<'frame, T>>, N::Error> {
unsafe {
self.drop_flag.inc();
new.try_new(Pin::new_unchecked(self.ptr))?;
Ok(MoveRef::into_pin(MoveRef::new_unchecked(
self.ptr.assume_init_mut(),
self.drop_flag,
)))
}
}
/// Converts this into a slot for a pinned `T`.
///
/// This is safe, since this `Slot` owns the referenced data, and
/// `Pin` is explicitly a `repr(transparent)` type.
pub fn into_pinned(self) -> Slot<'frame, Pin<T>> {
unsafe { self.cast() }
}
/// Converts this `Slot` from being a slot for a `T` to being a slot for
/// some other type `U`.
///
/// ```
/// # use moveit::{Slot, MoveRef};
/// moveit::slot!(place: u32);
/// let foo: MoveRef<u16> = unsafe { place.cast::<u16>() }.put(42);
/// ```
///
/// # Safety
///
/// `T` must have at least the size and alignment as `U`.
pub unsafe fn cast<U>(self) -> Slot<'frame, U> {
debug_assert!(mem::size_of::<T>() >= mem::size_of::<U>());
debug_assert!(mem::align_of::<T>() >= mem::align_of::<U>());
Slot {
ptr: &mut *self.ptr.as_mut_ptr().cast(),
drop_flag: self.drop_flag,
}
}
}
impl<'frame, T> Slot<'frame, Pin<T>> {
/// Converts this into a slot for an unpinned `T`.
///
/// This is safe, since this `Slot` owns the referenced data, and
/// `Pin` is explicitly a `repr(transparent)` type.
///
/// Moreover, no actual unpinning is occurring: the referenced data must
/// be uninitialized, so it cannot have a pinned referent.
pub fn into_unpinned(self) -> Slot<'frame, T> {
unsafe { self.cast() }
}
}
/// Similar to a [`Slot`], but able to drop its contents.
///
/// A `DroppingSlot` wraps a [`Slot`], and will drop its contents if the
/// [`Slot`]'s drop flag is dead at the time of the `DroppingSlot`'s
/// destruction.
///
/// This type has an API similar to [`Slot`]'s, but rather than returning
/// `MoveRef`s, which would own the contents of this slot, we return a `&mut T`
/// and a [`DropFlag`], which the caller can assemble into an
/// appropriately-shaped `MoveRef`. The drop flag will be one decrement away
/// from being dead; callers should make sure to decremement it to trigger
/// destruction.
///
/// `DroppingSlot` is intended to be used with [`DerefMove::deref_move()`],
/// and will usually not be created by `moveit`'s users. However, [`slot!()`]
/// provides `DroppingSlot` support, too. These slots will silently forget their
/// contents if the drop flag is left untouched, rather than crash.
pub struct DroppingSlot<'frame, T> {
ptr: &'frame mut MaybeUninit<T>,
drop_flag: DropFlag<'frame>,
}
impl<'frame, T> DroppingSlot<'frame, T> {
/// Creates a new `DroppingSlot` with the given pointer as its basis.
///
/// To safely construct a `DroppingSlot`, use [`slot!()`].
///
/// # Safety
///
/// `ptr` must not be outlived by any other pointers to its allocation.
///
/// `drop_flag`'s value must be dead, and must be a drop flag governing
/// the destruction of `ptr`'s storage in an appropriate manner as described
/// in [`moveit::drop_flag`][crate::drop_flag].
pub unsafe fn new_unchecked(
ptr: &'frame mut MaybeUninit<T>,
drop_flag: DropFlag<'frame>,
) -> Self {
drop_flag.inc();
Self { ptr, drop_flag }
}
/// Put `val` into this slot, returning a reference to it.
pub fn put(self, val: T) -> (&'frame mut T, DropFlag<'frame>) {
({ self.ptr }.write(val), self.drop_flag)
}
/// Pin `val` into this slot, returning a reference to it.
///
/// # Safety
///
/// This function pins the memory this slot wraps, but does not guarantee its
/// destructor is run; that is the caller's responsibility, by decrementing
/// the given [`DropFlag`].
pub unsafe fn pin(self, val: T) -> (Pin<&'frame mut T>, DropFlag<'frame>) {
self.emplace(new::of(val))
}
/// Emplace `new` into this slot, returning a reference to it.
///
/// # Safety
///
/// This function pins the memory this slot wraps, but does not guarantee its
/// destructor is run; that is the caller's responsibility, by decrementing
/// the given [`DropFlag`].
pub unsafe fn emplace<N: New<Output = T>>(
self,
new: N,
) -> (Pin<&'frame mut T>, DropFlag<'frame>) {
match self.try_emplace(new) {
Ok((x, d)) => (x, d),
Err(e) => match e {},
}
}
/// Try to emplace `new` into this slot, returning a reference to it.
///
/// # Safety
///
/// This function pins the memory this slot wraps, but does not guarantee its
/// destructor is run; that is the caller's responsibility, by decrementing
/// the given [`DropFlag`].
pub unsafe fn try_emplace<N: TryNew<Output = T>>(
self,
new: N,
) -> Result<(Pin<&'frame mut T>, DropFlag<'frame>), N::Error> {
self.drop_flag.inc();
new.try_new(Pin::new_unchecked(self.ptr))?;
Ok((
Pin::new_unchecked(self.ptr.assume_init_mut()),
self.drop_flag,
))
}
}
#[doc(hidden)]
#[allow(missing_docs)]
pub mod __macro {
use super::*;
use crate::drop_flag::QuietFlag;
pub use core;
pub struct SlotDropper<T> {
val: MaybeUninit<T>,
drop_flag: QuietFlag,
}
impl<T> SlotDropper<T> {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
val: MaybeUninit::uninit(),
drop_flag: QuietFlag::new(),
}
}
// Workaround for `unsafe {}` unhygine wrt to lints.
//
// This function is still `unsafe`.
pub fn new_unchecked_hygine_hack(&mut self) -> DroppingSlot<T> {
unsafe {
DroppingSlot::new_unchecked(&mut self.val, self.drop_flag.flag())
}
}
}
impl<T> Drop for SlotDropper<T> {
fn drop(&mut self) {
if self.drop_flag.flag().is_dead() {
unsafe { ptr::drop_in_place(self.val.assume_init_mut()) }
}
}
}
// Workaround for `unsafe {}` unhygine wrt to lints.
//
// This function is still `unsafe`.
pub fn new_unchecked_hygine_hack<'frame, T>(
ptr: &'frame mut MaybeUninit<T>,
drop_flag: DropFlag<'frame>,
) -> Slot<'frame, T> {
unsafe { Slot::new_unchecked(ptr, drop_flag) }
}
}
/// Constructs a new [`Slot`].
///
/// Because [`Slot`]s need to own data on the stack, but that data cannot
/// move with the [`Slot`], it must be constructed using this macro. For
/// example:
/// ```
/// moveit::slot!(x, y: bool);
/// let x = x.put(5);
/// let y = y.put(false);
/// ```
///
/// This macro is especially useful for passing data into functions that want to
/// emplace a value into the caller.
///
/// The `slot!(#[dropping] x)` syntax can be used to create a [`DroppingSlot`]
/// instead. This should be a comparatively rare operation.
///
/// This macro can also be used without arguments to create a *temporary*
/// [`Slot`]. Such types cannot be assigned to variables but can be used as
/// part of a larger expression:
///
/// ```compile_fail
/// # use moveit::Slot;
/// let bad: Slot<i32> = moveit::slot!();
/// bad.put(4); // Borrow check error.
/// ```
///
/// ```
/// # use moveit::Slot;
/// fn do_thing(x: Slot<i32>) { /* ... */ }
/// do_thing(moveit::slot!())
/// ```
#[macro_export]
macro_rules! slot {
() => {
$crate::slot::__macro::new_unchecked_hygine_hack(
&mut $crate::slot::__macro::core::mem::MaybeUninit::uninit(),
$crate::drop_flag::TrappedFlag::new().flag(),
)
};
(#[dropping]) => {
$crate::slot::__macro::SlotDropper::new().new_unchecked_hygine_hack()
};
($($name:ident $(: $ty:ty)?),* $(,)*) => {$(
let mut uninit = $crate::slot::__macro::core::mem::MaybeUninit::<
$crate::slot!(@tyof $($ty)?)
>::uninit();let trap = $crate::drop_flag::TrappedFlag::new();
let $name = $crate::slot::__macro::new_unchecked_hygine_hack(
&mut uninit,
trap.flag()
);
)*};
(#[dropping] $($name:ident $(: $ty:ty)?),* $(,)*) => {$(
let mut uninit = $crate::slot::__macro::SlotDropper::<
$crate::slot!(@tyof $($ty)?)
>::new();
#[allow(unsafe_code, unused_unsafe)]
let $name = uninit.new_unchecked_hygine_hack();
)*};
(@tyof) => {_};
(@tyof $ty:ty) => {$ty};
}