oden/third-party/vendor/psm/src/lib.rs
2024-03-08 11:03:01 -08:00

406 lines
15 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! # **P**ortable **S**tack **M**anipulation
//! This crate provides portable functions to control the stack pointer and inspect the properties
//! of the stack. This crate does not attempt to provide safe abstractions to any operations, the
//! only goals are correctness, portability and efficiency (in that exact order). As a consequence
//! most functions you will find in this crate are unsafe.
//!
//! Note, that the stack allocation is left up to the user. Unless youre writing a safe
//! abstraction over stack manipulation, this is unlikely to be the crate you want. Instead
//! consider one of the safe abstractions over this crate such as `stacker`. Another good place to
//! look at is the crates.ios reverse dependency list.
#![allow(unused_macros)]
#![no_std]
macro_rules! extern_item {
(unsafe $($toks: tt)+) => {
unsafe extern "C" $($toks)+
};
($($toks: tt)+) => {
extern "C" $($toks)+
};
}
// Surprising: turns out subsequent macro_rules! override previous definitions, instead of
// erroring? Convenient for us in this case, though.
#[cfg(target_arch = "x86_64")]
macro_rules! extern_item {
(unsafe $($toks: tt)+) => {
unsafe extern "sysv64" $($toks)+
};
($($toks: tt)+) => {
extern "sysv64" $($toks)+
};
}
#[cfg(target_arch = "x86")]
macro_rules! extern_item {
(unsafe $($toks: tt)+) => {
unsafe extern "fastcall" $($toks)+
};
($($toks: tt)+) => {
extern "fastcall" $($toks)+
};
}
#[cfg(target_arch = "arm")]
macro_rules! extern_item {
(unsafe $($toks: tt)+) => {
unsafe extern "aapcs" $($toks)+
};
($($toks: tt)+) => {
extern "aapcs" $($toks)+
};
}
// NB: this could be nicer across multiple blocks but we cannot do it because of
// https://github.com/rust-lang/rust/issues/65847
extern_item! { {
#![cfg_attr(asm, link(name="psm_s"))]
#[cfg(asm)]
fn rust_psm_stack_direction() -> u8;
#[cfg(asm)]
fn rust_psm_stack_pointer() -> *mut u8;
#[cfg(all(switchable_stack, not(target_os = "windows")))]
#[link_name="rust_psm_replace_stack"]
fn _rust_psm_replace_stack(
data: usize,
callback: extern_item!(unsafe fn(usize) -> !),
sp: *mut u8
) -> !;
#[cfg(all(switchable_stack, not(target_os = "windows")))]
#[link_name="rust_psm_on_stack"]
fn _rust_psm_on_stack(
data: usize,
return_ptr: usize,
callback: extern_item!(unsafe fn(usize, usize)),
sp: *mut u8,
);
#[cfg(all(switchable_stack, target_os = "windows"))]
fn rust_psm_replace_stack(
data: usize,
callback: extern_item!(unsafe fn(usize) -> !),
sp: *mut u8,
stack_base: *mut u8
) -> !;
#[cfg(all(switchable_stack, target_os = "windows"))]
fn rust_psm_on_stack(
data: usize,
return_ptr: usize,
callback: extern_item!(unsafe fn(usize, usize)),
sp: *mut u8,
stack_base: *mut u8
);
} }
#[cfg(all(switchable_stack, not(target_os = "windows")))]
#[inline(always)]
unsafe fn rust_psm_replace_stack(
data: usize,
callback: extern_item!(unsafe fn(usize) -> !),
sp: *mut u8,
_: *mut u8,
) -> ! {
_rust_psm_replace_stack(data, callback, sp)
}
#[cfg(all(switchable_stack, not(target_os = "windows")))]
#[inline(always)]
unsafe fn rust_psm_on_stack(
data: usize,
return_ptr: usize,
callback: extern_item!(unsafe fn(usize, usize)),
sp: *mut u8,
_: *mut u8,
) {
_rust_psm_on_stack(data, return_ptr, callback, sp)
}
/// Run the closure on the provided stack.
///
/// Once the closure completes its execution, the original stack pointer is restored and execution
/// returns to the caller.
///
/// `base` address must be the low address of the stack memory region, regardless of the stack
/// growth direction. It is not necessary for the whole region `[base; base + size]` to be usable
/// at the time this function called, however it is required that at least the following hold:
///
/// * Both `base` and `base + size` are aligned up to the target-specific requirements;
/// * Depending on `StackDirection` value for the platform, the end of the stack memory region,
/// which would end up containing the first frame(s), must have sufficient number of pages
/// allocated to execute code until more pages are commited. The other end should contain a guard
/// page (not writable, readable or executable) to ensure Rusts soundness guarantees.
///
/// Note, that some or all of these considerations are irrelevant to some applications. For
/// example, Rusts soundness story relies on all stacks having a guard-page, however if the user
/// is able to guarantee that the memory region used for stack cannot be exceeded, a guard page may
/// end up being an expensive unnecessity.
///
/// The previous stack may not be deallocated. If an ability to deallocate the old stack is desired
/// consider `replace_stack` instead.
///
/// # Guidelines
///
/// Memory regions that are aligned to a single page (usually 4kB) are an extremely portable choice
/// for stacks.
///
/// Allocate at least 4kB of stack. Some architectures (such as SPARC) consume stack memory
/// significantly faster compared to the more usual architectures such as x86 or ARM. Allocating
/// less than 4kB of memory may make it impossible to commit more pages without overflowing the
/// stack later on.
///
/// # Unsafety
///
/// The stack `base` address must be aligned as appropriate for the target.
///
/// The stack `size` must be a multiple of stack alignment required by target.
///
/// The `size` must not overflow `isize`.
///
/// `callback` must not unwind or return control flow by any other means than directly returning.
///
/// # Examples
///
/// ```
/// use std::alloc;
/// const STACK_ALIGN: usize = 4096;
/// const STACK_SIZE: usize = 4096;
/// unsafe {
/// let layout = alloc::Layout::from_size_align(STACK_SIZE, STACK_ALIGN).unwrap();
/// let new_stack = alloc::alloc(layout);
/// assert!(!new_stack.is_null(), "allocations must succeed!");
/// let (stack, result) = psm::on_stack(new_stack, STACK_SIZE, || {
/// (psm::stack_pointer(), 4 + 4)
/// });
/// println!("4 + 4 = {} has been calculated on stack {:p}", result, stack);
/// }
/// ```
#[cfg(switchable_stack)]
pub unsafe fn on_stack<R, F: FnOnce() -> R>(base: *mut u8, size: usize, callback: F) -> R {
use core::mem::MaybeUninit;
extern_item! {
unsafe fn with_on_stack<R, F: FnOnce() -> R>(callback_ptr: usize, return_ptr: usize) {
let return_ptr = (*(return_ptr as *mut MaybeUninit<R>)).as_mut_ptr();
let callback = (*(callback_ptr as *mut MaybeUninit<F>)).as_ptr();
// Safe to move out from `F`, because closure in is forgotten in `on_stack` and dropping
// only occurs in this callback.
return_ptr.write((callback.read())());
}
}
let sp = match StackDirection::new() {
StackDirection::Ascending => base,
StackDirection::Descending => base.offset(size as isize),
};
let mut callback: MaybeUninit<F> = MaybeUninit::new(callback);
let mut return_value: MaybeUninit<R> = MaybeUninit::uninit();
rust_psm_on_stack(
&mut callback as *mut MaybeUninit<F> as usize,
&mut return_value as *mut MaybeUninit<R> as usize,
with_on_stack::<R, F>,
sp,
base,
);
return return_value.assume_init();
}
/// Run the provided non-terminating computation on an entirely new stack.
///
/// `base` address must be the low address of the stack memory region, regardless of the stack
/// growth direction. It is not necessary for the whole region `[base; base + size]` to be usable
/// at the time this function called, however it is required that at least the following hold:
///
/// * Both `base` and `base + size` are aligned up to the target-specific requirements;
/// * Depending on `StackDirection` value for the platform, the end of the stack memory region,
/// which would end up containing the first frame(s), must have sufficient number of pages
/// allocated to execute code until more pages are commited. The other end should contain a guard
/// page (not writable, readable or executable) to ensure Rusts soundness guarantees.
///
/// Note, that some or all of these considerations are irrelevant to some applications. For
/// example, Rusts soundness story relies on all stacks having a guard-page, however if the user
/// is able to guarantee that the memory region used for stack cannot be exceeded, a guard page may
/// end up being an expensive unnecessity.
///
/// The previous stack is not deallocated and may not be deallocated unless the data on the old
/// stack is not referenced in any way (by e.g. the `callback` closure).
///
/// On platforms where multiple stack pointers are available, the “current” stack pointer is
/// replaced.
///
/// # Guidelines
///
/// Memory regions that are aligned to a single page (usually 4kB) are an extremely portable choice
/// for stacks.
///
/// Allocate at least 4kB of stack. Some architectures (such as SPARC) consume stack memory
/// significantly faster compared to the more usual architectures such as x86 or ARM. Allocating
/// less than 4kB of memory may make it impossible to commit more pages without overflowing the
/// stack later on.
///
/// # Unsafety
///
/// The stack `base` address must be aligned as appropriate for the target.
///
/// The stack `size` must be a multiple of stack alignment required by target.
///
/// The `size` must not overflow `isize`.
///
/// `callback` must not return (not enforced by typesystem currently because `!` is unstable),
/// unwind or otherwise return control flow to any of the previous frames.
#[cfg(switchable_stack)]
pub unsafe fn replace_stack<F: FnOnce()>(base: *mut u8, size: usize, callback: F) -> ! {
extern_item! { unsafe fn with_replaced_stack<F: FnOnce()>(d: usize) -> ! {
// Safe to move out, because the closure is essentially forgotten by
// this being required to never return...
::core::ptr::read(d as *const F)();
::core::hint::unreachable_unchecked();
} }
let sp = match StackDirection::new() {
StackDirection::Ascending => base,
StackDirection::Descending => base.offset(size as isize),
};
rust_psm_replace_stack(
&callback as *const F as usize,
with_replaced_stack::<F>,
sp,
base,
);
}
/// The direction into which stack grows as stack frames are made.
///
/// This is a target-specific property that can be obtained at runtime by calling
/// `StackDirection::new()`.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum StackDirection {
Ascending = 1,
Descending = 2,
}
impl StackDirection {
/// Obtain the stack growth direction.
#[cfg(asm)]
pub fn new() -> StackDirection {
const ASC: u8 = StackDirection::Ascending as u8;
const DSC: u8 = StackDirection::Descending as u8;
unsafe {
match rust_psm_stack_direction() {
ASC => StackDirection::Ascending,
DSC => StackDirection::Descending,
_ => ::core::hint::unreachable_unchecked(),
}
}
}
}
/// Returns current stack pointer.
///
/// Note, that the stack pointer returned is from the perspective of the caller. From the
/// perspective of `stack_pointer` function the pointer returned is the frame pointer.
///
/// While it is a goal to minimize the amount of stack used by this function, implementations for
/// some targets may be unable to avoid allocating a stack frame. This makes this function
/// suitable for stack exhaustion detection only in conjunction with sufficient padding.
///
/// Using `stack_pointer` to check for stack exhaustion is tricky to get right. It is impossible to
/// know the callees frame size, therefore such value must be derived some other way. A common
/// approach is to use stack padding (reserve enough stack space for any function to be called) and
/// check against the padded threshold. If padding is chosen incorrectly, a situation similar to
/// one described below may occur:
///
/// 1. For stack exhaustion check, remaining stack is checked against `stack_pointer` with the
/// padding applied;
/// 2. Callee allocates more stack than was accounted for with padding, and accesses pages outside
/// the stack, invalidating the execution (by e.g. crashing).
#[cfg(asm)]
pub fn stack_pointer() -> *mut u8 {
unsafe { rust_psm_stack_pointer() }
}
/// Macro that outputs its tokens only if `psm::on_stack` and `psm::replace_stack` are available.
///
/// # Examples
///
/// ```
/// # use psm::psm_stack_manipulation;
/// psm_stack_manipulation! {
/// yes {
/// /* Functions `on_stack` and `replace_stack` are available here */
/// }
/// no {
/// /* Functions `on_stack` and `replace_stack` are not available here */
/// }
/// }
/// ```
#[cfg(switchable_stack)]
#[macro_export]
macro_rules! psm_stack_manipulation {
(yes { $($yes: tt)* } no { $($no: tt)* }) => { $($yes)* };
}
/// Macro that outputs its tokens only if `psm::on_stack` and `psm::replace_stack` are available.
///
/// # Examples
///
/// ```
/// # use psm::psm_stack_manipulation;
/// psm_stack_manipulation! {
/// yes {
/// /* Functions `on_stack` and `replace_stack` are available here */
/// }
/// no {
/// /* Functions `on_stack` and `replace_stack` are not available here */
/// }
/// }
/// ```
#[cfg(not(switchable_stack))]
#[macro_export]
macro_rules! psm_stack_manipulation {
(yes { $($yes: tt)* } no { $($no: tt)* }) => { $($no)* };
}
/// Macro that outputs its tokens only if `psm::stack_pointer` and `psm::StackDirection::new` are
/// available.
///
/// # Examples
///
/// ```
/// # use psm::psm_stack_information;
/// psm_stack_information! {
/// yes {
/// /* `psm::stack_pointer` and `psm::StackDirection::new` are available here */
/// }
/// no {
/// /* `psm::stack_pointer` and `psm::StackDirection::new` are not available here */
/// }
/// }
/// ```
#[cfg(asm)]
#[macro_export]
macro_rules! psm_stack_information {
(yes { $($yes: tt)* } no { $($no: tt)* }) => { $($yes)* };
}
/// Macro that outputs its tokens only if `psm::stack_pointer` and `psm::StackDirection::new` are
/// available.
///
/// # Examples
///
/// ```
/// # use psm::psm_stack_information;
/// psm_stack_information! {
/// yes {
/// /* `psm::stack_pointer` and `psm::StackDirection::new` are available here */
/// }
/// no {
/// /* `psm::stack_pointer` and `psm::StackDirection::new` are not available here */
/// }
/// }
/// ```
#[cfg(not(asm))]
#[macro_export]
macro_rules! psm_stack_information {
(yes { $($yes: tt)* } no { $($no: tt)* }) => { $($no)* };
}