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