std/sys/thread_local/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//! Implementation of the `thread_local` macro.
//!
//! There are three different thread-local implementations:
//! * Some targets lack threading support, and hence have only one thread, so
//!   the TLS data is stored in a normal `static`.
//! * Some targets support TLS natively via the dynamic linker and C runtime.
//! * On some targets, the OS provides a library-based TLS implementation. The
//!   TLS data is heap-allocated and referenced using a TLS key.
//!
//! Each implementation provides a macro which generates the `LocalKey` `const`
//! used to reference the TLS variable, along with the necessary helper structs
//! to track the initialization/destruction state of the variable.
//!
//! Additionally, this module contains abstractions for the OS interfaces used
//! for these implementations.

#![cfg_attr(test, allow(unused))]
#![doc(hidden)]
#![forbid(unsafe_op_in_unsafe_fn)]
#![unstable(
    feature = "thread_local_internals",
    reason = "internal details of the thread_local macro",
    issue = "none"
)]

cfg_if::cfg_if! {
    if #[cfg(any(
        all(target_family = "wasm", not(target_feature = "atomics")),
        target_os = "uefi",
        target_os = "zkvm",
    ))] {
        mod statik;
        pub use statik::{EagerStorage, LazyStorage, thread_local_inner};
        pub(crate) use statik::{LocalPointer, local_pointer};
    } else if #[cfg(target_thread_local)] {
        mod native;
        pub use native::{EagerStorage, LazyStorage, thread_local_inner};
        pub(crate) use native::{LocalPointer, local_pointer};
    } else {
        mod os;
        pub use os::{Storage, thread_local_inner};
        pub(crate) use os::{LocalPointer, local_pointer};
    }
}

/// The native TLS implementation needs a way to register destructors for its data.
/// This module contains platform-specific implementations of that register.
///
/// It turns out however that most platforms don't have a way to register a
/// destructor for each variable. On these platforms, we keep track of the
/// destructors ourselves and register (through the [`guard`] module) only a
/// single callback that runs all of the destructors in the list.
#[cfg(all(target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics")))))]
pub(crate) mod destructors {
    cfg_if::cfg_if! {
        if #[cfg(any(
            target_os = "linux",
            target_os = "android",
            target_os = "fuchsia",
            target_os = "redox",
            target_os = "hurd",
            target_os = "netbsd",
            target_os = "dragonfly"
        ))] {
            mod linux_like;
            mod list;
            pub(super) use linux_like::register;
            pub(super) use list::run;
        } else {
            mod list;
            pub(super) use list::register;
            pub(crate) use list::run;
        }
    }
}

/// This module provides a way to schedule the execution of the destructor list
/// and the [runtime cleanup](crate::rt::thread_cleanup) function. Calling `enable`
/// should ensure that these functions are called at the right times.
pub(crate) mod guard {
    cfg_if::cfg_if! {
        if #[cfg(all(target_thread_local, target_vendor = "apple"))] {
            mod apple;
            pub(crate) use apple::enable;
        } else if #[cfg(target_os = "windows")] {
            mod windows;
            pub(crate) use windows::enable;
        } else if #[cfg(any(
            target_family = "wasm",
            target_os = "uefi",
            target_os = "zkvm",
        ))] {
            pub(crate) fn enable() {
                // FIXME: Right now there is no concept of "thread exit" on
                // wasm, but this is likely going to show up at some point in
                // the form of an exported symbol that the wasm runtime is going
                // to be expected to call. For now we just leak everything, but
                // if such a function starts to exist it will probably need to
                // iterate the destructor list with these functions:
                #[cfg(all(target_family = "wasm", target_feature = "atomics"))]
                #[allow(unused)]
                use super::destructors::run;
                #[allow(unused)]
                use crate::rt::thread_cleanup;
            }
        } else if #[cfg(any(
            target_os = "hermit",
            target_os = "xous",
        ))] {
            // `std` is the only runtime, so it just calls the destructor functions
            // itself when the time comes.
            pub(crate) fn enable() {}
        } else if #[cfg(target_os = "solid_asp3")] {
            mod solid;
            pub(crate) use solid::enable;
        } else {
            mod key;
            pub(crate) use key::enable;
        }
    }
}

/// `const`-creatable TLS keys.
///
/// Most OSs without native TLS will provide a library-based way to create TLS
/// storage. For each TLS variable, we create a key, which can then be used to
/// reference an entry in a thread-local table. This then associates each key
/// with a pointer which we can get and set to store our data.
pub(crate) mod key {
    cfg_if::cfg_if! {
        if #[cfg(any(
            all(
                not(target_vendor = "apple"),
                not(target_family = "wasm"),
                target_family = "unix",
            ),
            target_os = "teeos",
        ))] {
            mod racy;
            mod unix;
            #[cfg(test)]
            mod tests;
            pub(super) use racy::LazyKey;
            pub(super) use unix::{Key, set};
            #[cfg(any(not(target_thread_local), test))]
            pub(super) use unix::get;
            use unix::{create, destroy};
        } else if #[cfg(all(not(target_thread_local), target_os = "windows"))] {
            #[cfg(test)]
            mod tests;
            mod windows;
            pub(super) use windows::{Key, LazyKey, get, run_dtors, set};
        } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
            mod racy;
            mod sgx;
            #[cfg(test)]
            mod tests;
            pub(super) use racy::LazyKey;
            pub(super) use sgx::{Key, get, set};
            use sgx::{create, destroy};
        } else if #[cfg(target_os = "xous")] {
            mod racy;
            #[cfg(test)]
            mod tests;
            mod xous;
            pub(super) use racy::LazyKey;
            pub(crate) use xous::destroy_tls;
            pub(super) use xous::{Key, get, set};
            use xous::{create, destroy};
        }
    }
}

/// Run a callback in a scenario which must not unwind (such as a `extern "C"
/// fn` declared in a user crate). If the callback unwinds anyway, then
/// `rtabort` with a message about thread local panicking on drop.
#[inline]
#[allow(dead_code)]
fn abort_on_dtor_unwind(f: impl FnOnce()) {
    // Using a guard like this is lower cost.
    let guard = DtorUnwindGuard;
    f();
    core::mem::forget(guard);

    struct DtorUnwindGuard;
    impl Drop for DtorUnwindGuard {
        #[inline]
        fn drop(&mut self) {
            // This is not terribly descriptive, but it doesn't need to be as we'll
            // already have printed a panic message at this point.
            rtabort!("thread local panicked on drop");
        }
    }
}