diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d725dec --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on Keep a Changelog and this project adheres to Semantic Versioning. + +For all future releases, please manage versions as follows: + +1. Briefly summarize notable changes in this file (`CHANGELOG.md`). If the release includes *breaking changes*, provide clear migration instructions. +2. Publish the new version on crates.io and create a new release on [GitHub Releases](https://github.com/kaist-cp/circ/releases). + +--- + +## Unreleased + + +## Version 0.2.0 - 2024-10-03 + +### Features + +* Added implementation of standard traits for pointers. ([#3](https://github.com/kaist-cp/circ/pull/3)) + +### Bug Fixes + +* `PartialEq` for `Rc` and `Snapshot` now compares the objects they point to, rather than the pointers themselves. ([#3](https://github.com/kaist-cp/circ/pull/3)) (See compatibility note below.) + +### Compatibility Notes + +* `Rc::eq` and `Snapshot::eq` now compare the objects they point to instead of their pointer values. This change aligns with the behavior of `PartialEq` for other smart pointer types (e.g., `std::rc::Rc`), where equality is based on the objects being pointed to. The original pointer-based comparison is still available via the `ptr_eq` method. + * **Migration**: To compare pointer values, use the `ptr_eq` method. + +## Version 0.1.0 - 2024-06-12 + +* Initial release. diff --git a/Cargo.toml b/Cargo.toml index 528c2d1..d89f359 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "circ" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Efficient referenced counted pointers for non-blocking concurrency" diff --git a/src/ebr_impl/pointers.rs b/src/ebr_impl/pointers.rs index 8ffc879..5c5a22e 100644 --- a/src/ebr_impl/pointers.rs +++ b/src/ebr_impl/pointers.rs @@ -3,6 +3,7 @@ use core::marker::PhantomData; use core::mem::align_of; use core::ptr::null_mut; use core::sync::atomic::AtomicUsize; +use std::fmt::{Debug, Formatter, Pointer}; use atomic::{Atomic, Ordering}; @@ -12,6 +13,18 @@ pub struct Tagged { ptr: *mut T, } +impl Debug for Tagged { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Pointer::fmt(&self.as_raw(), f) + } +} + +impl Pointer for Tagged { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Pointer::fmt(&self.as_raw(), f) + } +} + impl Default for Tagged { fn default() -> Self { Self { ptr: null_mut() } @@ -26,14 +39,6 @@ impl Clone for Tagged { impl Copy for Tagged {} -impl PartialEq for Tagged { - fn eq(&self, other: &Self) -> bool { - self.with_high_tag(0).ptr == other.with_high_tag(0).ptr - } -} - -impl Eq for Tagged {} - impl Hash for Tagged { fn hash(&self, state: &mut H) { self.ptr.hash(state) @@ -125,6 +130,16 @@ impl Tagged { Some(self.deref()) } } + + /// Returns `true` if the two pointer values, including the tag values set by `with_tag`, + /// are identical. + pub fn ptr_eq(self, other: Self) -> bool { + // Instead of using a direct equality comparison (`==`), we use `ptr_eq`, which ignores + // the epoch tag in the high bits. This is because the epoch tags hold no significance + // for clients; they are only used internally by the CIRC engine to track the last + // accessed epoch for the pointer. + self.with_high_tag(0).ptr == other.with_high_tag(0).ptr + } } /// Returns a bitmask containing the unused least significant bits of an aligned pointer to `T`. @@ -196,6 +211,7 @@ impl RawAtomic { } } +// A shared pointer type only for the internal EBR implementation. pub(crate) struct RawShared<'g, T> { inner: Tagged, _marker: PhantomData<&'g T>, @@ -236,12 +252,6 @@ impl<'g, T> From> for RawShared<'g, T> { } } -impl<'g, T> PartialEq for RawShared<'g, T> { - fn eq(&self, other: &Self) -> bool { - self.inner == other.inner - } -} - impl<'g, T> RawShared<'g, T> { pub fn null() -> Self { Self { @@ -288,4 +298,14 @@ impl<'g, T> RawShared<'g, T> { pub fn is_null(self) -> bool { self.inner.is_null() } + + /// Returns `true` if the two pointer values, including the tag values set by `with_tag`, + /// are identical. + pub fn ptr_eq(&self, other: Self) -> bool { + // Instead of using a direct equality comparison (`==`), we use `ptr_eq`, which ignores + // the epoch tag in the high bits. This is because the epoch tags hold no significance + // for clients; they are only used internally by the CIRC engine to track the last + // accessed epoch for the pointer. + self.inner.ptr_eq(other.inner) + } } diff --git a/src/ebr_impl/sync/queue.rs b/src/ebr_impl/sync/queue.rs index 10011e5..5765794 100644 --- a/src/ebr_impl/sync/queue.rs +++ b/src/ebr_impl/sync/queue.rs @@ -122,7 +122,7 @@ impl Queue { .map(|_| { let tail = self.tail.load(Relaxed, guard); // Advance the tail so that we don't retire a pointer to a reachable node. - if head == tail { + if head.ptr_eq(tail) { let _ = self .tail .compare_exchange(tail, next, Release, Relaxed, guard); @@ -154,7 +154,7 @@ impl Queue { .map(|_| { let tail = self.tail.load(Relaxed, guard); // Advance the tail so that we don't retire a pointer to a reachable node. - if head == tail { + if head.ptr_eq(tail) { let _ = self .tail .compare_exchange(tail, next, Release, Relaxed, guard); diff --git a/src/strong.rs b/src/strong.rs index 50245be..906e695 100644 --- a/src/strong.rs +++ b/src/strong.rs @@ -1,5 +1,7 @@ use std::{ array, + fmt::{Debug, Formatter, Pointer}, + hash::{Hash, Hasher}, marker::PhantomData, mem::{forget, size_of}, sync::atomic::{AtomicUsize, Ordering}, @@ -196,24 +198,24 @@ impl AtomicRc { failure: Ordering, guard: &'g Guard, ) -> Result, CompareExchangeError, Snapshot<'g, T>>> { - let mut expected_ptr = expected.ptr; - let desired_ptr = desired.ptr.with_timestamp(); + let mut expected_raw = expected.ptr; + let desired_raw = desired.ptr.with_timestamp(); loop { match self .link - .compare_exchange(expected_ptr, desired_ptr, success, failure) + .compare_exchange(expected_raw, desired_raw, success, failure) { Ok(_) => { // Skip decrementing a strong count of the inserted pointer. forget(desired); - let rc = Rc::from_raw(expected_ptr); + let rc = Rc::from_raw(expected_raw); return Ok(rc); } - Err(current) => { - if current.with_high_tag(0) == expected_ptr.with_high_tag(0) { - expected_ptr = current; + Err(current_raw) => { + if current_raw.ptr_eq(expected_raw) { + expected_raw = current_raw; } else { - let current = Snapshot::from_raw(current, guard); + let current = Snapshot::from_raw(current_raw, guard); return Err(CompareExchangeError { desired, current }); } } @@ -248,24 +250,24 @@ impl AtomicRc { failure: Ordering, guard: &'g Guard, ) -> Result, CompareExchangeError, Snapshot<'g, T>>> { - let mut expected_ptr = expected.ptr; - let desired_ptr = desired.ptr.with_timestamp(); + let mut expected_raw = expected.ptr; + let desired_raw = desired.ptr.with_timestamp(); loop { match self .link - .compare_exchange_weak(expected_ptr, desired_ptr, success, failure) + .compare_exchange_weak(expected_raw, desired_raw, success, failure) { Ok(_) => { // Skip decrementing a strong count of the inserted pointer. forget(desired); - let rc = Rc::from_raw(expected_ptr); + let rc = Rc::from_raw(expected_raw); return Ok(rc); } - Err(current) => { - if current.with_high_tag(0) == expected_ptr.with_high_tag(0) { - expected_ptr = current; + Err(current_raw) => { + if current_raw.ptr_eq(expected_raw) { + expected_raw = current_raw; } else { - let current = Snapshot::from_raw(current, guard); + let current = Snapshot::from_raw(current_raw, guard); return Err(CompareExchangeError { desired, current }); } } @@ -312,14 +314,14 @@ impl AtomicRc { .link .compare_exchange(expected_raw, desired_raw, success, failure) { - Ok(current) => return Ok(Snapshot::from_raw(current, guard)), - Err(current) => { - if current.with_high_tag(0) == expected_raw.with_high_tag(0) { - expected_raw = current; + Ok(current_raw) => return Ok(Snapshot::from_raw(current_raw, guard)), + Err(current_raw) => { + if current_raw.ptr_eq(expected_raw) { + expected_raw = current_raw; } else { return Err(CompareExchangeError { desired: Snapshot::from_raw(desired_raw, guard), - current: Snapshot::from_raw(current, guard), + current: Snapshot::from_raw(current_raw, guard), }); } } @@ -381,6 +383,25 @@ impl From> for AtomicRc { } } +impl From<&Rc> for AtomicRc { + #[inline] + fn from(value: &Rc) -> Self { + Self::from(value.clone()) + } +} + +impl Debug for AtomicRc { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.link.load(Ordering::Relaxed), f) + } +} + +impl Pointer for AtomicRc { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Pointer::fmt(&self.link.load(Ordering::Relaxed), f) + } +} + /// A reference-counted pointer to an object of type `T`. /// /// When `T` implements [`Send`] and [`Sync`], [`Rc`] also implements these traits. @@ -588,6 +609,39 @@ impl Rc { Some(unsafe { self.deref_mut() }) } } + + /// Returns `true` if the two pointer values, including the tag values set by `with_tag`, + /// are identical. + #[inline] + pub fn ptr_eq(&self, other: &Self) -> bool { + // Instead of using a direct equality comparison (`==`), we use `ptr_eq`, which ignores + // the epoch tag in the high bits. This is because the epoch tags hold no significance + // for clients; they are only used internally by the CIRC engine to track the last + // accessed epoch for the pointer. + self.ptr.ptr_eq(other.ptr) + } +} + +impl<'g, T: RcObject> From> for Rc { + fn from(value: Snapshot<'g, T>) -> Self { + value.counted() + } +} + +impl Debug for Rc { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if let Some(cnt) = self.as_ref() { + f.debug_tuple("RcObject").field(cnt).finish() + } else { + f.write_str("Null") + } + } +} + +impl Pointer for Rc { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Pointer::fmt(&self.ptr, f) + } } impl Default for Rc { @@ -608,10 +662,30 @@ impl Drop for Rc { } } -impl PartialEq for Rc { +impl PartialEq for Rc { #[inline(always)] fn eq(&self, other: &Self) -> bool { - self.ptr == other.ptr + self.as_ref() == other.as_ref() + } +} + +impl Eq for Rc {} + +impl Hash for Rc { + fn hash(&self, state: &mut H) { + self.as_ref().hash(state); + } +} + +impl PartialOrd for Rc { + fn partial_cmp(&self, other: &Self) -> Option { + self.as_ref().partial_cmp(&other.as_ref()) + } +} + +impl Ord for Rc { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_ref().cmp(&other.as_ref()) } } @@ -775,6 +849,17 @@ impl<'g, T: RcObject> Snapshot<'g, T> { Some(unsafe { self.deref_mut() }) } } + + /// Returns `true` if the two pointer values, including the tag values set by `with_tag`, + /// are identical. + #[inline] + pub fn ptr_eq(self, other: Self) -> bool { + // Instead of using a direct equality comparison (`==`), we use `ptr_eq`, which ignores + // the epoch tag in the high bits. This is because the epoch tags hold no significance + // for clients; they are only used internally by the CIRC engine to track the last + // accessed epoch for the pointer. + self.ptr.ptr_eq(other.ptr) + } } impl<'g, T> Snapshot<'g, T> { @@ -803,9 +888,45 @@ impl<'g, T: RcObject> Default for Snapshot<'g, T> { } } -impl<'g, T> PartialEq for Snapshot<'g, T> { +impl<'g, T: RcObject + PartialEq> PartialEq for Snapshot<'g, T> { #[inline(always)] fn eq(&self, other: &Self) -> bool { - self.ptr.eq(&other.ptr) + self.as_ref() == other.as_ref() + } +} + +impl<'g, T: RcObject + Eq> Eq for Snapshot<'g, T> {} + +impl<'g, T: RcObject + Hash> Hash for Snapshot<'g, T> { + fn hash(&self, state: &mut H) { + self.as_ref().hash(state); + } +} + +impl<'g, T: RcObject + PartialOrd> PartialOrd for Snapshot<'g, T> { + fn partial_cmp(&self, other: &Self) -> Option { + self.as_ref().partial_cmp(&other.as_ref()) + } +} + +impl<'g, T: RcObject + Ord> Ord for Snapshot<'g, T> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_ref().cmp(&other.as_ref()) + } +} + +impl<'g, T: RcObject + Debug> Debug for Snapshot<'g, T> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if let Some(cnt) = self.as_ref() { + f.debug_tuple("RcObject").field(cnt).finish() + } else { + f.write_str("Null") + } + } +} + +impl<'g, T: RcObject> Pointer for Snapshot<'g, T> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Pointer::fmt(&self.ptr, f) } } diff --git a/src/weak.rs b/src/weak.rs index 837e9d0..cbf6fdd 100644 --- a/src/weak.rs +++ b/src/weak.rs @@ -1,4 +1,5 @@ use std::{ + fmt::{Debug, Formatter, Pointer}, marker::PhantomData, mem::{forget, size_of}, sync::atomic::{AtomicUsize, Ordering}, @@ -232,6 +233,32 @@ impl From> for AtomicWeak { } } +impl From<&Weak> for AtomicWeak { + #[inline] + fn from(value: &Weak) -> Self { + Self::from(value.clone()) + } +} + +impl From<&Rc> for AtomicWeak { + #[inline] + fn from(value: &Rc) -> Self { + Self::from(value.downgrade()) + } +} + +impl Debug for AtomicWeak { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.link.load(Ordering::Relaxed), f) + } +} + +impl Pointer for AtomicWeak { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Pointer::fmt(&self.link.load(Ordering::Relaxed), f) + } +} + impl Drop for AtomicWeak { #[inline(always)] fn drop(&mut self) { @@ -336,6 +363,17 @@ impl Weak { ptr.increment_weak(1); } } + + /// Returns `true` if the two pointer values, including the tag values set by `with_tag`, + /// are identical. + #[inline] + pub fn ptr_eq(&self, other: &Self) -> bool { + // Instead of using a direct equality comparison (`==`), we use `ptr_eq`, which ignores + // the epoch tag in the high bits. This is because the epoch tags hold no significance + // for clients; they are only used internally by the CIRC engine to track the last + // accessed epoch for the pointer. + self.ptr.ptr_eq(other.ptr) + } } impl Weak { @@ -364,10 +402,27 @@ impl Drop for Weak { } } -impl PartialEq for Weak { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - self.ptr == other.ptr +impl<'g, T> From> for Weak { + fn from(value: WeakSnapshot<'g, T>) -> Self { + value.counted() + } +} + +impl<'g, T: RcObject> From> for Weak { + fn from(value: Snapshot<'g, T>) -> Self { + value.downgrade().counted() + } +} + +impl Debug for Weak { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.ptr, f) + } +} + +impl Pointer for Weak { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Pointer::fmt(&self.ptr, f) } } @@ -451,6 +506,17 @@ impl<'g, T> WeakSnapshot<'g, T> { _marker: PhantomData, } } + + /// Returns `true` if the two pointer values, including the tag values set by `with_tag`, + /// are identical. + #[inline] + pub fn ptr_eq(self, other: Self) -> bool { + // Instead of using a direct equality comparison (`==`), we use `ptr_eq`, which ignores + // the epoch tag in the high bits. This is because the epoch tags hold no significance + // for clients; they are only used internally by the CIRC engine to track the last + // accessed epoch for the pointer. + self.ptr.ptr_eq(other.ptr) + } } impl<'g, T> Default for WeakSnapshot<'g, T> { @@ -460,9 +526,20 @@ impl<'g, T> Default for WeakSnapshot<'g, T> { } } -impl<'g, T> PartialEq for WeakSnapshot<'g, T> { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - self.ptr.eq(&other.ptr) +impl<'g, T: RcObject> From> for WeakSnapshot<'g, T> { + fn from(value: Snapshot<'g, T>) -> Self { + value.downgrade() + } +} + +impl<'g, T> Debug for WeakSnapshot<'g, T> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.ptr, f) + } +} + +impl<'g, T> Pointer for WeakSnapshot<'g, T> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Pointer::fmt(&self.ptr, f) } } diff --git a/tests/harris_list.rs b/tests/harris_list.rs index 3318efd..73e0ba3 100644 --- a/tests/harris_list.rs +++ b/tests/harris_list.rs @@ -105,7 +105,7 @@ impl<'g, K: Ord, V> Cursor<'g, K, V> { }; // If prev and curr WERE adjacent, no need to clean up - if prev_next == self.curr { + if prev_next.ptr_eq(self.curr) { return Ok(found); }