-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make Rc<T>::deref
and Arc<T>::deref
zero-cost
#132553
base: master
Are you sure you want to change the base?
Conversation
b283c44
to
ae36f44
Compare
This comment has been minimized.
This comment has been minimized.
Would it potentially enable those types to have an ffi compatible ABI? So that they could be returned and passed directly from /to ffi function, like |
This comment has been minimized.
This comment has been minimized.
I think in theory it is possible, at least for sized types, but I am not familiar with how to formally make it so. |
ae36f44
to
0d6165f
Compare
This comment has been minimized.
This comment has been minimized.
0d6165f
to
98edd5b
Compare
This comment has been minimized.
This comment has been minimized.
r? libs |
98edd5b
to
8beb51d
Compare
This comment has been minimized.
This comment has been minimized.
8beb51d
to
d7879fa
Compare
This comment has been minimized.
This comment has been minimized.
d7879fa
to
317aa0e
Compare
@EFanZh Is this ready for review? If so, please un-draft the PR. |
@joboet: The source code part is mostly done, but I haven’t finished updating LLDB and CDB pretty printers. The CI doesn’t seem to run those tests. |
No worries! I just didn't want to keep you waiting in case you had forgotten to change the state. |
f243654
to
1308bf6
Compare
ce3dab7
to
3641396
Compare
This comment has been minimized.
This comment has been minimized.
3641396
to
9a531fc
Compare
I have rewritten this PR as a proof of concept for the idea I mentioned in #132553 (comment). Specifically, I added an /// Shared reference counts type for both `Rc` and `Arc`.
/// The `UnsafeCell<usize>` type is used as `Cell<usize>` for `Rc`
/// and `AtomicUsize` for `Arc`.
pub struct RefCounts {
pub weak: UnsafeCell<usize>,
pub strong: UnsafeCell<usize>,
}
/// Base implementation for `rc::Weak` and `sync::Weak`.
pub struct RawWeak<T, A>
where
T: ?Sized,
{
/// Dangling or points to the value field of an rc allocation.
ptr: NonNull<T>,
alloc: A,
}
/// Base implementation for `rc::Rc` and `sync::Arc`.
#[repr(transparent)]
pub struct RawRc<T, A>
where
T: ?Sized,
{
/// A `RawRc` is just a `RawWeak` with a strong reference owned by itself.
weak: RawWeak<T, A>,
_phantom_data: PhantomData<T>,
}
/// Base implementation for `rc::UniqueRc` and possible future `sync::UniqueArc`.
#[repr(transparent)]
pub struct RawUniqueRc<T, A>
where
T: ?Sized,
{
/// A `RawUniqueRc` is just a `RawWeak` with an initialized value but has a
/// strong reference count of zero.
weak: RawWeak<T, A>,
} With these types above, the standard library types are implemented as: mod rc {
#[repr(transparent)]
pub struct Weak<T: ?Sized, A: Allocator = Global> {
raw_weak: RawWeak<T, A>,
}
#[repr(transparent)]
pub struct Rc<T: ?Sized, A: Allocator = Global> {
raw_rc: RawRc<T, A>,
}
#[repr(transparent)]
pub struct UniqueRc<T: ?Sized, A: Allocator = Global> {
raw_unique_rc: RawUniqueRc<T, A>,
}
}
mod sync {
#[repr(transparent)]
pub struct Weak<T: ?Sized, A: Allocator = Global> {
raw_weak: RawWeak<T, A>,
}
#[repr(transparent)]
pub struct Arc<T: ?Sized, A: Allocator = Global> {
raw_rc: RawRc<T, A>,
}
} The different behaviors of pub trait RcOps {
/// Used by `RawRc::clone` and `RawWeak::clone`.
unsafe fn increment_ref_count(count: &UnsafeCell<usize>);
/// Used by `RawRc::drop` and `RawWeak::drop`.
unsafe fn decrement_ref_count(count: &UnsafeCell<usize>) -> bool;
/// Used by `RawWeak::upgrade`.
unsafe fn upgrade(strong_count: &UnsafeCell<usize>) -> bool;
/// Used by `RawRc::downgrade`.
unsafe fn downgrade(weak_count: &UnsafeCell<usize>);
/// Used by `RawRc::try_unwrap`.
unsafe fn lock_strong_count(strong_count: &UnsafeCell<usize>) -> bool;
/// Used by `RawUniqueRc::into_rc`.
unsafe fn unlock_strong_count(strong_count: &UnsafeCell<usize>);
/// Used by `RawRc::get_mut`.
unsafe fn is_unique(strong_count: &UnsafeCell<usize>, weak_count: &UnsafeCell<usize>) -> bool;
/// Used by `RawRc::make_mut`.
#[cfg(not(no_global_oom_handling))]
unsafe fn make_unique<T, A, F, G>(rc: &mut RawRc<T, A>, by_clone: F, by_move: G)
where
T: ?Sized,
F: FnOnce(&mut RawRc<T, A>),
G: FnOnce(&mut RawRc<T, A>);
} Both mod raw_rc {
impl<T, A> RawRc<T, A> where T: ?Sized {
pub unsafe fn clone<R>(&self) -> Self where A: Clone, R: RcOps {
unsafe {
R::increment_ref_count(self.strong_count());
Self::from_weak(self.weak.clone_without_increment_weak_count())
}
}
}
}
mod rc {
enum RcOps {}
/// Specifies behaviors of `rc::Rc`, `rc::Weak` and `rc::UniqueRc`.
impl raw_rc::RcOps for RcOps { ... }
impl<T: ?Sized, A: Allocator + Clone> Clone for Rc<T, A> {
fn clone(&self) -> Self {
Self { raw_rc: unsafe { self.raw_rc.clone::<RcOps>() } }
}
}
}
mod sync {
enum RcOps {}
/// Specifies behaviors of `sync::Arc` and `sync::Weak`.
impl raw_rc::RcOps for RcOps { ... }
impl<T: ?Sized, A: Allocator + Clone> Clone for Arc<T, A> {
fn clone(&self) -> Self {
Self { raw_rc: unsafe { self.raw_rc.clone::<RcOps>() } }
}
}
} This PR is not a completed work:
Now, I think this PR is in a good shape to decide whether it is something the standard library wants. @joboet, @scottmcm: what do you think? Is it worth completing this PR? |
If you really want to do this, then you should place allocator in allocated block, not in |
Ping @scottmcm (see my prev. comment). Also, here allocators for |
@safinaskar: Personally, I am fine with the idea, but I think it’s better to implement it in a separate PR, since it will introduce API changes (For example, As for |
This comment has been minimized.
This comment has been minimized.
6e1cefc
to
8a7689b
Compare
This comment has been minimized.
This comment has been minimized.
8a7689b
to
b939b01
Compare
This comment has been minimized.
This comment has been minimized.
b939b01
to
d4c3550
Compare
This comment has been minimized.
This comment has been minimized.
2f8c95f
to
6f78608
Compare
This comment has been minimized.
This comment has been minimized.
dbe51dd
to
e4c2742
Compare
e4c2742
to
873f27a
Compare
This comment has been minimized.
This comment has been minimized.
873f27a
to
454eccf
Compare
This comment has been minimized.
This comment has been minimized.
454eccf
to
e37e38b
Compare
Currently,
Rc<T>
andArc<T>
store pointers toRcInner<T>
andArcInner<T>
. This PR changes the pointers so that they point toT
directly instead.This is based on the assumption that we access the
T
value more frequently than accessing reference counts. With this change, accessing the data can be done without offsetting pointers fromRcInner<T>
andArcInner<T>
to their contained data. This change might also enables some possibly useful future optimizations, such as:&[Rc<T>]
into&[&T]
within O(1) time.&[Rc<T>]
intoVec<&T>
utilizingmemcpy
.&Option<Rc<T>>
intoOption<&T>
without branching.Rc<T>
andArc<T>
FFI compatible types whereT: Sized
.