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}