exit_stack/
lib.rs

1//! Dynamically pin values to the stack.
2//!
3//! This is motivated by a short-coming of pinning in situations where no global allocator is
4//! available (or the use of one is not desired). The usual solution for pinning a value to the
5//! stack is by utilizing the `pin_utils` macro, which works by shadowing a value and thus making
6//! it impossible to avoid its Drop impl being run. However, by design this can only work if the
7//! number of values is statically known.
8//!
9//! This crate complements the mechanism. It leverages a pinned wrapper around an area of memory to
10//! constructs a linked list of values that are dropped together with that region, and thus can be
11//! treated as pinned as well. The downside of this is a more imprecise tracking which requires the
12//! values themselves to live for the `'static` lifetime by default.
13//!
14//! # Usage
15//!
16//! Use this to have subroutines that starts some task that must be pinned while it is running. An
17//! example for this is a DMA transfer, that absolutely must signal or wait for the remote end when
18//! it is dropped (as otherwise some memory might get corrupted later). This would usually not be
19//! easily possible as stack-pinning the tasks within the subroutine would immediately drop them on
20//! exit.
21//!
22//! ```
23//! use core::pin::Pin;
24//! use exit_stack::ExitStack;
25//!
26//! #[derive(Default)]
27//! struct DmaTask {
28//!     // ..
29//! # _inner: (),
30//! }
31//!
32//! impl DmaTask {
33//!     // Start a DMA transfer, return an identifier for it.
34//!     pub fn start(self: Pin<&mut Self>) -> usize {
35//!         // ..
36//! # 0
37//!     }
38//! }
39//!
40//! fn start_pinned(mut stack: Pin<&ExitStack<DmaTask>>) -> Option<usize> {
41//!     let task = stack
42//!         .slot()?
43//!         .pin(DmaTask::default());
44//!     Some(task.start())
45//! }
46//! ```
47#![no_std]
48use core::cell::Cell;
49use core::mem::MaybeUninit;
50use core::pin::Pin;
51use core::ptr::NonNull;
52
53use static_alloc::leaked::LeakBox;
54use static_alloc::unsync::Bump;
55
56/// Provides memory suitable for dynamically stack-allocating pinned values.
57///
58/// Alternatively, you can also use this as a pre-allocated buffer for pinning values to the heap.
59/// The internal implementation guarantees that the stack can allocate _at least one_ value of type
60/// `Mem`. Also, there will be no changes that _reduce_ the amount of values in a patch version, it
61/// will require require at least a minor version bump.
62pub struct ExitStack<Mem> {
63    /// Memory, padded to hold _at least_ one slot of `Mem`.
64    memory: Bump<PinSlot<Mem>>,
65    /// The exit handler to the last filled slot of the exit stack.
66    top: Cell<Option<ExitHandle>>,
67}
68
69type ExitHandle = NonNull<PinSlot<dyn Drop>>;
70
71/// A stack allocation that one can pin a value into.
72///
73/// This contains two references: One to uninitialized stack allocated memory large enough to store
74/// a value of the requested type into, and another reference to the head of a linked list of
75/// pinned values. When the caller decides to fill the slot then it is written to the stack memory
76/// and prepended to the linked list such that the value is guaranteed to drop before all other
77/// values contained in it.
78pub struct Slot<'lt, T> {
79    /// The slot which we can fill with a value and meta data.
80    value: LeakBox<'lt, MaybeUninit<PinSlot<T>>>,
81    /// Where we will commit ourselves to pin the value. If we write a pointer here then it _must_
82    /// be dropped before the box's memory is invalidated.
83    slotter: &'lt Cell<Option<ExitHandle>>,
84}
85
86/// One entry in the exit stack linked list.
87///
88/// It contains the link to the next entry and a pinned value that must be dropped in-place.
89struct PinSlot<T: ?Sized> {
90    link: Option<ExitHandle>,
91    value: T,
92}
93
94impl<Mem> ExitStack<Mem> {
95    /// Create an empty exit stack.
96    ///
97    /// The available memory for the stack is specified by the `Mem` type parameter.
98    pub fn new() -> Self {
99        ExitStack {
100            memory: Bump::uninit(),
101            top: Cell::new(None),
102        }
103    }
104
105    /// Prepare a slot for pinning a value to the stack.
106    ///
107    /// Note that dropping may be delayed arbitrarily since this method can't control when the
108    /// `ExitStack` itself is dropped. Thus the pinned value must not borrow temporary data.
109    pub fn slot<'stack, T: 'static>(self: Pin<&'stack Self>)
110        -> Option<Slot<'stack, T>>
111    {
112        let this: &'stack Self = self.get_ref();
113        let value = this.memory.bump_box().ok()?;
114        Some(Slot {
115            slotter: &this.top,
116            value,
117        })
118    }
119
120    /// Infallibly get a slot for the type of the memory, and pin a value.
121    ///
122    /// This is useful a small utility wrapper if you want to have a small 'stack' that can make
123    /// room for a single entry, on demand. All other entries in the exit stack will be popped, and
124    /// the memory allocator will be cleared before the new value is pinned.
125    ///
126    /// You can also use this method to defer dropping of a pinned task beyond your own method's
127    /// body.
128    ///
129    /// # Usage
130    ///
131    /// ```
132    /// use core::pin::Pin;
133    /// use exit_stack::ExitStack;
134    /// # use core::future::Future;
135    /// # use core::task::{Context, Poll};
136    ///
137    /// // Some async future that is not Unpin.
138    /// #[derive(Default)]
139    /// struct Task {
140    ///     // ..
141    /// # _i: core::marker::PhantomPinned,
142    /// }
143    ///
144    /// # impl Future for Task {
145    /// #   type Output = u32;
146    /// #   fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
147    /// #     Poll::Ready(0)
148    /// #   }
149    /// # }
150    ///
151    /// async fn with_stack(mut stack: Pin<&mut ExitStack<Task>>) {
152    ///     stack.as_mut().set(Task::default()).await;
153    ///     // Reuse the stack another time.
154    ///     // The previous task is dropped.
155    ///     stack.as_mut().set(Task::default()).await;
156    ///     // Note that the second task still lives when we return.
157    /// }
158    /// ```
159    pub fn set<'stack>(mut self: Pin<&'stack mut Self>, val: Mem)
160        -> Pin<&'stack mut Mem>
161    where
162        Mem: 'static,
163    {
164        self.as_mut().pop_all();
165        // The memory was reset here..
166        match self.into_ref().slot() {
167            Some(slot) => slot.pin(val),
168            None => unreachable!("The memory can allocate at least one slot"),
169        }
170    }
171
172    /// Drop all values, resetting the stack.
173    pub fn pop_all(self: Pin<&mut Self>) {
174        unsafe {
175            // SAFETY: inner_pop_all_with_unsafe_self_and_pinned will not move a pinned value.
176            self.get_unchecked_mut().inner_pop_all_with_unsafe_self_and_pinned()
177        }
178    }
179
180    fn inner_pop_all_with_unsafe_self_and_pinned(&mut self) {
181        // Ensures that, if we panic unwind, the rest continues to be dropped under the threat of aborting.
182        struct UnwindFlag {
183            chain: Option<ExitHandle>,
184        }
185
186        // !!! In case of another panic we _will_ abort, ourselves, not depending on Rust !!!
187        impl core::ops::Drop for UnwindFlag {
188            fn drop(&mut self) {
189                while let Some(exit_handle) = self.chain {
190                    // SAFETY: we only store pointers to the same stack here, which is still valid.
191                    let slot = unsafe { &mut *exit_handle.as_ptr() };
192                    let abort_flag = AbortOnDrop;
193                    // SAFETY: fulfilling our pin promise here.
194                    // This is also uniquely dropped as it was owned by `self.chain` before.
195                    unsafe { core::ptr::drop_in_place(&mut slot.value) };
196                    // Oh great, we didn't panic calling this drop. Let's not abort.
197                    core::mem::forget(abort_flag);
198                    self.chain = slot.link;
199                }
200            }
201        }
202
203        let mut flag = UnwindFlag { chain: self.top.take() };
204
205        while let Some(exit_handle) = flag.chain.take() {
206            // SAFETY: we only store pointers to the same stack here, which is still valid.
207            let slot = unsafe { &mut *exit_handle.as_ptr() };
208            // This is also uniquely dropped as it was owned by `flag.chain` before.
209            unsafe { core::ptr::drop_in_place(&mut slot.value) };
210            flag.chain = slot.link;
211        }
212
213        // No more values are present, feel free to move anything.
214        self.memory.reset()
215    }
216}
217
218impl<'lt, T> Slot<'lt, T> {
219    /// Pin a value to the stack.
220    ///
221    /// Returns the value if there is no more space in the reserved portion of memory to pin the
222    /// new value. You might try calling `pop_all` then to free some.
223    ///
224    /// Note that dropping might be delayed arbitrarily as the ExitStack has no control over its
225    /// own drop point, hence values must live for a static duration.
226    pub fn pin(self, value: T) -> Pin<&'lt mut T>
227    where
228        T: 'static
229    {
230        // Store the old pointer into our linked list.
231        let link = self.slotter.get();
232        let boxed = LeakBox::write(self.value, PinSlot { link, value });
233        // Now round-trip through pointer. Guarantees that the returned value is based on the
234        // pointer we store in the exit stack, which is required for provenance reasons.
235        // Has Shared-read-write ..
236        let pointer = LeakBox::into_raw(boxed);
237        // .. so does this shared-read-write.
238        let exit_handle: *mut PinSlot<dyn Drop> = pointer;
239        // Overwrite the pointer that is dropped first. The old still has a guarantee of being
240        // dropped because the ExitStack will iterate over us, guaranteed with this same write as
241        // we've set the link to the old pointer.
242        self.slotter.set(NonNull::new(exit_handle));
243        // .. so this is unique write above these two pointers.
244        // Pin is sound because we've registered ourselves in slotter.
245        unsafe { Pin::new_unchecked(&mut (*pointer).value) }
246    }
247
248    /// Pin a short-lived value to this exit stack.
249    ///
250    /// # Safety
251    ///
252    /// The caller guarantees that the exit stack is dropped _before_ the validity of `T` expires.
253    #[allow(dead_code)] // Might pub this later.
254    pub unsafe fn pin_local(self, value: T) -> Pin<&'lt mut T> {
255        // Store the old pointer into our linked list.
256        let link = self.slotter.get();
257        let boxed = LeakBox::write(self.value, PinSlot { link, value });
258        // Now round-trip through pointer. Guarantees that the returned value is based on the
259        // pointer we store in the exit stack, which is required for provenance reasons.
260        // Has Shared-read-write ..
261        let pointer = LeakBox::into_raw(boxed);
262        // .. so does this shared-read-write.
263        let raw_handle: *mut PinSlot<dyn Drop + 'lt> = pointer;
264        // Transmute away the lifetime. This is safe because we will only dereference the handle
265        // while the ExitStack is alive or just being dropped, and the caller promised us that it
266        // is okay in that lifetime.
267        let exit_handle: *mut PinSlot<dyn Drop> = core::mem::transmute(raw_handle);
268        // Overwrite the pointer that is dropped first. The old still has a guarantee of being
269        // dropped because the ExitStack will iterate over us, guaranteed with this same write as
270        // we've set the link to the old pointer.
271        self.slotter.set(NonNull::new(exit_handle));
272        // .. so this is unique write above these two pointers.
273        // Pin is sound because we've registered ourselves in slotter.
274        Pin::new_unchecked(&mut (*pointer).value)
275    }
276}
277
278impl<Mem> core::ops::Drop for ExitStack<Mem> {
279    fn drop(&mut self) {
280        self.inner_pop_all_with_unsafe_self_and_pinned()
281    }
282}
283
284/// An object-safe trait for types that are droppable. So, all of them.
285trait Drop {}
286
287impl<T: ?Sized> Drop for T {}
288
289// A struct that guarantees that no more execution happens after it is dropped. It must be
290// forgotten, a very strong pre-pooping of our pants.
291struct AbortOnDrop;
292impl core::ops::Drop for AbortOnDrop {
293    fn drop(&mut self) {
294        struct CauseDoublePanic;
295        impl core::ops::Drop for CauseDoublePanic {
296            fn drop(&mut self) { panic!() }
297        }
298        struct FallbackInCaseThisDidntAbort;
299        impl core::ops::Drop for FallbackInCaseThisDidntAbort {
300            fn drop(&mut self) { loop {} }
301        }
302        // Since we are no_std.. FIXME: use `cfg(accessible())` once stabilized to abort.
303        let _v = FallbackInCaseThisDidntAbort;
304        let _v = CauseDoublePanic;
305        panic!();
306    }
307}
308