stackalloc/lib.rs
1//! # Safe runtime stack allocations
2//!
3//! Provides methods for Rust to access and use runtime stack allocated buffers in a safe way.
4//! This is accomplished through a helper function that takes a closure of `FnOnce` that takes the stack allocated buffer slice as a parameter.
5//! The slice is considered to be valid only until this closure returns, at which point the stack is reverted back to the caller of the helper function. If you need a buffer that can be moved, use `Vec` or statically sized arrays.
6//! The memory is allocated on the closure's caller's stack frame, and is deallocated when the caller returns.
7//!
8//! This slice will be properly formed with regards to the expectations safe Rust has on slices.
9//! However, it is still possible to cause a stack overflow by allocating too much memory, so use this sparingly and never allocate unchecked amounts of stack memory blindly.
10//!
11//! # Examples
12//! Allocating a byte buffer on the stack.
13//! ```
14//! # use std::io::{self, Write, Read};
15//! # use stackalloc::*;
16//! fn copy_with_buffer<R: Read, W: Write>(mut from: R, mut to: W, bufsize: usize) -> io::Result<usize>
17//! {
18//! alloca_zeroed(bufsize, move |buf| -> io::Result<usize> {
19//! let mut read;
20//! let mut completed = 0;
21//! while { read = from.read(&mut buf[..])?; read != 0} {
22//! to.write_all(&buf[..read])?;
23//! completed += read;
24//! }
25//! Ok(completed)
26//! })
27//! }
28//! ```
29//! ## Arbitrary types
30//! Allocating a slice of any type on the stack.
31//! ```
32//! # use stackalloc::stackalloc;
33//! # fn _prevent_attempted_execution() {
34//! stackalloc(5, "str", |slice: &mut [&str]| {
35//! assert_eq!(&slice[..], &["str"; 5]);
36//! });
37//! # }
38//! ```
39//! ## Dropping
40//! The wrapper handles dropping of types that require it.
41//! ```
42//! # use stackalloc::stackalloc_with;
43//! # fn _prevent_attempted_execution() {
44//! stackalloc_with(5, || vec![String::from("string"); 10], |slice| {
45//! assert_eq!(&slice[0][0][..], "string");
46//! }); // The slice's elements will be dropped here
47//! # }
48//! ```
49//! ## `MaybeUninit`
50//! You can get the aligned stack memory directly with no initialisation.
51//! ```
52//! # use stackalloc::stackalloc_uninit;
53//! # use std::mem::MaybeUninit;
54//! # fn _prevent_attempted_execution() {
55//! stackalloc_uninit(5, |slice| {
56//! for s in slice.iter_mut()
57//! {
58//! *s = MaybeUninit::new(String::new());
59//! }
60//! // SAFETY: We have just initialised all elements of the slice.
61//! let slice = unsafe { stackalloc::helpers::slice_assume_init_mut(slice) };
62//!
63//! assert_eq!(&slice[..], &vec![String::new(); 5][..]);
64//!
65//! // SAFETY: We have to manually drop the slice in place to ensure its elements are dropped, as `stackalloc_uninit` does not attempt to drop the potentially uninitialised elements.
66//! unsafe {
67//! std::ptr::drop_in_place(slice as *mut [String]);
68//! }
69//! });
70//! # }
71//! ```
72//!
73//! # Performance
74//! For small (1k or lower) element arrays `stackalloc` can outperform `Vec` by about 50% or more. This performance difference decreases are the amount of memory allocated grows.
75//!
76//! * test tests::bench::stackalloc_of_uninit_bytes_known ... bench: 3 ns/iter (+/- 0)
77//! * test tests::bench::stackalloc_of_uninit_bytes_unknown ... bench: 3 ns/iter (+/- 0)
78//! * test tests::bench::stackalloc_of_zeroed_bytes_known ... bench: 22 ns/iter (+/- 0)
79//! * test tests::bench::stackalloc_of_zeroed_bytes_unknown ... bench: 17 ns/iter (+/- 0)
80//! * test tests::bench::vec_of_uninit_bytes_known ... bench: 13 ns/iter (+/- 0)
81//! * test tests::bench::vec_of_uninit_bytes_unknown ... bench: 55 ns/iter (+/- 0)
82//! * test tests::bench::vec_of_zeroed_bytes_known ... bench: 36 ns/iter (+/- 2)
83//! * test tests::bench::vec_of_zeroed_bytes_unknown ... bench: 37 ns/iter (+/- 0)
84//!
85//! # License
86//! MIT licensed
87
88#![cfg_attr(nightly, feature(test))]
89
90#![allow(dead_code)]
91
92
93#![cfg_attr(all(feature = "no_std", not(test)), no_std)]
94#![cfg_attr(all(feature = "no_std", not(feature="no_unwind_protection")), feature(core_intrinsics))]
95
96// NOTE: This feature `no_unwind_protection` doesn't actually exist at the moment; since a binary crate built with #![no_std] will not be using a stable compiler toolchain. It was just for testing.
97
98#[cfg(all(nightly, test))] extern crate test;
99
100#[allow(unused)]
101use core::{
102 mem::{
103 self,
104 MaybeUninit,
105 ManuallyDrop,
106 },
107 panic::{
108 self,
109 AssertUnwindSafe,
110 },
111 slice,
112 ffi::c_void,
113 ptr,
114};
115
116
117#[cfg(not(feature = "no_std"))]
118pub mod avec;
119#[cfg(not(feature = "no_std"))]
120pub use avec::AVec;
121
122mod ffi;
123
124/// Allocate a runtime length uninitialised byte buffer on the stack, call `callback` with this buffer, and then deallocate the buffer.
125///
126/// Call the closure with a stack allocated buffer of `MaybeUninit<u8>` on the caller's frame of `size`. The memory is popped off the stack regardless of how the function returns (unless it doesn't return at all.)
127///
128/// # Notes
129/// The buffer is allocated on the closure's caller's frame, and removed from the stack immediately after the closure returns (including a panic, or even a `longjmp()`).
130///
131/// # Panics
132/// If the closure panics, the panic is propagated after cleanup of the FFI call stack.
133///
134/// # Safety
135/// While this function *is* safe to call from safe Rust, allocating arbitrary stack memory has drawbacks.
136///
137/// ## Stack overflow potential
138/// It is possible to cause a stack overflow if the buffer you allocate is too large. (This is possible in many ways in safe Rust.)
139/// To avoid this possibility, generally only use this for small to medium size buffers of only runtime-known sizes (in the case of compile-time known sizes, use arrays. For large buffers, use `Vec`). The stack size can vary and what a safe size to `alloca` is can change throughout the runtime of the program and depending on the depth of function calls, but it is usually safe to do this.
140/// However, **do not** pass unvalidated input sizes (e.g. read from a socket or file) to this function, that is a sure way to crash your program.
141///
142/// This is not undefined behaviour however, it is just a kind of OOM and will terminate execution of the program.
143///
144/// ## 0 sizes
145/// If a size of 0 is passed, then a non-null, non-aliased, and properly aligned dangling pointer on the stack is used to construct the slice. This is safe and there is no performance difference (other than no allocation being performed.)
146///
147/// ## Initialisation
148/// The stack buffer is not explicitly initialised, so the slice's elements are wrapped in `MaybeUninit`. The contents of uninitialised stack allocated memory is *usually* 0.
149///
150/// ## Cleanup
151/// Immediately after the closure exits, the stack pointer is reset, effectively freeing the buffer. The pointer used for the creation of the slice is invalidated as soon as the closure exits. But in the absense of `unsafe` inside the closure, it isn't possible to keep this pointer around after the frame is destroyed.
152///
153/// ## Panics
154/// The closure can panic and it will be caught and propagated after exiting the FFI boundary and resetting the stack pointer.
155///
156/// # Internals
157/// This function creates a shim stack frame (by way of a small FFI function) and uses the same mechanism as a C VLA to extend the stack pointer by the size provided (plus alignment). Then, this pointer is passed to the provided closure, and after the closure returns to the shim stack frame, the stack pointer is reset to the base of the caller of this function.
158///
159/// ## Inlining
160/// In the absense of inlining LTO (which *is* enabled if possible), this funcion is entirely safe to inline without leaking the `alloca`'d memory into the caller's frame; however, the FFI wrapper call is prevented from doing so in case the FFI call gets inlined into this function call.
161/// It is unlikely the trampoline to the `callback` closure itself can be inlined.
162pub fn alloca<T, F>(size: usize, callback: F) -> T
163where F: FnOnce(&mut [MaybeUninit<u8>]) -> T
164{
165 let mut callback = ManuallyDrop::new(callback);
166 let mut rval = MaybeUninit::uninit();
167
168 let mut callback = |allocad_ptr: *mut c_void| {
169 unsafe {
170 let slice = slice::from_raw_parts_mut(allocad_ptr as *mut MaybeUninit<u8>, size);
171 let callback = ManuallyDrop::take(&mut callback);
172
173 #[cfg(feature = "no_std")]
174 {
175 rval = MaybeUninit::new(catch_unwind(move||{callback(slice)}));
176 }
177 #[cfg(not(feature = "no_std"))]
178 {
179 rval = MaybeUninit::new(std::panic::catch_unwind(AssertUnwindSafe(move || callback(slice))));
180 }
181 }
182 };
183
184 /// Create and use the trampoline for input closure `F`.
185 #[inline(always)] fn create_trampoline<F>(_: &F) -> ffi::CallbackRaw
186 where F: FnMut(*mut c_void)
187 {
188 unsafe extern "C" fn trampoline<F: FnMut(*mut c_void)>(ptr: *mut c_void, data: *mut c_void)
189 {
190 (&mut *(data as *mut F))(ptr);
191 }
192
193 trampoline::<F>
194 }
195
196 let rval = unsafe {
197 ffi::alloca_trampoline(size, create_trampoline(&callback), &mut callback as *mut _ as *mut c_void);
198 rval.assume_init()
199 };
200
201 #[cfg(not(feature = "no_std"))]
202 match rval
203 {
204 Ok(v) => v,
205 Err(pan) => std::panic::resume_unwind(pan),
206 }
207 #[cfg(feature = "no_std")]
208 return match rval{
209 Ok(v) => v,
210 Err(()) => core::panic!(),
211 }
212}
213
214
215
216
217#[cfg(all(feature = "no_std", feature = "no_unwind_protection"))]
218unsafe fn catch_unwind<R, F: FnOnce() -> R>(f: F) -> Result<R, ()> {
219 // Catching unwinds disabled for this build for now since it requires core intrinsics.
220 Ok(f())
221}
222
223#[cfg(all(feature = "no_std", not(feature = "no_unwind_protection")))]
224unsafe fn catch_unwind<R, F: FnOnce() -> R>(f: F) -> Result<R, ()>{
225
226 union Data<F, R> {
227 f: ManuallyDrop<F>,
228 r: ManuallyDrop<R>,
229 p: (),
230 }
231
232 #[inline]
233 fn do_call<F: FnOnce() -> R, R>(data: *mut u8) {
234 unsafe {
235 let data = data as *mut Data<F, R>;
236 let data = &mut (*data);
237 let f = ManuallyDrop::take(&mut data.f);
238 data.r = ManuallyDrop::new(f());
239 }
240 }
241
242 #[inline]
243 fn do_catch<F: FnOnce() -> R, R>(data: *mut u8, _payload: *mut u8) {
244 unsafe {
245 let data = data as *mut Data<F, R>;
246 let data = &mut (*data);
247 data.p = ()
248 }
249 }
250
251 let mut data = Data { f: ManuallyDrop::new(f) };
252 let data_ptr = &mut data as *mut _ as *mut u8;
253
254
255 if core::intrinsics::catch_unwind(do_call::<F, R>, data_ptr, do_catch::<F, R>) == 0{
256 Result::Ok(ManuallyDrop::into_inner(data.r))
257 }else{
258 Result::Err(())
259 }
260}
261
262/// A module of helper functions for slice memory manipulation
263///
264/// These are mostly re-implementations of unstable corelib functions in stable Rust.
265pub mod helpers {
266 use super::*;
267 #[inline(always)] pub(crate) fn align_buffer_to<T>(ptr: *mut u8) -> *mut T
268 {
269 use core::mem::align_of;
270 ((ptr as usize) + align_of::<T>() - (ptr as usize) % align_of::<T>()) as *mut T
271 }
272
273 /// Convert a slice of `MaybeUninit<T>` to `T`.
274 ///
275 /// This is the same as the unstable core library function `MaybeUninit::slice_assume_init()`
276 ///
277 /// # Safety
278 /// The caller must ensure all elements of `buf` have been initialised before calling this function.
279 #[inline(always)] pub unsafe fn slice_assume_init<T>(buf: & [MaybeUninit<T>]) -> &[T]
280 {
281 & *(buf as *const [MaybeUninit<T>] as *const [T]) // MaybeUninit::slice_assume_init()
282 }
283
284 /// Convert a mutable slice of `MaybeUninit<T>` to `T`.
285 ///
286 /// This is the same as the unstable core library function `MaybeUninit::slice_assume_init_mut()`
287 ///
288 /// # Safety
289 /// The caller must ensure all elements of `buf` have been initialised before calling this function.
290 #[inline(always)] pub unsafe fn slice_assume_init_mut<T>(buf: &mut [MaybeUninit<T>]) -> &mut [T]
291 {
292 &mut *(buf as *mut [MaybeUninit<T>] as *mut [T]) // MaybeUninit::slice_assume_init_mut()
293 }
294}
295
296use helpers::*;
297
298/// Allocate a runtime length zeroed byte buffer on the stack, call `callback` with this buffer, and then deallocate the buffer.
299///
300/// See `alloca()`.
301#[inline] pub fn alloca_zeroed<T, F>(size: usize, callback: F) -> T
302where F: FnOnce(&mut [u8]) -> T
303{
304 alloca(size, move |buf| {
305 // SAFETY: We zero-initialise the backing slice
306 callback(unsafe {
307 ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len()); // buf.fill(MaybeUninit::zeroed());
308 slice_assume_init_mut(buf)
309 })
310 })
311}
312
313
314/// Allocate a runtime length slice of uninitialised `T` on the stack, call `callback` with this buffer, and then deallocate the buffer.
315///
316/// The slice is aligned to type `T`.
317///
318/// See `alloca()`.
319#[inline] pub fn stackalloc_uninit<T, U, F>(size: usize, callback: F) -> U
320where F: FnOnce(&mut [MaybeUninit<T>]) -> U
321{
322 let size_bytes = (core::mem::size_of::<T>() * size) + core::mem::align_of::<T>();
323 alloca(size_bytes, move |buf| {
324 let abuf = align_buffer_to::<MaybeUninit<T>>(buf.as_mut_ptr() as *mut u8);
325 debug_assert!(buf.as_ptr_range().contains(&(abuf as *const _ as *const MaybeUninit<u8>)));
326 unsafe {
327 callback(slice::from_raw_parts_mut(abuf, size))
328 }
329 })
330}
331
332/// Allocate a runtime length slice of `T` on the stack, fill it by calling `init_with`, call `callback` with this buffer, and then drop and deallocate the buffer.
333///
334/// The slice is aligned to type `T`.
335///
336/// See `alloca()`.
337#[inline] pub fn stackalloc_with<T, U, F, I>(size: usize, mut init_with: I, callback: F) -> U
338where F: FnOnce(&mut [T]) -> U,
339 I: FnMut() -> T
340{
341 stackalloc_uninit(size, move |buf| {
342 buf.fill_with(move || MaybeUninit::new(init_with()));
343 // SAFETY: We have initialised the buffer above
344 let buf = unsafe { slice_assume_init_mut(buf) };
345 let ret = callback(buf);
346 if mem::needs_drop::<T>()
347 {
348 // SAFETY: We have initialised the buffer above
349 unsafe {
350 ptr::drop_in_place(buf as *mut _);
351 }
352 }
353 ret
354 })
355}
356
357/// Allocate a runtime length slice of `T` on the stack, fill it by cloning `init`, call `callback` with this buffer, and then drop and deallocate the buffer.
358///
359/// The slice is aligned to type `T`.
360///
361/// See `alloca()`.
362#[inline] pub fn stackalloc<T, U, F>(size: usize, init: T, callback: F) -> U
363where F: FnOnce(&mut [T]) -> U,
364 T: Clone
365{
366 stackalloc_with(size, move || init.clone(), callback)
367}
368
369
370/// Allocate a runtime length slice of `T` on the stack, fill it by calling `T::default()`, call `callback` with this buffer, and then drop and deallocate the buffer.
371///
372/// The slice is aligned to type `T`.
373///
374/// See `alloca()`.
375#[inline] pub fn stackalloc_with_default<T, U, F>(size: usize, callback: F) -> U
376where F: FnOnce(&mut [T]) -> U,
377 T: Default
378{
379 stackalloc_with(size, T::default, callback)
380}
381
382
383/// Collect an iterator into a stack allocated buffer up to `size` elements, call `callback` with this buffer, and then drop and deallocate the buffer.
384///
385/// See `stackalloc()`.
386///
387/// # Size
388/// We will only take up to `size` elements from the iterator, the rest of the iterator is dropped.
389/// If the iterator yield less elements than `size`, then the slice passed to callback will be smaller than `size` and only contain the elements actually yielded.
390#[inline] pub fn stackalloc_with_iter<I, T, U, F>(size: usize, iter: I, callback: F) -> U
391where F: FnOnce(&mut [T]) -> U,
392 I: IntoIterator<Item = T>,
393{
394 stackalloc_uninit(size, move |buf| {
395 let mut done = 0;
396 for (d, s) in buf.iter_mut().zip(iter.into_iter())
397 {
398 *d = MaybeUninit::new(s);
399 done+=1;
400 }
401 // SAFETY: We just initialised `done` elements of `buf` above.
402 let buf = unsafe {
403 slice_assume_init_mut(&mut buf[..done])
404 };
405 let ret = callback(buf);
406 if mem::needs_drop::<T>()
407 {
408 // SAFETY: We have initialised the `buf` above
409 unsafe {
410 ptr::drop_in_place(buf as *mut _);
411 }
412 }
413 ret
414 })
415}
416
417/// Collect an exact size iterator into a stack allocated slice, call `callback` with this buffer, and then drop and deallocate the buffer.
418///
419/// See `stackalloc_with_iter()`.
420///
421/// # Size
422/// If the implementation of `ExactSizeIterator` on `I` is incorrect and reports a longer length than the iterator actually produces, then the slice passed to `callback` is shortened to the number of elements actually produced.
423#[inline] pub fn stackalloc_from_iter_exact<I, T, U, F>(iter: I, callback: F) -> U
424where F: FnOnce(&mut [T]) -> U,
425 I: IntoIterator<Item = T>,
426 I::IntoIter: ExactSizeIterator,
427{
428 let iter = iter.into_iter();
429 stackalloc_with_iter(iter.len(), iter, callback)
430}
431
432/// Collect an iterator into a stack allocated buffer, call `callback` with this buffer, and then drop and deallocate the buffer.
433///
434/// # Safety
435/// While the slice passed to `callback` is guaranteed to be safe to use, regardless of if the iterator fills (or tries to overfill) it, this function is still marked as `unsafe` because it trusts the iterator `I` reports an accurate length with its `size_hint()`.
436/// It is recommended to instead use `stackalloc_with_iter()` specifying a strict upper bound on the buffer's size, or `stackalloc_from_iter_exact()` for `ExactSizeIterator`s, as this function may allocate far more, or far less (even 0) memory needed to hold all the iterator's elements; therefore this function will very easily not work properly and/or cause stack overflow if used carelessly.
437///
438/// If the standard library's `std::iter::TrustedLen` trait becomes stablised, this function will be changed to require that as a bound on `I` and this function will no longer be `unsafe`.
439///
440/// # Size
441/// The size allocated for the buffer will be the upper bound of the iterator's `size_hint()` if one exists. If not, then the size allocated will be the lower bound of `size_hint()`.
442/// This can potentially result in only some of the iterator being present in the buffer, or the buffer allocated being much larger than the iterator itself.
443/// If this iterator does not have a good `size_hint()` for this purpose, use `stackalloc_with_iter()`, or `stackalloc_from_iter_exact()` if the iterator has an exact size.
444#[inline] pub unsafe fn stackalloc_from_iter_trusted<I, T, U, F>(iter: I, callback: F) -> U
445where F: FnOnce(&mut [T]) -> U,
446 I: IntoIterator<Item = T>,
447{
448 let iter = iter.into_iter();
449 stackalloc_with_iter(match iter.size_hint() {
450 (_, Some(x)) |
451 (x, _) => x,
452 }, iter, callback)
453}
454
455
456#[cfg(test)]
457mod tests;