This commit is contained in:
Numbers
2025-02-08 20:45:45 -05:00
commit 89217ce137
9 changed files with 476 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

11
Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "e"
version = "0.1.0"
edition = "2024"
[features]
default = ["derive_debug"]
derive_debug = []
big_errors = []
[dependencies]

9
build.rs Normal file
View File

@@ -0,0 +1,9 @@
fn main() {
}

68
src/exception.rs Normal file
View File

@@ -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<U: UserException>(&self) -> bool {
self.typ == U::get_idx()
}
#[inline(always)]
pub unsafe fn cast_unchecked<U: UserException>(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<U: UserException>(self) -> Result<U, Self> {
if self.is::<U>() {
Ok(unsafe { self.cast_unchecked() })
} else {
Err(self)
}
}
}
impl<T: UserException> From<T> 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::<crate::user_types::DebugOrDrop>()
.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::<crate::user_types::DebugOrDrop>()
.add(self.typ as usize)
.read()(self.data.as_ptr() as _, None);
}
}
}

159
src/helpers.rs Normal file
View File

@@ -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<R> Ret for fn() -> R { type Ret=R; }
pub type Never = <fn()->! 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<Exception> 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<T> {
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: <Self as Try>::Residual) -> Self {
unreachable!()
}
}
impl<$($G: UserException),*> Try for $name<$($G),+> {
type Output = Never;
type Residual = crate::Result<Infallible>;
fn from_output(output: Self::Output) -> Self { unreachable!() }
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
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?,
}
}

33
src/lib.rs Normal file
View File

@@ -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::<u16>();
}
// Re-Exports
pub use exception::Exception;
pub use helpers::*;
// Aliases
pub type Result<T> = core::result::Result<T, Exception>;
pub type Throwable<T> = Result<T>;
// 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;

29
src/rand.rs Normal file
View File

@@ -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
}

104
src/user_types.rs Normal file
View File

@@ -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::<Self>()
);
// we moved the data, don't drop it!
core::mem::forget(self);
}
except
}
}
#[cfg(feature = "derive_debug")]
pub fn debug_or_drop<T: UserException + Debug>(
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<T: UserException>(
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 _
}
}
}

62
tests/test.rs Normal file
View File

@@ -0,0 +1,62 @@
e::new_exception!(GenericError, u32);
e::new_exception!(OtherError, Box<String>);
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}");
}
}