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}