Rollup merge of #76979 - fusion-engineering-forks:windows-fallback-check, r=dtolnay
Improve std::sys::windows::compat Improves the compat_fn macro in sys::windows, which is used for conditionally loading APIs that might not be available. - The module (dll) name can now be any string, not just an ident. (Not all Windows api modules are valid Rust identifiers. E.g. `WaitOnAddress` comes from `API-MS-Win-Core-Synch-l1-2-0.dll`.) - Adds `FuncName::is_available()` for checking if a function is really available without having to do a duplicate lookup. - Add comment explaining the lack of locking. - Use `$_:block` to simplify the macro_rules. - Apply `allow(unused_variables)` only to the fallback instead of everything. --- The second point (`is_available()`) simplifies code that needs to pick an implementation depening on what is available, like `sys/windows/mutex.rs`. Before this change, it'd do its own lookup and keep its own `AtomicUsize` to track the result. Now it can just use `c::AcquireSRWLockExclusive::is_available()` directly. This will also be useful when park/unpark/CondVar/etc. get improved implementations (e.g. from parking_lot or something else), as the best APIs for those are not available before Windows 8.
This commit is contained in:
commit
00b3450bbc
|
@ -1032,7 +1032,7 @@ extern "system" {
|
||||||
// Functions that aren't available on every version of Windows that we support,
|
// Functions that aren't available on every version of Windows that we support,
|
||||||
// but we still use them and just provide some form of a fallback implementation.
|
// but we still use them and just provide some form of a fallback implementation.
|
||||||
compat_fn! {
|
compat_fn! {
|
||||||
kernel32:
|
"kernel32":
|
||||||
|
|
||||||
pub fn CreateSymbolicLinkW(_lpSymlinkFileName: LPCWSTR,
|
pub fn CreateSymbolicLinkW(_lpSymlinkFileName: LPCWSTR,
|
||||||
_lpTargetFileName: LPCWSTR,
|
_lpTargetFileName: LPCWSTR,
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
//! function is available but afterwards it's just a load and a jump.
|
//! function is available but afterwards it's just a load and a jump.
|
||||||
|
|
||||||
use crate::ffi::CString;
|
use crate::ffi::CString;
|
||||||
use crate::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
use crate::sys::c;
|
use crate::sys::c;
|
||||||
|
|
||||||
pub fn lookup(module: &str, symbol: &str) -> Option<usize> {
|
pub fn lookup(module: &str, symbol: &str) -> Option<usize> {
|
||||||
|
@ -28,45 +27,69 @@ pub fn lookup(module: &str, symbol: &str) -> Option<usize> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_func(ptr: &AtomicUsize, module: &str, symbol: &str, fallback: usize) -> usize {
|
|
||||||
let value = lookup(module, symbol).unwrap_or(fallback);
|
|
||||||
ptr.store(value, Ordering::SeqCst);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! compat_fn {
|
macro_rules! compat_fn {
|
||||||
($module:ident: $(
|
($module:literal: $(
|
||||||
$(#[$meta:meta])*
|
$(#[$meta:meta])*
|
||||||
pub fn $symbol:ident($($argname:ident: $argtype:ty),*)
|
pub fn $symbol:ident($($argname:ident: $argtype:ty),*) -> $rettype:ty $body:block
|
||||||
-> $rettype:ty {
|
|
||||||
$($body:expr);*
|
|
||||||
}
|
|
||||||
)*) => ($(
|
)*) => ($(
|
||||||
#[allow(unused_variables)]
|
|
||||||
$(#[$meta])*
|
$(#[$meta])*
|
||||||
pub unsafe fn $symbol($($argname: $argtype),*) -> $rettype {
|
pub mod $symbol {
|
||||||
|
use super::*;
|
||||||
use crate::sync::atomic::{AtomicUsize, Ordering};
|
use crate::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use crate::mem;
|
use crate::mem;
|
||||||
|
|
||||||
type F = unsafe extern "system" fn($($argtype),*) -> $rettype;
|
type F = unsafe extern "system" fn($($argtype),*) -> $rettype;
|
||||||
|
|
||||||
static PTR: AtomicUsize = AtomicUsize::new(0);
|
static PTR: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
unsafe extern "system" fn fallback($($argname: $argtype),*) -> $rettype $body
|
||||||
|
|
||||||
|
/// This address is stored in `PTR` to incidate an unavailable API.
|
||||||
|
///
|
||||||
|
/// This way, call() will end up calling fallback() if it is unavailable.
|
||||||
|
///
|
||||||
|
/// This is a `static` to avoid rustc duplicating `fn fallback()`
|
||||||
|
/// into both load() and is_available(), which would break
|
||||||
|
/// is_available()'s comparison. By using the same static variable
|
||||||
|
/// in both places, they'll refer to the same (copy of the)
|
||||||
|
/// function.
|
||||||
|
///
|
||||||
|
/// LLVM merging the address of fallback with other functions
|
||||||
|
/// (because of unnamed_addr) is fine, since it's only compared to
|
||||||
|
/// an address from GetProcAddress from an external dll.
|
||||||
|
static FALLBACK: F = fallback;
|
||||||
|
|
||||||
|
#[cold]
|
||||||
fn load() -> usize {
|
fn load() -> usize {
|
||||||
crate::sys::compat::store_func(&PTR,
|
// There is no locking here. It's okay if this is executed by multiple threads in
|
||||||
stringify!($module),
|
// parallel. `lookup` will result in the same value, and it's okay if they overwrite
|
||||||
stringify!($symbol),
|
// eachothers result as long as they do so atomically. We don't need any guarantees
|
||||||
fallback as usize)
|
// about memory ordering, as this involves just a single atomic variable which is
|
||||||
}
|
// not used to protect or order anything else.
|
||||||
unsafe extern "system" fn fallback($($argname: $argtype),*)
|
let addr = crate::sys::compat::lookup($module, stringify!($symbol))
|
||||||
-> $rettype {
|
.unwrap_or(FALLBACK as usize);
|
||||||
$($body);*
|
PTR.store(addr, Ordering::Relaxed);
|
||||||
|
addr
|
||||||
}
|
}
|
||||||
|
|
||||||
let addr = match PTR.load(Ordering::SeqCst) {
|
fn addr() -> usize {
|
||||||
0 => load(),
|
match PTR.load(Ordering::Relaxed) {
|
||||||
n => n,
|
0 => load(),
|
||||||
};
|
addr => addr,
|
||||||
mem::transmute::<usize, F>(addr)($($argname),*)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn is_available() -> bool {
|
||||||
|
addr() != FALLBACK as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn call($($argname: $argtype),*) -> $rettype {
|
||||||
|
mem::transmute::<usize, F>(addr())($($argname),*)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use $symbol::call as $symbol;
|
||||||
)*)
|
)*)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ use crate::cell::{Cell, UnsafeCell};
|
||||||
use crate::mem::{self, MaybeUninit};
|
use crate::mem::{self, MaybeUninit};
|
||||||
use crate::sync::atomic::{AtomicUsize, Ordering};
|
use crate::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use crate::sys::c;
|
use crate::sys::c;
|
||||||
use crate::sys::compat;
|
|
||||||
|
|
||||||
pub struct Mutex {
|
pub struct Mutex {
|
||||||
// This is either directly an SRWLOCK (if supported), or a Box<Inner> otherwise.
|
// This is either directly an SRWLOCK (if supported), or a Box<Inner> otherwise.
|
||||||
|
@ -40,8 +39,8 @@ struct Inner {
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum Kind {
|
enum Kind {
|
||||||
SRWLock = 1,
|
SRWLock,
|
||||||
CriticalSection = 2,
|
CriticalSection,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -130,21 +129,7 @@ impl Mutex {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kind() -> Kind {
|
fn kind() -> Kind {
|
||||||
static KIND: AtomicUsize = AtomicUsize::new(0);
|
if c::AcquireSRWLockExclusive::is_available() { Kind::SRWLock } else { Kind::CriticalSection }
|
||||||
|
|
||||||
let val = KIND.load(Ordering::SeqCst);
|
|
||||||
if val == Kind::SRWLock as usize {
|
|
||||||
return Kind::SRWLock;
|
|
||||||
} else if val == Kind::CriticalSection as usize {
|
|
||||||
return Kind::CriticalSection;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ret = match compat::lookup("kernel32", "AcquireSRWLockExclusive") {
|
|
||||||
None => Kind::CriticalSection,
|
|
||||||
Some(..) => Kind::SRWLock,
|
|
||||||
};
|
|
||||||
KIND.store(ret as usize, Ordering::SeqCst);
|
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ReentrantMutex {
|
pub struct ReentrantMutex {
|
||||||
|
|
Loading…
Reference in New Issue