use super::{
backend::{ActiveBackend, RethrowHandle, ThrowByPointer, ThrowByValue},
heterogeneous_stack::unbounded::Stack,
};
use core::mem::{offset_of, ManuallyDrop};
unsafe impl ThrowByValue for ActiveBackend {
type RethrowHandle<E> = PointerRethrowHandle<E>;
#[inline]
unsafe fn throw<E>(cause: E) -> ! {
let ex = push(cause);
let ex = unsafe { Exception::header(ex) };
unsafe {
<Self as ThrowByPointer>::throw(ex);
}
}
#[inline]
unsafe fn intercept<Func: FnOnce() -> R, R, E>(
func: Func,
) -> Result<R, (E, Self::RethrowHandle<E>)> {
<Self as ThrowByPointer>::intercept(func).map_err(|ex| {
let ex = unsafe { Exception::<E>::from_header(ex) };
let cause = {
let ex_ref = unsafe { &mut *ex };
unsafe { ex_ref.cause() }
};
(cause, PointerRethrowHandle { ex })
})
}
}
#[derive(Debug)]
pub(crate) struct PointerRethrowHandle<E> {
ex: *mut Exception<E>,
}
impl<E> Drop for PointerRethrowHandle<E> {
#[inline]
fn drop(&mut self) {
unsafe { pop(self.ex) }
}
}
impl<E> RethrowHandle for PointerRethrowHandle<E> {
#[inline]
unsafe fn rethrow<F>(self, new_cause: F) -> ! {
let ex = core::mem::ManuallyDrop::new(self);
let ex = unsafe { replace_last(ex.ex, new_cause) };
let ex = unsafe { Exception::header(ex) };
unsafe {
<ActiveBackend as ThrowByPointer>::throw(ex);
}
}
}
type Header = <ActiveBackend as ThrowByPointer>::ExceptionHeader;
pub struct Exception<E> {
header: Header,
cause: ManuallyDrop<Unaligned<E>>,
}
#[repr(C, packed)]
struct Unaligned<T>(T);
impl<E> Exception<E> {
fn new(cause: E) -> Self {
Self {
header: ActiveBackend::new_header(),
cause: ManuallyDrop::new(Unaligned(cause)),
}
}
pub const unsafe fn header(ex: *mut Self) -> *mut Header {
unsafe { ex.byte_add(offset_of!(Self, header)) }.cast()
}
pub const unsafe fn from_header(header: *mut Header) -> *mut Self {
unsafe { header.byte_sub(offset_of!(Self, header)) }.cast()
}
pub unsafe fn cause(&mut self) -> E {
unsafe { ManuallyDrop::take(&mut self.cause).0 }
}
}
#[cfg(thread_local = "std")]
std::thread_local! {
static STACK: Stack<Header> = const { Stack::new() };
}
#[cfg(thread_local = "attribute")]
#[thread_local]
static STACK: Stack<Header> = const { Stack::new() };
#[inline]
unsafe fn get_stack() -> &'static Stack<Header> {
#[cfg(thread_local = "std")]
return STACK.with(|r| unsafe { core::mem::transmute(r) });
#[cfg(thread_local = "attribute")]
return unsafe { core::mem::transmute::<&Stack<Header>, &'static Stack<Header>>(&STACK) };
#[cfg(thread_local = "unimplemented")]
compile_error!("Unable to compile Lithium on a platform does not support thread locals")
}
const fn get_alloc_size<E>() -> usize {
const {
assert!(
align_of::<Exception<E>>() == align_of::<Header>(),
"Exception<E> has unexpected alignment",
);
}
size_of::<Exception<E>>()
}
#[inline(always)]
pub fn push<E>(cause: E) -> *mut Exception<E> {
let stack = unsafe { get_stack() };
let ex: *mut Exception<E> = stack.push(get_alloc_size::<E>()).cast();
unsafe {
ex.write(Exception::new(cause));
}
ex
}
pub unsafe fn pop<E>(ex: *mut Exception<E>) {
let stack = unsafe { get_stack() };
unsafe {
stack.pop(ex.cast(), get_alloc_size::<E>());
}
}
pub unsafe fn replace_last<E, F>(ex: *mut Exception<E>, cause: F) -> *mut Exception<F> {
let stack = unsafe { get_stack() };
let ex: *mut Exception<F> =
unsafe { stack.replace_last(ex.cast(), get_alloc_size::<E>(), get_alloc_size::<F>()) }
.cast();
unsafe {
ex.write(Exception::new(cause));
}
ex
}
#[cfg(test)]
mod test {
use super::*;
use alloc::string::String;
#[test]
fn exception_cause() {
let mut ex = Exception::new(String::from("Hello, world!"));
assert_eq!(unsafe { ex.cause() }, "Hello, world!");
}
#[test]
fn stack() {
let ex1 = push(String::from("Hello, world!"));
let ex2 = push(123i32);
assert_eq!(unsafe { (*ex2).cause() }, 123);
let ex3 = unsafe { replace_last(ex2, "Third time's a charm") };
assert_eq!(unsafe { (*ex3).cause() }, "Third time's a charm");
unsafe {
pop(ex3);
}
assert_eq!(unsafe { (*ex1).cause() }, "Hello, world!");
unsafe {
pop(ex1);
}
}
}