stringleton_dylib/
lib.rs

1//! Dynamic linking support for Stringleton.
2//!
3//! _[See the docs for `stringleton`](../stringleton/index.html)._
4//!
5//! This crate always produces a dynamic library, and it should be used by any
6//! crate that ends up being a `cdylib`. When this appears somewhere in the
7//! dependency graph, it causes the Rust compiler to produce a dynamic version
8//! of `stringleton-registry`, which means that both uses of `stringleton` and
9//! `stringleton-dylib` use the same symbol registry, so `Symbol`s can be safely
10//! passed across the dynamic linking boundary.
11//!
12//! The host crate can safely use `stringleton` as a dependency, **except** when
13//! dynamic libraries using `stringleton-dylib` are loaded at runtime (i.e.,
14//! Rust cannot know that `stringleton-registry` should be dynamically linked).
15//! In that case, the host crate should specify this crate as its dependency
16//! instead of `stringleton`.
17
18// XXX: This file is a copy of `../stringleton/lib.rs`.
19
20pub use stringleton_registry::{Registry, StaticSymbol, Symbol};
21
22/// Create a literal symbol from a literal identifier or string
23///
24/// Symbols created with the [`sym!(...)`](crate::sym) macro are statically
25/// allocated and deduplicated on program startup. This means that there is no
26/// discernible overhead at the point of use, making them suitable even in long
27/// chains of `if` statements and inner loops.
28///
29/// **IMPORTANT:** For this macro to work in a particular crate, the
30/// [`enable!()`](crate::enable) macro must appear exactly once in the crate's
31/// root. This creates the global registration table at link-time.
32///
33/// # Safety
34///
35/// This macro is safe (and performant) to use everywhere, with important
36/// caveats:
37///
38/// 1. If you are using "static initializers" (code that runs before `main()`,
39///    like through the `ctor` crate), this macro must **NOT** be called in such
40///    a static initializer function. See
41///    <https://github.com/mmastrac/rust-ctor/issues/159>. Using
42///    [`Symbol::new()`] in such a function is fine.
43///
44/// 2. If you are using C-style dynamic libraries (`cdylib` crate type), those
45///    libraries must use the `stringleton-dylib` crate instead of
46///    `stringleton`.
47///
48/// 3. If you are loading dynamic libraries at runtime (i.e., outside of Cargo's
49///    dependency graph), the host crate must also use the `stringleton-dylib`
50///    crate instead of `stringleton`.
51///
52/// # Low-level details
53///
54/// This macro creates an entry in a per-crate `linkme` "distributed slice", as
55/// well as a static initializer called by the OS when the current crate is
56/// loaded at runtime (before `main()`), either as part of an executable or as
57/// part of a dynamic library.
58///
59/// On x86-64 and ARM64, this macro is guaranteed to compile into a single
60/// relaxed atomic memory load instruction from an offset in the `.bss` segment.
61/// On x86, relaxed atomic load instructions have no additional overhead
62/// compared to non-atomic loads.
63///
64/// Internally, this uses the `linkme` and `ctor` crates to register this
65/// callsite in static binary memory and initialize it on startup. However, when
66/// running under Miri (or other platforms not supported by `linkme`), the
67/// implementation falls back on a slower implementation that effectively calls
68/// `Symbol::new()` every time, which takes a global read-lock.
69///
70/// When the `debug-assertions` feature is enabled, there is an additional check
71/// that panics if the call site has not been populated by a static ctor. This
72/// assertion will only be triggered if the current platform does not support
73/// static initializers.
74#[macro_export]
75#[allow(clippy::crate_in_macro_def)]
76macro_rules! sym {
77    ($sym:ident) => {
78        $crate::sym!(@impl stringify!($sym))
79    };
80    ($sym:literal) => {
81        $crate::sym!(@impl $sym)
82    };
83    (@impl $sym:expr) => {{
84        // Note: Using `crate` to refer to the calling crate - this is deliberate.
85        #[$crate::internal::linkme::distributed_slice(crate::_stringleton_enabled::TABLE)]
86        #[linkme(crate = $crate::internal::linkme)]
87        static SITE: $crate::internal::Site = $crate::internal::Site::new(&$sym);
88        unsafe {
89            // SAFETY: This site will be initialized by the static ctor because
90            // it participates in the distributed slice.
91            SITE.get_after_ctor()
92        }}
93    }
94}
95
96/// Create a static location for a literal symbol.
97///
98/// This macro works the same as [`sym!(...)`](crate::sym), except that it
99/// produces a [`StaticSymbol`] instead of a [`Symbol`]. [`StaticSymbol`]
100/// implements `Deref<Target = Symbol>`, so it can be used in most places where
101/// a `Symbol` is expected.
102///
103/// This macro also requires the presence of a call to the
104/// [`enable!()`](crate::enable) macro at the crate root.
105///
106/// This macro can be used in the initialization of a `static` or `const` variable:
107///
108/// ```rust,ignore
109/// static MY_SYMBOL: StaticSymbol = static_sym!("Hello, World!");
110/// const OTHER_SYMBOL: StaticSymbol = static_sym!(abc);
111///
112/// assert_eq!(MY_SYMBOL, sym!("Hello, World!"));
113/// assert_eq!(OTHER_SYMBOL, sym("abc"));
114/// ```
115///
116/// # Use case
117///
118/// Use this macro to avoid having too many "magic symbols" in your code
119/// (similar to "magic numbers"). Declare common symbol names centrally, and
120/// refer to them by their Rust names instead.
121///
122/// At runtime, using symbols declared as `static_sym!(...)` is actually very
123/// slightly less efficient than using `sym!(...)` directly, due to a necessary
124/// extra indirection. This is probably negligible in almost all cases, but it
125/// is counterintuitive nevertheless. _(This caveat may be lifted in future, but
126/// is due to a - potentially overzealous - check in the compiler which requires
127/// the indirection.)_
128///
129/// # Low-level details
130///
131/// Another (extremely niche) effect of using this macro over `sym!(...)` is
132/// that it can help reduce the link-time size of the symbol table. Each
133/// `sym!(...)` and `static_sym!(...)` call site adds 8 bytes to the `.bss`
134/// segment, so this can only matter when you have in the order of millions of
135/// symbols in your binary. Still, worth knowing if you are golfing binary size.
136#[macro_export]
137#[allow(clippy::crate_in_macro_def)]
138macro_rules! static_sym {
139    ($sym:ident) => {
140        $crate::static_sym!(@impl stringify!($sym))
141    };
142    ($sym:literal) => {
143        $crate::static_sym!(@impl $sym)
144    };
145    (@impl $sym:expr) => {{
146        unsafe {
147            // SAFETY: `new_unchecked()` is called with a `Site` that
148            // participates in the crate's symbol table.
149            $crate::StaticSymbol::new_unchecked({
150                // Tiny function just to get the `Site` for this symbol.
151                fn _stringleton_static_symbol_call_site() -> &'static $crate::internal::Site {
152                    // Note: Using `crate` to refer to the calling crate - this is deliberate.
153                    #[$crate::internal::linkme::distributed_slice(crate::_stringleton_enabled::TABLE)]
154                    #[linkme(crate = $crate::internal::linkme)]
155                    static SITE: $crate::internal::Site = $crate::internal::Site::new(&$sym);
156                    &SITE
157                }
158                _stringleton_static_symbol_call_site
159            })
160        }
161    }}
162}
163
164/// Enable the [`sym!(...)`](crate::sym) macro in the calling crate.
165///
166/// Put a call to this macro somewhere in the root of each crate that uses the
167/// `sym!(...)` macro.
168///
169/// ## Details
170///
171/// This creates a "distributed slice" containing all symbols in this crate, as
172/// well as a static constructor that deduplicates all symbols on startup, or
173/// when a dynamic library is loaded when the target binary is a `dylib` or a
174/// `cdylib`.
175///
176/// This macro may also be invoked with a module path to another crate, which
177/// causes symbols in this crate to be registered as part of symbols in the
178/// other crate.
179///
180/// **CAUTION:** Using the second variant is discouraged, because it will not
181/// work when the other crate is being loaded as a dynamic library. However, it
182/// is very slightly more efficient.
183///
184/// ## Why?
185///
186/// The reason that this macro is necessary is dynamic linking. Under "normal"
187/// circumstances where all dependencies are statically linked, all crates could
188/// share a single symbol table. But dynamic libraries are linked independently
189/// of their host binary, so they have no access to the host's symbol table, if
190/// it even has one.
191///
192/// On Unix-like platforms, there is likely a solution for this based on "weak"
193/// linkage, but:
194///
195/// 1. Weak linkage is not a thing in Windows (DLLs need to explicitly request
196///    functions from the host binary using `GetModuleHandle()`, which is more
197///    brittle).
198/// 2. The `#[linkage]` attribute is unstable in Rust.
199#[macro_export]
200macro_rules! enable {
201    () => {
202        #[doc(hidden)]
203        pub(crate) mod _stringleton_enabled {
204            #[$crate::internal::linkme::distributed_slice]
205            #[linkme(crate = $crate::internal::linkme)]
206            #[doc(hidden)]
207            pub(crate) static TABLE: [$crate::internal::Site] = [..];
208
209            $crate::internal::ctor::declarative::ctor! {
210                #[ctor]
211                #[doc(hidden)]
212                pub fn _stringleton_register_symbols() {
213                    unsafe {
214                        // SAFETY: This is a static ctor.
215                        $crate::internal::Registry::register_sites(&TABLE);
216                    }
217                }
218            }
219        }
220
221        #[allow(unused)]
222        #[doc(hidden)]
223        pub use _stringleton_enabled::_stringleton_register_symbols;
224    };
225    ($krate:path) => {
226        #[doc(hidden)]
227        pub(crate) use $krate::_stringleton_enabled;
228    };
229}
230
231#[doc(hidden)]
232pub mod internal {
233    pub use ctor;
234    pub use linkme;
235    pub use stringleton_registry::Registry;
236    pub use stringleton_registry::Site;
237}