#[cfg(feature = "thread-at-exit")]
use alloc::boxed::Box;
use core::ffi::{c_int, c_void};
use core::mem::{size_of, transmute, zeroed};
use core::ptr::{NonNull, null_mut, with_exposed_provenance_mut, without_provenance_mut};
use core::slice;
use rustix::io;
pub use rustix::thread::Pid as ThreadId;
unsafe extern "C" {
fn __cxa_thread_atexit_impl(
func: unsafe extern "C" fn(*mut c_void),
obj: *mut c_void,
_dso_symbol: *mut c_void,
) -> libc::c_int;
fn __errno_location() -> *mut c_int;
fn __tls_get_addr(p: &[usize; 2]) -> *mut c_void;
#[cfg(feature = "thread-at-exit")]
static __dso_handle: *const c_void;
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Thread(libc::pthread_t);
impl Thread {
#[inline]
pub fn from_raw(raw: *mut c_void) -> Self {
Self(raw.expose_provenance() as libc::pthread_t)
}
#[inline]
pub unsafe fn from_raw_unchecked(raw: *mut c_void) -> Self {
Self::from_raw(raw)
}
#[inline]
pub fn from_raw_non_null(raw: NonNull<c_void>) -> Self {
Self::from_raw(raw.as_ptr())
}
#[inline]
pub fn to_raw(self) -> *mut c_void {
with_exposed_provenance_mut(self.0 as usize)
}
#[inline]
pub fn to_raw_non_null(self) -> NonNull<c_void> {
NonNull::new(self.to_raw()).unwrap()
}
}
pub unsafe fn create(
fn_: unsafe fn(&mut [Option<NonNull<c_void>>]) -> Option<NonNull<c_void>>,
args: &[Option<NonNull<c_void>>],
stack_size: usize,
guard_size: usize,
) -> io::Result<Thread> {
extern "C" fn start(thread_arg_ptr: *mut c_void) -> *mut c_void {
unsafe {
let thread_arg_ptr = thread_arg_ptr.cast::<Option<NonNull<c_void>>>();
let num_args = match thread_arg_ptr.read() {
Some(ptr) => ptr.as_ptr().addr(),
None => 0,
};
let thread_args = slice::from_raw_parts_mut(thread_arg_ptr, num_args + 2);
let fn_: unsafe fn(&mut [Option<NonNull<c_void>>]) -> Option<NonNull<c_void>> =
transmute(thread_args[1]);
let args = &mut thread_args[2..];
let return_value = fn_(args);
libc::free(thread_arg_ptr.cast::<c_void>());
match return_value {
Some(return_value) => return_value.as_ptr(),
None => null_mut(),
}
}
}
unsafe {
let mut attr: libc::pthread_attr_t = zeroed();
match libc::pthread_attr_init(&mut attr) {
0 => (),
err => return Err(io::Errno::from_raw_os_error(err)),
}
match libc::pthread_attr_setstacksize(&mut attr, stack_size) {
0 => (),
err => return Err(io::Errno::from_raw_os_error(err)),
}
match libc::pthread_attr_setguardsize(&mut attr, guard_size) {
0 => (),
err => return Err(io::Errno::from_raw_os_error(err)),
}
let mut new_thread = zeroed();
let thread_arg_ptr = libc::malloc((args.len() + 2) * size_of::<*mut c_void>());
if thread_arg_ptr.is_null() {
return Err(io::Errno::NOMEM);
}
let thread_args = slice::from_raw_parts_mut(
thread_arg_ptr.cast::<Option<NonNull<c_void>>>(),
args.len() + 2,
);
thread_args[0] = NonNull::new(without_provenance_mut(args.len()));
thread_args[1] = NonNull::new(fn_ as _);
thread_args[2..].copy_from_slice(args);
match libc::pthread_create(&mut new_thread, &attr, start, thread_arg_ptr) {
0 => (),
err => return Err(io::Errno::from_raw_os_error(err)),
}
Ok(Thread(new_thread))
}
}
#[inline]
pub unsafe fn detach(thread: Thread) {
unsafe {
let thread = thread.0;
assert_eq!(libc::pthread_detach(thread), 0);
}
}
pub unsafe fn join(thread: Thread) -> Option<NonNull<c_void>> {
unsafe {
let thread = thread.0;
let mut return_value: *mut c_void = null_mut();
assert_eq!(libc::pthread_join(thread, &mut return_value), 0);
NonNull::new(return_value)
}
}
#[cfg(feature = "thread-at-exit")]
pub fn at_exit(func: Box<dyn FnOnce()>) {
unsafe extern "C" fn call(arg: *mut c_void) {
unsafe {
let arg = arg.cast::<Box<dyn FnOnce()>>();
let arg = Box::from_raw(arg);
arg()
}
}
unsafe {
let arg = Box::into_raw(Box::new(func));
assert_eq!(__cxa_thread_atexit_impl(call, arg.cast(), dso_handle()), 0);
}
}
#[inline]
#[must_use]
pub fn current() -> Thread {
unsafe { Thread(libc::pthread_self()) }
}
#[inline]
#[must_use]
pub fn current_id() -> ThreadId {
unsafe { ThreadId::from_raw_unchecked(libc::gettid()) }
}
#[doc(hidden)]
#[inline]
pub unsafe fn set_current_id_after_a_fork(tid: ThreadId) {
let _ = tid;
}
#[cfg(feature = "unstable-errno")]
#[inline]
pub fn errno_location() -> *mut i32 {
unsafe { __errno_location() }
}
#[inline]
#[must_use]
pub fn current_tls_addr(module: usize, offset: usize) -> *mut c_void {
unsafe { __tls_get_addr(&[module, offset]) }
}
#[inline]
#[must_use]
pub unsafe fn stack(thread: Thread) -> (*mut c_void, usize, usize) {
unsafe {
let thread = thread.0;
let mut attr: libc::pthread_attr_t = zeroed();
assert_eq!(libc::pthread_getattr_np(thread, &mut attr), 0);
let mut stack_size = 0;
let mut stack_addr = null_mut();
assert_eq!(
libc::pthread_attr_getstack(&attr, &mut stack_addr, &mut stack_size),
0
);
let mut guard_size = 0;
assert_eq!(libc::pthread_attr_getguardsize(&attr, &mut guard_size), 0);
(stack_addr, stack_size, guard_size)
}
}
#[inline]
#[must_use]
pub fn default_stack_size() -> usize {
unsafe {
let mut attr: libc::pthread_attr_t = zeroed();
assert_eq!(libc::pthread_getattr_np(libc::pthread_self(), &mut attr), 0);
let mut stack_size = 0;
assert_eq!(libc::pthread_attr_getstacksize(&attr, &mut stack_size), 0);
stack_size
}
}
#[inline]
#[must_use]
pub fn default_guard_size() -> usize {
unsafe {
let mut attr: libc::pthread_attr_t = zeroed();
assert_eq!(libc::pthread_getattr_np(libc::pthread_self(), &mut attr), 0);
let mut guard_size = 0;
assert_eq!(libc::pthread_attr_getguardsize(&attr, &mut guard_size), 0);
guard_size
}
}
#[inline]
pub fn yield_current() {
let _ = unsafe { libc::sched_yield() };
}
#[cfg(feature = "thread-at-exit")]
unsafe fn dso_handle() -> *mut c_void {
unsafe {
let dso_handle: *const *const c_void = &__dso_handle;
dso_handle.cast::<c_void>().cast_mut()
}
}