flyweights/raw.rs
1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::alloc::{alloc, dealloc, handle_alloc_error};
6
7use core::alloc::Layout;
8use core::ptr::{slice_from_raw_parts, NonNull};
9use core::sync::atomic::{AtomicUsize, Ordering};
10
11/// The maximum refcount is `isize::MAX` to match the standard library behavior for `Arc`.
12const MAX_REFCOUNT: usize = isize::MAX as usize;
13
14// The header for a memory allocation.
15#[repr(C)]
16struct Header {
17 ref_count: AtomicUsize,
18 len: usize,
19}
20
21/// A ZST used to indicate a pointer which points to the payload portion of a memory allocation.
22///
23/// A `Payload` is always located immediately after a `Header`, and is the start of the bytes of the
24/// slice. Users always point to the `Payload` because the most common operation we do is `as_slice`
25/// and if we already have the payload pointer then we can avoid a pointer offset operation.
26#[repr(transparent)]
27pub struct Payload {
28 // This guarantees `Payload` will always be a ZST with the same alignment as `Header`.`
29 _align: [Header; 0],
30}
31
32impl Payload {
33 #[inline]
34 fn layout(len: usize) -> Layout {
35 let (layout, byte_offset) = Layout::new::<Header>()
36 .extend(Layout::array::<u8>(len).unwrap())
37 .expect("attempted to allocate a FlyStr that was too large (~isize::MAX)");
38
39 debug_assert_eq!(byte_offset, size_of::<Header>());
40 debug_assert!(layout.align() > 1);
41
42 layout
43 }
44
45 /// Returns a pointer to a `Payload` containing a copy of `bytes`.
46 pub fn alloc(bytes: &[u8]) -> NonNull<Self> {
47 let layout = Self::layout(bytes.len());
48
49 // SAFETY: `layout` always has non-zero size because `Header` has non-zero size.
50 let ptr = unsafe { alloc(layout) };
51 if ptr.is_null() {
52 handle_alloc_error(layout);
53 }
54
55 let header = ptr.cast::<Header>();
56 // SAFETY: `header` points to memory suitable for a `Header` and is valid for writes.
57 unsafe {
58 header.write(Header { ref_count: AtomicUsize::new(1), len: bytes.len() });
59 }
60
61 // SAFETY: The size of `Header` fits in an `isize` and `header` points to an allocation
62 // of at least that size. Even if the payload is zero-sized, one-past-the-end pointers are
63 // valid to create.
64 let payload = unsafe { header.add(1).cast::<u8>() };
65
66 // SAFETY: `payload` points to `bytes.len()` bytes, per the constructed layout.
67 // `copy_to_nonoverlapping` is a no-op for bytes of length 0, this is always sound to do. We
68 // just have to make sure that the pointers are properly aligned, which they always are for
69 // `u8` which has an alignment of 1.
70 unsafe {
71 bytes.as_ptr().copy_to_nonoverlapping(payload, bytes.len());
72 }
73
74 // SAFETY: Allocation succeeded, so `payload` is guaranteed not to be null.
75 unsafe { NonNull::new_unchecked(payload.cast::<Payload>()) }
76 }
77
78 /// Deallocates the payload.
79 ///
80 /// # Safety
81 ///
82 /// `ptr` must be to a `Payload` returned from `Payload::alloc`.
83 #[inline]
84 pub unsafe fn dealloc(ptr: *mut Self) {
85 // SAFETY: The caller guaranteed that `ptr` is to a `Payload` returned from `Payload::alloc`.
86 let header = unsafe { Self::header(ptr) };
87 // SAFETY: `header` points to a `Header` which is valid for reads.
88 let len = unsafe { (*header).len };
89 let layout = Self::layout(len);
90
91 // SAFETY: `header` points to a memory allocation with layout `layout`.
92 unsafe {
93 dealloc(header.cast(), layout);
94 }
95 }
96
97 /// Returns the current refcount of a `Payload`.
98 ///
99 /// # Safety
100 ///
101 /// `ptr` must be to a `Payload` returned from `Payload::alloc`.
102 #[cfg(test)]
103 #[inline]
104 pub unsafe fn refcount(ptr: *mut Self) -> usize {
105 // SAFETY: The caller guaranteed that `ptr` is to a `Payload` returned from `Payload::alloc`.
106 let header = unsafe { Self::header(ptr) };
107 // SAFETY: `header` points to a `Header` which is valid for reads and writes.
108 unsafe { (*header).ref_count.load(Ordering::Relaxed) }
109 }
110
111 /// Increments the refcount of a `Payload` by one.
112 ///
113 /// # Safety
114 ///
115 /// `ptr` must be to a `Payload` returned from `Payload::alloc`.
116 #[inline]
117 pub unsafe fn inc_ref(ptr: *mut Self) -> usize {
118 // SAFETY: The caller guaranteed that `ptr` is to a `Payload` returned from `Payload::alloc`.
119 let header = unsafe { Self::header(ptr) };
120 // SAFETY: `header` points to a `Header` which is valid for reads and writes. Relaxed
121 // ordering is sufficient because headers and payloads are immutable. Any other ordering
122 // requirements are enforced externally, e.g. by thread synchronization to send data.
123 let prev_count = unsafe { (*header).ref_count.fetch_add(1, Ordering::Relaxed) };
124 if prev_count > MAX_REFCOUNT {
125 std::process::abort();
126 }
127 prev_count
128 }
129
130 /// Decrements the refcount of a `Payload` by one, returning the refcount prior to decrementing.
131 ///
132 /// Decrementing the refcount to zero does not deallocate the payload. Deallocating the payload
133 /// must be done manually.
134 ///
135 /// # Safety
136 ///
137 /// `ptr` must be to a `Payload` returned from `Payload::alloc`.
138 #[inline]
139 pub unsafe fn dec_ref(ptr: *mut Self) -> usize {
140 // SAFETY: The caller guaranteed that `ptr` is to a `Payload` returned from `Payload::alloc`.
141 let header = unsafe { Self::header(ptr) };
142 // SAFETY: `header` points to a `Header` which is valid for reads and writes. Relaxed
143 // ordering is sufficient here because the contained value is immutable and can't be
144 // modified after creation. We also don't have to drop any data, which isn't a necessary
145 // requirement but does provide an additional security.
146 unsafe { (*header).ref_count.fetch_sub(1, Ordering::Relaxed) }
147 }
148
149 /// # Safety
150 ///
151 /// `ptr` must be to a `Payload` returned from `Payload::alloc`.
152 #[inline]
153 unsafe fn header(ptr: *mut Self) -> *mut Header {
154 // SAFETY: `Payload` pointers are always preceded by a `Header`.
155 unsafe { ptr.cast::<Header>().sub(1) }
156 }
157
158 /// Returns the length of the byte slice contained in a `Payload`.
159 ///
160 /// # Safety
161 ///
162 /// `ptr` must be to a `Payload` returned from `Payload::alloc`.
163 #[inline]
164 pub unsafe fn len(ptr: *mut Self) -> usize {
165 // SAFETY: The caller guaranteed that `ptr` is to a `Payload` returned from `Payload::alloc`.
166 unsafe { (*Self::header(ptr)).len }
167 }
168
169 /// Returns a pointer to the byte slice of a `Payload`.
170 ///
171 /// # Safety
172 ///
173 /// `ptr` must be to a `Payload` returned from `Payload::alloc`.
174 #[inline]
175 pub unsafe fn bytes(ptr: *mut Self) -> *const [u8] {
176 // SAFETY: The caller guaranteed that `ptr` is to a `Payload` returned from `Payload::alloc`.
177 let len = unsafe { Self::len(ptr) };
178 slice_from_raw_parts(ptr.cast::<u8>(), len)
179 }
180}