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}