From 8c59c7b3ae123b7b1ce9fcb91f954ffb1946d070 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Thu, 27 Jun 2024 19:44:59 +0200 Subject: [PATCH] async --- .gitignore | 2 +- Cargo.lock | 54 -------- Cargo.toml | 12 -- README.md | 3 - async-experiments/Cargo.lock | 7 ++ async-experiments/Cargo.toml | 6 + async-experiments/src/executor.rs | 38 ++++++ async-experiments/src/join.rs | 75 +++++++++++ async-experiments/src/lib.rs | 7 ++ async-experiments/src/spawn_blocking.rs | 76 +++++++++++ async-experiments/tests/execute.rs | 23 ++++ examples/scratch.rs | 14 --- pm/Cargo.toml | 14 --- pm/src/lib.rs | 46 ------- pm/src/safe_extern.rs | 98 --------------- pm/src/scratch.rs | 49 -------- src/assert.rs | 67 ---------- src/cfg_match.rs | 58 --------- src/hashmaps/mod.rs | 94 -------------- src/hashmaps/simple_open_addressing.rs | 151 ---------------------- src/innocent_linked_list.rs | 37 ------ src/lib.rs | 17 --- src/scratch.rs | 83 ------------- src/sendsync.rs | 148 ---------------------- src/thin_u128.rs | 146 ---------------------- src/unroll_int.rs | 37 ------ src/unsized_clone.rs | 159 ------------------------ tests/safe_extern.rs | 18 --- 28 files changed, 233 insertions(+), 1306 deletions(-) delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml delete mode 100644 README.md create mode 100644 async-experiments/Cargo.lock create mode 100644 async-experiments/Cargo.toml create mode 100644 async-experiments/src/executor.rs create mode 100644 async-experiments/src/join.rs create mode 100644 async-experiments/src/lib.rs create mode 100644 async-experiments/src/spawn_blocking.rs create mode 100644 async-experiments/tests/execute.rs delete mode 100644 examples/scratch.rs delete mode 100644 pm/Cargo.toml delete mode 100644 pm/src/lib.rs delete mode 100644 pm/src/safe_extern.rs delete mode 100644 pm/src/scratch.rs delete mode 100644 src/assert.rs delete mode 100644 src/cfg_match.rs delete mode 100644 src/hashmaps/mod.rs delete mode 100644 src/hashmaps/simple_open_addressing.rs delete mode 100644 src/innocent_linked_list.rs delete mode 100644 src/lib.rs delete mode 100644 src/scratch.rs delete mode 100644 src/sendsync.rs delete mode 100644 src/thin_u128.rs delete mode 100644 src/unroll_int.rs delete mode 100644 src/unsized_clone.rs delete mode 100644 tests/safe_extern.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..eb5a316 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/target +target diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index efb9dae..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,54 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "pm" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "uwu" -version = "0.1.0" -dependencies = [ - "pm", -] diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index ef596e9..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[workspace] -members = [".", "./pm"] - -[package] -name = "uwu" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -pm = { path = "./pm" } diff --git a/README.md b/README.md deleted file mode 100644 index 316f32c..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# the-good-stuff - -random rust experiments diff --git a/async-experiments/Cargo.lock b/async-experiments/Cargo.lock new file mode 100644 index 0000000..dc94db8 --- /dev/null +++ b/async-experiments/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "async-experiments" +version = "0.1.0" diff --git a/async-experiments/Cargo.toml b/async-experiments/Cargo.toml new file mode 100644 index 0000000..07145a8 --- /dev/null +++ b/async-experiments/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "async-experiments" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/async-experiments/src/executor.rs b/async-experiments/src/executor.rs new file mode 100644 index 0000000..bdfadf4 --- /dev/null +++ b/async-experiments/src/executor.rs @@ -0,0 +1,38 @@ +use std::{ + future::Future, + pin::pin, + sync::Arc, + task::{Context, Poll, Wake, Waker}, +}; + +pub struct Executor {} + +impl Executor { + pub fn new() -> Self { + Executor {} + } + + pub fn block_on(&self, fut: F) -> F::Output { + let mut fut = pin!(fut); + let this_thread = std::thread::current(); + let waker = Waker::from(Arc::new(WakeFn(move || { + this_thread.unpark(); + }))); + let mut ctx = Context::from_waker(&waker); + + loop { + let result = fut.as_mut().poll(&mut ctx); + match result { + Poll::Ready(output) => return output, + Poll::Pending => std::thread::park(), + } + } + } +} + +struct WakeFn(F); +impl Wake for WakeFn { + fn wake(self: std::sync::Arc) { + (self.0)() + } +} diff --git a/async-experiments/src/join.rs b/async-experiments/src/join.rs new file mode 100644 index 0000000..7b256a4 --- /dev/null +++ b/async-experiments/src/join.rs @@ -0,0 +1,75 @@ +use std::{ + fmt::Debug, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +pub fn join2(fut1: F1, fut2: F2) -> Join2 +where + F1: Future, + F2: Future, +{ + Join2(JoinState::Pending(fut1), JoinState::Pending(fut2)) +} + +pub struct Join2(JoinState, JoinState); + +#[derive(Debug)] +enum JoinState { + Pending(F), + Ready(F::Output), + Stolen, +} +impl JoinState { + fn steal(&mut self) -> F::Output { + match std::mem::replace(self, JoinState::Stolen) { + JoinState::Ready(output) => output, + _ => unreachable!("tried to take output of non-ready join state"), + } + } +} + +impl Future for Join2 { + type Output = (F1::Output, F2::Output); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + + fn make_progress(field: &mut JoinState, cx: &mut Context<'_>) { + match field { + JoinState::Pending(fut) => match unsafe { Pin::new_unchecked(fut) }.poll(cx) { + Poll::Ready(result) => { + *field = JoinState::Ready(result); + } + Poll::Pending => {} + }, + + JoinState::Ready(_) => {} + JoinState::Stolen => unreachable!("future polled after completion"), + } + } + + make_progress(&mut this.0, cx); + make_progress(&mut this.1, cx); + + if let (JoinState::Ready(_), JoinState::Ready(_)) = (&this.0, &this.1) { + return Poll::Ready((this.0.steal(), this.1.steal())); + } + + Poll::Pending + } +} + +impl Debug for Join2 +where + F1::Output: Debug, + F2::Output: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Join2") + .field(&self.0) + .field(&self.1) + .finish() + } +} diff --git a/async-experiments/src/lib.rs b/async-experiments/src/lib.rs new file mode 100644 index 0000000..2ac21ae --- /dev/null +++ b/async-experiments/src/lib.rs @@ -0,0 +1,7 @@ +mod executor; +mod spawn_blocking; +mod join; + +pub use executor::*; +pub use spawn_blocking::*; +pub use join::*; diff --git a/async-experiments/src/spawn_blocking.rs b/async-experiments/src/spawn_blocking.rs new file mode 100644 index 0000000..133e4fa --- /dev/null +++ b/async-experiments/src/spawn_blocking.rs @@ -0,0 +1,76 @@ +use std::{ + fmt::Debug, + future::Future, + sync::{Arc, Mutex}, + task::{Poll, Waker}, +}; + +#[derive(Debug)] +pub struct JoinHandle { + inner: Arc>, +} + +struct Inner { + result: Mutex>, + waker: Mutex>, +} + +pub fn spawn_blocking(f: F) -> JoinHandle +where + R: Send + 'static, + F: Send + FnOnce() -> R + 'static, +{ + let inner = Arc::new(Inner { + result: Mutex::new(None), + waker: Mutex::new(None), + }); + let inner2 = inner.clone(); + std::thread::spawn(move || { + let result = f(); + *inner2.result.lock().unwrap() = Some(result); + if let Some(waker) = inner2.waker.lock().unwrap().take() { + waker.wake(); + } + }); + + JoinHandle { inner } +} + +impl Future for JoinHandle { + type Output = T; + + fn poll( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + let mut result = self.inner.result.lock().unwrap(); + match result.take() { + Some(result) => Poll::Ready(result), + None => { + *self.inner.waker.lock().unwrap() = Some(cx.waker().clone()); + Poll::Pending + } + } + } +} + +impl Debug for Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Inner") + .field("result", &self.result) + .finish_non_exhaustive() + } +} + +#[cfg(test)] +mod tests { + use crate::Executor; + + #[test] + fn spawn_value() { + let executor = Executor::new(); + + let result = executor.block_on(super::spawn_blocking(|| 1 + 1)); + assert_eq!(result, 2); + } +} diff --git a/async-experiments/tests/execute.rs b/async-experiments/tests/execute.rs new file mode 100644 index 0000000..cf651ed --- /dev/null +++ b/async-experiments/tests/execute.rs @@ -0,0 +1,23 @@ +use async_experiments::Executor; + +#[test] +fn execute() { + let executor = Executor::new(); + + executor.block_on(async {}); + executor.block_on(async {}); +} + +#[test] +fn join2() { + let exec = Executor::new(); + + let r = exec.block_on(async { + let t1 = async_experiments::spawn_blocking(|| 1); + let t2 = async_experiments::spawn_blocking(|| 2); + + let (r1, r2) = async_experiments::join2(t1, t2).await; + r1 + r2 + }); + assert_eq!(r, 3) +} diff --git a/examples/scratch.rs b/examples/scratch.rs deleted file mode 100644 index 93938d1..0000000 --- a/examples/scratch.rs +++ /dev/null @@ -1,14 +0,0 @@ -use uwu::scratch::{ - actual_scratch_read, actual_scratch_write, define_scratch, scratch_space, Scratch, -}; - -#[scratch_space] -fn has_scratch_space(mut scratch: Scratch<'_>) { - scratch_write!(scratch, 10u32); - let _: u32 = scratch_read!(scratch); -} - -fn main() { - define_scratch!(scratch, 10); - has_scratch_space(scratch); -} diff --git a/pm/Cargo.toml b/pm/Cargo.toml deleted file mode 100644 index e33fc91..0000000 --- a/pm/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "pm" -version = "0.1.0" -edition = "2021" - -[lib] -proc-macro = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -proc-macro2 = "1.0.49" -quote = "1.0.23" -syn = { version = "1.0.107", features = ["full", "fold"] } diff --git a/pm/src/lib.rs b/pm/src/lib.rs deleted file mode 100644 index b5a6949..0000000 --- a/pm/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -use proc_macro::TokenStream; - -mod safe_extern; -mod scratch; - -#[proc_macro_attribute] -pub fn scratch_space(attr: TokenStream, input: TokenStream) -> TokenStream { - scratch::scratch_space(attr, input) -} - -/// # safe-extern -/// -/// Mark foreign functions as to be safe to call. -/// -/// ```ignore -/// #[safe_extern] -/// extern "Rust" { -/// fn add(a: u8, b: u8) -> u8; -/// } -/// -/// fn main() { -/// assert_eq!(add(1, 2), 3); -/// } -/// ``` -/// -/// It works by expanding the above to this -/// -/// ```ignore -/// extern "Rust" { -/// #[link_name = "add"] -/// fn _safe_extern_inner_add(a: u8, b: u8) -> u8; -/// } -/// fn add(a: u8, b: u8) -> u8 { -/// unsafe { _safe_extern_inner_add(a, b) } -/// } -/// -/// fn main() { -/// assert_eq!(add(1, 2), 3); -/// } -/// ``` -/// -/// This is of course unsound and the macro needs to be `unsafe` somehow but I can't be bothered with that right now lol. -#[proc_macro_attribute] -pub fn safe_extern(attr: TokenStream, input: TokenStream) -> TokenStream { - safe_extern::safe_extern(attr, input) -} diff --git a/pm/src/safe_extern.rs b/pm/src/safe_extern.rs deleted file mode 100644 index 2d50727..0000000 --- a/pm/src/safe_extern.rs +++ /dev/null @@ -1,98 +0,0 @@ -use proc_macro::TokenStream; -use proc_macro2::Ident; -use quote::{quote, quote_spanned}; -use syn::{ - parse_macro_input, ForeignItem, ForeignItemFn, ItemFn, Pat, PatIdent, PatType, Visibility, -}; - -pub fn safe_extern(_: TokenStream, input: TokenStream) -> TokenStream { - let mut foreign = parse_macro_input!(input as syn::ItemForeignMod); - - let mut safe_wrappers = Vec::new(); - let src_items = std::mem::take(&mut foreign.items); - - for item in src_items { - match item { - ForeignItem::Fn(item_fn) => { - let (replacement, safe_wrapper) = mangle_ident_and_add_link_name(item_fn); - foreign.items.push(ForeignItem::Fn(replacement)); - - safe_wrappers.push(safe_wrapper); - } - item => match head_span_foreign_item(&item) { - Some(span) => { - return quote_spanned! { - span => compile_error! { "only foreign functions are allowed" } - } - .into(); - } - None => { - return quote! { - compile_error! { "only foreign functions are allowed" } - } - .into(); - } - }, - } - } - - quote! { #foreign #(#safe_wrappers)* }.into() -} - -fn mangle_ident_and_add_link_name(mut item: ForeignItemFn) -> (ForeignItemFn, ItemFn) { - if item.attrs.iter().any(|attr| { - attr.path - .get_ident() - .map_or(false, |ident| ident.to_string() == "link_name") - }) { - panic!("oh no you have alink name already") - } - - let vis = std::mem::replace(&mut item.vis, Visibility::Inherited); - - let name = item.sig.ident; - let name_str = name.to_string(); - if name_str.starts_with("r#") { - panic!("rawr :>("); - } - - let mangled = format!("_safe_extern_inner_{name_str}"); - let new_name = Ident::new(&mangled, name.span()); - item.sig.ident = new_name.clone(); - - item.attrs - .push(syn::parse_quote! { #[link_name = #name_str] }); - - let args = item.sig.inputs.iter().map(|param| match param { - syn::FnArg::Receiver(_) => panic!("cannot have reciver in foreign function"), - syn::FnArg::Typed(PatType { pat, .. }) => match &**pat { - Pat::Ident(PatIdent { ident, .. }) => quote! { #ident }, - _ => panic!("invalid argument in foreign function"), - }, - }); - - let mut safe_sig = item.sig.clone(); - safe_sig.ident = name; - let safe_wrapper = ItemFn { - attrs: Vec::new(), - vis, - sig: safe_sig, - block: syn::parse_quote! { - { - unsafe { #new_name(#(#args),*) } - } - }, - }; - - (item, safe_wrapper) -} - -fn head_span_foreign_item(item: &ForeignItem) -> Option { - Some(match item { - ForeignItem::Fn(_) => unreachable!(), - ForeignItem::Static(s) => s.static_token.span, - ForeignItem::Type(ty) => ty.type_token.span, - ForeignItem::Macro(m) => m.mac.path.segments[0].ident.span(), - _ => return None, - }) -} diff --git a/pm/src/scratch.rs b/pm/src/scratch.rs deleted file mode 100644 index f93a81b..0000000 --- a/pm/src/scratch.rs +++ /dev/null @@ -1,49 +0,0 @@ -use proc_macro::TokenStream; -use proc_macro2::{Ident, Span}; -use quote::quote; -use syn::{fold::Fold, parse_macro_input, parse_quote, ItemFn, Stmt}; - -pub fn scratch_space(_: TokenStream, input: TokenStream) -> TokenStream { - let fn_def = parse_macro_input!(input as ItemFn); - let track_ident = Ident::new("scratch_local", Span::mixed_site()); - - let mut fn_def = LocalInitFolder { - track_ident: track_ident.clone(), - } - .fold_item_fn(fn_def); - - let init: Stmt = parse_quote! { let #track_ident: (); }; - - fn_def.block.stmts.insert(0, init); - - quote! { #fn_def }.into() -} - -struct LocalInitFolder { - track_ident: Ident, -} - -impl syn::fold::Fold for LocalInitFolder { - fn fold_macro(&mut self, mut mac: syn::Macro) -> syn::Macro { - if let Some(last_path) = mac.path.segments.iter().next_back() { - match last_path.ident.to_string().as_str() { - "scratch_write" => { - let track_ident = &self.track_ident.clone(); - mac.path = parse_quote! { actual_scratch_write }; - mac.tokens.extend(quote! { ; #track_ident }); - } - "scratch_read" => { - let mut track_ident = self.track_ident.clone(); - track_ident.set_span(track_ident.span().located_at(last_path.ident.span())); - mac.path = parse_quote! { actual_scratch_read }; - mac.tokens.extend(quote! { ; #track_ident }); - } - _ => {} - } - - mac - } else { - mac - } - } -} diff --git a/src/assert.rs b/src/assert.rs deleted file mode 100644 index 12ef751..0000000 --- a/src/assert.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::fmt::Debug; - -pub fn assert(v: T) -> Assert { - Assert { v } -} - -pub struct Assert { - v: T, -} - -impl Assert { - #[track_caller] - pub fn is_true(self) { - assert!(self.v); - } - #[track_caller] - pub fn is_false(self) { - assert!(!self.v); - } -} - -impl Assert { - #[track_caller] - pub fn equals(self, other: U) - where - T: PartialEq, - { - assert_eq!(self.v, other); - } - #[track_caller] - pub fn not_equals(self, other: U) - where - T: PartialEq, - { - assert_ne!(self.v, other); - } -} - -impl> Assert { - #[track_caller] - pub fn contains(self, other: &str) { - assert!(self.v.as_ref().contains(other), "pattern '{other}' not found in string: {}", self.v.as_ref()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn assert_bool() { - assert(true).is_true(); - assert(false).is_false(); - } - - #[test] - fn assert_equal() { - assert(1).equals(1); - assert(2).not_equals(1); - } - - #[test] - fn assert_str() { - assert("uwu owo").contains("uwu"); - assert("uwu owo".to_owned()).contains("uwu"); - } -} diff --git a/src/cfg_match.rs b/src/cfg_match.rs deleted file mode 100644 index 5703544..0000000 --- a/src/cfg_match.rs +++ /dev/null @@ -1,58 +0,0 @@ -#[macro_export] -macro_rules! cfg_match { - () => {}; - (_ => { $($tt:tt)* }) => { - $($tt)* - }; - ( - $head_pattern:meta => { $($head_body:tt)* } - $($rest:tt)* - ) => { - - #[cfg($head_pattern)] - $crate::cfg_match! { _ => { $($head_body)* } } - - #[cfg(not($head_pattern))] - $crate::cfg_match! { - $($rest)* - } - }; -} - -#[cfg(test)] -mod tests { - #[test] - fn correct_one_selected() { - crate::cfg_match! { - any() => { - panic!(); - } - all() => { - - } - any() => { - panic!(); - } - } - } - - #[test] - fn underscore() { - crate::cfg_match! { - _ => {} - } - } - - #[test] - fn fallback() { - crate::cfg_match! { - any() => { - panic!(); - } - any() => { - panic!(); - } - _ => {} - } - } -} diff --git a/src/hashmaps/mod.rs b/src/hashmaps/mod.rs deleted file mode 100644 index 66379bb..0000000 --- a/src/hashmaps/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::hash::{BuildHasher, Hash}; - -pub mod simple_open_addressing; - -pub trait HashMapFamily { - type Map: HashMap; -} - -pub trait HashMap: IntoIterator { - fn with_hasher(state: S) -> Self; - - fn len(&self) -> usize; - - fn is_empty(&self) -> bool { - self.len() == 0 - } - - fn get(&self, key: &K) -> Option<&V> - where - K: Eq + Hash, - S: BuildHasher; - - fn insert(&mut self, key: K, value: V) -> Option - where - K: Eq + Hash, - S: BuildHasher; -} - -#[cfg(test)] -mod tests { - use std::hash::{BuildHasher, BuildHasherDefault, Hasher, RandomState}; - - use super::{HashMap, HashMapFamily}; - - #[derive(Default)] - struct CollidingHasher; - impl Hasher for CollidingHasher { - fn finish(&self) -> u64 { - 0 - } - fn write(&mut self, _bytes: &[u8]) {} - } - - pub(super) fn run_tests() - where - M: HashMapFamily, - { - let mk_str = || M::Map::<&str, &str, _>::with_hasher(RandomState::new()); - - let m = mk_str(); - assert_eq!(m.get(&"uwu"), None); - assert_eq!(m.get(&"uwu"), None); - - let mut m = mk_str(); - m.insert("hello", "world"); - assert_eq!(m.get(&"hello"), Some(&"world")); - assert_eq!(m.len(), 1); - m.insert("aaa", "yes"); - assert_eq!(m.get(&"hello"), Some(&"world")); - assert_eq!(m.get(&"aaa"), Some(&"yes")); - assert_eq!(m.len(), 2); - - let mut m = mk_str(); - m.insert("hello", "world"); - assert_eq!(m.get(&"hello"), Some(&"world")); - assert_eq!(m.len(), 1); - m.insert("hello", "no"); - assert_eq!(m.get(&"hello"), Some(&"no")); - assert_eq!(m.len(), 1); - - for count in [1, 10, 100, 1000, 10_000, 100_000] { - test_many::(count, RandomState::new()); - } - test_many::(1000, BuildHasherDefault::::default()); - } - - fn test_many(count: usize, h: H) { - let mut m = M::Map::with_hasher(h); - - for i in 0..count { - m.insert(i, i); - } - - let mut found = vec![false; count]; - for (k, v) in m.into_iter() { - assert_eq!(k, v); - assert!(!found[k], "duplicate element"); - found[k] = true; - } - for (i, found) in found.iter().enumerate() { - assert!(found, "element {i} was lost"); - } - } -} diff --git a/src/hashmaps/simple_open_addressing.rs b/src/hashmaps/simple_open_addressing.rs deleted file mode 100644 index a23a8d7..0000000 --- a/src/hashmaps/simple_open_addressing.rs +++ /dev/null @@ -1,151 +0,0 @@ -use super::{HashMap, HashMapFamily}; -use std::{ - hash::{BuildHasher, Hash, RandomState}, - vec, -}; - -type Entry = Option<(K, V)>; - -pub struct SimpleOAHashMap { - buckets: Vec>, - filled: usize, - s: S, -} - -impl SimpleOAHashMap { - pub fn new() -> Self { - Self::with_hasher(RandomState::new()) - } -} - -impl SimpleOAHashMap { - fn bucket_of_elem(&self, key: &K) -> usize { - assert_ne!(self.buckets.len(), 0, "cannot compute bucket of empty map"); - let hash = self.s.hash_one(&key) as usize; - hash % self.buckets.len() - } - - fn grow(&mut self) { - let len = self.buckets.len(); - let new = if len == 0 { 8 } else { len * 2 }; - let old = IntoIter::new(std::mem::take(&mut self.buckets)); - let new_buckets = (0..new).map(|_| None).collect(); - self.buckets = new_buckets; - self.extend(old); - } -} - -impl Extend<(K, V)> for SimpleOAHashMap { - fn extend>(&mut self, iter: T) { - iter.into_iter() - .for_each(|(key, value)| drop(self.insert(key, value))); - } -} - -impl super::HashMap for SimpleOAHashMap { - fn with_hasher(state: S) -> Self { - Self { - buckets: Vec::new(), - filled: 0, - s: state, - } - } - - fn len(&self) -> usize { - self.filled - } - - fn is_empty(&self) -> bool { - self.len() == 0 - } - - fn get(&self, key: &K) -> Option<&V> - where - K: Eq + Hash, - S: BuildHasher, - { - if self.is_empty() { - return None; - } - let bucket = self.bucket_of_elem(&key); - - let result = self.buckets[bucket..] - .iter() - .take_while(|elem| elem.is_some()) - .find(|elem| matches!(elem, Some((elem_key, _)) if elem_key == key)); - - if let Some(Some((_, value))) = result { - Some(value) - } else { - None - } - } - - fn insert(&mut self, key: K, value: V) -> Option - where - K: Eq + Hash, - S: BuildHasher, - { - if self.filled >= self.buckets.len() { - self.grow(); - } - loop { - let bucket = self.bucket_of_elem(&key); - let bucket = self.buckets[bucket..].iter_mut().find(|bucket| { - bucket.is_none() || matches!(bucket, Some((elem_key, _)) if *elem_key == key) - }); - if let Some(bucket) = bucket { - if bucket.is_none() { - self.filled += 1; - } - let before = std::mem::replace(bucket, Some((key, value))); - return before.map(|(_, v)| v); - } else { - self.grow(); - } - } - } -} - -pub struct IntoIter { - buckets: std::iter::FilterMap>, fn(Entry) -> Option<(K, V)>>, -} - -impl IntoIter { - fn new(buckets: Vec>) -> Self { - IntoIter { - buckets: buckets.into_iter().filter_map(std::convert::identity), - } - } -} - -impl Iterator for IntoIter { - type Item = (K, V); - - fn next(&mut self) -> Option { - self.buckets.next() - } -} - -impl IntoIterator for SimpleOAHashMap { - type Item = (K, V); - - type IntoIter = IntoIter; - - fn into_iter(self) -> Self::IntoIter { - IntoIter::new(self.buckets) - } -} - -pub struct SimpleOAHashMapFamily; -impl HashMapFamily for SimpleOAHashMapFamily { - type Map = SimpleOAHashMap; -} - -#[cfg(test)] -mod tests { - #[test] - fn do_tests() { - crate::hashmaps::tests::run_tests::(); - } -} diff --git a/src/innocent_linked_list.rs b/src/innocent_linked_list.rs deleted file mode 100644 index 9134dc5..0000000 --- a/src/innocent_linked_list.rs +++ /dev/null @@ -1,37 +0,0 @@ -#![allow(warnings)] - -pub struct Node<'a, 'n, T> { - item: T, - outer: Option<&'a mut Node<'a, 'n, T>>, -} - -impl<'a, 'n, T> Node<'a, 'n, T> { - pub fn new(item: T) -> Self { - Self { item, outer: None } - } - - pub fn push(&mut self, item: T, with_func: impl FnOnce(&mut Self) -> R) -> R { - let mut inner = Node { - item, - outer: Some(todo!()), - }; - with_func(&mut inner) - } -} - -#[cfg(test)] -mod tests { - use super::Node; - - #[test] - #[ignore = "todo"] - fn push() { - let mut list = Node::::new(0); - - inner(&mut list); - - fn inner(list: &mut Node) { - list.push(1, |list| {}); - } - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index c976be9..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![feature(ptr_metadata)] -#![feature(strict_provenance)] - -pub mod cfg_match; -pub mod hashmaps; -pub mod innocent_linked_list; -pub mod scratch; -#[cfg(FALSE)] -pub mod sendsync; -pub mod thin_u128; -pub mod unroll_int; -pub mod unsized_clone; -pub mod assert; - -pub mod safe_extern { - pub use pm::safe_extern; -} diff --git a/src/scratch.rs b/src/scratch.rs deleted file mode 100644 index 078c57a..0000000 --- a/src/scratch.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::mem::{self, MaybeUninit}; - -pub use pm::scratch_space; - -pub struct Scratch<'a>(&'a mut [MaybeUninit]); - -impl<'a> Scratch<'a> { - pub fn new(buf: &'a mut [MaybeUninit]) -> Self { - Self(buf) - } - - pub fn write(&mut self, _value: T) { - let size = mem::size_of::(); - assert!(size <= self.0.len()); - } - - pub fn read(&mut self) -> T { - T::default() - } -} - -#[macro_export] -macro_rules! scratch_write { - ($scratch:ident, $value:expr) => { - /* transformed to a call to actual_scratch_write */ - compile_error!("Failed to transform macro invocation"); - }; -} - -#[macro_export] -macro_rules! scratch_read { - ($scratch:ident) => { - /* transformed to a call to actual_scratch_write */ - compile_error!("Failed to transform macro invocation"); - }; -} - -#[macro_export] -macro_rules! actual_scratch_write { - ($scratch:ident, $value:expr ; $track_local:ident) => { - $track_local = (); - $scratch.write($value); - }; -} - -#[macro_export] -macro_rules! actual_scratch_read { - ($scratch:ident ; $track_local:ident) => {{ - let _read = $track_local; - $scratch.read() - }}; -} - -#[macro_export] -macro_rules! define_scratch { - ($name:ident, $size:expr) => { - let mut __buffer: [::core::mem::MaybeUninit; $size] = - unsafe { ::core::mem::MaybeUninit::uninit().assume_init() }; - #[allow(unused_mut)] - let mut $name = $crate::scratch::Scratch::new(&mut __buffer); - }; -} - -pub use {actual_scratch_read, actual_scratch_write, define_scratch, scratch_read, scratch_write}; - -#[cfg(test)] -mod tests { - use pm::scratch_space; - - use super::Scratch; - - #[scratch_space] - fn has_scratch_space(mut scratch: Scratch<'_>) { - scratch_write!(scratch, 10u32); - let _: u32 = scratch_read!(scratch); - } - - #[test] - fn simple_scratch() { - define_scratch!(scratch, 100); - has_scratch_space(scratch); - } -} diff --git a/src/sendsync.rs b/src/sendsync.rs deleted file mode 100644 index 98e2a61..0000000 --- a/src/sendsync.rs +++ /dev/null @@ -1,148 +0,0 @@ -#![cfg_attr(not(test), allow(unused))] -#![allow(dropping_copy_types)] - -use std::{ - cell::{Cell, UnsafeCell}, - rc::Rc, - sync::Arc, -}; - -// Today we want to design the safe `std::thread::spawn` function and the traits around that. - -// First we have the following signature. - -pub fn spawn(f: F) { - // SAFETY: Well, that's what we're here for today. - unsafe { magic_unchecked_spawn_for_our_convenience(f) } -} - -#[test] -fn send_over_integer() { - // This is perfectly safe. No data is shared. Our function allows this, which is very nice. - - let x = 0; - spawn(move || drop(dbg!(x))); -} - -#[test] -fn rc_the_new_contender() { - // Now, let's send over a more complex type like an Rc. - let x = Rc::new(0); - let x2 = x.clone(); - spawn(move || { - let _ = x2.clone(); - }); - let _ = x.clone(); // DATA RACE -} - -// Oh no, we have a data race. This is not exactly good, in fact it's really bad. -// So, how can we forbid Rc from being sent over? -// We need some kind of "this can be sent to other threads" trait. Let's call it "Send". -pub unsafe auto trait Send {} -// It's an auto trait because we really don't want everyone having to implement this manually. -// It's also unsafe because the safety of our spawn function relies on it. - -// Why exactly was Rc able to trigger a data race here? The key lies in interior mutability. -// Interior mutability like Cells but also raw pointers should therefore be forbidden by default. -impl !Send for *const T {} -impl !Send for *mut T {} -impl !Send for UnsafeCell {} - -// When we now add a F: Send bound to our spawn function, the Rc stops cinoukubgè - -#[test] -fn but_arc_is_fine() { - // Now, let's send over a more complex type like an Rc. - let x = Arc::new(0); - let x2 = x.clone(); - spawn(move || { - let _ = x2.clone(); - }); - let _ = x.clone(); -} - -// Arc is fine here because it uses atomics internally. But it fails to compile! Here, Arc (or us in this case) -// needs to assert that it's fine: -unsafe impl Send for Arc {} - -// So now, everything is good. - -#[test] -fn an_arc_of_sadness() { - let x = Arc::new(Cell::new(0)); - let x2 = x.clone(); - spawn(move || { - x2.set(0); - }); - x.set(1); // DATA RACE -} - -// Oh, not quite. We have an issue. Luckily it's a simple one, we just forgot to put a `T: Send` bound -// on the impl. - -// unsafe impl Send for Arc {} - -// After we fix this, it fails to compile as desired. - -#[test] -fn i_am_just_sending_over_a_cell() { - // We just send the Cell over and only ever use it from the other thread. - // This is perfectly sound. We want to allow this. - - let x = Cell::new(0); - spawn(move || { - let x = x; - x.set(1) - }); -} - -// The example above fails to compile. But there is no unsoundness here, we want to allow this. -// But as we've seen above, we cannot make `Cell: Send`. - -// Really, we have two concepts at play here -// - Something that we can send owned top a thread. -// - Something that we can send a reference of to another thread -// Rc can support neither of those, as its possibly unsoundness (clone) can be triggered just -// with a shared reference to it, but also with an owned Rc because two owned Rcs can point to the same memory. -// Cell is different. Having a &Cell across threads can lead to the data race. But having an owned Cell cannot -// trigger the unsoundness, as it will just mutate the local value. - -// Let's add a new trait for types that support being shared behind a reference. - -pub unsafe auto trait Sync {} - -// UnsafeCell is the key here and will make sure that types like Cell are !Sync. -impl !Sync for UnsafeCell {} - -// Also forbid pointers to make sure that unsafe datastructures have to manually assert syncness. -impl !Sync for *const T {} -impl !Sync for *mut T {} - -// Now we can actually implement Send for UnsafeCell again. Sending a Cell-like type to another thread -// is not problematic, only sharing it is. -// -impl !Send for UnsafeCell {} - -// Now we just need one last piece, the interactions of Send and Sync. -// Sync means that we can share a reference across a thread, so let's represent that in an impl. -unsafe impl Send for &T {} - -// The same "reference like behavior" applies to Arc. We are only allowed to Send an Arc to another thread -// if the thing it holds is Sync. Arc> is therefore not Send, as this type is not thread-safe. -// unsafe impl Send for Arc {} - -// In general, anything that provides shared access to T needs a T: Sync bound on its Send impl. - -// Bonus: The cursed impl of magic_unchecked_spawn_for_our_convenience. -pub unsafe fn magic_unchecked_spawn_for_our_convenience(f: F) { - // Pretend that we're Send. - struct AssertSend(T); - unsafe impl std::marker::Send for AssertSend {} - - // Get over the annoying 'static requirement by just sending over an erased pointer and reading from it. - let s = Box::into_raw(Box::new(f)); - let p = AssertSend(s.cast::<()>()); - std::thread::spawn(|| { - let p = unsafe { Box::from_raw({ p }.0 as *mut F) }; - (p)(); - }); -} diff --git a/src/thin_u128.rs b/src/thin_u128.rs deleted file mode 100644 index 0c5afe2..0000000 --- a/src/thin_u128.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::{ - fmt::{Debug, Display}, - num::NonZeroUsize, - ptr::{self, NonNull}, -}; - -/// thin. -/// ```text -/// 000000 ... 000000 0000000 -/// ^-always 1 for niche -/// ^- tag, 1 for inline u62, 0 for box -/// ``` -pub struct ThinU128(NonNull); - -enum Repr { - Inline(u128), - Boxed(NonNull), -} - -const USIZE_TWO_BIT_LESS_MAX: u128 = (usize::MAX as u128) >> 2; - -const ALWAYS_ONE_NICHE: usize = 0b1; -const TAG_MASK: usize = 0b10; - -impl ThinU128 { - pub fn new(int: u128) -> Self { - if int > USIZE_TWO_BIT_LESS_MAX { - let ptr = Box::into_raw(Box::new(int)); - let repr = ptr.map_addr(|addr| addr | ALWAYS_ONE_NICHE); - unsafe { Self(NonNull::new_unchecked(repr)) } - } else { - let value = (int as usize) << 2; - let repr = value | TAG_MASK | ALWAYS_ONE_NICHE; - Self(NonNull::new(ptr::without_provenance_mut(repr)).unwrap()) - } - } - - fn is_inline(&self) -> bool { - (self.addr() & TAG_MASK) != 0 - } - - fn addr(&self) -> usize { - self.0.addr().get() - } - - fn repr(&self) -> Repr { - if self.is_inline() { - let value = self.addr() >> 2; - Repr::Inline(value as u128) - } else { - let ptr = self.0.map_addr(|addr| unsafe { - NonZeroUsize::new_unchecked(addr.get() & !ALWAYS_ONE_NICHE) - }); - Repr::Boxed(ptr) - } - } - - pub fn value(&self) -> u128 { - match self.repr() { - Repr::Inline(value) => value, - Repr::Boxed(ptr) => unsafe { ptr.as_ptr().read() }, - } - } -} - -impl Drop for ThinU128 { - fn drop(&mut self) { - if let Repr::Boxed(ptr) = self.repr() { - unsafe { - drop(Box::from_raw(ptr.as_ptr())); - } - } - } -} - -impl Debug for ThinU128 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&self.value(), f) - } -} - -impl Display for ThinU128 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.value(), f) - } -} - -impl PartialEq for ThinU128 { - fn eq(&self, other: &Self) -> bool { - self.value().eq(&other.value()) - } -} - -impl Eq for ThinU128 {} - -impl PartialOrd for ThinU128 { - fn partial_cmp(&self, other: &Self) -> Option { - self.value().partial_cmp(&other.value()) - } -} - -impl Ord for ThinU128 { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.value().cmp(&other.value()) - } -} - -impl Clone for ThinU128 { - fn clone(&self) -> Self { - Self::new(self.value()) - } -} - -unsafe impl Send for ThinU128 {} -unsafe impl Sync for ThinU128 {} - -#[cfg(test)] -mod tests { - use super::ThinU128; - - fn roundtrip(a: u128) { - let thin = ThinU128::new(a); - assert_eq!(thin.value(), a); - - let other = ThinU128::new(a); - assert_eq!(thin, other); - let dbg_a = format!("{a:?}{a}"); - let dbg_thin = format!("{thin:?}{thin}"); - assert_eq!(dbg_a, dbg_thin); - } - - #[test] - fn small() { - roundtrip(0); - roundtrip(1); - roundtrip(100); - roundtrip((usize::MAX >> 2) as u128); - } - - #[test] - fn big() { - roundtrip(((usize::MAX >> 2) as u128) + 1); - roundtrip(usize::MAX as u128); - roundtrip(u128::MAX); - } -} diff --git a/src/unroll_int.rs b/src/unroll_int.rs deleted file mode 100644 index f9f421a..0000000 --- a/src/unroll_int.rs +++ /dev/null @@ -1,37 +0,0 @@ -macro_rules! create_unroll_int { - // (_, 5) => 5 - (replace@ ($a:tt, $($b:tt)*)) => { $($b)* }; - - // 2, 1, 0 => [0, 0, 0] - (turn_into_zero_array@ $($num:literal)*) => { - [$( create_unroll_int!(replace@ ($num, 0)) ),*] - }; - - ([$first:tt $($rest:tt)*] | $($acc:tt)*) => { - create_unroll_int! { - [$($rest)*] - | - ($first) => { create_unroll_int!(turn_into_zero_array@ $($rest)*) }; - $($acc)* - } - }; - - ([] | $($acc:tt)*) => { - macro_rules! unroll_int { - $($acc)* - } - }; - - ($($num:tt)*) => { - create_unroll_int! { [$($num)*] | } - }; -} - -create_unroll_int! { - 20 19 18 17 16 15 14 13 12 11 - 10 9 8 7 6 5 4 3 1 2 0 -} - -pub fn x() { - let _ = unroll_int!(20); -} diff --git a/src/unsized_clone.rs b/src/unsized_clone.rs deleted file mode 100644 index 2a84234..0000000 --- a/src/unsized_clone.rs +++ /dev/null @@ -1,159 +0,0 @@ -// TODO: This should probably be fallible instead of panic -// TODO: Needs more safety docs around alignment - -use std::{marker::PhantomData, ptr::Pointee}; - -/// a replacement for Clone (ignoring the old methods) -pub trait NewClone { - fn clone_unsized

(&self, place: ClonePlace) -> InitClonePlace; -} - -/// a replacement for Copy -pub trait NewCopy: NewClone {} - -/// A trait which denotes a pointer to a place -pub trait Pointer { - /// Create a pointer from a raw pointer - /// # Safety - /// The pointer needs to be valid to create a `Self`. This method can't really be called - /// generically, but `ClonePlace` provides a safe interface over it. - unsafe fn from_raw(ptr: *mut T) -> Self; -} - -impl Pointer for Box { - unsafe fn from_raw(ptr: *mut T) -> Self { - Self::from_raw(ptr) - } -} - -impl Pointer for &mut T { - unsafe fn from_raw(ptr: *mut T) -> Self { - &mut *ptr - } -} - -// more impls... - -/// Denotes a place which something can be cloned into. -pub struct ClonePlace { - ptr: *mut u8, - max_size: usize, - _boo: PhantomData<(P, *const T)>, -} - -/// Denotes a place where something has been cloned into successfully. -pub struct InitClonePlace { - ptr: *mut u8, - metadata: ::Metadata, - _boo: PhantomData

, -} - -impl ClonePlace { - /// Get the raw pointer of the place to write things yourself - pub fn as_ptr(&self) -> *const u8 { - self.ptr - } - - /// Get the maximum allocation size of the place - pub fn max_size(&self) -> usize { - self.max_size - } - - /// Create a new ClonePlace from a pointer and maximum size - /// # Safety - /// `ptr` has to be valid for writes of size `max_size` - unsafe fn from_raw(ptr: *mut u8, max_size: usize) -> Self { - Self { - ptr, - max_size, - _boo: PhantomData, - } - } - - /// Unsafely assert that the place has been initialized for as many bytes as covered - /// by the metadata. This is done by using `as_ptr` and writing to it before - /// # Safety - /// `self.ptr` must be valid for reads of at least as much bytes as denoted by the `metadata` - unsafe fn assert_init_with_meta( - self, - metadata: ::Metadata, - ) -> InitClonePlace { - InitClonePlace { - ptr: self.ptr, - metadata, - _boo: PhantomData, - } - } -} - -impl ClonePlace { - /// Safe convenience function for implementing Clone via Copy - pub fn copy_trivially(self, data: &T) -> InitClonePlace { - let size = std::mem::size_of_val(data); - assert!(self.max_size() >= size); - // SAFETY: `data` is valid for reads of `sizeof(data)` - // `self.ptr` must be writable for at least as many bytes as `self.max_size`, which we just asserted - // We have initialized `self.ptr` by `sizeof(data)` bytes, meaning it's fine to assert it as init - unsafe { - std::ptr::copy_nonoverlapping(data as *const T as *const u8, self.ptr, size); - ClonePlace::assert_init_with_meta(self, std::ptr::metadata(data)) - } - } -} - -impl, T: ?Sized> InitClonePlace { - /// Turn the initialized place into the safe pointer type - pub fn into_init_value(self) -> P { - // SAFETY: Our pointer must point to valid initialized data - // The way it has been created initially asserts that it's valid for the pointer type or something like that i guess - unsafe { P::from_raw(std::ptr::from_raw_parts_mut(self.ptr.cast(), self.metadata)) } - } -} - -// convenience function -impl ClonePlace, T> { - /// Creates a new boxed ClonePlace and allocates as many bytes as required for `value` - pub fn boxed(value: &T) -> Self { - // SAFETY: We checked the pointer for null meaning it's valid for `laoyut.size()` bytes - // That's the safety requirement for creating a box basically so we're fine - unsafe { - let layout = std::alloc::Layout::for_value(value); - let allocated = std::alloc::alloc(layout); - if allocated.is_null() { - std::alloc::handle_alloc_error(layout); - } - Self::from_raw(allocated, layout.size()) - } - } -} - -impl NewClone for str { - fn clone_unsized

(&self, place: ClonePlace) -> InitClonePlace { - place.copy_trivially(self) - } -} - -impl NewCopy for str {} - -#[test] -fn boxit() { - let str = "aaaa"; - let place = ClonePlace::boxed(str); - let init_place = str.clone_unsized(place); - let the_box = init_place.into_init_value(); - assert_eq!(&*the_box, "aaaa"); -} - -#[test] -fn on_the_stack() { - let mut storage = [std::mem::MaybeUninit::::uninit(); 10]; - let str = "aaaa"; - - // SAFETY: `storage` is valid for writes of 10 bytes. - let place: ClonePlace<&mut str, _> = - unsafe { ClonePlace::from_raw(storage.as_mut_ptr().cast::(), 10) }; - - let init_place = str.clone_unsized(place); - let the_box = init_place.into_init_value(); - assert_eq!(&*the_box, "aaaa"); -} diff --git a/tests/safe_extern.rs b/tests/safe_extern.rs deleted file mode 100644 index 2de4fd7..0000000 --- a/tests/safe_extern.rs +++ /dev/null @@ -1,18 +0,0 @@ -use uwu::safe_extern::safe_extern; - -#[safe_extern] -extern "Rust" { - fn add(a: u8, b: u8) -> u8; -} - -mod _impl { - #[no_mangle] - pub(super) fn add(a: u8, b: u8) -> u8 { - a + b - } -} - -#[test] -fn adding() { - assert_eq!(add(1, 2), 3); -}