linkme/distributed_slice.rs
1use crate::private::Slice;
2use core::fmt::{self, Debug};
3#[cfg(any(target_os = "uefi", target_os = "windows"))]
4use core::hint;
5use core::mem;
6use core::num::NonZeroUsize;
7use core::ops::Deref;
8use core::slice;
9
10/// Collection of static elements that are gathered into a contiguous section of
11/// the binary by the linker.
12///
13/// The implementation is based on `link_section` attributes and
14/// platform-specific linker support. It does not involve life-before-main or
15/// any other runtime initialization on any platform. This is a zero-cost safe
16/// abstraction that operates entirely during compilation and linking.
17///
18/// ## Declaration
19///
20/// A static distributed slice may be declared by writing `#[distributed_slice]`
21/// on a static item whose type is `[T]` for some type `T`.
22///
23/// ```
24/// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))]
25/// #
26/// # struct Bencher;
27/// #
28/// use linkme::distributed_slice;
29///
30/// #[distributed_slice]
31/// pub static BENCHMARKS: [fn(&mut Bencher)];
32/// ```
33///
34/// The attribute rewrites the `[T]` type of the static into
35/// `DistributedSlice<[T]>`, so the static in the example technically has type
36/// `DistributedSlice<[fn(&mut Bencher)]>`.
37///
38/// ## Elements
39///
40/// Slice elements may be registered into a distributed slice by a
41/// `#[distributed_slice(...)]` attribute in which the path to the distributed
42/// slice is given in the parentheses. The initializer is required to be a const
43/// expression.
44///
45/// Elements may be defined in the same crate that declares the distributed
46/// slice, or in any downstream crate. Elements across all crates linked into
47/// the final binary will be observed to be present in the slice at runtime.
48///
49/// ```
50/// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))]
51/// #
52/// # mod other_crate {
53/// # use linkme::distributed_slice;
54/// #
55/// # pub struct Bencher;
56/// #
57/// # #[distributed_slice]
58/// # pub static BENCHMARKS: [fn(&mut Bencher)];
59/// # }
60/// #
61/// # use other_crate::Bencher;
62/// #
63/// use linkme::distributed_slice;
64/// use other_crate::BENCHMARKS;
65///
66/// #[distributed_slice(BENCHMARKS)]
67/// static BENCH_DESERIALIZE: fn(&mut Bencher) = bench_deserialize;
68///
69/// fn bench_deserialize(b: &mut Bencher) {
70/// /* ... */
71/// }
72/// ```
73///
74/// The compiler will require that the static element type matches with the
75/// element type of the distributed slice. If the two do not match, the program
76/// will not compile.
77///
78/// ```compile_fail
79/// # mod other_crate {
80/// # use linkme::distributed_slice;
81/// #
82/// # pub struct Bencher;
83/// #
84/// # #[distributed_slice]
85/// # pub static BENCHMARKS: [fn(&mut Bencher)];
86/// # }
87/// #
88/// # use linkme::distributed_slice;
89/// # use other_crate::BENCHMARKS;
90/// #
91/// #[distributed_slice(BENCHMARKS)]
92/// static BENCH_WTF: usize = 999;
93/// ```
94///
95/// ```text
96/// error[E0308]: mismatched types
97/// --> src/distributed_slice.rs:65:19
98/// |
99/// 17 | static BENCH_WTF: usize = 999;
100/// | ^^^^^ expected fn pointer, found `usize`
101/// |
102/// = note: expected fn pointer `fn(&mut other_crate::Bencher)`
103/// found type `usize`
104/// ```
105///
106/// ## Function elements
107///
108/// As a shorthand for the common case of distributed slices containing function
109/// pointers, the distributed\_slice attribute may be applied directly to a
110/// function definition to place a pointer to that function into a distributed
111/// slice.
112///
113/// ```
114/// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))]
115/// #
116/// # pub struct Bencher;
117/// #
118/// use linkme::distributed_slice;
119///
120/// #[distributed_slice]
121/// pub static BENCHMARKS: [fn(&mut Bencher)];
122///
123/// // Equivalent to:
124/// //
125/// // #[distributed_slice(BENCHMARKS)]
126/// // static _: fn(&mut Bencher) = bench_deserialize;
127/// //
128/// #[distributed_slice(BENCHMARKS)]
129/// fn bench_deserialize(b: &mut Bencher) {
130/// /* ... */
131/// }
132/// ```
133pub struct DistributedSlice<T: ?Sized + Slice> {
134 name: &'static str,
135 stride: NonZeroUsize,
136 section_start: StaticPtr<T::Element>,
137 section_stop: StaticPtr<T::Element>,
138 dupcheck_start: StaticPtr<isize>,
139 dupcheck_stop: StaticPtr<isize>,
140}
141
142struct StaticPtr<T> {
143 ptr: *const T,
144}
145
146unsafe impl<T> Send for StaticPtr<T> {}
147
148unsafe impl<T> Sync for StaticPtr<T> {}
149
150impl<T> Copy for StaticPtr<T> {}
151
152impl<T> Clone for StaticPtr<T> {
153 fn clone(&self) -> Self {
154 *self
155 }
156}
157
158impl<T> DistributedSlice<[T]> {
159 #[doc(hidden)]
160 #[track_caller]
161 pub const unsafe fn private_new(
162 name: &'static str,
163 section_start: *const [T; 0],
164 section_stop: *const [T; 0],
165 dupcheck_start: *const (),
166 dupcheck_stop: *const (),
167 ) -> Self {
168 let Some(stride) = NonZeroUsize::new(mem::size_of::<T>()) else {
169 panic!("#[distributed_slice] requires that the slice element type has nonzero size");
170 };
171
172 DistributedSlice {
173 name,
174 stride,
175 section_start: StaticPtr {
176 ptr: section_start.cast::<T>(),
177 },
178 section_stop: StaticPtr {
179 ptr: section_stop.cast::<T>(),
180 },
181 dupcheck_start: StaticPtr {
182 ptr: dupcheck_start.cast::<isize>(),
183 },
184 dupcheck_stop: StaticPtr {
185 ptr: dupcheck_stop.cast::<isize>(),
186 },
187 }
188 }
189
190 #[doc(hidden)]
191 #[inline]
192 pub unsafe fn private_typecheck(self, get: fn() -> &'static T) {
193 let _ = get;
194 }
195
196 /// Retrieve a contiguous slice containing all the elements linked into this
197 /// program.
198 ///
199 /// **Note**: Ordinarily this method should not need to be called because
200 /// `DistributedSlice<[T]>` already behaves like `&'static [T]` in most ways
201 /// through the power of `Deref`. In particular, iteration and indexing and
202 /// method calls can all happen directly on the static without calling
203 /// `static_slice()`.
204 ///
205 /// ```no_run
206 /// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))]
207 /// #
208 /// # struct Bencher;
209 /// #
210 /// use linkme::distributed_slice;
211 ///
212 /// #[distributed_slice]
213 /// static BENCHMARKS: [fn(&mut Bencher)];
214 ///
215 /// fn main() {
216 /// // Iterate the elements.
217 /// for bench in BENCHMARKS {
218 /// /* ... */
219 /// }
220 ///
221 /// // Index into the elements.
222 /// let first = BENCHMARKS[0];
223 ///
224 /// // Slice the elements.
225 /// let except_first = &BENCHMARKS[1..];
226 ///
227 /// // Invoke methods on the underlying slice.
228 /// let len = BENCHMARKS.len();
229 /// }
230 /// ```
231 pub fn static_slice(self) -> &'static [T] {
232 if self.dupcheck_start.ptr.wrapping_add(1) < self.dupcheck_stop.ptr {
233 panic!("duplicate #[distributed_slice] with name \"{}\"", self.name);
234 }
235
236 let start = self.section_start.ptr;
237 let stop = self.section_stop.ptr;
238 let byte_offset = stop as usize - start as usize;
239 let len = byte_offset / self.stride;
240
241 // On Windows, the implementation involves growing a &[T; 0] to
242 // encompass elements that we have asked the linker to place immediately
243 // after that location. The compiler sees this as going "out of bounds"
244 // based on provenance, so we must conceal what is going on.
245 #[cfg(any(target_os = "uefi", target_os = "windows"))]
246 let start = hint::black_box(start);
247
248 unsafe { slice::from_raw_parts(start, len) }
249 }
250}
251
252impl<T> Copy for DistributedSlice<[T]> {}
253
254impl<T> Clone for DistributedSlice<[T]> {
255 fn clone(&self) -> Self {
256 *self
257 }
258}
259
260impl<T: 'static> Deref for DistributedSlice<[T]> {
261 type Target = [T];
262 fn deref(&self) -> &'static Self::Target {
263 self.static_slice()
264 }
265}
266
267impl<T: 'static> IntoIterator for DistributedSlice<[T]> {
268 type Item = &'static T;
269 type IntoIter = slice::Iter<'static, T>;
270 fn into_iter(self) -> Self::IntoIter {
271 self.static_slice().iter()
272 }
273}
274
275impl<T> Debug for DistributedSlice<[T]>
276where
277 T: Debug + 'static,
278{
279 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
280 Debug::fmt(self.static_slice(), formatter)
281 }
282}