From 9bab547bcf9845a232b8394a7df7b05a7209ba22 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:59:08 +0200 Subject: [PATCH] move --- old-stuff/Cargo.lock | 54 ++++++ old-stuff/Cargo.toml | 12 ++ old-stuff/README.md | 3 + old-stuff/examples/scratch.rs | 14 ++ old-stuff/pm/Cargo.toml | 14 ++ old-stuff/pm/src/lib.rs | 46 +++++ old-stuff/pm/src/safe_extern.rs | 98 +++++++++++ old-stuff/pm/src/scratch.rs | 49 ++++++ old-stuff/src/assert.rs | 67 ++++++++ old-stuff/src/cfg_match.rs | 58 +++++++ old-stuff/src/hashmaps/mod.rs | 94 +++++++++++ .../src/hashmaps/simple_open_addressing.rs | 151 +++++++++++++++++ old-stuff/src/innocent_linked_list.rs | 37 ++++ old-stuff/src/lib.rs | 17 ++ old-stuff/src/scratch.rs | 83 +++++++++ old-stuff/src/sendsync.rs | 148 ++++++++++++++++ old-stuff/src/thin_u128.rs | 146 ++++++++++++++++ old-stuff/src/unroll_int.rs | 37 ++++ old-stuff/src/unsized_clone.rs | 159 ++++++++++++++++++ old-stuff/tests/safe_extern.rs | 18 ++ 20 files changed, 1305 insertions(+) create mode 100644 old-stuff/Cargo.lock create mode 100644 old-stuff/Cargo.toml create mode 100644 old-stuff/README.md create mode 100644 old-stuff/examples/scratch.rs create mode 100644 old-stuff/pm/Cargo.toml create mode 100644 old-stuff/pm/src/lib.rs create mode 100644 old-stuff/pm/src/safe_extern.rs create mode 100644 old-stuff/pm/src/scratch.rs create mode 100644 old-stuff/src/assert.rs create mode 100644 old-stuff/src/cfg_match.rs create mode 100644 old-stuff/src/hashmaps/mod.rs create mode 100644 old-stuff/src/hashmaps/simple_open_addressing.rs create mode 100644 old-stuff/src/innocent_linked_list.rs create mode 100644 old-stuff/src/lib.rs create mode 100644 old-stuff/src/scratch.rs create mode 100644 old-stuff/src/sendsync.rs create mode 100644 old-stuff/src/thin_u128.rs create mode 100644 old-stuff/src/unroll_int.rs create mode 100644 old-stuff/src/unsized_clone.rs create mode 100644 old-stuff/tests/safe_extern.rs diff --git a/old-stuff/Cargo.lock b/old-stuff/Cargo.lock new file mode 100644 index 0000000..f59727d --- /dev/null +++ b/old-stuff/Cargo.lock @@ -0,0 +1,54 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "old-stuff" +version = "0.1.0" +dependencies = [ + "pm", +] + +[[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" diff --git a/old-stuff/Cargo.toml b/old-stuff/Cargo.toml new file mode 100644 index 0000000..e5cf25d --- /dev/null +++ b/old-stuff/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] +members = [".", "./pm"] + +[package] +name = "old-stuff" +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/old-stuff/README.md b/old-stuff/README.md new file mode 100644 index 0000000..316f32c --- /dev/null +++ b/old-stuff/README.md @@ -0,0 +1,3 @@ +# the-good-stuff + +random rust experiments diff --git a/old-stuff/examples/scratch.rs b/old-stuff/examples/scratch.rs new file mode 100644 index 0000000..93938d1 --- /dev/null +++ b/old-stuff/examples/scratch.rs @@ -0,0 +1,14 @@ +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/old-stuff/pm/Cargo.toml b/old-stuff/pm/Cargo.toml new file mode 100644 index 0000000..e33fc91 --- /dev/null +++ b/old-stuff/pm/Cargo.toml @@ -0,0 +1,14 @@ +[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/old-stuff/pm/src/lib.rs b/old-stuff/pm/src/lib.rs new file mode 100644 index 0000000..b5a6949 --- /dev/null +++ b/old-stuff/pm/src/lib.rs @@ -0,0 +1,46 @@ +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/old-stuff/pm/src/safe_extern.rs b/old-stuff/pm/src/safe_extern.rs new file mode 100644 index 0000000..2d50727 --- /dev/null +++ b/old-stuff/pm/src/safe_extern.rs @@ -0,0 +1,98 @@ +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/old-stuff/pm/src/scratch.rs b/old-stuff/pm/src/scratch.rs new file mode 100644 index 0000000..f93a81b --- /dev/null +++ b/old-stuff/pm/src/scratch.rs @@ -0,0 +1,49 @@ +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/old-stuff/src/assert.rs b/old-stuff/src/assert.rs new file mode 100644 index 0000000..12ef751 --- /dev/null +++ b/old-stuff/src/assert.rs @@ -0,0 +1,67 @@ +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/old-stuff/src/cfg_match.rs b/old-stuff/src/cfg_match.rs new file mode 100644 index 0000000..5703544 --- /dev/null +++ b/old-stuff/src/cfg_match.rs @@ -0,0 +1,58 @@ +#[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/old-stuff/src/hashmaps/mod.rs b/old-stuff/src/hashmaps/mod.rs new file mode 100644 index 0000000..66379bb --- /dev/null +++ b/old-stuff/src/hashmaps/mod.rs @@ -0,0 +1,94 @@ +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/old-stuff/src/hashmaps/simple_open_addressing.rs b/old-stuff/src/hashmaps/simple_open_addressing.rs new file mode 100644 index 0000000..a23a8d7 --- /dev/null +++ b/old-stuff/src/hashmaps/simple_open_addressing.rs @@ -0,0 +1,151 @@ +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/old-stuff/src/innocent_linked_list.rs b/old-stuff/src/innocent_linked_list.rs new file mode 100644 index 0000000..9134dc5 --- /dev/null +++ b/old-stuff/src/innocent_linked_list.rs @@ -0,0 +1,37 @@ +#![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/old-stuff/src/lib.rs b/old-stuff/src/lib.rs new file mode 100644 index 0000000..c976be9 --- /dev/null +++ b/old-stuff/src/lib.rs @@ -0,0 +1,17 @@ +#![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/old-stuff/src/scratch.rs b/old-stuff/src/scratch.rs new file mode 100644 index 0000000..078c57a --- /dev/null +++ b/old-stuff/src/scratch.rs @@ -0,0 +1,83 @@ +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/old-stuff/src/sendsync.rs b/old-stuff/src/sendsync.rs new file mode 100644 index 0000000..98e2a61 --- /dev/null +++ b/old-stuff/src/sendsync.rs @@ -0,0 +1,148 @@ +#![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/old-stuff/src/thin_u128.rs b/old-stuff/src/thin_u128.rs new file mode 100644 index 0000000..0c5afe2 --- /dev/null +++ b/old-stuff/src/thin_u128.rs @@ -0,0 +1,146 @@ +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/old-stuff/src/unroll_int.rs b/old-stuff/src/unroll_int.rs new file mode 100644 index 0000000..f9f421a --- /dev/null +++ b/old-stuff/src/unroll_int.rs @@ -0,0 +1,37 @@ +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/old-stuff/src/unsized_clone.rs b/old-stuff/src/unsized_clone.rs new file mode 100644 index 0000000..26e77f3 --- /dev/null +++ b/old-stuff/src/unsized_clone.rs @@ -0,0 +1,159 @@ +// 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, 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/old-stuff/tests/safe_extern.rs b/old-stuff/tests/safe_extern.rs new file mode 100644 index 0000000..2de4fd7 --- /dev/null +++ b/old-stuff/tests/safe_extern.rs @@ -0,0 +1,18 @@ +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); +}