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