Skip to content

Commit 7214019

Browse files
committed
Fix publish of stringleton-dylib
1 parent 49a073d commit 7214019

File tree

1 file changed

+219
-4
lines changed

1 file changed

+219
-4
lines changed

stringleton-dylib/lib.rs

Lines changed: 219 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,223 @@
1515
//! In that case, the host crate should specify this crate as its dependency
1616
//! instead of `stringleton`.
1717
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`.
2119

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

Comments
 (0)