mirror of
https://github.com/Noratrieb/the-good-stuff.git
synced 2026-01-14 16:45:01 +01:00
move
This commit is contained in:
parent
1721d6a45a
commit
9bab547bcf
20 changed files with 1305 additions and 0 deletions
67
old-stuff/src/assert.rs
Normal file
67
old-stuff/src/assert.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
58
old-stuff/src/cfg_match.rs
Normal file
58
old-stuff/src/cfg_match.rs
Normal file
|
|
@ -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!();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
old-stuff/src/hashmaps/mod.rs
Normal file
94
old-stuff/src/hashmaps/mod.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
151
old-stuff/src/hashmaps/simple_open_addressing.rs
Normal file
151
old-stuff/src/hashmaps/simple_open_addressing.rs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
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>();
|
||||
}
|
||||
}
|
||||
37
old-stuff/src/innocent_linked_list.rs
Normal file
37
old-stuff/src/innocent_linked_list.rs
Normal file
|
|
@ -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<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
old-stuff/src/lib.rs
Normal file
17
old-stuff/src/lib.rs
Normal file
|
|
@ -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;
|
||||
}
|
||||
83
old-stuff/src/scratch.rs
Normal file
83
old-stuff/src/scratch.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
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
old-stuff/src/sendsync.rs
Normal file
148
old-stuff/src/sendsync.rs
Normal file
|
|
@ -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. <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
old-stuff/src/thin_u128.rs
Normal file
146
old-stuff/src/thin_u128.rs
Normal file
|
|
@ -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<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);
|
||||
}
|
||||
}
|
||||
37
old-stuff/src/unroll_int.rs
Normal file
37
old-stuff/src/unroll_int.rs
Normal file
|
|
@ -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);
|
||||
}
|
||||
159
old-stuff/src/unsized_clone.rs
Normal file
159
old-stuff/src/unsized_clone.rs
Normal file
|
|
@ -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<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, 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");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue