mirror of
https://github.com/Noratrieb/the-good-stuff.git
synced 2026-01-14 16:45:01 +01:00
async
This commit is contained in:
parent
9bab547bcf
commit
8c59c7b3ae
28 changed files with 233 additions and 1306 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1 +1 @@
|
||||||
/target
|
target
|
||||||
|
|
|
||||||
54
Cargo.lock
generated
54
Cargo.lock
generated
|
|
@ -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",
|
|
||||||
]
|
|
||||||
12
Cargo.toml
12
Cargo.toml
|
|
@ -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" }
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# the-good-stuff
|
|
||||||
|
|
||||||
random rust experiments
|
|
||||||
7
async-experiments/Cargo.lock
generated
Normal file
7
async-experiments/Cargo.lock
generated
Normal file
|
|
@ -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"
|
||||||
6
async-experiments/Cargo.toml
Normal file
6
async-experiments/Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "async-experiments"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
38
async-experiments/src/executor.rs
Normal file
38
async-experiments/src/executor.rs
Normal file
|
|
@ -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<F: Future>(&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>(F);
|
||||||
|
impl<F: Fn()> Wake for WakeFn<F> {
|
||||||
|
fn wake(self: std::sync::Arc<Self>) {
|
||||||
|
(self.0)()
|
||||||
|
}
|
||||||
|
}
|
||||||
75
async-experiments/src/join.rs
Normal file
75
async-experiments/src/join.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn join2<F1, F2>(fut1: F1, fut2: F2) -> Join2<F1, F2>
|
||||||
|
where
|
||||||
|
F1: Future,
|
||||||
|
F2: Future,
|
||||||
|
{
|
||||||
|
Join2(JoinState::Pending(fut1), JoinState::Pending(fut2))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Join2<F1: Future, F2: Future>(JoinState<F1>, JoinState<F2>);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum JoinState<F: Future> {
|
||||||
|
Pending(F),
|
||||||
|
Ready(F::Output),
|
||||||
|
Stolen,
|
||||||
|
}
|
||||||
|
impl<F: Future> JoinState<F> {
|
||||||
|
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<F1: Future, F2: Future> Future for Join2<F1, F2> {
|
||||||
|
type Output = (F1::Output, F2::Output);
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = unsafe { self.get_unchecked_mut() };
|
||||||
|
|
||||||
|
fn make_progress<F: Future>(field: &mut JoinState<F>, 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<F1: Future + Debug, F2: Future + Debug> Debug for Join2<F1, F2>
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
7
async-experiments/src/lib.rs
Normal file
7
async-experiments/src/lib.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
mod executor;
|
||||||
|
mod spawn_blocking;
|
||||||
|
mod join;
|
||||||
|
|
||||||
|
pub use executor::*;
|
||||||
|
pub use spawn_blocking::*;
|
||||||
|
pub use join::*;
|
||||||
76
async-experiments/src/spawn_blocking.rs
Normal file
76
async-experiments/src/spawn_blocking.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
future::Future,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
task::{Poll, Waker},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct JoinHandle<T> {
|
||||||
|
inner: Arc<Inner<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Inner<T> {
|
||||||
|
result: Mutex<Option<T>>,
|
||||||
|
waker: Mutex<Option<Waker>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_blocking<F, R>(f: F) -> JoinHandle<R>
|
||||||
|
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<T> Future for JoinHandle<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn poll(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Self::Output> {
|
||||||
|
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<T: Debug> Debug for Inner<T> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
async-experiments/tests/execute.rs
Normal file
23
async-experiments/tests/execute.rs
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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"] }
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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<proc_macro2::Span> {
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
pub fn assert<T>(v: T) -> Assert<T> {
|
|
||||||
Assert { v }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Assert<T> {
|
|
||||||
v: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Assert<bool> {
|
|
||||||
#[track_caller]
|
|
||||||
pub fn is_true(self) {
|
|
||||||
assert!(self.v);
|
|
||||||
}
|
|
||||||
#[track_caller]
|
|
||||||
pub fn is_false(self) {
|
|
||||||
assert!(!self.v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Debug> Assert<T> {
|
|
||||||
#[track_caller]
|
|
||||||
pub fn equals<U: Debug>(self, other: U)
|
|
||||||
where
|
|
||||||
T: PartialEq<U>,
|
|
||||||
{
|
|
||||||
assert_eq!(self.v, other);
|
|
||||||
}
|
|
||||||
#[track_caller]
|
|
||||||
pub fn not_equals<U: Debug>(self, other: U)
|
|
||||||
where
|
|
||||||
T: PartialEq<U>,
|
|
||||||
{
|
|
||||||
assert_ne!(self.v, other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<str>> Assert<T> {
|
|
||||||
#[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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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!();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
use std::hash::{BuildHasher, Hash};
|
|
||||||
|
|
||||||
pub mod simple_open_addressing;
|
|
||||||
|
|
||||||
pub trait HashMapFamily {
|
|
||||||
type Map<K, V, S>: HashMap<K, V, S>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HashMap<K, V, S>: IntoIterator<Item = (K, V)> {
|
|
||||||
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<V>
|
|
||||||
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<M>()
|
|
||||||
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::<M, _>(count, RandomState::new());
|
|
||||||
}
|
|
||||||
test_many::<M, _>(1000, BuildHasherDefault::<CollidingHasher>::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_many<M: HashMapFamily, H: BuildHasher>(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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
use super::{HashMap, HashMapFamily};
|
|
||||||
use std::{
|
|
||||||
hash::{BuildHasher, Hash, RandomState},
|
|
||||||
vec,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Entry<K, V> = Option<(K, V)>;
|
|
||||||
|
|
||||||
pub struct SimpleOAHashMap<K, V, S = RandomState> {
|
|
||||||
buckets: Vec<Entry<K, V>>,
|
|
||||||
filled: usize,
|
|
||||||
s: S,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Eq + Hash, V> SimpleOAHashMap<K, V, RandomState> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::with_hasher(RandomState::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Eq + Hash, V, S: BuildHasher> SimpleOAHashMap<K, V, S> {
|
|
||||||
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<K: Eq + Hash, V, S: BuildHasher> Extend<(K, V)> for SimpleOAHashMap<K, V, S> {
|
|
||||||
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
|
|
||||||
iter.into_iter()
|
|
||||||
.for_each(|(key, value)| drop(self.insert(key, value)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V, S> super::HashMap<K, V, S> for SimpleOAHashMap<K, V, S> {
|
|
||||||
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<V>
|
|
||||||
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<K, V> {
|
|
||||||
buckets: std::iter::FilterMap<vec::IntoIter<Entry<K, V>>, fn(Entry<K, V>) -> Option<(K, V)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V> IntoIter<K, V> {
|
|
||||||
fn new(buckets: Vec<Entry<K, V>>) -> Self {
|
|
||||||
IntoIter {
|
|
||||||
buckets: buckets.into_iter().filter_map(std::convert::identity),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V> Iterator for IntoIter<K, V> {
|
|
||||||
type Item = (K, V);
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
self.buckets.next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, V, S> IntoIterator for SimpleOAHashMap<K, V, S> {
|
|
||||||
type Item = (K, V);
|
|
||||||
|
|
||||||
type IntoIter = IntoIter<K, V>;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
IntoIter::new(self.buckets)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SimpleOAHashMapFamily;
|
|
||||||
impl HashMapFamily for SimpleOAHashMapFamily {
|
|
||||||
type Map<K, V, S> = SimpleOAHashMap<K, V, S>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn do_tests() {
|
|
||||||
crate::hashmaps::tests::run_tests::<super::SimpleOAHashMapFamily>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<R>(&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::<u8>::new(0);
|
|
||||||
|
|
||||||
inner(&mut list);
|
|
||||||
|
|
||||||
fn inner(list: &mut Node<u8>) {
|
|
||||||
list.push(1, |list| {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
src/lib.rs
17
src/lib.rs
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
use std::mem::{self, MaybeUninit};
|
|
||||||
|
|
||||||
pub use pm::scratch_space;
|
|
||||||
|
|
||||||
pub struct Scratch<'a>(&'a mut [MaybeUninit<u8>]);
|
|
||||||
|
|
||||||
impl<'a> Scratch<'a> {
|
|
||||||
pub fn new(buf: &'a mut [MaybeUninit<u8>]) -> Self {
|
|
||||||
Self(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write<T>(&mut self, _value: T) {
|
|
||||||
let size = mem::size_of::<T>();
|
|
||||||
assert!(size <= self.0.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read<T: Default>(&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<u8>; $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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
148
src/sendsync.rs
148
src/sendsync.rs
|
|
@ -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. <maybe start without 'static?>
|
|
||||||
|
|
||||||
pub fn spawn<F: FnOnce() + 'static>(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<T> !Send for *const T {}
|
|
||||||
impl<T> !Send for *mut T {}
|
|
||||||
impl<T> !Send for UnsafeCell<T> {}
|
|
||||||
|
|
||||||
// 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<T> Send for Arc<T> {}
|
|
||||||
|
|
||||||
// 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<T: Send> Send for Arc<T> {}
|
|
||||||
|
|
||||||
// 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<T> !Sync for UnsafeCell<T> {}
|
|
||||||
|
|
||||||
// Also forbid pointers to make sure that unsafe datastructures have to manually assert syncness.
|
|
||||||
impl<T> !Sync for *const T {}
|
|
||||||
impl<T> !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<T> !Send for UnsafeCell<T> {}
|
|
||||||
|
|
||||||
// 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<T: Sync> 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<Cell<u8>> is therefore not Send, as this type is not thread-safe.
|
|
||||||
// unsafe impl<T: Sync> Send for Arc<T> {}
|
|
||||||
|
|
||||||
// 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: FnOnce()>(f: F) {
|
|
||||||
// Pretend that we're Send.
|
|
||||||
struct AssertSend<T>(T);
|
|
||||||
unsafe impl<T> std::marker::Send for AssertSend<T> {}
|
|
||||||
|
|
||||||
// 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)();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
146
src/thin_u128.rs
146
src/thin_u128.rs
|
|
@ -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<u128>);
|
|
||||||
|
|
||||||
enum Repr {
|
|
||||||
Inline(u128),
|
|
||||||
Boxed(NonNull<u128>),
|
|
||||||
}
|
|
||||||
|
|
||||||
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<std::cmp::Ordering> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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<P>(&self, place: ClonePlace<P, Self>) -> InitClonePlace<P, Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a replacement for Copy
|
|
||||||
pub trait NewCopy: NewClone {}
|
|
||||||
|
|
||||||
/// A trait which denotes a pointer to a place
|
|
||||||
pub trait Pointer<T: ?Sized> {
|
|
||||||
/// 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<T: ?Sized> Pointer<T> for Box<T> {
|
|
||||||
unsafe fn from_raw(ptr: *mut T) -> Self {
|
|
||||||
Self::from_raw(ptr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ?Sized> Pointer<T> 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<P, T: ?Sized> {
|
|
||||||
ptr: *mut u8,
|
|
||||||
max_size: usize,
|
|
||||||
_boo: PhantomData<(P, *const T)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Denotes a place where something has been cloned into successfully.
|
|
||||||
pub struct InitClonePlace<P, T: ?Sized> {
|
|
||||||
ptr: *mut u8,
|
|
||||||
metadata: <T as Pointee>::Metadata,
|
|
||||||
_boo: PhantomData<P>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P, T: ?Sized> ClonePlace<P, T> {
|
|
||||||
/// 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: <T as Pointee>::Metadata,
|
|
||||||
) -> InitClonePlace<P, T> {
|
|
||||||
InitClonePlace {
|
|
||||||
ptr: self.ptr,
|
|
||||||
metadata,
|
|
||||||
_boo: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P, T: ?Sized + NewCopy> ClonePlace<P, T> {
|
|
||||||
/// Safe convenience function for implementing Clone via Copy
|
|
||||||
pub fn copy_trivially(self, data: &T) -> InitClonePlace<P, T> {
|
|
||||||
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<P: Pointer<T>, T: ?Sized> InitClonePlace<P, T> {
|
|
||||||
/// 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<T: ?Sized> ClonePlace<Box<T>, 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<P>(&self, place: ClonePlace<P, Self>) -> InitClonePlace<P, Self> {
|
|
||||||
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::<u8>::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::<u8>(), 10) };
|
|
||||||
|
|
||||||
let init_place = str.clone_unsized(place);
|
|
||||||
let the_box = init_place.into_init_value();
|
|
||||||
assert_eq!(&*the_box, "aaaa");
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue