//! 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(
            all(target_family = "wasm", not(
                all(target_os = "wasi", target_env = "p1", target_feature = "atomics")
            )),
            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",
            all(target_os = "wasi", target_env = "p1", target_feature = "atomics"),
        ))] {
            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");
        }
    }
}
