Skip to content

Commit b2a8d9d

Browse files
authored
Rollup merge of #100984 - ChrisDenton:reinstate-init, r=Mark-Simulacrum
Reinstate preloading of some dll imports I've now come around to the conclusion that there is a justification for pre-loading the synchronization functions `WaitOnAddress` and `WakeByAddressSingle`. I've found this to have a particularly impact in testing frameworks that may have short lived processes which immediately spawn lots of threads. Also, because pre-main initializers imply a single-threaded environment, we can switch back to using relaxed atomics which might be a minor perf improvement on some platforms (though I doubt it's particularly notable). r? ``@Mark-Simulacrum`` and sorry for the churn here. For convenience I'll summarise previous issues with preloading and the solutions that are included in this PR (if any): **Issue:** User pre-main initializers may be run before std's **Solution:** The std now uses initializers that are guaranteed to run earlier than the old initializers. A note is also added that users should not copy std's behaviour if they want to ensure they run their initializers after std. **Issue:** Miri does not understand pre-main initializers. **Solution:** For miri only, run the function loading lazily instead. **Issue:** We should ideally use `LoadLibrary` to get "api-ms-win-core-synch-l1-2-0". Only "ntdll" and "kernel32" are guaranteed to always be loaded. **Solution:** None. We can't use `LoadLibrary` pre-main. However, in the past `GetModuleHandle` has always worked in practice so this should hopefully not be a problem. If/when Windows 7 support is dropped, we can finally remove all this for good and just use normal imports.
2 parents ea9c370 + 7bb47a6 commit b2a8d9d

File tree

2 files changed

+52
-33
lines changed

2 files changed

+52
-33
lines changed

library/std/src/sys/windows/c.rs

-3
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,6 @@ pub const IPV6_ADD_MEMBERSHIP: c_int = 12;
228228
pub const IPV6_DROP_MEMBERSHIP: c_int = 13;
229229
pub const MSG_PEEK: c_int = 0x2;
230230

231-
pub const LOAD_LIBRARY_SEARCH_SYSTEM32: u32 = 0x800;
232-
233231
#[repr(C)]
234232
#[derive(Copy, Clone)]
235233
pub struct linger {
@@ -1032,7 +1030,6 @@ extern "system" {
10321030
pub fn GetProcAddress(handle: HMODULE, name: LPCSTR) -> *mut c_void;
10331031
pub fn GetModuleHandleA(lpModuleName: LPCSTR) -> HMODULE;
10341032
pub fn GetModuleHandleW(lpModuleName: LPCWSTR) -> HMODULE;
1035-
pub fn LoadLibraryExA(lplibfilename: *const i8, hfile: HANDLE, dwflags: u32) -> HINSTANCE;
10361033

10371034
pub fn GetSystemTimeAsFileTime(lpSystemTimeAsFileTime: LPFILETIME);
10381035
pub fn GetSystemInfo(lpSystemInfo: LPSYSTEM_INFO);

library/std/src/sys/windows/compat.rs

+52-30
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,52 @@
2121
2222
use crate::ffi::{c_void, CStr};
2323
use crate::ptr::NonNull;
24-
use crate::sync::atomic::{AtomicBool, Ordering};
24+
use crate::sync::atomic::Ordering;
2525
use crate::sys::c;
2626

27+
// This uses a static initializer to preload some imported functions.
28+
// The CRT (C runtime) executes static initializers before `main`
29+
// is called (for binaries) and before `DllMain` is called (for DLLs).
30+
//
31+
// It works by contributing a global symbol to the `.CRT$XCT` section.
32+
// The linker builds a table of all static initializer functions.
33+
// The CRT startup code then iterates that table, calling each
34+
// initializer function.
35+
//
36+
// NOTE: User code should instead use .CRT$XCU to reliably run after std's initializer.
37+
// If you're reading this and would like a guarantee here, please
38+
// file an issue for discussion; currently we don't guarantee any functionality
39+
// before main.
40+
// See https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=msvc-170
41+
#[used]
42+
#[link_section = ".CRT$XCT"]
43+
static INIT_TABLE_ENTRY: unsafe extern "C" fn() = init;
44+
45+
/// Preload some imported functions.
46+
///
47+
/// Note that any functions included here will be unconditionally loaded in
48+
/// the final binary, regardless of whether or not they're actually used.
49+
///
50+
/// Therefore, this should be limited to `compat_fn_optional` functions which
51+
/// must be preloaded or any functions where lazier loading demonstrates a
52+
/// negative performance impact in practical situations.
53+
///
54+
/// Currently we only preload `WaitOnAddress` and `WakeByAddressSingle`.
55+
unsafe extern "C" fn init() {
56+
// In an exe this code is executed before main() so is single threaded.
57+
// In a DLL the system's loader lock will be held thereby synchronizing
58+
// access. So the same best practices apply here as they do to running in DllMain:
59+
// https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices
60+
//
61+
// DO NOT do anything interesting or complicated in this function! DO NOT call
62+
// any Rust functions or CRT functions if those functions touch any global state,
63+
// because this function runs during global initialization. For example, DO NOT
64+
// do any dynamic allocation, don't call LoadLibrary, etc.
65+
66+
// Attempt to preload the synch functions.
67+
load_synch_functions();
68+
}
69+
2770
/// Helper macro for creating CStrs from literals and symbol names.
2871
macro_rules! ansi_str {
2972
(sym $ident:ident) => {{
@@ -75,20 +118,6 @@ impl Module {
75118
NonNull::new(module).map(Self)
76119
}
77120

78-
/// Load the library (if not already loaded)
79-
///
80-
/// # Safety
81-
///
82-
/// The module must not be unloaded.
83-
pub unsafe fn load_system_library(name: &CStr) -> Option<Self> {
84-
let module = c::LoadLibraryExA(
85-
name.as_ptr(),
86-
crate::ptr::null_mut(),
87-
c::LOAD_LIBRARY_SEARCH_SYSTEM32,
88-
);
89-
NonNull::new(module).map(Self)
90-
}
91-
92121
// Try to get the address of a function.
93122
pub fn proc_address(self, name: &CStr) -> Option<NonNull<c_void>> {
94123
// SAFETY:
@@ -182,14 +211,10 @@ macro_rules! compat_fn_optional {
182211

183212
#[inline(always)]
184213
pub fn option() -> Option<F> {
185-
let f = PTR.load(Ordering::Acquire);
186-
if !f.is_null() { Some(unsafe { mem::transmute(f) }) } else { try_load() }
187-
}
188-
189-
#[cold]
190-
fn try_load() -> Option<F> {
191-
$load_functions;
192-
NonNull::new(PTR.load(Ordering::Acquire)).map(|f| unsafe { mem::transmute(f) })
214+
// Miri does not understand the way we do preloading
215+
// therefore load the function here instead.
216+
#[cfg(miri)] $load_functions;
217+
NonNull::new(PTR.load(Ordering::Relaxed)).map(|f| unsafe { mem::transmute(f) })
193218
}
194219
}
195220
)+
@@ -205,17 +230,14 @@ pub(super) fn load_synch_functions() {
205230

206231
// Try loading the library and all the required functions.
207232
// If any step fails, then they all fail.
208-
let library = unsafe { Module::load_system_library(MODULE_NAME) }?;
233+
let library = unsafe { Module::new(MODULE_NAME) }?;
209234
let wait_on_address = library.proc_address(WAIT_ON_ADDRESS)?;
210235
let wake_by_address_single = library.proc_address(WAKE_BY_ADDRESS_SINGLE)?;
211236

212-
c::WaitOnAddress::PTR.store(wait_on_address.as_ptr(), Ordering::Release);
213-
c::WakeByAddressSingle::PTR.store(wake_by_address_single.as_ptr(), Ordering::Release);
237+
c::WaitOnAddress::PTR.store(wait_on_address.as_ptr(), Ordering::Relaxed);
238+
c::WakeByAddressSingle::PTR.store(wake_by_address_single.as_ptr(), Ordering::Relaxed);
214239
Some(())
215240
}
216241

217-
// Try to load the module but skip loading if a previous attempt failed.
218-
static LOAD_MODULE: AtomicBool = AtomicBool::new(true);
219-
let module_loaded = LOAD_MODULE.load(Ordering::Acquire) && try_load().is_some();
220-
LOAD_MODULE.store(module_loaded, Ordering::Release)
242+
try_load();
221243
}

0 commit comments

Comments
 (0)