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;