yoke/
cartable_ptr.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Types for optional pointers with niche optimization.
6//!
7//! The main type is [`CartableOptionPointer`], which is like `Option<Rc>` but
8//! with a niche so that the resulting `Yoke` has a niche. The following four
9//! types can be stored in the `CartableOptionPointer`:
10//!
11//! 1. `&T`
12//! 2. `Box<T>`
13//! 3. `Rc<T>`
14//! 4. `Arc<T>`
15//!
16//! These four types implement the sealed unsafe trait [`CartablePointerLike`].
17//! In addition, all except `Box<T>` impl [`CloneableCartablePointerLike`],
18//! which allows [`CartableOptionPointer`] to implement `Clone`.
19
20use crate::CloneableCart;
21#[cfg(feature = "alloc")]
22use alloc::boxed::Box;
23#[cfg(feature = "alloc")]
24use alloc::rc::Rc;
25#[cfg(feature = "alloc")]
26use alloc::sync::Arc;
27#[cfg(test)]
28use core::cell::Cell;
29use core::marker::PhantomData;
30use core::ptr::NonNull;
31use stable_deref_trait::StableDeref;
32
33#[inline]
34fn sentinel_for<T>() -> NonNull<T> {
35    static SENTINEL: &u8 = &0x1a; // SUB
36    unsafe { NonNull::new_unchecked(SENTINEL as *const u8 as *mut T) }
37}
38
39#[cfg(test)]
40thread_local! {
41    static DROP_INVOCATIONS: Cell<usize> = const { Cell::new(0) };
42}
43
44mod private {
45    pub trait Sealed {}
46}
47
48use private::Sealed;
49
50/// An object fully representable by a non-null pointer.
51///
52/// # Safety
53///
54/// Implementer safety:
55///
56/// 1. `into_raw` transfers ownership of the values referenced by StableDeref to the caller,
57///    if there is ownership to transfer
58/// 2. `drop_raw` returns ownership back to the impl, if there is ownership to transfer
59/// 3. `into_raw` must not return the sentinel pointer
60///
61/// Note: the pointer `NonNull<Self::Raw>` may or may not be aligned and it should never
62/// be dereferenced. Rust allows unaligned pointers; see [`std::ptr::read_unaligned`].
63pub unsafe trait CartablePointerLike: StableDeref + Sealed {
64    /// The raw type used for [`Self::into_raw`] and [`Self::drop_raw`].
65    #[doc(hidden)]
66    type Raw;
67
68    /// Converts this pointer-like into a pointer.
69    #[doc(hidden)]
70    fn into_raw(self) -> NonNull<Self::Raw>;
71
72    /// Drops any memory associated with this pointer-like.
73    ///
74    /// # Safety
75    ///
76    /// Caller safety:
77    ///
78    /// 1. The pointer MUST have been returned by this impl's `into_raw`.
79    /// 2. The pointer MUST NOT be dangling.
80    #[doc(hidden)]
81    unsafe fn drop_raw(pointer: NonNull<Self::Raw>);
82}
83
84/// An object that implements [`CartablePointerLike`] that also
85/// supports cloning without changing the address of referenced data.
86///
87/// # Safety
88///
89/// Implementer safety:
90///
91/// 1. `addref_raw` must create a new owner such that an additional call to
92///    `drop_raw` does not create a dangling pointer
93/// 2. `addref_raw` must not change the address of any referenced data.
94pub unsafe trait CloneableCartablePointerLike: CartablePointerLike {
95    /// Clones this pointer-like.
96    ///
97    /// # Safety
98    ///
99    /// Caller safety:
100    ///
101    /// 1. The pointer MUST have been returned by this impl's `into_raw`.
102    /// 2. The pointer MUST NOT be dangling.
103    #[doc(hidden)]
104    unsafe fn addref_raw(pointer: NonNull<Self::Raw>);
105}
106
107impl<'a, T> Sealed for &'a T {}
108
109// Safety:
110// 1. There is no ownership to transfer
111// 2. There is no ownership to transfer
112// 3. External clients do not have the sentinel address so it won't be used in this reference.
113unsafe impl<'a, T> CartablePointerLike for &'a T {
114    type Raw = T;
115
116    #[inline]
117    fn into_raw(self) -> NonNull<T> {
118        self.into()
119    }
120    #[inline]
121    unsafe fn drop_raw(_pointer: NonNull<T>) {
122        // No-op: references are borrowed from elsewhere
123    }
124}
125
126// Safety:
127// 1. There is no ownership
128// 2. The impl is a no-op so no addresses are changed.
129unsafe impl<'a, T> CloneableCartablePointerLike for &'a T {
130    #[inline]
131    unsafe fn addref_raw(_pointer: NonNull<T>) {
132        // No-op: references are borrowed from elsewhere
133    }
134}
135
136#[cfg(feature = "alloc")]
137impl<T> Sealed for Box<T> {}
138
139// Safety:
140// 1. `Box::into_raw` says: "After calling this function, the caller is responsible for the
141//    memory previously managed by the Box."
142// 2. `Box::from_raw` says: "After calling this function, the raw pointer is owned by the
143//    resulting Box."
144// 3. The pointer comes from the Box so it won't be the sentinel pointer.
145#[cfg(feature = "alloc")]
146unsafe impl<T> CartablePointerLike for Box<T> {
147    type Raw = T;
148
149    #[inline]
150    fn into_raw(self) -> NonNull<T> {
151        // Safety: `Box::into_raw` says: "The pointer will be properly aligned and non-null."
152        unsafe { NonNull::new_unchecked(Box::into_raw(self)) }
153    }
154    #[inline]
155    unsafe fn drop_raw(pointer: NonNull<T>) {
156        let _box = Box::from_raw(pointer.as_ptr());
157
158        // Boxes are always dropped
159        #[cfg(test)]
160        DROP_INVOCATIONS.with(|x| x.set(x.get() + 1))
161    }
162}
163
164#[cfg(feature = "alloc")]
165impl<T> Sealed for Rc<T> {}
166
167// Safety:
168// 1. `Rc::into_raw` says: "Consumes the Rc, returning the wrapped pointer. To avoid a memory
169//    leak the pointer must be converted back to an Rc using Rc::from_raw."
170// 2. See 1.
171// 3. The pointer comes from the Rc so it won't be the sentinel pointer.
172#[cfg(feature = "alloc")]
173unsafe impl<T> CartablePointerLike for Rc<T> {
174    type Raw = T;
175
176    #[inline]
177    fn into_raw(self) -> NonNull<T> {
178        // Safety: Rcs must contain data (and not be null)
179        unsafe { NonNull::new_unchecked(Rc::into_raw(self) as *mut T) }
180    }
181    #[inline]
182    unsafe fn drop_raw(pointer: NonNull<T>) {
183        let _rc = Rc::from_raw(pointer.as_ptr());
184
185        // Rc is dropped if refcount is 1
186        #[cfg(test)]
187        if Rc::strong_count(&_rc) == 1 {
188            DROP_INVOCATIONS.with(|x| x.set(x.get() + 1))
189        }
190    }
191}
192
193// Safety:
194// 1. The impl increases the refcount such that `Drop` will decrease it.
195// 2. The impl increases refcount without changing the address of data.
196#[cfg(feature = "alloc")]
197unsafe impl<T> CloneableCartablePointerLike for Rc<T> {
198    #[inline]
199    unsafe fn addref_raw(pointer: NonNull<T>) {
200        // Safety: The caller safety of this function says that:
201        // 1. The pointer was obtained through Rc::into_raw
202        // 2. The associated Rc instance is valid
203        // Further, this impl is not defined for anything but the global allocator.
204        Rc::increment_strong_count(pointer.as_ptr());
205    }
206}
207
208#[cfg(feature = "alloc")]
209impl<T> Sealed for Arc<T> {}
210
211// Safety:
212// 1. `Rc::into_raw` says: "Consumes the Arc, returning the wrapped pointer. To avoid a memory
213//    leak the pointer must be converted back to an Arc using Arc::from_raw."
214// 2. See 1.
215// 3. The pointer comes from the Arc so it won't be the sentinel pointer.
216#[cfg(feature = "alloc")]
217unsafe impl<T> CartablePointerLike for Arc<T> {
218    type Raw = T;
219
220    #[inline]
221    fn into_raw(self) -> NonNull<T> {
222        // Safety: Arcs must contain data (and not be null)
223        unsafe { NonNull::new_unchecked(Arc::into_raw(self) as *mut T) }
224    }
225    #[inline]
226    unsafe fn drop_raw(pointer: NonNull<T>) {
227        let _arc = Arc::from_raw(pointer.as_ptr());
228
229        // Arc is dropped if refcount is 1
230        #[cfg(test)]
231        if Arc::strong_count(&_arc) == 1 {
232            DROP_INVOCATIONS.with(|x| x.set(x.get() + 1))
233        }
234    }
235}
236
237// Safety:
238// 1. The impl increases the refcount such that `Drop` will decrease it.
239// 2. The impl increases refcount without changing the address of data.
240#[cfg(feature = "alloc")]
241unsafe impl<T> CloneableCartablePointerLike for Arc<T> {
242    #[inline]
243    unsafe fn addref_raw(pointer: NonNull<T>) {
244        // Safety: The caller safety of this function says that:
245        // 1. The pointer was obtained through Arc::into_raw
246        // 2. The associated Arc instance is valid
247        // Further, this impl is not defined for anything but the global allocator.
248        Arc::increment_strong_count(pointer.as_ptr());
249    }
250}
251
252/// A type with similar semantics as `Option<C<T>>` but with a niche.
253///
254/// This type cannot be publicly constructed. To use this in a `Yoke`, see
255/// [`Yoke::convert_cart_into_option_pointer`].
256///
257/// [`Yoke::convert_cart_into_option_pointer`]: crate::Yoke::convert_cart_into_option_pointer
258#[derive(Debug)]
259pub struct CartableOptionPointer<C>
260where
261    C: CartablePointerLike,
262{
263    /// The inner pointer.
264    ///
265    /// # Invariants
266    ///
267    /// 1. Must be either `SENTINEL_PTR` or created from `CartablePointerLike::into_raw`
268    /// 2. If non-sentinel, must _always_ be for a valid SelectedRc
269    inner: NonNull<C::Raw>,
270    _cartable: PhantomData<C>,
271}
272
273impl<C> CartableOptionPointer<C>
274where
275    C: CartablePointerLike,
276{
277    /// Creates a new instance corresponding to a `None` value.
278    #[inline]
279    pub(crate) fn none() -> Self {
280        Self {
281            inner: sentinel_for::<C::Raw>(),
282            _cartable: PhantomData,
283        }
284    }
285
286    /// Creates a new instance corresponding to a `Some` value.
287    #[inline]
288    pub(crate) fn from_cartable(cartable: C) -> Self {
289        Self {
290            inner: cartable.into_raw(),
291            _cartable: PhantomData,
292        }
293    }
294
295    /// Returns whether this instance is `None`. From the return value:
296    ///
297    /// - If `true`, the instance is `None`
298    /// - If `false`, the instance is a valid `SelectedRc`
299    #[inline]
300    pub fn is_none(&self) -> bool {
301        self.inner == sentinel_for::<C::Raw>()
302    }
303}
304
305impl<C> Drop for CartableOptionPointer<C>
306where
307    C: CartablePointerLike,
308{
309    #[inline]
310    fn drop(&mut self) {
311        let ptr = self.inner;
312        if ptr != sentinel_for::<C::Raw>() {
313            // By the invariants, `ptr` is a valid raw value since it's
314            // either that or sentinel, and we just checked for sentinel.
315            // We will replace it with the sentinel and then drop `ptr`.
316            self.inner = sentinel_for::<C::Raw>();
317            // Safety: by the invariants, `ptr` is a valid raw value.
318            unsafe { C::drop_raw(ptr) }
319        }
320    }
321}
322
323impl<C> Clone for CartableOptionPointer<C>
324where
325    C: CloneableCartablePointerLike,
326{
327    #[inline]
328    fn clone(&self) -> Self {
329        let ptr = self.inner;
330        if ptr != sentinel_for::<C::Raw>() {
331            // By the invariants, `ptr` is a valid raw value since it's
332            // either that or sentinel, and we just checked for sentinel.
333            // Safety: by the invariants, `ptr` is a valid raw value.
334            unsafe { C::addref_raw(ptr) }
335        }
336        Self {
337            inner: self.inner,
338            _cartable: PhantomData,
339        }
340    }
341}
342
343// Safety: logically an Option<C>. Has same bounds as Option<C>.
344// The `StableDeref` parts of `C` continue to be `StableDeref`.
345unsafe impl<C> CloneableCart for CartableOptionPointer<C> where
346    C: CloneableCartablePointerLike + CloneableCart
347{
348}
349
350// Safety: logically an Option<C>. Has same bounds as Option<C>
351unsafe impl<C> Send for CartableOptionPointer<C> where C: Sync + CartablePointerLike {}
352
353// Safety: logically an Option<C>. Has same bounds as Option<C>
354unsafe impl<C> Sync for CartableOptionPointer<C> where C: Send + CartablePointerLike {}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359    use crate::Yoke;
360    use core::mem::size_of;
361
362    const SAMPLE_BYTES: &[u8] = b"abCDEfg";
363    const W: usize = size_of::<usize>();
364
365    #[test]
366    fn test_sizes() {
367        assert_eq!(W * 4, size_of::<Yoke<[usize; 3], &&[u8]>>());
368        assert_eq!(W * 4, size_of::<Yoke<[usize; 3], Option<&&[u8]>>>());
369        assert_eq!(
370            W * 4,
371            size_of::<Yoke<[usize; 3], CartableOptionPointer<&&[u8]>>>()
372        );
373
374        assert_eq!(W * 4, size_of::<Option<Yoke<[usize; 3], &&[u8]>>>());
375        assert_eq!(W * 5, size_of::<Option<Yoke<[usize; 3], Option<&&[u8]>>>>());
376        assert_eq!(
377            W * 4,
378            size_of::<Option<Yoke<[usize; 3], CartableOptionPointer<&&[u8]>>>>()
379        );
380    }
381
382    #[test]
383    fn test_new_sentinel() {
384        let start = DROP_INVOCATIONS.with(Cell::get);
385        {
386            let _ = CartableOptionPointer::<Rc<&[u8]>>::none();
387        }
388        assert_eq!(start, DROP_INVOCATIONS.with(Cell::get));
389        {
390            let _ = CartableOptionPointer::<Rc<&[u8]>>::none();
391        }
392        assert_eq!(start, DROP_INVOCATIONS.with(Cell::get));
393    }
394
395    #[test]
396    fn test_new_rc() {
397        let start = DROP_INVOCATIONS.with(Cell::get);
398        {
399            let _ = CartableOptionPointer::<Rc<&[u8]>>::from_cartable(SAMPLE_BYTES.into());
400        }
401        assert_eq!(start + 1, DROP_INVOCATIONS.with(Cell::get));
402    }
403
404    #[test]
405    fn test_rc_clone() {
406        let start = DROP_INVOCATIONS.with(Cell::get);
407        {
408            let x = CartableOptionPointer::<Rc<&[u8]>>::from_cartable(SAMPLE_BYTES.into());
409            assert_eq!(start, DROP_INVOCATIONS.with(Cell::get));
410            {
411                let _ = x.clone();
412            }
413            assert_eq!(start, DROP_INVOCATIONS.with(Cell::get));
414            {
415                let _ = x.clone();
416                let _ = x.clone();
417                let _ = x.clone();
418            }
419            assert_eq!(start, DROP_INVOCATIONS.with(Cell::get));
420        }
421        assert_eq!(start + 1, DROP_INVOCATIONS.with(Cell::get));
422    }
423}