about summary refs log tree commit diff
path: root/library/std/src/sys/thread_local/destructors
diff options
context:
space:
mode:
authorjoboet <jonasboettiger@icloud.com>2024-06-15 17:47:35 +0200
committerjoboet <jonasboettiger@icloud.com>2024-06-15 17:47:35 +0200
commitf3facf11758af878bcfaf47fc773962dbb565024 (patch)
tree81d1238932f3d528e11dcb2e8857fc5a39587164 /library/std/src/sys/thread_local/destructors
parentd2ad293851dc8e14a61355d0358490b77efae8cb (diff)
downloadrust-f3facf11758af878bcfaf47fc773962dbb565024.tar.gz
rust-f3facf11758af878bcfaf47fc773962dbb565024.zip
std: refactor the TLS implementation
As discovered by Mara in #110897, our TLS implementation is a total mess. In the past months, I have simplified the actual macros and their expansions, but the majority of the complexity comes from the platform-specific support code needed to create keys and register destructors. In keeping with #117276, I have therefore moved all of the `thread_local_key`/`thread_local_dtor` modules to the `thread_local` module in `sys` and merged them into a new structure, so that future porters of `std` can simply mix-and-match the existing code instead of having to copy the same (bad) implementation everywhere. The new structure should become obvious when looking at `sys/thread_local/mod.rs`.

Unfortunately, the documentation changes associated with the refactoring have made this PR rather large. That said, this contains no functional changes except for two small ones:
* the key-based destructor fallback now, by virtue of sharing the implementation used by macOS and others, stores its list in a `#[thread_local]` static instead of in the key, eliminating one indirection layer and drastically simplifying its code.
* I've switched over ZKVM (tier 3) to use the same implementation as WebAssembly, as the implementation was just a way worse version of that

Please let me know if I can make this easier to review! I know these large PRs aren't optimal, but I couldn't think of any good intermediate steps.

@rustbot label +A-thread-locals
Diffstat (limited to 'library/std/src/sys/thread_local/destructors')
-rw-r--r--library/std/src/sys/thread_local/destructors/linux.rs58
-rw-r--r--library/std/src/sys/thread_local/destructors/list.rs44
2 files changed, 102 insertions, 0 deletions
diff --git a/library/std/src/sys/thread_local/destructors/linux.rs b/library/std/src/sys/thread_local/destructors/linux.rs
new file mode 100644
index 00000000000..e00ca9a91bd
--- /dev/null
+++ b/library/std/src/sys/thread_local/destructors/linux.rs
@@ -0,0 +1,58 @@
+//! Destructor registration for Linux-like systems.
+//!
+//! Since what appears to be version 2.18, glibc has shipped the
+//! `__cxa_thread_atexit_impl` symbol which GCC and clang both use to invoke
+//! destructors in C++ thread_local globals. This function does exactly what
+//! we want: it schedules a callback which will be run at thread exit with the
+//! provided argument.
+//!
+//! Unfortunately, our minimum supported glibc version (at the time of writing)
+//! is 2.17, so we can only link this symbol weakly and need to use the
+//! [`list`](super::list) destructor implementation as fallback.
+
+use crate::mem::transmute;
+
+// FIXME: The Rust compiler currently omits weakly function definitions (i.e.,
+// __cxa_thread_atexit_impl) and its metadata from LLVM IR.
+#[no_sanitize(cfi, kcfi)]
+pub unsafe fn register(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
+    /// This is necessary because the __cxa_thread_atexit_impl implementation
+    /// std links to by default may be a C or C++ implementation that was not
+    /// compiled using the Clang integer normalization option.
+    #[cfg(sanitizer_cfi_normalize_integers)]
+    use core::ffi::c_int;
+    #[cfg(not(sanitizer_cfi_normalize_integers))]
+    #[cfi_encoding = "i"]
+    #[repr(transparent)]
+    #[allow(non_camel_case_types)]
+    pub struct c_int(#[allow(dead_code)] pub libc::c_int);
+
+    extern "C" {
+        #[linkage = "extern_weak"]
+        static __dso_handle: *mut u8;
+        #[linkage = "extern_weak"]
+        static __cxa_thread_atexit_impl: Option<
+            extern "C" fn(
+                unsafe extern "C" fn(*mut libc::c_void),
+                *mut libc::c_void,
+                *mut libc::c_void,
+            ) -> c_int,
+        >;
+    }
+
+    if let Some(f) = unsafe { __cxa_thread_atexit_impl } {
+        unsafe {
+            f(
+                transmute::<unsafe extern "C" fn(*mut u8), unsafe extern "C" fn(*mut libc::c_void)>(
+                    dtor,
+                ),
+                t.cast(),
+                core::ptr::addr_of!(__dso_handle) as *mut _,
+            );
+        }
+    } else {
+        unsafe {
+            super::list::register(t, dtor);
+        }
+    }
+}
diff --git a/library/std/src/sys/thread_local/destructors/list.rs b/library/std/src/sys/thread_local/destructors/list.rs
new file mode 100644
index 00000000000..b9d5214c438
--- /dev/null
+++ b/library/std/src/sys/thread_local/destructors/list.rs
@@ -0,0 +1,44 @@
+use crate::cell::RefCell;
+use crate::sys::thread_local::guard;
+
+#[thread_local]
+static DTORS: RefCell<Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>> = RefCell::new(Vec::new());
+
+pub unsafe fn register(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
+    let Ok(mut dtors) = DTORS.try_borrow_mut() else {
+        // This point can only be reached if the global allocator calls this
+        // function again.
+        // FIXME: maybe use the system allocator instead?
+        rtabort!("the global allocator may not use TLS with destructors");
+    };
+
+    guard::enable();
+
+    dtors.push((t, dtor));
+}
+
+/// The [`guard`] module contains platform-specific functions which will run this
+/// function on thread exit if [`guard::enable`] has been called.
+///
+/// # Safety
+///
+/// May only be run on thread exit to guarantee that there are no live references
+/// to TLS variables while they are destroyed.
+pub unsafe fn run() {
+    loop {
+        let mut dtors = DTORS.borrow_mut();
+        match dtors.pop() {
+            Some((t, dtor)) => {
+                drop(dtors);
+                unsafe {
+                    dtor(t);
+                }
+            }
+            None => {
+                // Free the list memory.
+                *dtors = Vec::new();
+                break;
+            }
+        }
+    }
+}