Skip to content

Commit 0b56db0

Browse files
committed
Auto merge of #146019 - joboet:better-dlsym, r=tgross35
std: optimize `dlsym!` macro and add a test for it The `dlsym!` macro always ensures that the name string is nul-terminated, so there is no need to perform the check at runtime. Also, acquire loads are generally faster than a load and a barrier, so use them. This is only false in the case where the symbol is missing, but that shouldn't matter too much.
2 parents 51ff895 + d0a2761 commit 0b56db0

File tree

3 files changed

+101
-51
lines changed

3 files changed

+101
-51
lines changed

library/std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@
348348
#![feature(float_gamma)]
349349
#![feature(float_minimum_maximum)]
350350
#![feature(fmt_internals)]
351+
#![feature(fn_ptr_trait)]
351352
#![feature(generic_atomic)]
352353
#![feature(hasher_prefixfree_extras)]
353354
#![feature(hashmap_internals)]

library/std/src/sys/pal/unix/weak.rs

Lines changed: 69 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@
2222
#![allow(dead_code, unused_macros)]
2323
#![forbid(unsafe_op_in_unsafe_fn)]
2424

25-
use crate::ffi::CStr;
26-
use crate::marker::PhantomData;
27-
use crate::sync::atomic::{self, Atomic, AtomicPtr, Ordering};
25+
use crate::ffi::{CStr, c_char, c_void};
26+
use crate::marker::{FnPtr, PhantomData};
27+
use crate::sync::atomic::{Atomic, AtomicPtr, Ordering};
2828
use crate::{mem, ptr};
2929

30+
#[cfg(test)]
31+
mod tests;
32+
3033
// We can use true weak linkage on ELF targets.
3134
#[cfg(all(unix, not(target_vendor = "apple")))]
3235
pub(crate) macro weak {
@@ -64,7 +67,7 @@ impl<F: Copy> ExternWeak<F> {
6467

6568
pub(crate) macro dlsym {
6669
(fn $name:ident($($param:ident : $t:ty),* $(,)?) -> $ret:ty;) => (
67-
dlsym!(
70+
dlsym!(
6871
#[link_name = stringify!($name)]
6972
fn $name($($param : $t),*) -> $ret;
7073
);
@@ -73,84 +76,99 @@ pub(crate) macro dlsym {
7376
#[link_name = $sym:expr]
7477
fn $name:ident($($param:ident : $t:ty),* $(,)?) -> $ret:ty;
7578
) => (
76-
static DLSYM: DlsymWeak<unsafe extern "C" fn($($t),*) -> $ret> =
77-
DlsymWeak::new(concat!($sym, '\0'));
79+
static DLSYM: DlsymWeak<unsafe extern "C" fn($($t),*) -> $ret> = {
80+
let Ok(name) = CStr::from_bytes_with_nul(concat!($sym, '\0').as_bytes()) else {
81+
panic!("symbol name may not contain NUL")
82+
};
83+
84+
// SAFETY: Whoever calls the function pointer returned by `get()`
85+
// is responsible for ensuring that the signature is correct. Just
86+
// like with extern blocks, this is syntactically enforced by making
87+
// the function pointer be unsafe.
88+
unsafe { DlsymWeak::new(name) }
89+
};
90+
7891
let $name = &DLSYM;
7992
)
8093
}
94+
8195
pub(crate) struct DlsymWeak<F> {
82-
name: &'static str,
96+
/// A pointer to the nul-terminated name of the symbol.
97+
// Use a pointer instead of `&'static CStr` to save space.
98+
name: *const c_char,
8399
func: Atomic<*mut libc::c_void>,
84100
_marker: PhantomData<F>,
85101
}
86102

87-
impl<F> DlsymWeak<F> {
88-
pub(crate) const fn new(name: &'static str) -> Self {
103+
impl<F: FnPtr> DlsymWeak<F> {
104+
/// # Safety
105+
///
106+
/// If the signature of `F` does not match the signature of the symbol (if
107+
/// it exists), calling the function pointer returned by `get()` is
108+
/// undefined behaviour.
109+
pub(crate) const unsafe fn new(name: &'static CStr) -> Self {
89110
DlsymWeak {
90-
name,
111+
name: name.as_ptr(),
91112
func: AtomicPtr::new(ptr::without_provenance_mut(1)),
92113
_marker: PhantomData,
93114
}
94115
}
95116

96117
#[inline]
97118
pub(crate) fn get(&self) -> Option<F> {
98-
unsafe {
99-
// Relaxed is fine here because we fence before reading through the
100-
// pointer (see the comment below).
101-
match self.func.load(Ordering::Relaxed) {
102-
func if func.addr() == 1 => self.initialize(),
103-
func if func.is_null() => None,
104-
func => {
105-
let func = mem::transmute_copy::<*mut libc::c_void, F>(&func);
106-
// The caller is presumably going to read through this value
107-
// (by calling the function we've dlsymed). This means we'd
108-
// need to have loaded it with at least C11's consume
109-
// ordering in order to be guaranteed that the data we read
110-
// from the pointer isn't from before the pointer was
111-
// stored. Rust has no equivalent to memory_order_consume,
112-
// so we use an acquire fence (sorry, ARM).
113-
//
114-
// Now, in practice this likely isn't needed even on CPUs
115-
// where relaxed and consume mean different things. The
116-
// symbols we're loading are probably present (or not) at
117-
// init, and even if they aren't the runtime dynamic loader
118-
// is extremely likely have sufficient barriers internally
119-
// (possibly implicitly, for example the ones provided by
120-
// invoking `mprotect`).
121-
//
122-
// That said, none of that's *guaranteed*, and so we fence.
123-
atomic::fence(Ordering::Acquire);
124-
Some(func)
125-
}
126-
}
119+
// The caller is presumably going to read through this value
120+
// (by calling the function we've dlsymed). This means we'd
121+
// need to have loaded it with at least C11's consume
122+
// ordering in order to be guaranteed that the data we read
123+
// from the pointer isn't from before the pointer was
124+
// stored. Rust has no equivalent to memory_order_consume,
125+
// so we use an acquire load (sorry, ARM).
126+
//
127+
// Now, in practice this likely isn't needed even on CPUs
128+
// where relaxed and consume mean different things. The
129+
// symbols we're loading are probably present (or not) at
130+
// init, and even if they aren't the runtime dynamic loader
131+
// is extremely likely have sufficient barriers internally
132+
// (possibly implicitly, for example the ones provided by
133+
// invoking `mprotect`).
134+
//
135+
// That said, none of that's *guaranteed*, so we use acquire.
136+
match self.func.load(Ordering::Acquire) {
137+
func if func.addr() == 1 => self.initialize(),
138+
func if func.is_null() => None,
139+
// SAFETY:
140+
// `func` is not null and `F` implements `FnPtr`, thus this
141+
// transmutation is well-defined. It is the responsibility of the
142+
// creator of this `DlsymWeak` to ensure that calling the resulting
143+
// function pointer does not result in undefined behaviour (though
144+
// the `dlsym!` macro delegates this responsibility to the caller
145+
// of the function by using `unsafe` function pointers).
146+
// FIXME: use `transmute` once it stops complaining about generics.
147+
func => Some(unsafe { mem::transmute_copy::<*mut c_void, F>(&func) }),
127148
}
128149
}
129150

130151
// Cold because it should only happen during first-time initialization.
131152
#[cold]
132-
unsafe fn initialize(&self) -> Option<F> {
133-
assert_eq!(size_of::<F>(), size_of::<*mut libc::c_void>());
134-
135-
let val = unsafe { fetch(self.name) };
136-
// This synchronizes with the acquire fence in `get`.
153+
fn initialize(&self) -> Option<F> {
154+
// SAFETY: `self.name` was created from a `&'static CStr` and is
155+
// therefore a valid C string pointer.
156+
let val = unsafe { libc::dlsym(libc::RTLD_DEFAULT, self.name) };
157+
// This synchronizes with the acquire load in `get`.
137158
self.func.store(val, Ordering::Release);
138159

139160
if val.is_null() {
140161
None
141162
} else {
163+
// SAFETY: see the comment in `get`.
164+
// FIXME: use `transmute` once it stops complaining about generics.
142165
Some(unsafe { mem::transmute_copy::<*mut libc::c_void, F>(&val) })
143166
}
144167
}
145168
}
146169

147-
unsafe fn fetch(name: &str) -> *mut libc::c_void {
148-
let name = match CStr::from_bytes_with_nul(name.as_bytes()) {
149-
Ok(cstr) => cstr,
150-
Err(..) => return ptr::null_mut(),
151-
};
152-
unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr()) }
153-
}
170+
unsafe impl<F> Send for DlsymWeak<F> {}
171+
unsafe impl<F> Sync for DlsymWeak<F> {}
154172

155173
#[cfg(not(any(target_os = "linux", target_os = "android")))]
156174
pub(crate) macro syscall {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use super::*;
2+
use crate::ffi::c_int;
3+
4+
#[test]
5+
fn dlsym_existing() {
6+
// Try to find a symbol that definitely exists.
7+
dlsym! {
8+
fn abs(i: c_int) -> c_int;
9+
}
10+
11+
dlsym! {
12+
#[link_name = "abs"]
13+
fn custom_name(i: c_int) -> c_int;
14+
}
15+
16+
let abs = abs.get().unwrap();
17+
assert_eq!(unsafe { abs(-1) }, 1);
18+
19+
let custom_name = custom_name.get().unwrap();
20+
assert_eq!(unsafe { custom_name(-1) }, 1);
21+
}
22+
23+
#[test]
24+
fn dlsym_missing() {
25+
// Try to find a symbol that definitely does not exist.
26+
dlsym! {
27+
fn test_symbol_that_does_not_exist() -> c_int;
28+
}
29+
30+
assert!(test_symbol_that_does_not_exist.get().is_none());
31+
}

0 commit comments

Comments
 (0)