|
15 | 15 | //! In that case, the host crate should specify this crate as its dependency |
16 | 16 | //! instead of `stringleton`. |
17 | 17 |
|
18 | | -// Note: This perma-fails in rust-analyzer, but it's fine. |
19 | | -#[path = "../stringleton/lib.rs"] |
20 | | -mod lib_; |
| 18 | +// XXX: This file is a copy of `../stringleton/lib.rs`. |
21 | 19 |
|
22 | | -pub use lib_::*; |
| 20 | +pub 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)] |
| 76 | +macro_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)] |
| 138 | +macro_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] |
| 200 | +macro_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)] |
| 232 | +pub mod internal { |
| 233 | + pub use ctor; |
| 234 | + pub use linkme; |
| 235 | + pub use stringleton_registry::Registry; |
| 236 | + pub use stringleton_registry::Site; |
| 237 | +} |
0 commit comments