diff --git a/development/libs/atomptr/.envrc b/development/libs/atomptr/.envrc new file mode 100644 index 00000000000..051d09d292a --- /dev/null +++ b/development/libs/atomptr/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" diff --git a/development/libs/atomptr/.gitignore b/development/libs/atomptr/.gitignore new file mode 100644 index 00000000000..143b1ca0147 --- /dev/null +++ b/development/libs/atomptr/.gitignore @@ -0,0 +1,4 @@ + +/target/ +**/*.rs.bk +Cargo.lock diff --git a/development/libs/atomptr/Cargo.toml b/development/libs/atomptr/Cargo.toml new file mode 100644 index 00000000000..a1561ab3c8a --- /dev/null +++ b/development/libs/atomptr/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "atomptr" +description = "A safe, dependency-less abstraction for typed atomic smart pointers" +version = "1.0.0" +authors = ["Mx Kookie "] +edition = "2018" +license = "GPL-3.0-or-later" +repository = "https://git.spacekookie.de/kookienomicon" +keywords = ["memory", "concurrency", "performance", "lock-free", "atomic"] +categories = ["concurrency", "memory-management"] +readme = "README.md" \ No newline at end of file diff --git a/development/libs/atomptr/LICENSE b/development/libs/atomptr/LICENSE new file mode 120000 index 00000000000..f837ab4da5f --- /dev/null +++ b/development/libs/atomptr/LICENSE @@ -0,0 +1 @@ +../../../licenses/GPL-3.0 \ No newline at end of file diff --git a/development/libs/atomptr/README.md b/development/libs/atomptr/README.md new file mode 100644 index 00000000000..8bbd11ff450 --- /dev/null +++ b/development/libs/atomptr/README.md @@ -0,0 +1,51 @@ +# AtomPtr + +A safe, strongly typed (generic) atomic pointer abstraction to build +datastructures, and lock-free algorithms on top of. Only uses +`libstd`. + +The standard library contains an `AtomicPtr` type, which by itself +isn't very ergonomic to use, because it deals with raw pointers. This +library assumes that types can always be heap allocated, wrapping them +in a `Box`, and provides a nicer (and safe!) abstraction for +`std::sync::atomic::AtomicPtr`. Using this crate is fairely +self-explanatory: + +```rust +use atomptr::AtomPtr; + +struct MyData { name: String } +let data = MyData { name: "Kookie".into() }; + +let a = AtomPtr::new(data); +println!("Name is: {}", a.get_ref().name); + +let old_ref = a.swap(MyData { name: "Bob".into() }); +println!("Name now is: {}, was {}", a.get_ref().name, old_ref.name); +``` + +Note that the type that is returned by `get_ref` and `swap` is +`Ref`, which means that the old data is not de-allocated after a +swap, before this last reference goes out of scope. You can of course +always manually call `drop()` on it. + + +## License + +This micro-library is free software, and licensed under the GNU +General Public License, version 3.0 or (at your choice), any later +version. + +**Additional Permissions:** For Submission to the Apple App Store: +Provided that you are otherwise in compliance with the GPLv3 for each +covered work you convey (including without limitation making the +Corresponding Source available in compliance with Section 6 of the +GPLv3), the qaul.net developers also grant you the additional +permission to convey through the Apple App Store non-source executable +versions of the Program as incorporated into each applicable covered +work as Executable Versions only under the Mozilla Public License +version 2.0. + +A copy of both the GPL-3.0 and MPL-2.0 license texts are included in +this repository. + diff --git a/development/libs/atomptr/shell.nix b/development/libs/atomptr/shell.nix new file mode 100644 index 00000000000..3a13912055f --- /dev/null +++ b/development/libs/atomptr/shell.nix @@ -0,0 +1,8 @@ +with import {}; + +stdenv.mkDerivation { + name = "atomicptr"; + buildInputs = with pkgs; [ + rustracer rustup clangStdenv + ]; +} diff --git a/development/libs/atomptr/src/lib.rs b/development/libs/atomptr/src/lib.rs new file mode 100644 index 00000000000..92410fc4bc3 --- /dev/null +++ b/development/libs/atomptr/src/lib.rs @@ -0,0 +1,120 @@ +#![feature(external_doc)] +#![doc(include = "../README.md")] + +use std::sync::{ + atomic::{AtomicPtr, Ordering}, + Arc, +}; +use std::{cmp::PartialEq, ops::Deref}; + +/// An alias for a referenced pointer +pub struct Ref { + inner: Box>, +} + +impl Deref for Ref { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// A safe atomic pointer wrapper +#[derive(Clone, Debug)] +pub struct AtomPtr { + inner: Arc>>, +} + +// Implement Default for all T that implement default +impl Default for AtomPtr { + fn default() -> Self { + Self::new(T::default()) + } +} + +impl PartialEq for AtomPtr { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.get_ref().inner, &other.get_ref().inner) + } +} + +impl AtomPtr { + fn make_raw_ptr(t: T) -> *mut Arc { + Box::into_raw(Box::new(Arc::new(t))) + } + + /// Create a new atomic pointer for a type + pub fn new(t: T) -> Self { + let ptr = Self::make_raw_ptr(t); + let inner = Arc::new(AtomicPtr::from(ptr)); + Self { inner } + } + + /// Get an immutable reference to the current value + pub fn get_ref(&self) -> Ref { + let ptr = self.inner.load(Ordering::Relaxed); + let b = unsafe { Box::from_raw(ptr) }; + + let arc = Arc::clone(&*b); + std::mem::forget(b); + + Ref { + inner: Box::new(arc), + } + } + + /// Swap the data entry with a new value, returning the old + pub fn swap(&self, new: T) -> Ref { + let new = Self::make_raw_ptr(new); + let prev = self.inner.swap(new, Ordering::Relaxed); + + let b = unsafe { Box::from_raw(prev) }; + let arc = Arc::clone(&*b); + std::mem::forget(b); + + Ref { + inner: Box::new(arc), + } + } +} + +#[cfg(test)] +#[derive(Clone, Debug, PartialEq)] +struct TestStruct { + name: String, +} + +#[test] +fn cloned() { + let ts = TestStruct { + name: "Hello".into(), + }; + + let ptr1 = AtomPtr::new(ts); + let ptr2 = ptr1.clone(); + + assert_eq!(ptr1, ptr2); +} + +#[test] +fn swap() { + let ts1 = TestStruct { + name: "Hello 1".into(), + }; + + let ts2 = TestStruct { + name: "Hello 2".into(), + }; + + // Make an AtomPtr with some data + let ptr = AtomPtr::new(ts1.clone()); + assert_eq!(ptr.get_ref().name, "Hello 1".to_string()); + + // Swap the data + let still_ts1 = ptr.swap(ts2); + assert_eq!(ptr.get_ref().name, "Hello 2".to_string()); + + // But the old ref is still valid + assert_eq!(ts1, *still_ts1.as_ref()); +}