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//! # Safety
61//!
62//! Although the [set_entry] macro does not require an unsafe to call, it is
63//! not entirely safe.  The caller is required to ensure all entries in the
64//! set are valid and in the proper format.  Rust may add unsafe macros at
65//! some point, but at present there is no way to declare that a given
66//! third-party macro is unsafe, even though Rust 2024 has some attributes
67//! that require an unsafe to be used.
68//!
69//! # History
70//!
71//! This idea comes from [Clustrix], the best distributed relational
72//! database in the world, which no one knew about.  Clustrix was written
73//! in a very unusual but very interesting style of C.  Much of it was
74//! written in [continuation-passing style]([CPS]), and continuations and
75//! lightweight threads (fibers) ran on top of a scheduler very similar to
76//! the asynchronous runtimes like Tokio which later became popular.  (But
77//! Clustrix was started in 2006, before that popularity.)
78//!
79//! Linker sets were used extensively in the Clustrix code to do things
80//! such as specify initialization or other system processes via graphs
81//! (initgraphs), automatically create heaps for memory allocation,
82//! automatically allocate integers or flags for what would otherwise have
83//! to be centrally controlled constants, and automatically register
84//! structures or handlers with a subsystem.
85//!
86//! This concept was present in the oldest version of the Clustrix code in
87//! Git.  A prior Subversion repository seemed to have been lost.  The
88//! inspiration appears to have come from [FreeBSD], which has several
89//! macros whose names match exactly macros used in the Clustrix source
90//! code.
91//!
92//! [Clustrix]: https://en.wikipedia.org/wiki/Clustrix
93//! [CPS]: https://en.wikipedia.org/wiki/Continuation-passing_style
94//! [FreeBSD]: https://github.com/freebsd/freebsd-src/blob/main/sys/sys/linker_set.h
95
96pub use linker_set_proc::set_entry;
97pub use paste::paste;
98
99/// An iterator that yields the elements in a linker set.
100pub struct LinkerSetIter<T> {
101    next: *const T,
102    stop: *const T,
103}
104
105impl<T> LinkerSetIter<T> {
106    /// Create a new iterator for a linker set.
107    ///
108    /// Users should call the [set!] macro instead of this function.
109    ///
110    /// # Safety
111    /// The pointers must be start and end pointers generated by the linker.
112    pub unsafe fn new(start: *const T, stop: *const T) -> Self {
113        assert!(start < stop);
114        Self { next: start, stop }
115    }
116}
117
118impl<T> Iterator for LinkerSetIter<T>
119where
120    T: 'static,
121{
122    type Item = &'static T;
123
124    fn next(&mut self) -> Option<Self::Item> {
125        if self.next == self.stop {
126            None
127        } else {
128            unsafe {
129                let x = self.next.as_ref();
130                self.next = self.next.add(1);
131                x
132            }
133        }
134    }
135
136    fn count(self) -> usize {
137        self.len()
138    }
139
140    fn size_hint(&self) -> (usize, Option<usize>) {
141        let len = self.len();
142        (len, Some(len))
143    }
144}
145
146impl<T> ExactSizeIterator for LinkerSetIter<T>
147where
148    T: 'static,
149{
150    fn len(&self) -> usize {
151        unsafe { self.stop.offset_from(self.next).try_into().unwrap() }
152    }
153}
154
155impl<T> std::iter::FusedIterator for LinkerSetIter<T> where T: 'static {}
156
157unsafe impl<T: Send> Send for LinkerSetIter<T> {}
158
159/// A proxy object that represents a linker set.
160///
161/// You can store this object if you should want the ability to create
162/// multiple iterators on the linker set, or maybe if you wanted to keep
163/// track of a specific linker set out of some number of them.
164pub struct LinkerSet<T>
165where
166    T: 'static,
167{
168    start: *const T,
169    stop: *const T,
170    slice: &'static [T],
171}
172
173impl<T> LinkerSet<T>
174where
175    T: 'static,
176{
177    /// Create a new object to represent a linker set.
178    ///
179    /// # Safety
180    /// The pointers must be start and end pointers generated by the linker.
181    pub unsafe fn new(start: *const T, stop: *const T) -> Self {
182        assert!(start < stop);
183        let slice = unsafe {
184            let len = stop.offset_from(start).try_into().unwrap();
185            std::slice::from_raw_parts(start, len)
186        };
187        Self { start, stop, slice }
188    }
189
190    /// Returns an iterator over the items in the linker set.
191    pub fn iter(&self) -> LinkerSetIter<T> {
192        unsafe { LinkerSetIter::new(self.start, self.stop) }
193    }
194
195    /// Returns the number of elements in the linker set.
196    pub fn len(&self) -> usize {
197        self.slice.len()
198    }
199
200    /// Returns true if the linker set contains zero elements.
201    pub fn is_empty(&self) -> bool {
202        self.start == self.stop
203    }
204}
205
206impl<T> IntoIterator for LinkerSet<T>
207where
208    T: 'static,
209{
210    type Item = &'static T;
211    type IntoIter = LinkerSetIter<T>;
212
213    fn into_iter(self) -> Self::IntoIter {
214        self.iter()
215    }
216}
217
218impl<T, I> std::ops::Index<I> for LinkerSet<T>
219where
220    T: 'static,
221    I: std::slice::SliceIndex<[T], Output = T>,
222{
223    type Output = T;
224
225    fn index(&self, i: I) -> &Self::Output {
226        self.slice.index(i)
227    }
228}
229
230unsafe impl<T: Send> Send for LinkerSet<T> {}
231unsafe impl<T: Sync> Sync for LinkerSet<T> {} // readonly once created
232
233/// Declare the name of a linker set.
234///
235/// This macro outputs a module into the current scope.  The module must
236/// be brought into scope should the linker set be used within another module.
237#[macro_export]
238macro_rules! set_declare {
239    ($set:ident, $type:ty) => {
240        pub mod $set {
241            #[allow(unused_imports)]
242            use super::*;
243            $crate::paste! {
244                unsafe extern {
245                    /* rust thinks we're allowing these things to come in from
246                     * C code, so if type is a function, it gets cranky because
247                     * it thinks we're proposing to call a function in C with
248                     * rust calling convention. */
249                    #[allow(improper_ctypes)]
250                    pub static [<__start_set_ $set>]: $type;
251                    #[allow(improper_ctypes)]
252                    pub static [<__stop_set_ $set>]: $type;
253                }
254            }
255        }
256    };
257}
258
259/// Create a linker set proxy object for iteration or indexing.
260#[macro_export]
261macro_rules! set {
262    ($set:ident) => {{
263        $crate::paste! {
264            unsafe {
265                LinkerSet::new(
266                    &$set::[<__start_set_ $set>],
267                    &$set::[<__stop_set_ $set>],
268                )
269            }
270        }
271    }};
272}
273
274#[cfg(test)]
275mod test {
276    use super::*;
277    use std::collections::HashSet;
278
279    set_declare!(stuff, u64);
280
281    #[set_entry(stuff)]
282    static FOO: u64 = 0x4F202A76B86A7299u64;
283    #[set_entry(stuff)]
284    static BAR: u64 = 0x560E9309456ACCE0u64;
285
286    #[test]
287    fn test_set_contents() {
288        let actual = set!(stuff).iter().collect::<HashSet<_>>();
289        let expect = HashSet::from([&FOO, &BAR, &0x6666666666666666]);
290        assert_eq!(actual, expect);
291    }
292
293    #[test]
294    fn test_set_iter_len() {
295        const LEN: usize = 3;
296        let iter = set!(stuff).iter();
297        assert_eq!(iter.len(), LEN);
298        assert_eq!(iter.size_hint(), (LEN, Some(LEN)));
299        assert_eq!(iter.count(), LEN);
300    }
301
302    #[test]
303    fn test_into() {
304        let mut actual = HashSet::new();
305        for i in set!(stuff) {
306            actual.insert(i);
307        }
308        let expect = HashSet::from([&FOO, &BAR, &0x6666666666666666]);
309        assert_eq!(actual, expect);
310    }
311
312    #[test]
313    fn test_index() {
314        let set = set!(stuff);
315        assert_eq!(set.len(), 3);
316        let mut actual = HashSet::new();
317        for i in 0..set.len() {
318            actual.insert(set[i]); // this is u64; compiler auto derefs
319        }
320        let expect = HashSet::from([FOO, BAR, 0x6666666666666666]);
321        assert_eq!(actual, expect);
322    }
323
324    #[test]
325    fn test_is_empty() {
326        assert!(!set!(stuff).is_empty());
327    }
328
329    #[derive(Debug, Eq, PartialEq, Hash)]
330    pub(crate) struct Foo {
331        a: u32,
332        b: u8,
333    }
334
335    set_declare!(aaa, Foo);
336
337    #[set_entry(aaa)]
338    static AAA: Foo = Foo { a: 1, b: 5 };
339
340    #[test]
341    fn test_struct() {
342        let actual = set!(aaa).iter().collect::<HashSet<_>>();
343        let expect = HashSet::from([&AAA]);
344        assert_eq!(actual, expect);
345    }
346
347    #[test]
348    fn test_traits() {
349        fn require_send<T: Send>(_: T) {}
350        fn require_sync<T: Sync>(_: T) {}
351
352        require_send(set!(aaa));
353        require_sync(set!(aaa));
354        require_send(set!(aaa).iter());
355    }
356}
357
358#[cfg(test)]
359mod test_use_ext {
360    use super::*;
361    use test::stuff;
362
363    #[set_entry(stuff)]
364    static FOO: u64 = 0x6666666666666666;
365
366    #[test]
367    fn test_use() {
368        const LEN: usize = 3;
369        let iter = set!(stuff).iter();
370        assert_eq!(iter.len(), LEN);
371    }
372}