commit 89217ce1376c1d4c1bac5501b8179ad43a03fb6c Author: Numbers Date: Sat Feb 8 20:45:45 2025 -0500 . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e7e7f67 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "e" +version = "0.1.0" +edition = "2024" + +[features] +default = ["derive_debug"] +derive_debug = [] +big_errors = [] + +[dependencies] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..bac00c7 --- /dev/null +++ b/build.rs @@ -0,0 +1,9 @@ + + + + +fn main() { + + + +} diff --git a/src/exception.rs b/src/exception.rs new file mode 100644 index 0000000..7897408 --- /dev/null +++ b/src/exception.rs @@ -0,0 +1,68 @@ +use crate::user_types::UserException; +use core::fmt::{Debug, Formatter}; +use core::mem; +use crate::config::USER_ERROR_SIZE; + +#[repr(C, align(0x10))] +pub struct Exception { + pub(crate) data: [u8; USER_ERROR_SIZE], + pub(crate) typ: u16, +} + +impl Exception { + + #[inline(always)] + pub fn is(&self) -> bool { + self.typ == U::get_idx() + } + + #[inline(always)] + pub unsafe fn cast_unchecked(self) -> U { + let r = unsafe { (self.data.as_ptr() as *const U).read_unaligned() }; + mem::forget(self); + r + } + + /// cast into the provided exception + #[inline(always)] + pub fn cast(self) -> Result { + if self.is::() { + Ok(unsafe { self.cast_unchecked() }) + } else { + Err(self) + } + } +} + +impl From for Exception { + fn from(value: T) -> Self { + UserException::convert(value) + } +} + +#[cfg(feature = "derive_debug")] +impl Debug for Exception { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + unsafe { + #[allow(static_mut_refs)] + crate::user_types::ERROR_TABLE_START + .as_ptr() + .cast::() + .add(self.typ as usize) + .read()(self.data.as_ptr() as _, Some(f)) + } + } +} + +impl Drop for Exception { + fn drop(&mut self) { + unsafe { + #[allow(static_mut_refs)] + crate::user_types::ERROR_TABLE_START + .as_ptr() + .cast::() + .add(self.typ as usize) + .read()(self.data.as_ptr() as _, None); + } + } +} diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 0000000..64dc4c0 --- /dev/null +++ b/src/helpers.rs @@ -0,0 +1,159 @@ +use core::convert::Infallible; +use crate::{Exception, user_types::UserException}; +use core::ops::{ControlFlow, FromResidual, Try}; +use core::fmt::{Debug, Formatter}; +use core::marker::PhantomData; + + + +pub trait Ret { type Ret; } +impl Ret for fn() -> R { type Ret=R; } +pub type Never = ! as Ret>::Ret; + +// Macro to generate the helper enums +macro_rules! generate_enum { + ($name:ident;$($V:ident, $G:ident);+;) => { + + pub enum $name< + $($G: UserException),* + > { + $($V($G),)* + Unmatched(Exception) + } + + impl< $($G: UserException),* > From for $name< $($G),+ > { + fn from(value: Exception) -> Self { + $(if value.is::<$G>() { Self::$V(unsafe { value.cast_unchecked::<$G>()}) } else)+ + { Self::Unmatched(value) } + } + } + + impl< $($G: UserException),* > From<$name<$($G),+>> for Exception { + fn from(value: $name::<$($G),+>) -> Self { + match value { + $( $name::$V (e) => UserException::convert(e)),+, + $name::Unmatched(e) => e, + } + } + } + + impl< T, $($G: UserException),* > From<$name<$($G),+>> for crate::Result { + fn from(value: $name::<$($G),+>) -> Self { + match value { + $( $name::$V (e) => Err(UserException::convert(e))),+, + $name::Unmatched(e) => Err(e), + } + } + } + + impl<$($G: UserException),*> FromResidual for $name<$($G),+> { + fn from_residual(residual: ::Residual) -> Self { + unreachable!() + } + } + + impl<$($G: UserException),*> Try for $name<$($G),+> { + type Output = Never; + type Residual = crate::Result; + fn from_output(output: Self::Output) -> Self { unreachable!() } + fn branch(self) -> ControlFlow { + ControlFlow::Break(self.into()) + } + } + + } +} + + + + +generate_enum! { + M1; + V0, A0; +} + +generate_enum! { + M2; + V0, A0; + V1, A1; +} + +generate_enum! { + M3; + V0, A0; + V1, A1; + V2, A2; +} + +generate_enum! { + M4; + V0, A0; + V1, A1; + V2, A2; + V3, A3; +} + + +generate_enum! { + M5; + V0, A0; + V1, A1; + V2, A2; + V3, A3; + V4, A4; +} + +generate_enum! { + M6; + V0, A0; + V1, A1; + V2, A2; + V3, A3; + V4, A4; + V5, A5; +} + +generate_enum! { + M7; + V0, A0; + V1, A1; + V2, A2; + V3, A3; + V4, A4; + V5, A5; + V6, A6; +} + +generate_enum! { + M8; + V0, A0; + V1, A1; + V2, A2; + V3, A3; + V4, A4; + V5, A5; + V6, A6; + V7, A7; +} + + + +pub macro alias_macro { + ($m:ident: $i0:ident) => { use M1 as $m; }, + ($m:ident: $i0:ident, $i1:ident) => { use M2 as $m; }, + ($m:ident: $i0:ident, $i1:ident, $i2:ident) => { use M3 as $m; }, + + +} + +pub macro handle( $exc:ident: $($e:ident @ $t:ty => $b:expr),+ $(,)?) { + alias_macro!(MatcherEnum: $($e),*); + + match MatcherEnum::from( $exc ) { + $( + MatcherEnum::V0( $e ) if { let _: & $t = & $e; true } => {} + ),+, + _error => _error?, + } + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..90818d2 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,33 @@ +#![no_std] #![feature(decl_macro, generic_const_exprs, freeze, try_trait_v2)] +#![allow(incomplete_features, unused)] + +pub(crate) mod config { + + #[cfg(not(feature = "big_errors"))] + pub const ERROR_SIZE: usize = 16; + + #[cfg(feature = "big_errors")] + pub const ERROR_SIZE: usize = 32; + + pub const USER_ERROR_SIZE: usize = ERROR_SIZE - size_of::(); +} + +// Re-Exports +pub use exception::Exception; +pub use helpers::*; + +// Aliases +pub type Result = core::result::Result; +pub type Throwable = Result; + +// Modules +pub(crate) mod rand; +pub(crate) mod user_types; +pub(crate) mod exception; +pub(crate) mod helpers; + +// Re-Export Macros +pub use user_types::new_exception; + + +pub use user_types::UserException; \ No newline at end of file diff --git a/src/rand.rs b/src/rand.rs new file mode 100644 index 0000000..ceaa6cc --- /dev/null +++ b/src/rand.rs @@ -0,0 +1,29 @@ + +#[track_caller] +pub const fn random(seed: usize) -> u64 { + + let value = match core::option_env!("SEED") { + Some(_seed) => hash(_seed.as_bytes(), INITIAL_STATE), + None => INITIAL_STATE, + }; + + let value = hash(&seed.to_ne_bytes(), value); + let caller = core::panic::Location::caller(); + let value = hash(&caller.file().as_bytes(), value); + let value = hash(&caller.line().to_ne_bytes(), value); + let value = hash(&caller.column().to_ne_bytes(), value); + value +} + + +const INITIAL_STATE: u64 = 0xcbf29ce484222325; +const PRIME: u64 = 0x100000001b3; +const fn hash(bytes: &[u8], mut hash: u64) -> u64 { + let mut i = 0; + while i < bytes.len() { + hash = hash ^ bytes[i] as u64; + hash = hash.wrapping_mul(PRIME); + i += 1; + } + hash +} \ No newline at end of file diff --git a/src/user_types.rs b/src/user_types.rs new file mode 100644 index 0000000..e7fb8b7 --- /dev/null +++ b/src/user_types.rs @@ -0,0 +1,104 @@ +use core::fmt::{Debug, Formatter}; +use core::ptr::drop_in_place; + +use crate::Exception; +use crate::rand::random; +use crate::config::USER_ERROR_SIZE; + +/// the start of the error table +#[unsafe(link_section=".rdata$etypes.0")] +pub static mut ERROR_TABLE_START: [u8;0] = []; + +/// the error table is made up of pointers to these functions +pub type DebugOrDrop = fn(*const(), Option<&mut core::fmt::Formatter>) -> core::fmt::Result; + +/// Represents a type that is an exception +pub trait UserException: Sized + core::marker::Freeze { + + fn get_idx() -> u16; + + #[inline(always)] + fn convert(self) -> Exception { + let mut except = Exception { + typ: Self::get_idx(), + data: [0u8;USER_ERROR_SIZE] + }; + unsafe { + core::ptr::copy_nonoverlapping( + &self as *const _ as *const u8, + except.data.as_mut_ptr(), + size_of::() + ); + // we moved the data, don't drop it! + core::mem::forget(self); + } + except + } +} + + +#[cfg(feature = "derive_debug")] +pub fn debug_or_drop( + ptr: *const (), fmt: Option<&mut core::fmt::Formatter> +) -> core::fmt::Result { + // this is safe because the data is stored at offset 0 of our exception type. + // so the alignment will always be correct (not shifted by 2) + match fmt { + // fmt is provided, write write the format... + Some(fmt) => unsafe { &*(ptr as *const T)}.fmt(fmt), + + // fmt was not provided, this function acts as the destructor + None => unsafe { + drop_in_place(ptr as *const T as *mut T); + Ok(()) + } + } +} + +#[cfg(not(feature = "derive_debug"))] +pub fn debug_or_drop( + ptr: *const (), fmt: Option<&mut core::fmt::Formatter> +) -> core::fmt::Result { + if fmt.is_none() { + drop_in_place(ptr as *const T as *mut T); + } + Ok(()) +} + +pub macro new_exception( $name:ident $(, $($arg:tt)*)? ) { + + #[derive(Debug)] + #[allow(dead_code)] + pub struct $name $( ( $( $arg )* ) )?; + + const _: () = assert!(size_of::<$name>() <= USER_ERROR_SIZE, " is too large"); + impl UserException for $name { + + #[inline(always)] fn get_idx() -> u16 { + #[unsafe(link_section=".rdata$etypes.1")] + static mut ERROR_TABLE_ENTRY: DebugOrDrop = debug_or_drop::<$name>; + let mut idx: u64 = 0; + unsafe { + core::arch::asm!( + "mov {o:}, offset {}@SECREL32 + {c}", + "sub {o:}, offset {}@SECREL32 + {c}", + sym ERROR_TABLE_ENTRY, + sym ERROR_TABLE_START, + o = out(reg) idx, + c = const { random(1) } + ) + } + (idx >> 3) as _ + } + } +} + + + + + + + + + + diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..68ed678 --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,62 @@ + +e::new_exception!(GenericError, u32); +e::new_exception!(OtherError, Box); +e::new_exception!(DropError, DropTest); + + +#[test] +fn test() { + let e = test2(); + println!("{:?}", e); +} + + +fn test2() -> e::Result<()> { + + for i in 0..3 { + let exception = generic(i).unwrap_err(); + + + } + + + for i in 0..3 { + let exception = generic(i).unwrap_err(); + + + match e::M2::from(exception) { + + e::M2::V0(e) if { let _: &GenericError = &e; true } => { + println!("Generic: {e:?}") + } + + e::M2::V1(e) => { + let _: &OtherError = &e; + println!("Other: {e:?}"); + } + + error => error?, + + } + } + Ok(()) + +} + + +fn generic(i: u32) -> e::Result<()> { + match i { + 0 => Err(GenericError(0x69u32))?, + 1 => Err(OtherError(Box::new("Hello World".to_string())))?, + 2 => Err(DropError(DropTest))?, + _ => unreachable!() + } +} + +#[derive(Debug)] +struct DropTest; +impl Drop for DropTest { + fn drop(&mut self) { + println!("Dropped @ {self:p}"); + } +} \ No newline at end of file