|
6 | 6 | // option. This file may not be copied, modified, or distributed |
7 | 7 | // except according to those terms. |
8 | 8 | #![allow(dead_code)] |
9 | | -use crate::{util::LazyUsize, Error}; |
10 | | -use core::{num::NonZeroU32, ptr::NonNull}; |
| 9 | +use crate::Error; |
| 10 | +use core::{ |
| 11 | + num::NonZeroU32, |
| 12 | + ptr::NonNull, |
| 13 | + sync::atomic::{AtomicPtr, Ordering}, |
| 14 | +}; |
| 15 | +use libc::c_void; |
11 | 16 |
|
12 | 17 | cfg_if! { |
13 | 18 | if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android"))] { |
@@ -76,29 +81,48 @@ pub fn sys_fill_exact( |
76 | 81 |
|
77 | 82 | // A "weak" binding to a C function that may or may not be present at runtime. |
78 | 83 | // Used for supporting newer OS features while still building on older systems. |
79 | | -// F must be a function pointer of type `unsafe extern "C" fn`. Based off of the |
80 | | -// weak! macro in libstd. |
| 84 | +// Based off of the DlsymWeak struct in libstd (src/sys/unix/weak.rs), except |
| 85 | +// that the caller must cast self.ptr() to a function pointer. |
81 | 86 | pub struct Weak { |
82 | 87 | name: &'static str, |
83 | | - addr: LazyUsize, |
| 88 | + addr: AtomicPtr<c_void>, |
84 | 89 | } |
85 | 90 |
|
86 | 91 | impl Weak { |
| 92 | + // A non-null pointer value which indicates we are uninitialized. This |
| 93 | + // constant should ideally not be a valid address of a function pointer. |
| 94 | + // However, if by chance libc::dlsym does return UNINIT, there will not |
| 95 | + // be undefined behavior. libc::dlsym will just be called each time ptr() |
| 96 | + // is called. This would be inefficient, but correct. |
| 97 | + // TODO: Replace with core::ptr::invalid_mut(1) when that is stable. |
| 98 | + const UNINIT: *mut c_void = 1 as *mut c_void; |
| 99 | + |
87 | 100 | // Construct a binding to a C function with a given name. This function is |
88 | 101 | // unsafe because `name` _must_ be null terminated. |
89 | 102 | pub const unsafe fn new(name: &'static str) -> Self { |
90 | 103 | Self { |
91 | 104 | name, |
92 | | - addr: LazyUsize::new(), |
| 105 | + addr: AtomicPtr::new(Self::UNINIT), |
93 | 106 | } |
94 | 107 | } |
95 | 108 |
|
96 | | - // Return a function pointer if present at runtime. Otherwise, return null. |
97 | | - pub fn ptr(&self) -> Option<NonNull<libc::c_void>> { |
98 | | - let addr = self.addr.unsync_init(|| unsafe { |
99 | | - libc::dlsym(libc::RTLD_DEFAULT, self.name.as_ptr() as *const _) as usize |
100 | | - }); |
101 | | - NonNull::new(addr as *mut _) |
| 109 | + // Return the address of a function if present at runtime. Otherwise, |
| 110 | + // return null. Multiple callers can call ptr() concurrently. It will |
| 111 | + // always return _some_ value returned by libc::dlsym. However, the |
| 112 | + // dlsym function may be called multiple times. |
| 113 | + pub fn ptr(&self) -> Option<NonNull<c_void>> { |
| 114 | + // Despite having only a single atomic variable (self.addr), we still |
| 115 | + // need a "consume" ordering as we will generally be reading though |
| 116 | + // this value (by calling the function we dlsym'ed). Rust lacks this |
| 117 | + // ordering, so we have to go with the next strongest: Acquire/Release. |
| 118 | + // As noted in libstd, this might be unnecessary. |
| 119 | + let mut addr = self.addr.load(Ordering::Acquire); |
| 120 | + if addr == Self::UNINIT { |
| 121 | + let symbol = self.name.as_ptr() as *const _; |
| 122 | + addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, symbol) }; |
| 123 | + self.addr.store(addr, Ordering::Release); |
| 124 | + } |
| 125 | + NonNull::new(addr) |
102 | 126 | } |
103 | 127 | } |
104 | 128 |
|
|
0 commit comments