Skip to content

Unify TLS destructor list implementations #116850

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
std: move UNIX to new destructor list implementation
  • Loading branch information
joboet committed Feb 16, 2024
commit 13cc6af2b548ba4c57700cd21c2d407e903931dc
2 changes: 1 addition & 1 deletion library/std/src/sys/pal/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub mod rand;
pub mod stack_overflow;
pub mod stdio;
pub mod thread;
pub mod thread_local_dtor;
pub mod thread_local_guard;
pub mod thread_local_key;
pub mod thread_parking;
pub mod time;
Expand Down
97 changes: 44 additions & 53 deletions library/std/src/sys/pal/unix/thread_local_dtor.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Ensures that thread-local destructors are run on thread exit.

#![cfg(target_thread_local)]
#![unstable(feature = "thread_local_internals", issue = "none")]

//! Provides thread-local destructors without an associated "key", which
//! can be more efficient.
use crate::ptr;
use crate::sys::common::thread_local::run_dtors;

// Since what appears to be glibc 2.18 this symbol has been shipped which
// GCC and clang both use to invoke destructors in thread_local globals, so
Expand All @@ -23,9 +25,10 @@
// 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_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
pub fn activate() {
use crate::cell::Cell;
use crate::mem;
use crate::sys_common::thread_local_dtor::register_dtor_fallback;
use crate::sys_common::thread_local_key::StaticKey;

/// 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
Expand All @@ -50,64 +53,47 @@ pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
>;
}

if let Some(f) = __cxa_thread_atexit_impl {
unsafe {
f(
mem::transmute::<
unsafe extern "C" fn(*mut u8),
unsafe extern "C" fn(*mut libc::c_void),
>(dtor),
t.cast(),
&__dso_handle as *const _ as *mut _,
);
unsafe {
if let Some(atexit) = __cxa_thread_atexit_impl {
#[thread_local]
static REGISTERED: Cell<bool> = Cell::new(false);
if !REGISTERED.get() {
atexit(
mem::transmute::<
unsafe extern "C" fn(*mut u8),
unsafe extern "C" fn(*mut libc::c_void),
>(run_dtors),
ptr::null_mut(),
&__dso_handle as *const _ as *mut _,
);
REGISTERED.set(true);
}
} else {
static KEY: StaticKey = StaticKey::new(Some(run_dtors));

KEY.set(ptr::invalid_mut(1));
}
return;
}
register_dtor_fallback(t, dtor);
}

// This implementation is very similar to register_dtor_fallback in
// sys_common/thread_local.rs. The main difference is that we want to hook into
// macOS's analog of the above linux function, _tlv_atexit. OSX will run the
// registered dtors before any TLS slots get freed, and when the main thread
// We hook into macOS's analog of the above linux function, _tlv_atexit. OSX
// will run `run_dtors` before any TLS slots get freed, and when the main thread
// exits.
//
// Unfortunately, calling _tlv_atexit while tls dtors are running is UB. The
// workaround below is to register, via _tlv_atexit, a custom DTOR list once per
// thread. thread_local dtors are pushed to the DTOR list without calling
// _tlv_atexit.
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos", target_os = "tvos"))]
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
use crate::cell::{Cell, RefCell};
use crate::ptr;

#[thread_local]
static REGISTERED: Cell<bool> = Cell::new(false);

#[thread_local]
static DTORS: RefCell<Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>> = RefCell::new(Vec::new());

if !REGISTERED.get() {
_tlv_atexit(run_dtors, ptr::null_mut());
REGISTERED.set(true);
}
pub fn activate() {
use crate::cell::Cell;

extern "C" {
fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8);
}

match DTORS.try_borrow_mut() {
Ok(mut dtors) => dtors.push((t, dtor)),
Err(_) => rtabort!("global allocator may not use TLS"),
}
#[thread_local]
static REGISTERED: Cell<bool> = Cell::new(false);

unsafe extern "C" fn run_dtors(_: *mut u8) {
let mut list = DTORS.take();
while !list.is_empty() {
for (ptr, dtor) in list {
dtor(ptr);
}
list = DTORS.take();
if !REGISTERED.get() {
unsafe {
_tlv_atexit(run_dtors, ptr::null_mut());
REGISTERED.set(true);
}
}
}
Expand All @@ -120,7 +106,12 @@ pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
target_os = "freebsd",
))]
#[cfg_attr(target_family = "wasm", allow(unused))] // might remain unused depending on target details (e.g. wasm32-unknown-emscripten)
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
use crate::sys_common::thread_local_dtor::register_dtor_fallback;
register_dtor_fallback(t, dtor);
pub fn activate() {
use crate::sys_common::thread_local_key::StaticKey;

static KEY: StaticKey = StaticKey::new(Some(run_dtors));

unsafe {
KEY.set(ptr::invalid_mut(1));
}
}
118 changes: 118 additions & 0 deletions library/std/src/sys/pal/unix/thread_local_guard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! Ensures that thread-local destructors are run on thread exit.

#![cfg(target_thread_local)]
#![unstable(feature = "thread_local_internals", issue = "none")]

use crate::ptr;
use crate::sys::common::thread_local::run_dtors;

// Since what appears to be glibc 2.18 this symbol has been shipped which
// GCC and clang both use to invoke destructors in thread_local globals, so
// let's do the same!
//
// Note, however, that we run on lots older linuxes, as well as cross
// compiling from a newer linux to an older linux, so we also have a
// fallback implementation to use as well.
#[cfg_attr(bootstrap, allow(unexpected_cfgs))]
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "fuchsia",
target_os = "redox",
target_os = "hurd",
target_os = "freebsd",
target_os = "netbsd",
target_os = "dragonfly"
))]
// 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 fn activate() {
use crate::cell::Cell;
use crate::mem;
use crate::sys_common::thread_local_key::StaticKey;

/// 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)]
pub struct c_int(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,
>;
}

unsafe {
if let Some(atexit) = __cxa_thread_atexit_impl {
#[thread_local]
static REGISTERED: Cell<bool> = Cell::new(false);
if !REGISTERED.get() {
atexit(
mem::transmute::<
unsafe extern "C" fn(*mut u8),
unsafe extern "C" fn(*mut libc::c_void),
>(run_dtors),
ptr::null_mut(),
&__dso_handle as *const _ as *mut _,
);
REGISTERED.set(true);
}
} else {
static KEY: StaticKey = StaticKey::new(Some(run_dtors));

KEY.set(ptr::invalid_mut(1));
}
}
}

// We hook into macOS's analog of the above linux function, _tlv_atexit. OSX
// will run `run_dtors` before any TLS slots get freed, and when the main thread
// exits.
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos", target_os = "tvos"))]
pub fn activate() {
use crate::cell::Cell;

extern "C" {
fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8);
}

#[thread_local]
static REGISTERED: Cell<bool> = Cell::new(false);

if !REGISTERED.get() {
unsafe {
_tlv_atexit(run_dtors, ptr::null_mut());
REGISTERED.set(true);
}
}
}

#[cfg(any(
target_os = "vxworks",
target_os = "horizon",
target_os = "emscripten",
target_os = "aix"
))]
#[cfg_attr(target_family = "wasm", allow(unused))] // might remain unused depending on target details (e.g. wasm32-unknown-emscripten)
pub fn activate() {
use crate::sys_common::thread_local_key::StaticKey;

static KEY: StaticKey = StaticKey::new(Some(run_dtors));

unsafe {
KEY.set(ptr::invalid_mut(1));
}
}
1 change: 0 additions & 1 deletion library/std/src/sys_common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ pub mod once;
pub mod process;
pub mod thread;
pub mod thread_info;
pub mod thread_local_dtor;
pub mod thread_parking;
pub mod wstr;
pub mod wtf8;
Expand Down
56 changes: 0 additions & 56 deletions library/std/src/sys_common/thread_local_dtor.rs

This file was deleted.