linker_set/
lib.rs

1#![warn(missing_docs)]
2
3//! Declarative programming via embedded configuration data
4//!
5//! A linker set allows you to program declaratively rather than
6//! imperatively by embedding configuration or behavior into a program as
7//! data.
8//!
9//! Using a linker set, you can scatter instances of a certain type all
10//! over your program, and, with the proper annotations, the linker will
11//! gather them up into a special section of the ELF binary, forming an
12//! array, which can be iterated at runtime.
13//!
14//! # Example
15//!
16//! ```
17//! use std::collections::HashSet;
18//! use linker_set::*;
19//!
20//! set_declare!(stuff, u64);
21//!
22//! #[set_entry(stuff)]
23//! static FOO: u64 = 0x4F202A76B86A7299u64;
24//! #[set_entry(stuff)]
25//! static BAR: u64 = 0x560E9309456ACCE0u64;
26//!
27//! # fn main() {
28//! let actual = set!(stuff).iter().collect::<HashSet<_>>();
29//! let expect = HashSet::from([&FOO, &BAR]);
30//! assert_eq!(actual, expect);
31//! # }
32//! ```
33//!
34//! The [set_declare!] macro outputs a module definition.  The module must
35//! be imported into the scope of calls to the [set_entry] attribute and the
36//! [set!] macro.
37//!
38//! If you make a linker set of an integer type, you should use typed
39//! literals, not generic integer literals.  I.e.
40//!
41//! ```
42//! use linker_set::*;
43//!
44//! set_declare!(foo, u64);
45//!
46//! #[set_entry(foo)]
47//! static FOO: u64 = 1000u64; // not 1000 ❌
48//! ```
49//!
50//! Generic integer literals will defeat a typechecking mechanism that is
51//! output by the [set_entry] macro.
52//!
53//! All items in a set should be of the same size, the size of the declared
54//! type.  Otherwise, stuff won't work.  The macros make an attempt to
55//! typecheck set entries, but they aren't foolproof.  Caveat scriptor.
56//!
57//! The index operator is kind of just for fun.  Obviously you shouldn't
58//! depend on the linker to provide any specific ordering.
59//!
60//! # History
61//!
62//! This idea comes from [Clustrix], the best distributed relational
63//! database in the world, which no one knew about.  Clustrix was written
64//! in a very unusual but very interesting style of C.  Much of it was
65//! written in [continuation-passing style]([CPS]), and continuations and
66//! lightweight threads (fibers) ran on top of a scheduler very similar to
67//! the asynchronous runtimes like Tokio which later became popular.  (But
68//! Clustrix was started in 2006, before that popularity.)
69//!
70//! Linker sets were used extensively in the Clustrix code to do things
71//! such as specify initialization or other system processes via graphs
72//! (initgraphs), automatically create heaps for memory allocation,
73//! automatically allocate integers or flags for what would otherwise have
74//! to be centrally controlled constants, and automatically register
75//! structures or handlers with a subsystem.
76//!
77//! This concept was present in the oldest version of the Clustrix code in
78//! Git.  A prior Subversion repository seemed to have been lost.  The
79//! inspiration appears to have come from [FreeBSD], which has several
80//! macros whose names match exactly macros used in the Clustrix source
81//! code.
82//!
83//! [Clustrix]: https://en.wikipedia.org/wiki/Clustrix
84//! [CPS]: https://en.wikipedia.org/wiki/Continuation-passing_style
85//! [FreeBSD]: https://github.com/freebsd/freebsd-src/blob/main/sys/sys/linker_set.h
86
87pub use linker_set_proc::set_entry;
88
89/// An iterator that yields the elements in a linker set.
90pub struct LinkerSetIter<T> {
91    next: *const T,
92    stop: *const T,
93}
94
95impl<T> LinkerSetIter<T> {
96    /// Create a new iterator for a linker set.
97    ///
98    /// Users should call the [set!] macro instead of this function.
99    ///
100    /// # Safety
101    /// The pointers must be start and end pointers generated by the linker.
102    pub unsafe fn new(start: *const T, stop: *const T) -> Self {
103        assert!(start < stop);
104        Self { next: start, stop }
105    }
106}
107
108impl<T> Iterator for LinkerSetIter<T>
109where
110    T: 'static,
111{
112    type Item = &'static T;
113
114    fn next(&mut self) -> Option<Self::Item> {
115        if self.next == self.stop {
116            None
117        } else {
118            unsafe {
119                let x = self.next.as_ref();
120                self.next = self.next.add(1);
121                x
122            }
123        }
124    }
125
126    fn count(self) -> usize {
127        self.len()
128    }
129
130    fn size_hint(&self) -> (usize, Option<usize>) {
131        let len = self.len();
132        (len, Some(len))
133    }
134}
135
136impl<T> ExactSizeIterator for LinkerSetIter<T>
137where
138    T: 'static,
139{
140    fn len(&self) -> usize {
141        unsafe { self.stop.offset_from(self.next).try_into().unwrap() }
142    }
143}
144
145impl<T> std::iter::FusedIterator for LinkerSetIter<T> where T: 'static {}
146
147unsafe impl<T: Send> Send for LinkerSetIter<T> {}
148
149/// A proxy object that represents a linker set.
150///
151/// You can store this object if you should want the ability to create
152/// multiple iterators on the linker set, or maybe if you wanted to keep
153/// track of a specific linker set out of some number of them.
154pub struct LinkerSet<T>
155where
156    T: 'static,
157{
158    start: *const T,
159    stop: *const T,
160    slice: &'static [T],
161}
162
163impl<T> LinkerSet<T>
164where
165    T: 'static,
166{
167    /// Create a new object to represent a linker set.
168    ///
169    /// # Safety
170    /// The pointers must be start and end pointers generated by the linker.
171    pub unsafe fn new(start: *const T, stop: *const T) -> Self {
172        assert!(start < stop);
173        let slice = unsafe {
174            let len = stop.offset_from(start).try_into().unwrap();
175            std::slice::from_raw_parts(start, len)
176        };
177        Self { start, stop, slice }
178    }
179
180    /// Returns an iterator over the items in the linker set.
181    pub fn iter(&self) -> LinkerSetIter<T> {
182        unsafe { LinkerSetIter::new(self.start, self.stop) }
183    }
184
185    /// Returns the number of elements in the linker set.
186    pub fn len(&self) -> usize {
187        self.slice.len()
188    }
189
190    /// Returns true if the linker set contains zero elements.
191    pub fn is_empty(&self) -> bool {
192        self.start == self.stop
193    }
194}
195
196impl<T> IntoIterator for LinkerSet<T>
197where
198    T: 'static,
199{
200    type Item = &'static T;
201    type IntoIter = LinkerSetIter<T>;
202
203    fn into_iter(self) -> Self::IntoIter {
204        self.iter()
205    }
206}
207
208impl<T, I> std::ops::Index<I> for LinkerSet<T>
209where
210    T: 'static,
211    I: std::slice::SliceIndex<[T], Output = T>,
212{
213    type Output = T;
214
215    fn index(&self, i: I) -> &Self::Output {
216        self.slice.index(i)
217    }
218}
219
220unsafe impl<T: Send> Send for LinkerSet<T> {}
221unsafe impl<T: Sync> Sync for LinkerSet<T> {} // readonly once created
222
223/// Declare the name of a linker set.
224///
225/// This macro outputs a module into the current scope.  The module must
226/// be brought into scope should the linker set be used within another module.
227#[macro_export]
228macro_rules! set_declare {
229    ($set:ident, $type:ty) => {
230        pub mod $set {
231            #[allow(unused_imports)]
232            use super::*;
233            paste::paste! {
234                unsafe extern {
235                    /* rust thinks we're allowing these things to come in from
236                     * C code, so if type is a function, it gets cranky because
237                     * it thinks we're proposing to call a function in C with
238                     * rust calling convention. */
239                    #[allow(improper_ctypes)]
240                    pub static [<__start_set_ $set>]: $type;
241                    #[allow(improper_ctypes)]
242                    pub static [<__stop_set_ $set>]: $type;
243                }
244            }
245        }
246    };
247}
248
249/// Create a linker set proxy object for iteration or indexing.
250#[macro_export]
251macro_rules! set {
252    ($set:ident) => {{
253        paste::paste! {
254            unsafe {
255                LinkerSet::new(
256                    &$set::[<__start_set_ $set>],
257                    &$set::[<__stop_set_ $set>],
258                )
259            }
260        }
261    }};
262}
263
264#[cfg(test)]
265mod test {
266    use super::*;
267    use std::collections::HashSet;
268
269    set_declare!(stuff, u64);
270
271    #[set_entry(stuff)]
272    static FOO: u64 = 0x4F202A76B86A7299u64;
273    #[set_entry(stuff)]
274    static BAR: u64 = 0x560E9309456ACCE0u64;
275
276    #[test]
277    fn test_set_contents() {
278        let actual = set!(stuff).iter().collect::<HashSet<_>>();
279        let expect = HashSet::from([&FOO, &BAR, &0x6666666666666666]);
280        assert_eq!(actual, expect);
281    }
282
283    #[test]
284    fn test_set_iter_len() {
285        const LEN: usize = 3;
286        let iter = set!(stuff).iter();
287        assert_eq!(iter.len(), LEN);
288        assert_eq!(iter.size_hint(), (LEN, Some(LEN)));
289        assert_eq!(iter.count(), LEN);
290    }
291
292    #[test]
293    fn test_into() {
294        let mut actual = HashSet::new();
295        for i in set!(stuff) {
296            actual.insert(i);
297        }
298        let expect = HashSet::from([&FOO, &BAR, &0x6666666666666666]);
299        assert_eq!(actual, expect);
300    }
301
302    #[test]
303    fn test_index() {
304        let set = set!(stuff);
305        assert_eq!(set.len(), 3);
306        let mut actual = HashSet::new();
307        for i in 0..set.len() {
308            actual.insert(set[i]); // this is u64; compiler auto derefs
309        }
310        let expect = HashSet::from([FOO, BAR, 0x6666666666666666]);
311        assert_eq!(actual, expect);
312    }
313
314    #[test]
315    fn test_is_empty() {
316        assert!(!set!(stuff).is_empty());
317    }
318
319    #[derive(Debug, Eq, PartialEq, Hash)]
320    pub(crate) struct Foo {
321        a: u32,
322        b: u8,
323    }
324
325    set_declare!(aaa, Foo);
326
327    #[set_entry(aaa)]
328    static AAA: Foo = Foo { a: 1, b: 5 };
329
330    #[test]
331    fn test_struct() {
332        let actual = set!(aaa).iter().collect::<HashSet<_>>();
333        let expect = HashSet::from([&AAA]);
334        assert_eq!(actual, expect);
335    }
336
337    #[test]
338    fn test_traits() {
339        fn require_send<T: Send>(_: T) {}
340        fn require_sync<T: Sync>(_: T) {}
341
342        require_send(set!(aaa));
343        require_sync(set!(aaa));
344        require_send(set!(aaa).iter());
345    }
346}
347
348#[cfg(test)]
349mod test_use_ext {
350    use super::*;
351    use test::stuff;
352
353    #[set_entry(stuff)]
354    static FOO: u64 = 0x6666666666666666;
355
356    #[test]
357    fn test_use() {
358        const LEN: usize = 3;
359        let iter = set!(stuff).iter();
360        assert_eq!(iter.len(), LEN);
361    }
362}