1 | #![cfg (target_thread_local)] |
2 | #![unstable (feature = "thread_local_internals" , issue = "none" )] |
3 | |
4 | //! Provides thread-local destructors without an associated "key", which |
5 | //! can be more efficient. |
6 | |
7 | // Since what appears to be glibc 2.18 this symbol has been shipped which |
8 | // GCC and clang both use to invoke destructors in thread_local globals, so |
9 | // let's do the same! |
10 | // |
11 | // Note, however, that we run on lots older linuxes, as well as cross |
12 | // compiling from a newer linux to an older linux, so we also have a |
13 | // fallback implementation to use as well. |
14 | #[cfg (any( |
15 | target_os = "linux" , |
16 | target_os = "android" , |
17 | target_os = "fuchsia" , |
18 | target_os = "redox" , |
19 | target_os = "hurd" , |
20 | target_os = "netbsd" , |
21 | target_os = "dragonfly" |
22 | ))] |
23 | // FIXME: The Rust compiler currently omits weakly function definitions (i.e., |
24 | // __cxa_thread_atexit_impl) and its metadata from LLVM IR. |
25 | #[no_sanitize (cfi, kcfi)] |
26 | pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { |
27 | use crate::mem; |
28 | use crate::sys_common::thread_local_dtor::register_dtor_fallback; |
29 | |
30 | /// This is necessary because the __cxa_thread_atexit_impl implementation |
31 | /// std links to by default may be a C or C++ implementation that was not |
32 | /// compiled using the Clang integer normalization option. |
33 | #[cfg (sanitizer_cfi_normalize_integers)] |
34 | use core::ffi::c_int; |
35 | #[cfg (not(sanitizer_cfi_normalize_integers))] |
36 | #[cfi_encoding = "i" ] |
37 | #[repr (transparent)] |
38 | pub struct c_int(#[allow (dead_code)] pub libc::c_int); |
39 | |
40 | extern "C" { |
41 | #[linkage = "extern_weak" ] |
42 | static __dso_handle: *mut u8; |
43 | #[linkage = "extern_weak" ] |
44 | static __cxa_thread_atexit_impl: Option< |
45 | extern "C" fn( |
46 | unsafe extern "C" fn(*mut libc::c_void), |
47 | *mut libc::c_void, |
48 | *mut libc::c_void, |
49 | ) -> c_int, |
50 | >; |
51 | } |
52 | |
53 | if let Some(f) = __cxa_thread_atexit_impl { |
54 | unsafe { |
55 | f( |
56 | mem::transmute::< |
57 | unsafe extern "C" fn(*mut u8), |
58 | unsafe extern "C" fn(*mut libc::c_void), |
59 | >(dtor), |
60 | t.cast(), |
61 | core::ptr::addr_of!(__dso_handle) as *mut _, |
62 | ); |
63 | } |
64 | return; |
65 | } |
66 | register_dtor_fallback(t, dtor); |
67 | } |
68 | |
69 | // This implementation is very similar to register_dtor_fallback in |
70 | // sys_common/thread_local.rs. The main difference is that we want to hook into |
71 | // macOS's analog of the above linux function, _tlv_atexit. OSX will run the |
72 | // registered dtors before any TLS slots get freed, and when the main thread |
73 | // exits. |
74 | // |
75 | // Unfortunately, calling _tlv_atexit while tls dtors are running is UB. The |
76 | // workaround below is to register, via _tlv_atexit, a custom DTOR list once per |
77 | // thread. thread_local dtors are pushed to the DTOR list without calling |
78 | // _tlv_atexit. |
79 | #[cfg (any( |
80 | target_os = "macos" , |
81 | target_os = "ios" , |
82 | target_os = "watchos" , |
83 | target_os = "visionos" , |
84 | target_os = "tvos" |
85 | ))] |
86 | pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { |
87 | use crate::cell::{Cell, RefCell}; |
88 | use crate::ptr; |
89 | |
90 | #[thread_local ] |
91 | static REGISTERED: Cell<bool> = Cell::new(false); |
92 | |
93 | #[thread_local ] |
94 | static DTORS: RefCell<Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>> = RefCell::new(Vec::new()); |
95 | |
96 | if !REGISTERED.get() { |
97 | _tlv_atexit(run_dtors, ptr::null_mut()); |
98 | REGISTERED.set(true); |
99 | } |
100 | |
101 | extern "C" { |
102 | fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8); |
103 | } |
104 | |
105 | match DTORS.try_borrow_mut() { |
106 | Ok(mut dtors) => dtors.push((t, dtor)), |
107 | Err(_) => rtabort!("global allocator may not use TLS" ), |
108 | } |
109 | |
110 | unsafe extern "C" fn run_dtors(_: *mut u8) { |
111 | let mut list = DTORS.take(); |
112 | while !list.is_empty() { |
113 | for (ptr, dtor) in list { |
114 | dtor(ptr); |
115 | } |
116 | list = DTORS.take(); |
117 | } |
118 | } |
119 | } |
120 | |
121 | #[cfg (any( |
122 | target_os = "vxworks" , |
123 | target_os = "horizon" , |
124 | target_os = "emscripten" , |
125 | target_os = "aix" , |
126 | target_os = "freebsd" , |
127 | ))] |
128 | #[cfg_attr (target_family = "wasm" , allow(unused))] // might remain unused depending on target details (e.g. wasm32-unknown-emscripten) |
129 | pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { |
130 | use crate::sys_common::thread_local_dtor::register_dtor_fallback; |
131 | register_dtor_fallback(t, dtor); |
132 | } |
133 | |